| Document number: | J16/06-0006 = WG21 N1936 |
| Date: | 2006-02-24 |
| Project: | Programming Language C++ |
| Reference: | ISO/IEC IS 14882:2003 |
| Reply to: | William M. Miller |
| Edison Design Group, Inc. | |
| wmm@edg.com |
This document contains the C++ core language issues that have been categorized as Defect Reports by the Committee (J16 + WG21), that is, issues with status "DR," "WP," and "TC1," along with their proposed resolutions. ONLY RESOLUTIONS FOR ISSUES WITH TC1 STATUS ARE PART OF THE INTERNATIONAL STANDARD FOR C++. The other issues are provided for informational purposes only, as an indication of the intent of the Committee. They should not be considered definitive until or unless they appear in an approved Technical Corrigendum or revised International Standard for C++.
This document is part of a group of related documents that together describe the issues that have been raised regarding the C++ Standard. The other documents in the group are:
For more information, including a description of the meaning of the issue status codes and instructions on reporting new issues, please see the Active Issues List.
[Voted into WP at October 2005 meeting.]
Consider the following bit of code:
namespace N {
struct S {
void f();
};
}
using namespace N;
void S::f() {
extern void g(); // ::g or N::g?
}
In 3.5 basic.link paragraph 7 the Standard says (among other things),
When a block scope declaration of an entity with linkage is not found to refer to some other declaration, then that entity is a member of the innermost enclosing namespace.
The question then is whether N is an “enclosing namespace” for the local declaration of g()?
Proposed resolution (October 2004):
Add the following text as a new paragraph at the end of 7.3.1 namespace.def:
The enclosing namespaces of a declaration are those namespaces in which the declaration lexically appears, except for a redeclaration of a namespace member outside its original namespace (e.g., a definition as specified in 7.3.1.2 namespace.memdef). Such a redeclaration has the same enclosing namespaces as the original declaration. [Example:namespace Q { namespace V { void f(); // enclosing namespaces are the global namespace, Q, and Q::V class C { void m(); }; } void V::f() { // enclosing namespaces are the global namespace, Q, and Q::V extern void h(); // ... so this declares Q::V::h } void V::C::m() { // enclosing namespaces are the global namespace, Q, and Q::V } }—end example]
[Voted into WP at October 2005 meeting.]
Standard is clear on behaviour of default allocation/deallocation functions. However, it is surpisingly vague on requirements to the behaviour of user-defined deallocation function and an interaction between delete-expression and deallocation function. This caused a heated argument on fido7.su.c-cpp newsgroup.
Resume:
It is not clear if user-supplied deallocation function is called from delete-expr when the operand of delete-expr is the null pointer (5.3.5 expr.delete). If it is, standard does not specify what user-supplied deallocation function shall do with the null pointer operand (18.4.1 lib.new.delete). Instead, Standard uses the term "has no effect", which meaning is too vague in context given (5.3.5 expr.delete).
Description:
Consider statements
char* p= 0; //result of failed non-throwing ::new char[] ::delete[] p;Argument passed to delete-expression is valid - it is the result of a call to the non-throwing version of ::new, which has been failed. 5.3.5 expr.delete paragraph 1 explicitly prohibit us to pass 0 without having the ::new failure.
Standard does NOT specify whether user-defined deallocation function should be called in this case, or not.
Specifically, standard says in 5.3.5 expr.delete paragraph 2:
...if the value of the operand of delete is the null pointer the operation has no effect.Standard doesn't specify term "has no effect". It is not clear from this context, whether the called deallocation function is required to have no effect, or delete-expression shall not call the deallocation function.
Furthermore, in para 4 standard says on default deallocation function:
If the delete-expression calls the implementation deallocation function (3.7.3.2 basic.stc.dynamic.deallocation), if the operand of the delete expression is not the null pointer constant, ...Why it is so specific on interaction of default deallocation function and delete-expr?
If "has no effect" is a requirement to the deallocation function, then it should be stated in 3.7.3.2 basic.stc.dynamic.deallocation, or in 18.4.1.1 lib.new.delete.single and 18.4.1.2 lib.new.delete.array, and it should be stated explicitly.
Furthermore, standard does NOT specify what actions shall be performed by user-supplied deallocation function if NULL is given (18.4.1.1 lib.new.delete.single paragraph 12):
Required behaviour: accept a value of ptr that is null or that was returned by an earlier call to the default operator new(std::size_t) or operator new(std::size_t, const std::nothrow_t&).
The same corresponds to ::delete[] case.
Expected solution:
Notes from October 2002 meeting:
We believe that study of 18.4.1.1 lib.new.delete.single paragraphs 12 and 13, 18.4.1.2 lib.new.delete.array paragraphs 11 and 12, and 3.7.3.2 basic.stc.dynamic.deallocation paragraph 3 shows that the system-provided operator delete functions must accept a null pointer and ignore it. Those sections also show that a user-written replacement for the system-provided operator delete functions must accept a null pointer. There is no requirement that such functions ignore a null pointer, which is okay -- perhaps the reason for replacing the system-provided functions is to do something special with null pointer values (e.g., log such calls and return).
We believe that the standard should not require an implementation to call a delete function with a null pointer, but it must allow that. For the system-provided delete functions or replacements thereof, the standard already makes it clear that the delete function must accept a null pointer. For class-specific delete functions, we believe the standard should require that such functions accept a null pointer, though it should not mandate what they do with null pointers.
5.3.5 expr.delete needs to be updated to say that it is unspecified whether or not the operator delete function is called with a null pointer, and 3.7.3.2 basic.stc.dynamic.deallocation needs to be updated to say that any deallocation function must accept a null pointer.
Proposed resolution (October, 2004):
Change 5.3.5 expr.delete paragraph 2 as indicated:
If the operand has a class type, the operand is converted to a pointer type by calling the above-mentioned conversion function, and the converted operand is used in place of the original operand for the remainder of this section. In either alternative,ifthe value of the operand of deleteis the null pointer the operation has no effectmay be a null pointer value. If it is not a null pointer value, inInthe first alternative (delete object), the value of the operand of delete shall be a pointer to a non-array object or a pointer to a sub-object (1.8 intro.object) representing a base class of such an object (clause 10 class.derived)...
Change 5.3.5 expr.delete paragraph 4 as follows (note that the old wording reflects the changes proposed by issue 442:
The cast-expression in a delete-expression shall be evaluated exactly once.
If the delete-expression calls the implementation deallocation function (3.7.3.2 basic.stc.dynamic.deallocation), and if the value of the operand of the delete expression is not a null pointer, the deallocation function will deallocate the storage referenced by the pointer thus rendering the pointer invalid. [Note: the value of a pointer that refers to deallocated storage is indeterminate. —end note]
Change 5.3.5 expr.delete paragraphs 6-7 as follows:
TheIf the value of the operand of the delete-expression is not a null pointer value, the delete-expression will invoke the destructor (if any) for the object or the elements of the array being deleted. In the case of an array, the elements will be destroyed in order of decreasing address (that is, in reverse order of the completion of their constructor; see 12.6.2 class.base.init).
TheIf the value of the operand of the delete-expression is not a null pointer value, the delete-expression will call a deallocation function (3.7.3.2 basic.stc.dynamic.deallocation). Otherwise, it is unspecified whether the deallocation function will be called. [Note: The deallocation function is called regardless of whether the destructor for the object or some element of the array throws an exception. —end note]
Change 3.7.3.2 basic.stc.dynamic.deallocation paragraph 3 as indicated:
The value of the first argument supplied toone of thea deallocation functions provided in the standard librarymay be a null pointer value; if so, and if the deallocation function is one supplied in the standard library, the callto the deallocation functionhas no effect. Otherwise, the value supplied to operator delete(void*) in the standard library shall be one of the values returned by a previous invocation of either operator new(std::size_t) or operator new(std::size_t, const std::nothrow_t&) in the standard library, and the value supplied to operator delete[](void*) in the standard library shall be one of the values returned by a previous invocation of either operator new[](std::size_t) or operator new[](std::size_t, const std::nothrow_t&) in the standard library.
[Note: this resolution also resolves issue 442.]
[Voted into WP at October 2005 meeting.]
Clause 5 expr par. 5 of the standard says:
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined, unless such an expression is a constant expression (5.19), in which case the program is ill-formed.
Well, we do know that except in some contexts (e.g. controlling expression of a #if, array bounds), a compiler is not required to evaluate constant-expressions in compile time, right?
Now, let us consider, the following simple snippet:
if (a && 1/0)
...
with a, to fix our attention, being *not* a constant expression. The
quote above seems to say that since 1/0 is a constant
(sub-)expression, the program is ill-formed. So, is it the intent that
such ill-formedness is diagnosable at run-time? Or is it the intent
that the above gives undefined behavior (if 1/0 is evaluated) and is
not ill-formed?
I think the intent is actually the latter, so I propose the following rewording of the quoted section:
If an expression is evaluated but its result is not mathematically defined or not in the range of representable values for its type the behavior is undefined, unless such an expression is a constant expression (5.19) that shall be evaluated during program translation, in which case the program is ill-formed.
Rationale (March, 2004):
We feel the standard is clear enough. The quoted sentence does begin "If during the evaluation of an expression, ..." so the rest of the sentence does not apply to an expression that is not evaluated.
Note (September, 2004):
Gennaro Prota feels that the CWG missed the point of his original comment: unless a constant expression appears in a context that requires a constant expression, an implementation is permitted to defer its evaluation to runtime. An evaluation that fails at runtime cannot affect the well-formedness of the program; only expressions that are evaluated at compile time can make a program ill-formed.
The status has been reset to “open” to allow further discussion.
Proposed resolution (October, 2004):
Change paragraph 5 of 5 expr as indicated:
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined, unless such an expressionis a constant expressionappears where an integral constant expression is required (5.19 expr.const), in which case the program is ill-formed.
[Voted into WP at October 2005 meeting.]
In 5.3.4 expr.new, the standard says that the expression in an array-new has to have integral type. There's already a DR (issue 74) that says it should also be allowed to have enumeration type. But, it should probably also say that it can have a class type with a single conversion to integral type; in other words the same thing as in 6.4.2 stmt.switch paragraph 2.
Suggested resolution:
In 5.3.4 expr.new paragraph 6, replace "integral or enumeration type (3.9.1 basic.fundamental)" with "integral or enumeration type (3.9.1 basic.fundamental), or a class type for which a single conversion function to integral or enumeration type exists".
Proposed resolution (October, 2004):
Change 5.3.4 expr.new paragraph 6 as follows:
Every constant-expression in a direct-new-declarator shall be an integral constant expression (5.19 expr.const) and evaluate to a strictly positive value. The expression in a direct-new-declarator shallhavebe of integral type,orenumeration type(3.9.1), or a class type for which a single conversion function to integral or enumeration type exists (12.3 class.conv). If the expression is of class type, the expression is converted by calling the conversion function, and the result of the conversion is used in place of the original expression. The value of the expression shall bewith anon-negativevalue. [Example: ...
Proposed resolution (April, 2005):
Change 5.3.4 expr.new paragraph 6 as follows:
Every constant-expression in a direct-new-declarator shall be an integral constant expression (5.19 expr.const) and evaluate to a strictly positive value. The expression in a direct-new-declarator shallhave integral or enumeration type (3.9.1 basic.fundamental) with a non-negative valuebe of integral type, enumeration type, or a class type for which a single conversion function to integral or enumeration type exists (12.3 class.conv). If the expression is of class type, the expression is converted by calling that conversion function, and the result of the conversion is used in place of the original expression. If the value of the expression is negative, the behavior is undefined. [Example: ...
[Voted into WP at October 2005 meeting.]
After some discussion in comp.lang.c++.moderated we came to the conclusion that there seems to be a defect in 5.3.5 expr.delete/4, which says:
The cast-expression in a delete-expression shall be evaluated exactly once. If the delete-expression calls the implementation deallocation function (3.7.3.2), and if the operand of the delete expression is not the null pointer constant, the deallocation function will deallocate the storage referenced by the pointer thus rendering the pointer invalid. [Note: the value of a pointer that refers to deallocated storage is indeterminate. ]
In the second sentence, the term "null pointer constant" should be changed to "null pointer". In its present form, the passage claims that the deallocation function will deallocate the storage refered to by a null pointer that did not come from a null pointer constant in the delete expression. Besides, how can the null pointer constant be the operand of a delete expression, as "delete 0" is an error because delete requires a pointer type or a class type having a single conversion function to a pointer type?
See also issue 348.
Proposed resolution:
Change the indicated sentence of 5.3.5 expr.delete paragraph 4 as follows:
If the delete-expression calls the implementation deallocation function (3.7.3.2 basic.stc.dynamic.deallocation), and if the value of the operand of the delete expression is notthea null pointerconstant, the deallocation function will deallocate the storage referenced by the pointer thus rendering the pointer invalid.
Notes from October 2004 meeting:
This wording is superseded by, and this issue will be resolved by, the resolution of issue 348.
Proposed resolution (April, 2005):
This issue is resolved by the resolution of issue 348.
[Voted into WP at October 2005 meeting.]
5.5 expr.mptr.oper paragraph 5 contains the following example:
struct S {
mutable int i;
};
const S cs;
int S::* pm = &S::i; // pm refers to mutable member S::i
cs.*pm = 88; // ill-formed: cs is a const object
The const object cs is not explicitly initialized, and class S does not have a user-declared default constructor. This makes the code ill-formed as per 8.5 dcl.init paragraph 9.
Proposed resolution (April, 2005):
Change the example in 5.5 expr.mptr.oper paragraph 5 to read as follows:
struct S {
S() : i(0) { }
mutable int i;
};
void f()
{
const S cs;
int S::* pm = &S::i; // pm refers to mutable member S::i
cs.*pm = 88; // ill-formed: cs is a const object
}
[Voted into WP at October 2005 meeting.]
The problem occurs when the value of the operator is determined to be an rvalue, the selected argument is an lvalue, the type is a class type and a non-const member is invoked on the modifiable rvalue result.
struct B {
int v;
B (int v) : v(v) { }
void inc () { ++ v; }
};
struct D : B {
D (int v) : B(v) { }
};
B b1(42);
(0 ? B(13) : b1).inc();
assert(b1.v == 42);
The types of the second and third operands are the same and one is an rvalue. Nothing changes until p6 where an lvalue to rvalue conversion is performed on the third operand. 12.2 class.temporary states that an lvalue to rvalue conversion produces a temporary and there is nothing to remove it. It seems clear that the assertion must pass, yet most implementations fail.
There seems to be a defect in p3 b2 b1. First, the conditions to get here and pass the test.
If E1 and E2 have class type, and the underlying class types are the same or one is a base class of the other: E1 can be converted to match E2 if the class of T2 is the same type as, or a base class of, the class of T1, and the cv-qualification of T2 is the same cv-qualification as, or a greater cv-qualification than, the cv-qualification of T1.
If both E1 and E2 are lvalues, passing the conditions here also passes the conditions for p3 b1. Thus, at least one is an rvalue. The case of two rvalues is not interesting and the action is covered by the case when E1 is an rvalue.
(0 ? D(13) : b1).inc();
assert(b1.v == 42);
E1 is changed to an rvalue of type T2 that still refers to the original source class object (or the appropriate subobject thereof). [Note: that is, no copy is made. ]
Having changed the rvalue to base type, we are back to the above case where an lvalue to rvalue conversion is required on the third operand at p6. Again, most implementations fail.
The remaining case, E1 an lvalue and E2 an rvalue, is the defect.
D d1(42);
(0 ? B(13) : d1).inc();
assert(d1.v == 42);
The above quote states that an lvalue of type T1 is changed to an rvalue of type T2 without creating a temporary. This is in contradiction to everything else in the standard about lvalue to rvalue conversions. Most implementations pass in spite of the defect.
The usual accessible and unambiguous is missing from the base class.
There seems to be two possible solutions. Following other temporary creations would produce a temporary rvalue of type T1 and change it to an rvalue of type T2. Keeping the no copy aspect of this bullet intact would change the lvalue of type T1 to an lvalue of type T2. In this case the lvalue to rvalue conversion would happen in p6 as usual.
Suggested wording for p3 b2 b1
The base part:
If E1 and E2 have class type, and the underlying class types are the same or one is a base class of the other: E1 can be converted to match E2 if the class of T2 is the same type as, or an accessible and unambiguous base class of, the class of T1, and the cv-qualification of T2 is the same cv-qualification as, or a greater cv-qualification than, the cv-qualification of T1. If the conversion is applied:
The same type temporary version:
If E1 is an lvalue, an lvalue to rvalue conversion is applied. The resulting or original rvalue is changed to an rvalue of type T2 that refers to the same class object (or the appropriate subobject thereof). [Note: that is, no copy is made in changing the type of the rvalue. ]
The never copy version:
The lvalue(rvalue) E1 is changed to an lvalue(rvalue) of type T2 that refers to the original class object (or the appropriate subobject thereof). [Note: that is, no copy is made. ]
The test case was posted to clc++m and results for implementations were reported.
#include <cassert>
struct B {
int v;
B (int v) : v(v) { }
void inc () { ++ v; }
};
struct D : B {
D (int v) : B(v) { }
};
int main () {
B b1(42);
D d1(42);
(0 ? B(13) : b1).inc();
assert(b1.v == 42);
(0 ? D(13) : b1).inc();
assert(b1.v == 42);
(0 ? B(13) : d1).inc();
assert(d1.v == 42);
}
// CbuilderX(EDG301) FFF Rob Williscroft
// ICC-8.0 FFF Alexander Stippler
// COMO-4.301 FFF Alexander Stippler
// BCC-5.4 FFP Rob Williscroft
// BCC32-5.5 FFP John Potter
// BCC32-5.65 FFP Rob Williscroft
// VC-6.0 FFP Stephen Howe
// VC-7.0 FFP Ben Hutchings
// VC-7.1 FFP Stephen Howe
// OpenWatcom-1.1 FFP Stephen Howe
// Sun C++-6.2 PFF Ron Natalie
// GCC-3.2 PFP John Potter
// GCC-3.3 PFP Alexander Stippler
// GCC-2.95 PPP Ben Hutchings
// GCC-3.4 PPP Florian Weimer
I see no defect with regards to lvalue to rvalue conversions; however, there seems to be disagreement about what it means by implementers. It may not be surprising because 5.16 and passing a POD struct to an ellipsis are the only places where an lvalue to rvalue conversion applies to a class type. Most lvalue to rvalue conversions are on basic types as operands of builtin operators.
Notes from the March 2004 meeting:
We decided all "?" operators that return a class rvalue should copy the second or third operand to a temporary. See issue 86.
Proposed resolution (October 2004):
Change 5.16 expr.cond paragraph 3 bullet 2 sub-bullet 1 as follows:
if E1 and E2 have class type, and the underlying class types are the same or one is a base class of the other: E1 can be converted to match E2 if the class of T2 is the same type as, or a base class of, the class of T1, and the cv-qualification of T2 is the same cv-qualification as, or a greater cv-qualification than, the cv-qualification of T1. If the conversion is applied, E1 is changed to an rvalue of type T2that still refers to the original source class object (or the appropriate subobject thereof). [Note: that is, no copy is made. —end note]by copy-initializing a temporary of type T2 from E1 and using that temporary as the converted operand.
Change 5.16 expr.cond paragraph 6 bullet 1 as follows:
The second and third operands have the same type; the result is of that type. If the operands have class type, the result is an rvalue temporary of the result type, which is copy-initialized from either the second operand or the third operand depending on the value of the first operand.
Change 4.1 conv.lval paragraph 2 as follows:
The value contained in the object indicated by the lvalue is the rvalue result.When an lvalue-to-rvalue conversion occurs within the operand of sizeof (5.3.3 expr.sizeof) the value contained in the referenced object is not accessed, since that operator does not evaluate its operand. Otherwise, if the lvalue has a class type, the conversion copy-initializes a temporary of type T from the lvalue and the result of the conversion is an rvalue for the temporary. Otherwise, the value contained in the object indicated by the lvalue is the rvalue result.
[Note: this wording partially resolves issue 86. See also issue 462.]
[Voted into WP at October 2005 meeting.]
Steve Clamage: Consider this sequence of declarations:
void foo() { ... }
inline void foo();
The non-inline definition of foo precedes the inline declaration.
It seems to me this code should be ill-formed, but I could not find
anything in the standard to cover the situation.
Bjarne Stroustrup: Neither could I, so I looked in the ARM, which addressed this case (apparently for member function only) in some detail in 7.1.2 (pp103-104).
The ARM allows declaring a function inline after its initial declaration, as long as it has not been called.
Steve Clamage: If the above code is valid, how about this:
void foo() { ... } // define foo
void bar() { foo(); } // use foo
inline void foo(); // declare foo inline
Bjarne Stroustrup: ... and [the ARM] disallows declaring a function inline after it has been called.
This may still be a good resolution.
Steve Clamage: But the situation in the ARM is the reverse: Declare a function inline, and define it later (with no intervening call). That's a long-standing rule in C++, and allows you to write member function definitions outside the class.
In my example, the compiler could reasonably process the entire function as out-of-line, and not discover the inline declaration until it was too late to save the information necessary for inline generation. The equivalent of another compiler pass would be needed to handle this situation.
Bjarne Stroustrup: I see, and I think your argument it conclusive.
Steve Clamage: I'd like to open a core issue on this point, and I recommend wording along the lines of: "A function defined without an inline specifier shall not be followed by a declaration having an inline specifier."
I'd still like to allow the common idiom
class T {
int f();
};
inline int T::f() { ... }
Martin Sebor: Since the inline keyword is just a hint to the compiler, I don't see any harm in allowing the construct. Your hypothetical compiler can simply ignore the inline on the second declaration. On the other hand, I feel that adding another special rule will unnecessarily complicate the language.
Steve Clamage: The inline specifier is more than a hint. You can have multiple definitions of inline functions, but only one definition of a function not declared inline. In particular, suppose the above example were in a header file, and included multiple times in a program.
Proposed resolution (October, 2004):
Add the indicated words to 7.1.2 dcl.fct.spec paragraph 4:
An inline function shall be defined in every translation unit in which it is used and shall have exactly the same definition in every case (3.2 basic.def.odr). [Note: a call to the inline function may be encountered before its definition appears in the translation unit. —end note] If the definition of a function appears in a translation unit before its first declaration as inline, the program is ill-formed. If a function with external linkage is declared inline in one translation unit...
[Voted into WP at October 2005 meeting.]
There is a place in the Standard where overload resolution is implied but the way that a set of candidate functions is to be formed is omitted. See below.
According to the Standard, when initializing a reference to a non-volatile const class type (cv1 T1) with an rvalue expression (cv2 T2) where cv1 T1 is reference compatible with cv2 T2, the implementation shall proceed in one of the following ways (except when initializing the implicit object parameter of a copy constructor) 8.5.3 dcl.init.ref paragraph 5 bullet 2 sub-bullet 1:
While the first case is quite obvious, the second one is a bit unclear as it says "a constructor is called to copy the entire rvalue object into the temporary" without specifying how the temporary is created -- by direct-initialization or by copy-initialization? As stated in DR 152, this can make a difference when the copy constructor is declared as explicit. How should the set of candidate functions be formed? The most appropriate guess is that it shall proceed as per 13.3.1.3 over.match.ctor.
Another detail worth of note is that in the draft version of the Standard as of 2 December 1996 the second bullet read:
J. Stephen Adamczyk replied that the reason for changing "a copy constructor" to "a constructor" was to allow for member template converting constructors.
However, the new wording is somewhat in conflict with the footnote #93 that says that when initializing the implicit object parameter of a copy constructor an implementation must eventually choose the first alternative (binding without copying) to avoid infinite recursion. This seems to suggest that a copy constructor is always used for initializing the temporary of type "cv1 T2".
Furthermore, now that the set of candidate functions is not limited to only the copy constructors of T2, there might be some unpleasant consequences. Consider a rather contrived sample below:
int * pi = ::new(std::nothrow) int;
const std::auto_ptr<int> & ri = std::auto_ptr<int>(pi);
In this example the initialization of the temporary of type '<TT>const std::auto_ptr<int>' (to which 'ri' is meant to be subsequently bound) doesn't fail, as it would had the approach with copy constructors been retained, instead, a yet another temporary gets created as the well-known sequence:
std::auto_ptr<int>::operator std::auto_ptr_ref<int>()
std::auto_ptr<int>(std::auto_ptr_ref<int>)
is called (assuming, of course, that the set of candidate functions is formed as per 13.3.1.3 over.match.ctor). The second temporary is transient and gets destroyed at the end of the initialization. I doubt that this is the way that the committee wanted this kind of reference binding to go.
Besides, even if the approach restricting the set of candidates to copy constructors is restored, it is still not clear how the initialization of the temporary (to which the reference is intended to be bound) is to be performed -- using direct-initialization or copy-initialization.
Another place in the Standard that would benefit from a similar clarification is the creation of an exception object, which is delineated in 15.1 except.throw.
David Abrahams (February 2004): It appears, looking at core 291, that there may be a need to tighten up 8.5.3 dcl.init.ref/5.
Please see the attached example file, which demonstrates "move semantics" in C++98. Many compilers fail to compile test 10 because of the way 8.5.3/5 is interpreted. My problem with that interpretation is that test 20:
typedef X const XC;
sink2(XC(X()));
does compile. In other words, it *is* possible to construct the const
temporary from the rvalue. IMO, that is the proper test.
8.5.3/5 doesn't demand that a "copy constructor" is used to copy the temporary, only that a constructor is used "to copy the temporary". I hope that when the language is tightened up to specify direct (or copy initialization), that it also unambiguously allows the enclosed test to compile. Not only is it, I believe, within the scope of reasonable interpretation of the current standard, but it's an incredibly important piece of functionality for library writers and users alike.
#include <iostream>
#include <cassert>
template <class T, class X>
struct enable_if_same
{
};
template <class X>
struct enable_if_same<X, X>
{
typedef char type;
};
struct X
{
static int cnt; // count the number of Xs
X()
: id(++cnt)
, owner(true)
{
std::cout << "X() #" << id << std::endl;
}
// non-const lvalue - copy ctor
X(X& rhs)
: id(++cnt)
, owner(true)
{
std::cout << "copy #" << id << " <- #" << rhs.id << std::endl;
}
// const lvalue - T will be deduced as X const
template <class T>
X(T& rhs, typename enable_if_same<X const,T>::type = 0)
: id(++cnt)
, owner(true)
{
std::cout << "copy #" << id << " <- #" << rhs.id << " (const)" << std::endl;
}
~X()
{
std::cout << "destroy #" << id << (owner?"":" (EMPTY)") << std::endl;
}
private: // Move stuff
struct ref { ref(X*p) : p(p) {} X* p; };
public: // Move stuff
operator ref() {
return ref(this);
}
// non-const rvalue
X(ref rhs)
: id(++cnt)
, owner(rhs.p->owner)
{
std::cout << "MOVE #" << id << " <== #" << rhs.p->id << std::endl;
rhs.p->owner = false;
assert(owner);
}
private: // Data members
int id;
bool owner;
};
int X::cnt;
X source()
{
return X();
}
X const csource()
{
return X();
}
void sink(X)
{
std::cout << "in rvalue sink" << std::endl;
}
void sink2(X&)
{
std::cout << "in non-const lvalue sink2" << std::endl;
}
void sink2(X const&)
{
std::cout << "in const lvalue sink2" << std::endl;
}
void sink3(X&)
{
std::cout << "in non-const lvalue sink3" << std::endl;
}
template <class T>
void tsink(T)
{
std::cout << "in templated rvalue sink" << std::endl;
}
int main()
{
std::cout << " ------ test 1, direct init from rvalue ------- " << std::endl;
#ifdef __GNUC__ // GCC having trouble parsing the extra parens
X z2((0, X() ));
#else
X z2((X()));
#endif
std::cout << " ------ test 2, copy init from rvalue ------- " << std::endl;
X z4 = X();
std::cout << " ------ test 3, copy init from lvalue ------- " << std::endl;
X z5 = z4;
std::cout << " ------ test 4, direct init from lvalue ------- " << std::endl;
X z6(z4);
std::cout << " ------ test 5, construct const ------- " << std::endl;
X const z7;
std::cout << " ------ test 6, copy init from lvalue ------- " << std::endl;
X z8 = z7;
std::cout << " ------ test 7, direct init from lvalue ------- " << std::endl;
X z9(z7);
std::cout << " ------ test 8, pass rvalue by-value ------- " << std::endl;
sink(source());
std::cout << " ------ test 9, pass const rvalue by-value ------- " << std::endl;
sink(csource());
std::cout << " ------ test 10, pass rvalue by overloaded reference ------- " << std::endl;
// This one fails in Comeau's strict mode due to 8.5.3/5. GCC 3.3.1 passes it.
sink2(source());
std::cout << " ------ test 11, pass const rvalue by overloaded reference ------- " << std::endl;
sink2(csource());
#if 0 // These two correctly fail to compile, just as desired
std::cout << " ------ test 12, pass rvalue by non-const reference ------- " << std::endl;
sink3(source());
std::cout << " ------ test 13, pass const rvalue by non-const reference ------- " << std::endl;
sink3(csource());
#endif
std::cout << " ------ test 14, pass lvalue by-value ------- " << std::endl;
sink(z5);
std::cout << " ------ test 15, pass const lvalue by-value ------- " << std::endl;
sink(z7);
std::cout << " ------ test 16, pass lvalue by-reference ------- " << std::endl;
sink2(z4);
std::cout << " ------ test 17, pass const lvalue by const reference ------- " << std::endl;
sink2(z7);
std::cout << " ------ test 18, pass const lvalue by-reference ------- " << std::endl;
#if 0 // correctly fails to compile, just as desired
sink3(z7);
#endif
std::cout << " ------ test 19, pass rvalue by value to template param ------- " << std::endl;
tsink(source());
std::cout << " ------ test 20, direct initialize a const A with an A ------- " << std::endl;
typedef X const XC;
sink2(XC(X()));
}
Proposed Resolution:
(As proposed by N1610 section 5, with editing.)
Change paragraph 5, second bullet, first sub-bullet, second sub-sub-bullet as follows:
A temporary of type "cv1 T2" [sic] is created, and a constructor is called to copy the entire rvalue object into the temporaryvia copy-initialization from the entire rvalue object. The reference is bound to the temporary or to a sub-object within the temporary.
The text immediately following that is changed as follows:
The constructor that would be used to make the copy shall be callable whether or not the copy is actually done.The constructor and any conversion function that would be used in the initialization shall be callable whether or not the temporary is actually created.
Note, however, that the way the core working group is leaning on issue 391 (i.e., requiring direct binding) would make this change unnecessary.
Proposed resolution (April, 2005):
This issue is resolved by the resolution of issue 391.
[Voted into WP at October 2005 meeting.]
After some email exchanges with Rani Sharoni, I've come up with the following proposal to allow reference binding to non-copyable rvalues in some cases. Rationale and some background appear afterwards.
---- proposal ----
Replace the section of 8.5.3 dcl.init.ref paragraph 5 that begins "If the initializer expression is an rvalue" with the following:
---- rationale ----
class nc {
nc (nc const &); // private, nowhere defined
public:
nc ();
nc const &by_ref () const { return *this; }
};
void f () {
void g (nc const &);
g (nc()); // Ill-formed
g (nc().by_ref()); // Ok - binds directly to rvalue
}
Forcing a direct binding in this way is possible wherever
the lifetime of the reference does not extend beyond the
containing full expression, since the reference returned
by the member function remains valid for this long.
---- background ----
The proposal is based on a recent discussion in this group. I originally wanted to leave the implementation free to copy the rvalue if there was a callable copy constructor, and only have to bind directly if none was callable. Unfortunately, a traditional compiler can't always tell whether a function is callable or not, e.g. if the copy constructor is declared but not defined. Rani pointed this out in an example, and suggested that maybe trivial copy constructors should still be allowed (by extension, maybe wherever the compiler can determine callability). I've gone with this version because it's simpler, and I also figure the "as if" rule gives the compiler some freedom with POD types anyway.
Notes from April 2003 meeting:
We agreed generally with the proposal. We were unsure about the need for the restriction regarding long-lived references. We will check with the proposer about that.
Jason Merrill points out that the test case in issue 86 may be a case where we do not want to require direct binding.
Further information from Rani Sharoni (April 2003):
I wasn't aware about the latest suggestion of Raoul as it appears in core issue 391. In our discussions we tried to formulate a different proposal.
The rational, as we understood, behind the implementation freedom to make an extra copying (8.5.3/5/2/12) of the rvalue is to allow return values in registers which on some architectures are not addressable. The example that Raoul and I presented shows that this implementation freedom is not always possible since we can "force" the rvalue to be addressable using additional member function (by_ref). The example only works for short lived rvalues and this is probably why Raoul narrow the suggestion.
I had different rational which was related to the implementation of conditional operator in VC. It seems that when conditional operator is involved VC does use an extra copying when the lifetime of the temporary is extended:
struct A { /* ctor with side effect */};
void f(A& x) {
A const& r = cond ? A(1) : x; // VC actually make an extra copy of
// the rvalue A(1)
}
I don't know what the consideration behind the VC implementation was (I saw open bug on this issue) but it convinced me to narrow the suggestion.
IMHO such limitation seems to be too strict because it might limit the optimizer since returning class rvalues in registers might be useful (although I'm not aware about any implementation that actually does it). My suggestion was to forbid the extra copying if the ctor is not viable (e.g. A::A(A&) ). In this case the implementation "freedom" doesn't exist (since the code might not compile) and only limits the programmer freedom (e.g. Move Constructors - http://www.cuj.com/experts/2102/alexandr.htm).
Core issue 291 is strongly related to the above issue and I personally prefer to see it resolved first. It seems that VC already supports the resolution I prefer.
Notes from October 2003 meeting:
We ended up feeling that this is just one of a number of cases of optimizations that are widely done by compilers and allowed but not required by the standard. We don't see any strong reason to require compilers to do this particular optimization.
Notes from the March 2004 meeting:
After discussing issue 450, we found ourselves reconsidering this, and we are now inclined to make a change to require the direct binding in all cases, with no restriction on long-lived references. Note that such a change would eliminate the need for a change for issue 291.
Proposed resolution (October, 2004):
Change 8.5.3 dcl.init.ref paragraph 5 bullet 2 sub-bullet 1 as follows:
If the initializer expression is an rvalue, with T2 a class type, and "cv1 T1" is reference-compatible with "cv2 T2", the reference is bound to the object represented by the rvalue (see 3.10 basic.lval) or to a sub-object within that object.in one of the following ways (the choice is implementation-defined):[Example:The constructor that would be used to make the copy shall be callable whether or not the copy is actually done.
- The reference is bound to the object represented by the rvalue (see 3.10 basic.lval) or to a sub-object within that object.
- A temporary of type "cv1 T2" [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. The reference is bound to the temporary or to a sub-object within the temporary.
struct A { }; struct B : public A { } b; extern B f(); const A& rca = f (); // Bound—end example]Either boundto the A sub-object of the B rvalue, // or the entire B object is copied and the reference // is bound to the A sub-object of the copy
[This resolution also resolves issue 291.]
[Voted into WP at October 2005 meeting.]
It's unclear whether the following is valid:
const int N = 10;
const int M = 20;
typedef int T;
void f(T const (&x)[N][M]){}
struct X {
int i[10][20];
};
X g();
int main()
{
f(g().i);
}
When you run this through 8.5.3 dcl.init.ref, you sort of end up falling off the end of the standard's description of reference binding. The standard says in the final bullet of paragraph 5 that an array temporary should be created and copy-initialized from the rvalue array, which seems implausible.
I'm not sure what the right answer is. I think I'd be happy with allowing the binding in this case. We would have to introduce a special case like the one for class rvalues.
Notes from the March 2004 meeting:
g++ and EDG give an error. Microsoft (8.0 beta) and Sun accept the example. Our preference is to allow the direct binding (no copy). See the similar issue with class rvalues in issue 391.
Proposed resolution (October, 2004):
Insert a new bullet in 8.5.3 dcl.init.ref paragraph 5 bullet 2 before sub-bullet 2 (which begins, “Otherwise, a temporary of type ‘cv1 T1’ is created...”):
If the initializer expression is an rvalue, with T2 an array type, and “cv1 T1” is reference-compatible with “cv2 T2”, the reference is bound to the object represented by the rvalue (see 3.10 basic.lval).
Change 3.10 basic.lval paragraph 2 as follows:
An lvalue refers to an object or function. Some rvalue expressions — those of (possibly cv-qualified) class or array typeor cv-qualified class type— also refer to objects.
[Voted into WP at October 2005 meeting.]
Is the following well-formed?
class policy {};
class policy_interface {};
template <class POLICY_INTERFACE>
class aph {
protected:
typedef POLICY_INTERFACE PI;
};
template <class POLICY, class BASE, class PI = typename BASE::PI>
class ConcretePolicyHolder : public BASE, protected POLICY
{};
ConcretePolicyHolder < policy , aph < policy_interface > > foo;
void xx() { }
The issue is whether the access to the default argument type BASE::PI is checked before or after it is known that BASE is a base class of the template. To some extent, one needs to develop the list of template arguments (and therefore evaluate the default argument) before one can instantiate the template, and one does not know what base classes the template has until it has been instantiated.
Notes from April 2003 meeting:
Shortened example:
class B {
protected:
typedef int A;
};
template<class T, class U = typename T::A>
class X : public T
{ };
The convincing argument here is that if we had only the declaration of the template (including the default argument), we would expect it to be usable in exactly the same way as the version with the definition. However, the special access needed is visible only when the definition is available. So the above should be an error, and information from the definition cannot affect the access of the default arguments.
Proposed Resolution (April 2003):
Add a new paragraph 16 to 14.1 temp.param after paragraph 15:
Since a default template-argument is encountered before any
base-clause there is no special access to members used in a default
template-argument. [Example:
class B {};
template <class T> class C {
protected:
typedef T TT;
};
template <class U, class V = typename U::TT>
class D : public U {};
D <C<B> > d; // access error, C::TT is protected
--- end example]
Notes from October 2003 meeting:
We decided that template parameter default arguments should have their access checked in the context where they appear without special access for the entity declared (i.e., they are different than normal function default arguments). One reason: we don't know the instance of the template when we need the value. Second reason: compilers want to parse and throw away the form of the template parameter default argument, not save it and check it for each instantiation.
Class templates should be treated the same as function templates in this regard. The base class information is in the same category as friend declarations inside the class itself -- not available. If the body were used one would need to instantiate it in order to know whether one can name it.
Proposed resolution (October, 2004):
Add the following as a new paragraph following the last paragraph of 11 class.access (but before the new paragraph inserted by the resolution of issue 372, if adopted):
The names in a default template-argument (14.1 temp.param) have their access checked in the context in which they appear rather than at any points of use of the default template-argument. [Example:
class B {}; template <class T> class C { protected: typedef T TT; }; template <class U, class V = typename U::TT> class D : public U {}; D <C<B> >* d; // access error, C::TT is protected—end example]
[Voted into WP at October 2005 meeting.]
The standard does not permit a null value to be used as a nontype template argument for a nontype template parameter that is a pointer.
This code is accepted by EDG, Microsoft, Borland and Cfront, but rejected by g++ and Sun:
template <int *p> struct A {};
A<(int*)0> ai;
I'm not sure this was ever explicitly considered by the committee. Is there any reason to permit this kind of usage?
Jason Merrill: I suppose it might be useful for a program to be able to express a degenerate case using a null template argument. I think allowing it would be harmless.
Notes from October 2004 meeting:
CWG decided that it would be desirable to allow null pointers as nontype template arguments, even though they are not representable in some current ABIs. There was some discussion over whether to allow a bare 0 to be used with a pointer nontype template parameter. The following case was decisive:
template<int i> void foo();
template<int* i> void foo();
...
foo<0>();
The current wording of 14.3 temp.arg paragraph 7 disambiguates the function call in favor of the int version. If the null pointer conversion were allowed for pointer nontype template parameters, this case would become ambiguous, so it was decided to require a cast.
Proposed resolution (April, 2005):
In 14.3.2 temp.arg.nontype paragraph 1, insert the following after the third bullet:
Add the indicated text to the note in the second bullet of 14.3.2 temp.arg.nontype paragraph 5:
[Note: In particular, neither the null pointer conversion (4.10 conv.ptr) nor the derived-to-base conversion (4.10 conv.ptr) are applied. Although 0 is a valid template-argument for a non-type template-parameter of integral type, it is not a valid template-argument for a non-type template-parameter of pointer type. However, (int*)0 is a valid template-argument for a non-type template-parameter of type “pointer to int.” —end note]
Replace the normative wording of 14.4 temp.type paragraph 1 with the following:
Two template-ids refer to the same class or function if
- their template-names refer to the same template, and
- their corresponding type template-arguments are the same type, and
- their corresponding non-type template-arguments of integral or enumeration type have identical values, and
- their corresponding non-type template-arguments of pointer type refer to the same external object or function or are both the null pointer value, and
- their corresponding non-type template-arguments of pointer-to-member type refer to the same class member or are both the null member pointer value, and
- their corresponding non-type template-argumentss for template parameters of reference type refer to the same external object or function, and
- their corresponding template template-arguments refer to the same template.
[Voted into WP at October 2005 meeting.]
P. J. Plauger, among others, has noted that typename is hard to use, because in a given context it's either required or forbidden, and it's often hard to tell which. It would make life easier for programmers if typename could be allowed in places where it is not required, e.g., outside of templates.
Notes from the April 2003 meeting:
There was unanimity on relaxing this requirement on typename. The question was how much to relax it. Everyone agreed on allowing it on all qualified names, which is an easy fix (no syntax change required). But should it be allowed other places? P.J. Plauger said he'd like to see it allowed anywhere a type name is allowed, and that it could actually be a decades-late assist for the infamous "the ice is thin here" typedef problem noted in K&R I.
Proposed resolution (April 2003):
Replace the text at the start of 14.6 temp.res paragraph 3:
A qualified-id that refers to a type and in which the nested-name-specifier depends on a template-parameter (14.6.2 temp.dep) shall be prefixed by the keyword typename to indicate that the qualified-id denotes a type, forming an elaborated-type-specifier (7.1.5.3 dcl.type.elab).
With:
The keyword typename can only be applied to a qualified-id. A qualified-id that refers to a type and in which the nested-name-specifier depends on a template-parameter (14.6.2 temp.dep) shall be prefixed by the keyword typename to indicate that the qualified-id denotes a type, forming an elaborated-type-specifier (7.1.5.3 dcl.type.elab). If a qualified-id which has been prefixed by the keyword typename does not denote a type the program is ill-formed. [ Note: The keyword is only required on a qualified-id within a template declaration or definition in which the nested-name-specifier depends on a template-parameter. ]
Remove 14.6 temp.res paragraph 5:
The keyword typename shall only be used in template declarations and definitions, including in the return type of a function template or member function template, in the return type for the definition of a member function of a class template or of a class nested within a class template, and in the type-specifier for the definition of a static member of a class template or of a class nested within a class template. The keyword typename shall be applied only to qualified names, but those names need not be dependent. The keyword typename shall be used only in contexts in which dependent names can be used. This includes template declarations and definitions but excludes explicit specialization declarations and explicit instantiation declarations. The keyword typename is not permitted in a base-specifier or in a mem-initializer; in these contexts a qualified-id that depends on a template-parameter (temp.dep) is implicitly assumed to be a type name.
Note: the claim here that a qualified name preceded by typename forms an elaborated type specifier conflicts with the changes made in issue 254 (see N1376=02-0034), which introduces typename-specifier.
Notes from October 2003 meeting:
We considered whether typename should be allowed in more places, and decided we only wanted to allow it in qualified names (for now at least).
Core issue 254 changed elaborated-type-specifier to typename-specifier. It also changed 14.6 temp.res paragraph 5, which this proposed resolution deletes.
See also issue 468.
Proposed resolution (October, 2004):
Change 14.6 temp.res paragraph 3 as follows:
AWhen a qualified-idthat refers to a type andin which the nested-name-specifier depends on a template-parameter (14.6.2 temp.dep) is intended to refer to a type, it shall be prefixed by the keyword typenameto indicate that the qualified-id denotes a type, forming a typename-specifier. If the qualified-id in a typename-specifier does not denote a type, the program is ill-formed.
Change 14.6 temp.res paragraph 5 as follows:
The keyword typename shall only be used in template declarations and definitions, including in the return type of a function template or member function template, in the return type for the definition of a member function of a class template or of a class nested within a class template, and in the type-specifier for the definition of a static member of a class template or of a class nested within a class template. The keyword typename shall be applied only to qualified names, but those names need not be dependent. The keyword typename shall be used only in contexts in which dependent names can be used. This includes template declarations and definitions but excludes explicit specialization declarations and explicit instantiation declarations.A qualified name used as the name in a mem-initializer-id, a base-specifier, or an elaborated-type-specifier is implicitly assumed to name a type, without the use of the typename keyword. [Note: the typename keyword is not permitted by the syntax of these constructs. —end note]
[Voted into WP at October 2005 meeting.]
As far as I can tell, the standard doesn't say whether "offsetof(...)" is type-dependent. In the abstract, it shouldn't be -- an "offsetof" expression is always of type "size_t". But the standard doesn't say to what the definition of the macro is, so I don't think one can deduce that it will always be considered non-dependent by a conforming compiler.
John Spicer: (1) I agree that you can't know if offsetof is dependent because you don't know what it expands to. (2) In principle, offsetof should be like sizeof -- it is value-dependent if its argument is type-dependent.
Mark Mitchell: I think we should say that: (a) offsetof is not type-dependent, and (b) offsetof is value dependent iff the first argument is type-dependent
Everyone is using slightly different builtins to implement this functionality, and I don't think that there's any guarantee that they're all behaving the same here.
Notes from the March 2004 meeting:
Note that any such requirement would be in the library section, not core.
Proposed resolution (October, 2004):
At the end of 14.6.2.2 temp.dep.expr paragraph 4, add after the list that ends with throw assignment-expression:
[Note: For the standard library macro offsetof, see 18.1 lib.support.types. —end note]
At the end of 14.6.2.3 temp.dep.constexpr paragraph 2, add after the list that ends with sizeof(type-id):
[Note: For the standard library macro offsetof, see 18.1 lib.support.types. —end note]
In 18.1 lib.support.types paragraph 4, replace
The macro offsetof accepts a restricted set of type arguments in this International Standard. If type is not a POD structure or a POD union the results are undefined. The result of applying the offsetof macro to a field that is a static data member or a function member is undefined.
with
The macro offsetof(type, member-designator) accepts a restricted set of type arguments in this International Standard. If type is not a POD structure or a POD union (clause 9 class), the results are undefined. The expression offsetof(type, member-designator) is never type-dependent (14.6.2.2 temp.dep.expr) and it is value-dependent (14.6.2.3 temp.dep.constexpr) if and only if type is dependent. The result of applying the offsetof macro to a field that is a static data member or a function member is undefined.
[Note: the original wording shown here reflects the resolutions of library issues 306 and 449.]
[Voted into WP at October 2005 meeting.]
The example in 14.6 temp.res paragraph 9 is incorrect, according to 14.6.4.2 temp.dep.candidate . The example reads,
void f(char);
template <class T> void g(T t)
{
f(1); // f(char);
f(T(1)); // dependent
f(t); // dependent
dd++; // not dependent
// error: declaration for dd not found
}
void f(int);
double dd;
void h()
{
g(2); // will cause one call of f(char) followed
// by two calls of f(int)
g('a'); // will cause three calls of f(char)
}
Since 14.6.4.2
temp.dep.candidate
says that
only Koenig lookup is done from the instantiation context, and since
3.4.2
basic.lookup.argdep
says that
fundamental types have no associated namespaces, either the example is
incorrect (and f(int) will never be called) or the
specification in 14.6.4.2
temp.dep.candidate
is incorrect.
Notes from 04/00 meeting:
The core working group agreed that the example as written is incorrect and should be reformulated to use a class type instead of a fundamental type. It was also decided to open a new issue dealing more generally with Koenig lookup and fundamental types.
(See also issues 213 and 225.)
Proposed resolution (April, 2005):
Change the example in 14.6 temp.res paragraph 9 as follows:
void f(char);
template <class T> void g(T t)
{
f(1); // f(char);
f(T(1)); // dependent
f(t); // dependent
dd++; // not dependent
// error: declaration for dd not found
}
enum E { e };
void f(intE);
double dd;
void h()
{
g(2e); // will cause one call of f(char) followed
// by two calls of f(intE)
g('a'); // will cause three calls of f(char)
}
[Voted into WP at October 2005 meeting.]
In 14.7.2 temp.explicit paragraph 7 we read:
The explicit instantiation of a class template specialization implies the instantiation of all of its members not previously explicitly specialized in the translation unit containing the explicit instantiation.
Is "member" intended to mean "non-inherited member?" If yes, maybe it should be clarified since 10 class.derived paragraph 1 says,
Unless redefined in the derived class, members of a base class are also considered to be members of the derived class.
Proposed resolution (October, 2004):
Fixed by the resolution of issue 470.
[Voted into WP at October 2005 meeting.]
14.7.2 temp.explicit paragraph 7 says,
The explicit instantiation of a class template specialization implies the instantiation of all of its members not previously explicitly specialized in the translation unit containing the explicit instantiation.
It's not clear whether this “implied” instantiation is implicit or explicit instantiation. It makes a difference in cases like the following:
template <typename T> struct foo {
struct bar { };
};
template struct foo<int>; // #1
template struct foo<int>::bar; // #2
If the instantiation of foo<int>::bar implied by #1 is implicit, the explicit instantiation in #2 is well-formed. Otherwise, #2 violates the requirement in 14.7 temp.spec that
No program shall explicitly instantiate any template more than once ... for a given set of template-arguments.
It's also unclear whether the implied instantiation applies only to direct members of the class template or to inherited members, as well.
John Spicer: I have always interpreted this as meaning only the members declared in the class, not those inherited from other classes. This is what EDG does, and appears to be what g++, Microsoft and Sun do, too. I also think this is the correct thing for the Standard to require. If I were to derive a class from a class in the standard library, an explicit instantiation of my class should not cause the explicit instantiation of things in the standard library (because the library might provide such explicit instantiations, thus causing my program to run afoul of the "can't instantiate more than once" rule).
Proposed resolution (October, 2004):
Change 14.7.2 temp.explicit paragraph 7 as follows:
The explicit instantiation of a class template specializationimplies the instantiation of allalso explicitly instantiates each of its membersnot(not including members inherited from base classes) whose definition is visible at the point of instantiation and that has not been previously explicitly specialized in the translation unit containing the explicit instantiation.
[Voted into WP at March 2004 meeting.]
Should this program do what its author obviously expects? As far as I can tell, the standard says that the point of instantiation for Fib<n-1>::Value is the same as the point of instantiation as the enclosing specialization, i.e., Fib<n>::Value. What in the standard actually says that these things get initialized in the right order?
template<int n>
struct Fib { static int Value; };
template <>
int Fib<0>::Value = 0;
template <>
int Fib<1>::Value = 1;
template<int n>
int Fib<n>::Value = Fib<n-1>::Value + Fib<n-2>::Value;
int f ()
{
return Fib<40>::Value;
}
John Spicer: My opinion is that the standard does not specify the behavior of this program. I thought there was a core issue related to this, but I could not find it. The issue that I recall proposed tightening up the static initialization rules to make more cases well defined.
Your comment about point of instantiation is correct, but I don't think that really matters. What matters is the order of execution of the initialization code at execution time. Instantiations don't really live in "translation units" according to the standard. They live in "instantiation units", and the handling of instantiation units in initialization is unspecified (which should probably be another core issue). See 2.1 lex.phases paragraph 8.
Notes from October 2002 meeting:
We discussed this and agreed that we really do mean the the order is unspecified. John Spicer will propose wording on handling of instantiation units in initialization.
Proposed resolution (April 2003):
TC1 contains the following text in 3.6.2 basic.start.init paragraph 1:
Objects with static storage duration defined in namespace scope in the same translation unit and dynamically initialized shall be initialized in the order in which their definition appears in the translation unit.
This was revised by issue 270 to read:
Dynamic initialization of an object is either ordered or unordered. Explicit specializations and definitions of class template static data members have ordered initialization. Other class template static data member instances have unordered initialization. Other objects defined in namespace scope have ordered initialization. Objects defined within a single translation unit and with ordered initialization shall be initialized in the order of their definitions in the translation unit. The order of initialization is unspecified for objects with unordered initialization and for objects defined in different translation units.
This addresses this issue but while reviewing this issue some additional changes were suggested for the above wording:
Dynamic initialization of an object is either ordered or unordered. Definitions of explicitly specializedExplicit specializations and definitions ofclass template static data members have ordered initialization. Other class template static data members (i.e., implicitly or explicitly instantiated specializations)instanceshave unordered initialization. Other objects defined in namespace scope have ordered initialization. Objects defined within a single translation unit and with ordered initialization shall be initialized in the order of their definitions in the translation unit. The order of initialization is unspecified for objects with unordered initialization and for objects defined in different translation units.
[Moved to DR at October 2002 meeting.]
3.2 basic.def.odr paragraph 2 says that a deallocation function is "used" by a new-expression or delete-expression appearing in a potentially-evaluated expression. 3.2 basic.def.odr paragraph 3 requires only that "used" functions be defined.
This wording runs afoul of the typical implementation technique for polymorphic delete-expressions in which the deallocation function is invoked from the virtual destructor of the most-derived class. The problem is that the destructor must be defined, because it's virtual, and if it contains an implicit reference to the deallocation function, the deallocation function must also be defined, even if there are no relevant new-expressions or delete-expressions in the program.
For example:
struct B { virtual ~B() { } };
struct D: B {
void operator delete(void*);
~D() { }
};
Is it required that D::operator delete(void*) be defined, even if no B or D objects are ever created or deleted?
Suggested resolution: Add the words "or if it is found by the lookup at the point of definition of a virtual destructor (12.4 class.dtor)" to the specification in 3.2 basic.def.odr paragraph 2.
Notes from 04/01 meeting:
The consensus was in favor of requiring that any declared non-placement operator delete member function be defined if the destructor for the class is defined (whether virtual or not), and similarly for a non-placement operator new if a constructor is defined.
Proposed resolution (10/01):
In 3.2 basic.def.odr paragraph 2, add the indicated text:
An allocation or deallocation function for a class is used by a new expression appearing in a potentially-evaluated expression as specified in 5.3.4 expr.new and 12.5 class.free. A deallocation function for a class is used by a delete expression appearing in a potentially-evaluated expression as specified in 5.3.5 expr.delete and 12.5 class.free. A non-placement allocation or deallocation function for a class is used by the definition of a constructor of that class. A non-placement deallocation function for a class is used by the definition of the destructor of that class, or by being selected by the lookup at the point of definition of a virtual destructor (12.4 class.dtor). [Footnote: An implementation is not required to call allocation and deallocation functions from constructors or destructors; however, this is a permissible implementation technique.]
[Moved to DR at October 2002 meeting.]
3.2 basic.def.odr paragraph 4 has a note listing the contexts that require a class type to be complete. It does not list use as a base class as being one of those contexts.
Proposed resolution (10/01):
In 3.2 basic.def.odr paragraph 4 add a new bullet at the end of the note as the next-to-last bullet:
[Voted into WP at March 2004 meeting.]
Consider the following translation unit:
template<class T> struct S {
void f(union U*); // (1)
};
template<class T> void S<T>::f(union U*) {} // (2)
U *p; // (3)
Does (1) introduce U as a visible name in the surrounding namespace scope?
If not, then (2) could presumably be an error since the "union U" in that definition does not find the same type as the declaration (1).
If yes, then (3) is OK too. However, we have gone through much trouble to allow template implementations that do not pre-parse the template definitions, but requiring (1) to be visible would change that.
A slightly different case is the following:
template<typename> void f() { union U *p; }
U *q; // Should this be valid?
Notes from October 2003 meeting:
There was consensus that example 1 should be allowed. (Compilers already parse declarations in templates; even MSVC++ 6.0 accepts this case.) The vote was 7-2.
Example 2, on the other hand, is wrong; the union name goes into a block scope anyway.
Proposed resolution:
In 3.3.1 basic.scope.pdecl change the second bullet of paragraph 5 as follows:
for an elaborated-type-specifier of the formclass-key identifierif the elaborated-type-specifier is used in the decl-specifier-seq or parameter-declaration-clause of a function defined in namespace scope, the identifier is declared as a class-name in the namespace that contains the declaration; otherwise, except as a friend declaration, the identifier is declared in the smallest non-class, non-function-prototype scope that contains the declaration. [Note: These rules also apply within templates.] [Note: ...]
[Voted into WP at March 2004 meeting.]
Consider the following example (inspired by a question from comp.lang.c++.moderated):
template<typename> struct B {};
template<typename T> struct D: B<D> {};
Most (all?) compilers reject this code because D is handled as a template name rather than as the injected class name.
9 class/2 says that the injected class name is "inserted into the scope of the class."
3.3.6 basic.scope.class/1 seems to be the text intended to describe what "scope of a class" means, but it assumes that every name in that scope was introduced using a "declarator". For an implicit declaration such as the injected-class name it is not clear what that means.
So my questions:
John Spicer: I do not believe the injected class name should be available in the base specifier. I think the semantics of injected class names should be as if a magic declaration were inserted after the opening "{" of the class definition. The injected class name is a member of the class and members don't exist at the point where the base specifiers are scanned.
John Spicer: I believe the 3.3.6 basic.scope.class wording should be updated to reflect the fact that not all names come from declarators.
Notes from October 2003 meeting:
We agree with John Spicer's suggested answers above.
Proposed Resolution (October 2003):
The answer to question 1 above is No and no change is required.
For question 1, change 3.3.6 basic.scope.class paragraph 1 rule 1 to:
1) The potential scope of a name declared in a class consists not only of the declarative region following the name's point of declarationdeclarator, but also of all function bodies, default arguments, and constructor ctor-initializers in that class (including such things in nested classes). The point of declaration of an injected-class-name (clause 9 class) is immediately following the opening brace of the class definition.
(Note that this change overlaps a change in issue 417.)
Also change 3.3.1 basic.scope.pdecl by adding a new paragraph 8 for the injected-class-name case:
The point of declaration for an injected-class-name (clause 9 class) is immediately following the opening brace of the class definition.
Alternatively this paragraph could be added after paragraph 5 and before the two note paragraphs (i.e. it would become paragraph 5a).
[Moved to DR at 10/01 meeting.]
The example in 3.4.1 basic.lookup.unqual paragraph 3 is incorrect:
typedef int f;
struct A {
friend void f(A &);
operator int();
void g(A a) {
f(a);
}
};
Regardless of the resolution of other issues concerning the lookup
of names in friend declarations, this example is ill-formed
(the function and the typedef cannot exist in the same scope).
One possible repair of the example would be to make f a class with a constructor taking either A or int as its parameter.
(See also issues 95, 136, 138, 143, 165, and 166.)
Proposed resolution (04/01):
Change the example in 3.4.1 basic.lookup.unqual paragraph 3 to read:
typedef int f;
namespace N {
struct A {
friend int f(A &);
operator int();
void g(A a) {
int i = f(a);
// f is the typedef, not the friend function:
// equivalent to int(a)
}
};
}
Delete the sentence immediately following the example:
The expression f(a) is a cast-expression equivalent to int(a).
[Moved to DR at 4/02 meeting.]
Paragraphs 1 and 2 of 3.4.2 basic.lookup.argdep say, in part,
When an unqualified name is used as the postfix-expression in a function call (5.2.2 expr.call )... namespace-scope friend function declarations (11.4 class.friend ) not otherwise visible may be found... the set of declarations found by the lookup of the function name [includes] the set of declarations found in the... classes associated with the argument types.The most straightforward reading of this wording is that if a function of namespace scope (as opposed to a class member function) is declared as a friend in a class, and that class is an associated class in a function call, the friend function will be part of the overload set, even if it is not visible to normal lookup.
Consider the following example:
namespace A {
class S;
};
namespace B {
void f(A::S);
};
namespace A {
class S {
int i;
friend void B::f(S);
};
}
void g() {
A::S s;
f(s); // should find B::f(A::S)
}
This example would seem to satisfy the criteria from
3.4.2
basic.lookup.argdep
:
A::S is an associated class of the argument, and
A::S has a
friend declaration of the namespace-scope function
B::f(A::S),
so Koenig lookup should include B::f(A::S) as part of the
overload set in the call.
Another interpretation is that, instead of finding the friend declarations in associated classes, one only looks for namespace-scope functions, visible or invisible, in the namespaces of which the the associated classes are members; the only use of the friend declarations in the associated classes is to validate whether an invisible function declaration came from an associated class or not and thus whether it should be included in the overload set or not. By this interpretation, the call f(s) in the example will fail, because B::f(A::S) is not a member of namespace A and thus is not found by the lookup.
Notes from 10/99 meeting: The second interpretation is correct. The wording should be revised to make clear that Koenig lookup works by finding "invisible" declarations in namespace scope and not by finding friend declarations in associated classes.
Proposed resolution (04/01): The "associated classes" are handled adequately under this interpretation by 3.4.2 basic.lookup.argdep paragraph 3, which describes the lookup in the associated namespaces as including the friend declarations from the associated classes. Other mentions of the associated classes should be removed or qualified to avoid the impression that there is a lookup in those classes:
In 3.4.2 basic.lookup.argdep, change
When an unqualified name is used as the postfix-expression in a function call (5.2.2 expr.call), other namespaces not considered during the usual unqualified lookup (3.4.1 basic.lookup.unqual) may be searched, and namespace-scope friend function declarations (11.4 class.friend) not otherwise visible may be found.
to
When an unqualified name is used as the postfix-expression in a function call (5.2.2 expr.call), other namespaces not considered during the usual unqualified lookup (3.4.1 basic.lookup.unqual) may be searched, and in those namespaces, namespace-scope friend function declarations (11.4 class.friend) not otherwise visible may be found.
In 3.4.2 basic.lookup.argdep paragraph 2, delete the words and classes in the following two sentences:
If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated namespacesand classesare not considered. Otherwise the set of declarations found by the lookup of the function name is the union of the set of declarations found using ordinary unqualified lookup and the set of declarations found in the namespacesand classesassociated with the argument types.
(See also issues 95, 136, 138, 139, 165, 166, and 218.)
[Voted into WP at March 2004 meeting.]
Spun off from issue 384.
3.4.2 basic.lookup.argdep says:
If T is a template-id, its associated namespaces and classes are the namespace in which the template is defined; for member templates, the member template's class; the namespaces and classes associated with the types of the template arguments provided for template type parameters (excluding template template parameters); the namespaces in which any template template arguments are defined; and the classes in which any member templates used as template template arguments are defined. [Note: non-type template arguments do not contribute to the set of associated namespaces. ]There is a problem with the term "is a template-id". template-id is a syntactic construct and you can't really talk about a type being a template-id. Presumably, this is intended to mean "If T is the type of a class template specialization ...".
Proposed Resolution (October 2003):
In 3.4.2 basic.lookup.argdep, paragraph 2, bullet 8, replace
If T is a template-id ...with
If T is a class template specialization ...
[Voted into WP at April 2003 meeting.]
Can a typedef T to a cv-qualified class type be used in a qualified name T::x?
struct A { static int i; };
typedef const A CA;
int main () {
CA::i = 0; // Okay?
}
Suggested answer: Yes. All the compilers I tried accept the test case.
Proposed resolution (10/01):
In 3.4.3.1 class.qual paragraph 1 add the indicated text:
If the nested-name-specifier of a qualified-id nominates a class, the name specified after the nested-name-specifier is looked up in the scope of the class (10.2 class.member.lookup), except for the cases listed below. The name shall represent one or more members of that class or of one of its base classes (clause 10 class.derived). If the class-or-namespace-name of the nested-name-specifier names a cv-qualified class type, it nominates the underlying class (the cv-qualifiers are ignored).
Notes from 4/02 meeting:
There is a problem in that class-or-namespace-name does not include typedef names for cv-qualified class types. See 7.1.3 dcl.typedef paragraph 4:
Argument and text removed from proposed resolution (October 2002):
7.1.3 dcl.typedef paragraph 5:
Here's a good question: in this example, should X be used as a name-for-linkage-purposes (FLP name)?
typedef class { } const X;
Because a type-qualifier is parsed as a decl-specifier, it isn't possible to declare cv-qualified and cv-unqualified typedefs for a type in a single declaration. Also, of course, there's no way to declare a typedef for the cv-unqualified version of a type for which only a cv-qualified version has a name. So, in the above example, if X isn't used as the FLP name, then there can be no FLP name. Also note that a FLP name usually represents a parameter type, where top-level cv-qualifiers are usually irrelevant anyway.
Data points: for the above example, Microsoft uses X as the FLP name; GNU and EDG do not.
My recommendation: for consistency with the direction we're going on this issue, for simplicity of description (e.g., "the first class-name declared by the declaration"), and for (very slightly) increased utility, I think Microsoft has this right.
If the typedef declaration defines an unnamed class type (or enum type), the first typedef-name declared by the declaration tobehave thatclasstype(or enum type)or a cv-qualified version thereof is used to denote the class type (or enum type) for linkage purposes only (3.5 basic.link). [Example: ...
Proposed resolution (October 2002):
3.4.4 basic.lookup.elab paragraphs 2 and 3:
This sentence is deleted twice:
...If this name lookup finds a typedef-name, the elaborated-type-specifier is ill-formed....
Note that the above changes are included in N1376 as part of the resolution of issue 245.
5.1 expr.prim paragraph 7:
This is only a note, and it is at least incomplete (and quite possibly inaccurate), despite (or because of) its complexity. I propose to delete it.
... [Note: a typedef-name that names a class is a class-name (9.1 class.name).Except as the identifier in the declarator for a constructor or destructor definition outside of a class member-specification (12.1 class.ctor, 12.4 class.dtor), a typedef-name that names a class may be used in a qualified-id to refer to a constructor or destructor.]
7.1.3 dcl.typedef paragraph 4:
My first choice would have been to make this the primary statement about the equivalence of typedef-name and class-name, since the equivalence comes about as a result of a typedef declaration. Unfortunately, references to class-name point to 9.1 class.name, so it would seem that the primary statement should be there instead. To avoid the possiblity of conflicts in the future, I propose to make this a note.
[Note: A typedef-name that names a class type, or a cv-qualified version thereof, is also a class-name (9.1 class.name). If a typedef-name is usedfollowing the class-key in an elaborated-type-specifier (7.1.5.3 dcl.type.elab), or in the class-head of a class declaration (9 class), or is used as the identifier in the declarator for a constructor or destructor declaration (12.1 class.ctor, 12.4 class.dtor),to identify the subject of an elaborated-type-specifier (7.1.5.3 dcl.type.elab), class declaration (clause 9 class), constructor declaration (12.1 class.ctor), or destructor declaration (12.4 class.dtor), the program is ill-formed. ] [Example: ...
7.1.5.3 dcl.type.elab paragraph 2:
This is the only remaining (normative) statement that a typedef-name can't be used in an elaborated-type-specifier. The reference to template type-parameter is deleted by the resolution of issue 283.
... If the identifier resolves to a typedef-nameor a template type-parameter, the elaborated-type-specifier is ill-formed. [Note: ...
8 dcl.decl grammar rule declarator-id:
When I looked carefully into the statement of the rule prohibiting a typedef-name in a constructor declaration, it appeared to me that this grammar rule (inadvertently?) allows something that's always forbidden semantically.
declarator-id:
id-expression
::opt nested-name-specifieropttype-nameclass-name
9.1 class.name paragraph 5:
Unlike the prohibitions against appearing in an elaborated-type-specifier or constructor or destructor declarator, each of which was expressed more than once, the prohibition against a typedef-name appearing in a class-head was previously stated only in 7.1.3 dcl.typedef. It seems to me that that prohibition belongs here instead. Also, it seems to me important to clarify that a typedef-name that is a class-name is still a typedef-name. Otherwise, the various prohibitions can be argued around easily, if perversely ("But that isn't a typedef-name, it's a class-name; it says so right there in 9.1 class.name.")
A typedef-name (7.1.3 dcl.typedef) that names a class type or a cv-qualified version thereof is also a class-name, but shall not be usedin an elaborated-type-specifier; see also 7.1.3 dcl.typedef.as the identifier in a class-head.
12.1 class.ctor paragraph 3:
The new nonterminal references are needed to really nail down what we're talking about here. Otherwise, I'm just eliminating redundancy. (A typedef-name that doesn't name a class type is no more valid here than one that does.)
A typedef-name that names a class is a class-name (7.1.3 dcl.typedef); however, aA typedef-namethat names a classshall not be used as theidentifierclass-name in thedeclaratordeclarator-id for a constructor declaration.
12.4 class.dtor paragraph 1:
The same comments apply here as to 12.1 class.ctor.
...A typedef-name that names a class is a class-name (7.1.3); however, aA typedef-namethat names a classshall not be used as theidentifierclass-name following the ~ in the declarator for a destructor declaration.
[Voted into WP at April 2003 meeting.]
A use of an injected-class-name in an elaborated-type-specifier should not name the constructor of the class, but rather the class itself, because in that context we know that we're looking for a type. See issue 147.
Proposed Resolution (revised October 2002):
This clarifies the changes made in the TC for issue 147.
In 3.4.3.1 class.qual paragraph 1a replace:
If the nested-name-specifier nominates a class C, and the name specified after the nested-name-specifier, when looked up in C, is the injected class name of C (clause 9 class), the name is instead considered to name the constructor of class C.
with
In a lookup in which the constructor is an acceptable lookup result, if the nested-name-specifier nominates a class C and the name specified after the nested-name-specifier, when looked up in C, is the injected class name of C (clause 9 class), the name is instead considered to name the constructor of class C. [Note: For example, the constructor is not an acceptable lookup result in an elaborated type specifier so the constructor would not be used in place of the injected class name.]
Note that issue 263 updates a part of the same paragraph.
Append to the example:
struct A::A a2; // object of type A
[Voted into WP at March 2004 meeting.]
Consider this code:
struct A { int i; struct i {}; };
struct B { int i; struct i {}; };
struct D : public A, public B { using A::i; void f (); };
void D::f () { struct i x; }
I can't find anything in the standard that says definitively what this means. 7.3.3 namespace.udecl says that a using-declaration shall name "a member of a base class" -- but here we have two members, the data member A::i and the class A::i.
Personally, I'd find it more attractive if this code did not work. I'd like "using A::i" to mean "lookup A::i in the usual way and bind B::i to that", which would mean that while "i = 3" would be valid in D::f, "struct i x" would not be. However, if there were no A::i data member, then "A::i" would find the struct and the code in D::f would be valid.
John Spicer: I agree with you, but unfortunately the standard committee did not.
I remembered that this was discussed by the committee and that a resolution was adopted that was different than what I hoped for, but I had a hard time finding definitive wording in the standard.
I went back though my records and found the paper that proposed a resolution and the associated