Document number: | WG21 N4019 |
Date: | 2014-05-27 |
Project: | Programming Language C++ |
Reference: | ISO/IEC IS 14882:2003 |
Reply to: | William M. Miller |
Edison Design Group, Inc. | |
[email protected] |
This document contains the C++ core language issues that have been categorized as Defect Reports by the Committee (PL22.16 + WG21) and other accepted issues, that is, issues with status "DR," "accepted," "DRWP," "WP," "CD1," "CD2," "CD3," "TC1," and "C++11," along with their proposed resolutions. Issues with DR, accepted, DRWP, and WP status are NOT part of the International Standard for C++. They 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.
Section references in this document reflect the section numbering of document WG21 N3936.
[Moved to DR at the February, 2014 meeting as paper N3910.]
The wording of 1.9 [intro.execution] paragraph 6 is intended to describe the values of objects upon entry to and exit from the handler — i.e., that signal handler cannot rely on non-atomic objects being in a consistent state upon entry, nor can it reliably set the value of non-atomic objects and expect that they will continue to have those values after the handler exits. However, the wording could be read as saying even during the execution of the handler it cannot set and use non-atomic objects. The wording should be clarified.
[Moved to DR at the February, 2014 meeting as part of document N3914.]
[The following is reproduced verbatim from WG14 DR406 as a C-liaison issue.]
It has been mathematically proved that a simplification can be made to the memory model as it is specified in the final draft of the C++11 standard. Essentially, the restriction defining visible sequence of side effects (vsse) is redundant and can be removed with no ill effects. The main motivation for doing this is that the current restriction is misleading. 5.1.2.4p22 defines vsse's:
The visible sequence of side effects on an atomic object M, with respect to a value computation B of M, is a maximal contiguous sub-sequence of side effects in the modification order of M, where the first side effect is visible with respect to B, and for every subsequent side effect, it is not the case that B happens before it. The value of an atomic object M, as determined by evaluation B, shall be the value stored by some operation in the visible sequence of M with respect to B.
The wording of this paragraph makes it seem as if the vsse identifies the writes that an atomic read is allowed to read from, but this is not the case. There can be writes in the vsse that cannot be read due to the coherence requirements (to be included in C, 1.10p15 through 1.10p18 in C++ N3291). Consequently this is even more confusing than it at first appears.
Also propose changing 5.1.2.4p22 to the following:
The value of an atomic object M, as determined by evaluation B, shall be the value stored by some side effect A that modifies M, where B does not happen before A.
With a note to remind the reader of the coherence requirements:
NOTE: The set of side effects that a given evaluation might take its value from is also restricted by the rest of the rules described here, and in particular, by the coherence requirements below
If the committee is concerned about allowing a differing text from C++11, then a note could be added to assure the reader:
NOTE: Although the rules for multi-threaded executions differ here from those of C++11, the executions they allow are precisely the same. Visible sequences of side effects are a redundant restriction.
Proposed resolution (January, 2014) [SSUPERSEDED]:
Change 1.10 [intro.multithread] paragraph 14 as follows:
The visible sequence of side effects on an atomic object M, with respect to a value computation B of M, is a maximal contiguous sub-sequence of side effects in the modification order of M, where the first side effect is visible with respect to B, and for every side effect, it is not the case that B happens before it. The value of an atomic object M, as determined by evaluation B, shall be the value stored by some operation in the visible sequence of M with respect to B side effect A that modifies M, where B does not happen before A. [Note: It can be shown that the visible sequence of side effects of a value computation is unique given The set of side effects that a given evaluation might take its value from is also restricted by the rest of the rules described here, and in particular, by the coherence requirements below. —end note]
Change 1.10 [intro.multithread] paragraph 20 as follows:
[Note: The visible sequence of side effects value observed by a load of an atomic depends on the “happens before” relation, which depends on the values observed by loads of atomics, which we are restricting here. The intended reading is that there must exist an association of atomic loads with modifications they observe that, together with suitably chosen modification orders and the “happens before” relation derived as described above, satisfy the resulting constraints as imposed here. —end note]
Change 1.10 [intro.multithread] paragraph 22 as follows:
[Note: Compiler transformations that introduce assignments to a potentially shared memory location that would not be modified by the abstract machine are generally precluded by this standard, since such an assignment might overwrite another assignment by a different thread in cases in which an abstract machine execution would not have encountered a data race. This includes implementations of data member assignment that overwrite adjacent members in separate memory locations. Reordering of atomic loads in cases in which the atomics in question may alias is also generally precluded, since this may violate the “visible sequence” coherence rules. —end note]
Change 29.3 [atomics.order] paragraph 3 as follows:
There shall be a single total order S on all memory_order_seq_cst operations, consistent with the “happens before” order and modification orders for all affected locations, such that each memory_order_seq_cst operation B that loads a value from an atomic object M observes one of the following values:
the result of the last modification A of M that precedes B in S, if it exists, or
if A exists, the result of some modification of M in the visible sequence of side effects with respect to B that is not memory_order_seq_cst and that does not happen before A, or
if A does not exist, the result of some modification of M in the visible sequence of side effects with respect to B that is not memory_order_seq_cst.
[Note:...
[Moved to DR at the February, 2014 meeting.]
According to phase 2 of 2.2 [lex.phases] paragraph 1,
Each instance of a backslash character (\) immediately followed by a new-line character is deleted, splicing physical source lines to form logical source lines. Only the last backslash on any physical source line shall be eligible for being part of such a splice. If, as a result, a character sequence that matches the syntax of a universal-character-name is produced, the behavior is undefined.
There does not appear to be a good reason for the behavior to be undefined when the line splice occurs within a raw string literal, since the splicing will be reverted (2.5 [lex.pptoken] paragraph 3).
Proposed resolution (September, 2013):
Change 2.2 [lex.phases] paragraph 1 phase 2 as follows:
Each instance of a backslash character (\) immediately followed by a new-line character is deleted, splicing physical source lines to form logical source lines. Only the last backslash on any physical source line shall be eligible for being part of such a splice. If, as a result, Except for splices reverted in a raw string literal, if a splice results in a character sequence that matches the syntax of a universal-character-name is produced, the behavior is undefined. A source file that is not empty and that does not end in a new-line character, or that ends in a new-line character immediately preceded by a backslash character before any such splicing takes place, shall be processed as if an additional new-line character were appended to the file.
[Moved to DR at the February, 2014 meeting as part of document N3914.]
A UTF-8 string literal might result in a code unit with the value 0x80. However, plain char is not guaranteed to be able to represent 0x80.
[Moved to DR at the February, 2014 meeting.]
According to 4.1 [conv.lval] paragraph 2,
if the glvalue has a class type, the [lvalue-to-rvalue] conversion copy-initializes a temporary of type T from the glvalue and the result of the conversion is a prvalue for the temporary.
The implications of such a conversion for odr-use do not appear to have been factored into 3.2 [basic.def.odr] paragraph 3, which exempts constant objects that are immediately lvalue-to-rvalue converted. For example, given
struct S { int n; }; struct T { static constexpr S s = {}; }; void f(...); void g() { f(T::s); }
Does this odr-use T::s, requiring it to have a definition, because of binding it to the reference parameter of S's copy constructor? How about
struct S { int n; }; void f(...); void g() { constexpr S s = {}; [] { f(s); }; }
Does s need to be captured? There is implementation variance on both these examples.
Proposed resolution (September, 2013):
Change 3.2 [basic.def.odr] paragraph 3 as follows:
A variable x whose name appears as a potentially-evaluated expression ex is odr-used unless x satisfies the requirements for appearing in a constant expression (5.19 [expr.const]) applying the lvalue-to-rvalue conversion (4.1 [conv.lval]) to x yields a constant expression (5.19 [expr.const]) that does not invoke any non-trivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (4.1 [conv.lval]) is applied to e, or e is a discarded-value expression (Clause 5 [expr]). this is odr-used...
[Moved to DR at the February, 2014 meeting.]
We can return a lambda or an object of a local class type from a lambda in c++11, and we can return them from normal functions in c++14. If those lambdas and normal functions are in a namespace, the returned lambdas/local-classes apparently aren't in that namespace, or even if they are, ADL won't find them. Is this intended?
(See also issue 1664.)
Proposed resolution (September, 2013):
This issue is resolved by the resolution of issue 1691.
[Moved to DR at the February, 2014 meeting.]
According to 3.4.2 [basic.lookup.argdep] paragraph 2,
If T is an enumeration type, its associated namespace is the namespace in which it is defined. If it is class member, its associated class is the member's class; else it has no associated class.
This does not take into account opaque enumerations, which can be defined in an enclosing namespace of the one of which is a member.
Proposed resolution (September, 2013):
Change 3.4.2 [basic.lookup.argdep] paragraph 2 as follows:
...The sets of namespaces and classes are determined in the following way:
...
If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the innermost enclosing namespaces of which its associated classes are members. Furthermore..
If T is an enumeration type, its associated namespace is the innermost enclosing namespace in which it is defined of its declaration. If it is a class member, its associated class is the member's class; else it has no associated class.
...
This resolution also resolves issues 1690 and 1692.
[Moved to DR at the February, 2014 meeting.]
According to 3.4.2 [basic.lookup.argdep] paragraph 2,
If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the namespaces of which its associated classes are members.
Consider an example like
struct A { struct B { struct C { }; }; };
A has one associated class, itself, and has the global namespace as its associated namespace. A::B has two associated classes, A and itself, and by virtue of its association with A, has the global namespace as its associated namespace. A::B::C has two associated classes, A::B and itself. However, because neither A::B nor A::B::C is a member of a namespace, A::B::C has no associated namespaces.
This seems like a defect.
Proposed resolution (September, 2013):
This issue is resolved by the resolution of issue 1691.
[Moved to DR at the February, 2014 meeting.]
According to 3.6.2 [basic.start.init] paragraph 2,
...Constant initialization is performed:
if each full-expression (including implicit conversions) that appears in the initializer of a reference with static or thread storage duration is a constant expression (5.19 [expr.const]) and the reference is bound to an lvalue designating an object with static storage duration or to a temporary (see 12.2 [class.temporary]);
...
This wording, presumably inadvertently, excludes a reference to a function from being constant initialization.
Proposed resolution (January, 2014):
Change 3.6.2 [basic.start.init] paragraph 2 as follows:
...Constant initialization is performed:
if each full-expression (including implicit conversions) that appears in the initializer of a reference with static or thread storage duration is a constant expression (5.19 [expr.const]) and the reference is bound to an lvalue designating an object with static storage duration, or to a temporary (see 12.2 [class.temporary]), or to a function;
...
[Moved to DR at the February, 2014 meeting.]
According to 3.9 [basic.types] paragraph 9,
Arithmetic types (3.9.1 [basic.fundamental]), enumeration types, pointer types, pointer to member types (3.9.2 [basic.compound]), std::nullptr_t, and cv-qualified versions of these types (3.9.3 [basic.type.qualifier]) are collectively called scalar types... Scalar types, trivially copyable class types (Clause 9 [class]), arrays of such types, and non-volatile const-qualified versions of these types (3.9.3 [basic.type.qualifier]) are collectively called trivially copyable types.
This is confusing, because “scalar types” include volatile-qualified types, but the intent of the definition of “trivially copyable type” appears to be to exclude volatile-qualified types. Perhaps the second quoted sentence should read something like,
A non-volatile type T or an array of such T is called a trivially copyable type if T is either a scalar type or a trivially copyable class type.
(Note that the following sentence, defining “trivial type,” has a similar formal issue, although it has no actual significance because all cv-qualifiers are permitted.)
Proposed resolution (January, 2014):
Change 3.9 [basic.types] paragraph 10 as follows:
...Scalar Cv-unqualified scalar types, trivially copyable class types (Clause 9 [class]), arrays of such types, and non-volatile const-qualified versions of these types (3.9.3 [basic.type.qualifier]) are collectively called trivially copyable types...
[Moved to DR at the February, 2014 meeting.]
Consider an example like the following:
struct Base { virtual int call() = 0; }; Base *foo() { constexpr int x = 0; struct Local : Base { virtual int call() { return x; } }; static Local local; return &local; } int main() { return foo()->call(); }
While the likely intention is that the lvalue-to-rvalue conversion of the block-scope constant is implemented by using the value of the constant expression in place of reading from storage, it seems that the wording of 4.1 [conv.lval] paragraph 2 does not prevent this program from being subject to undefined behaviour caused by lifetime violation. In particular, it seems that a name expression that appears in a potentially-evaluated expression such that the object named is not odr-used (by that instance of the name) may still be evaluated, in theory, as an lvalue through which the object named or a subobject thereof is accessed.
Proposed resolution (September, 2013):
Change 4.1 [conv.lval] paragraph 2 as follows:
When an lvalue-to-rvalue conversion occurs in an unevaluated operand or a subexpression thereof (Clause 5 [expr]) is applied to an expression e, and either
e is not potentially evaluated, or
the evaluation of e results in the evaluation of a member ex of the set of potential results of e, and ex names a variable x that is not odr-used by ex (3.2 [basic.def.odr]),
the value contained in the referenced object is not accessed. [Example:
struct S { int n; }; auto f() { S x { 1 }; constexpr S y { 2 }; return [&](bool b) { return (b ? y : x).n; }; } auto g = f(); int m = g(false); // undefined behavior due to access of x.n outside its lifetime int n = g(true); // OK, does not access y.n
—end example] In all other cases, the result of the conversion...
[Moved to DR at the February, 2014 meeting as part of document N3914.]
The current wording of 4.1 [conv.lval] gives the result of fetching an uninitialized unsigned character an unspecified value, which is then stable: assigned to a different variable, it will be the same throughout the lifetime of that variable. It would be more helpful to optimizers for the unspecified value to be viral, so that fetching from the second variable would also yield an unspecified result, not necessarily the same each time.
[Moved to DR at the February, 2014 meeting.]
Lambda expressions cannot appear in unevaluated operands nor in evaluated portions of constant expressions. However, the following example appears to circumvent those restrictions:
template <bool> struct BoolSink { typedef void type; }; template <typename T, typename U> struct AddRvalueReferenceImpl { typedef T type; }; template <typename T> struct AddRvalueReferenceImpl<T, typename BoolSink<false && [] { extern T &&tref; }>::type> { typedef T &&type; }; template <typename T> struct AddRvalueReference : AddRvalueReferenceImpl<T, void> { }; namespace ImplHelpers { template <typename T> typename AddRvalueReference<T>::type create(void) { } } template <typename T, typename U, typename ...Args> struct IsConstructibleImpl { enum { value = 0 }; }; template <typename T, typename ...Args> struct IsConstructibleImpl<T, typename BoolSink<false && [] { T t( ::ImplHelpers::create<Args>() ...); }>::type, Args ...> { enum { value = 1 }; }; template <typename T, typename ...Args> struct IsConstructible : IsConstructibleImpl<T, void, Args ...> { }; struct DestroyMe { ~DestroyMe() = delete; }; static_assert(+IsConstructible<int>::value, "error"); static_assert(!IsConstructible<void>::value, "error"); static_assert(+IsConstructible<int [1]>::value, "error"); static_assert(!IsConstructible<DestroyMe>::value, "error"); static_assert(!IsConstructible<int *, char *>::value, "error"); static_assert(+IsConstructible<int &&, int>::value, "error"); static_assert(!IsConstructible<int &&, int &>::value, "error"); static_assert(+IsConstructible<int &&, int &&>::value, "error");
Is this intended?
Additional notes, April, 2013:
Further discussion has arisen regarding lambda-expressions in function template signatures. Although the restriction that lambda-expressions cannot appear as unevaluated operands (5.1.2 [expr.prim.lambda] paragraph 2) was intended to avert the need to deal with them in function template signatures, the fact that 5.19 [expr.const] treats unevaluated subexpressions separately from unevaluated operands opens another avenue for lambda-expressions in template signatures, e.g.,
template<typename T> void f(int [(0 && [] { for (auto x : T()) {} }, 1)]);
Four possible approaches for dealing with this issue have been suggested:
Allow lambda-expressions in function template signatures. This would be costly in some implementations.
Give a function template internal linkage if its signature includes a lambda-expression. This would allow SFINAE and redeclaration to work without requiring that lambda-expressions be mangled.
Specify that a function signature containing a lambda-expression is not a redeclaration of any other function template, which would allow SFINAE to work but not require declaration matching and mangling.
Do not allow lambda-expressions in function template signatures.
If any of these approaches were adopted, the rationale for disallowing lambda-expressions in unevaluated operands would be removed, so it might make sense to remove the restriction at the same time.
Proposed resolution (September, 2013):
Change 5.1.2 [expr.prim.lambda] paragraph 2 as follows:
...A lambda-expression shall not appear in an unevaluated operand (Clause 5 [expr]), in a template-argument, in an alias-declaration, in a typedef declaration, or in the declaration of a function or function template outside its function body and default arguments. [Note: The intention is to prevent lambdas from appearing in a signature —end note]. [Note: A closure object behaves like a function object (20.9 [function.objects]). —end note]
[Moved to DR at the February, 2014 meeting.]
It is not clear from the description of capturing in 5.1.2 [expr.prim.lambda] whether an implicit capture resulting from the odr-use of a member of an anonymous union captures that member or the anonymous union, and there is implementation divergence. For example,
int main() { static int result; struct A { int x; }; struct B { int y; }; union { A a; B b; }; a.x = 1; [=]() mutable { a.x = 2; result = b.y; }(); if (result == 1) return 0; throw 0; }
Notes from the April, 2013 meeting:
CWG decided that an attempt to capture a member of an anonymous union should be ill-formed.
Proposed resolution (September, 2013):
Change 5.1.2 [expr.prim.lambda] paragraph 15 as follows:
...An array of runtime bound (8.3.4 [dcl.array]) or a member of an anonymous union shall not be captured by copy.
Change 5.1.2 [expr.prim.lambda] paragraph 16 as follows:
...It is unspecified whether additional unnamed non-static data members are declared in the closure type for entities captured by reference. A member of an anonymous union shall not be captured by reference. [Note:...
[Moved to DR at the February, 2014 meeting.]
Since instances of a variable in constant expressions may be odr-uses, the ordering of:
constant expression evaluation,
the determination of implicit captures, and
the transformation to use closure members for odr-uses of captured variables
may affect the semantics of a program such as the one below.
The transformation under 5.1.2 [expr.prim.lambda] paragraph 17 introduces uses of the this pointer of the operator() in its function-body. These instances of this are invalid under issue 1369 if the transformation is applied before the evaluation of the constant expressions. Without the resolution of issue 1369, another situation occurs where instances of this in the compound-statement are transformed into class member access expressions (see the initializations of addrEqA and addrEqB below).
Also, for the initialization of nonZero below, the expression fails to be a constant expression if the transformation is applied before constant expression evaluation.
Finally, the answer to the static assertion changes depending on whether the constant expression evaluation is performed before the transformation as opposed to after and whether the proposed resolution issue 1472 is enabled.
There appears to be implementation divergence regarding
whether the lexical instances of this in the initialization of addrEqB is valid and
whether the static assertion should pass.
Using explicit value captures is not a panacea, since the paragraph 17 transformations only apply to odr-uses. As a result of the resolution of issue 1472, if the reference r below happened to have been initialized with a constant expression, the value of its (modifiable) target is not captured; if the same target were specified in the initialization of the reference with a non-constant expression, its value would be captured.
struct A { void foo(); }; struct LitType { int val; }; constexpr int ceFunc(const LitType &x) { return x.val; } void A::foo() { constexpr LitType y = { 0 }; static int z; int x, &r = z; [=] { constexpr bool addrEqA = &x == &x; // ill-formed under issue 1369 after transformation // under paragraph 17 constexpr bool addrEqB = &*this == &*this; // well-formed after transformation under N3290 // paragraph 17 constexpr bool nonZero = ceFunc(y); // lvalue-to-rvalue conversion occurs only after // function invocation substitution; the closure member, // being not a variable, cannot be constexpr static_assert(&r != &z, "reference which could be captured by value found to alias target"); // affected by issue 1472 }; }
Proposed resolution (September, 2013):
Add the following bullet to 5.19 [expr.const] paragraph 2:
A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (1.9 [intro.execution]), would evaluate one of the following expressions:
...
in a lambda-expression, a reference to this or to a variable with automatic storage duration defined outside that lambda-expression, where the reference would be an odr-use (3.2 [basic.def.odr], 5.1.2 [expr.prim.lambda]);
a conversion from type cv void * to a pointer-to-object type;
...
[Moved to DR at the February, 2014 meeting.]
The description of the characteristics of a closure class in 5.1.2 [expr.prim.lambda] leaves open the possibility that such a class might be a literal type, although it could also be a non-literal type. It would probably be good to specify that a closure class is not a literal type, in the interests of portability.
On a related note, it might be wise to remove the implementation freedom to make a closure class either a POD or not a POD; it seems only to be an invitation for portability problems with no real advantage.
Additional note, April, 2013:
Some have expressed the opinion that such portability issues are just “par for the course,” and that specifying these characteristics at this point might unnecessarily limit the ability to extend the language in desirable directions at some point in the future.
Proposed resolution (April, 2013) [superseded]:
Change 5.1.2 [expr.prim.lambda] paragraph 3 as follows:
...An implementation may define the closure type differently from what is described below provided this does not alter the observable behavior of the program other than by changing:
the size and/or alignment of the closure type,
whether the closure type is trivially copyable (Clause 9 [class]),
- whether the closure type is a literal type (3.9 [basic.types]),
whether the closure type is a standard-layout class (Clause 9 [class]), or
whether the closure type is a POD class (Clause 9 [class]).
Notes from the September, 2013 meeting:
At the suggestion of EWG, it was decided that closure types should be specified as not literal, rather than being unspecified as in the April, 2013 proposed resolution. The issue has thus been returned to "drafting" status.
Proposed resolution (January, 2014):
Change 5.1.2 [expr.prim.lambda] paragraph 3 as follows:
The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type — called the closure type — whose properties are described below. This class type is not neither an aggregate (8.5.1 [dcl.init.aggr]) nor a literal type (3.9 [basic.types]). The closure type is declared...
[Moved to DR at the February, 2014 meeting.]
The example in 5.1.2 [expr.prim.lambda] paragraph 24 reads,
template<class... Args> void f(Args... args) { auto lm = [&, args...] { return g(args...); }; lm(); }
However, it's not clear how this example squares with the requirements in paragraph 10,
The identifier in a simple-capture is looked up using the usual rules for unqualified name lookup (3.4.1 [basic.lookup.unqual]); each such lookup shall find a variable with automatic storage duration declared in the reaching scope of the local lambda expression.
since a function parameter pack is not a variable. Although the requirement might be met in a given specialization of the function template, is there a rule that relaxes it in the context of the function template definition?
Proposed resolution (September, 2013):
Change 5.1.2 [expr.prim.lambda] paragraph 10 as follows:
The identifier in a simple-capture is looked up using the usual rules for unqualified name lookup (3.4.1 [basic.lookup.unqual]); each such lookup shall find an entity. An entity that is designated by a simple-capture is said to be explicitly captured, and shall be this or a variable with automatic storage duration declared in the reaching scope of the local lambda expression. An entity (i.e. a variable or this) is said to be explicitly captured if it is found by this process.
Change 14.5.3 [temp.variadic] paragraph 4 as follows:
...Pack expansions can occur in the following contexts:
...
For the purpose of determining whether a parameter pack satisfies a rule regarding entities other than parameter packs, the parameter pack is considered to be the entity that would result from an instantiation of the pattern in which it appears.
[Example:
...
Change 14.5.3 [temp.variadic] paragraph 6 as follows:
...Each Ei is generated by instantiating the pattern and replacing each pack expansion parameter with its ith element. Such an element, in the context of the instantiation, is interpreted as follows:
if the pack is a template parameter pack, the element is a template parameter (14.1 [temp.param]) of the corresponding kind (type or non-type) designating the type or value from the template argument; otherwise,
if the pack is a function parameter pack, the element is an id-expression designating the function parameter that resulted from the instantiation of the pattern where the pack is declared.
All of the Ei become elements in the enclosing list. [Note:...
[Moved to DR at the February, 2014 meeting.]
According to 5.1.2 [expr.prim.lambda] paragraph 3,
The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression. [Note: This determines the set of namespaces and classes associated with the closure type (3.4.2 [basic.lookup.argdep]). The parameter types of a lambda-declarator do not affect these associated namespaces and classes. —end note]
However, 14.7.1 [temp.inst] paragraph 13 says,
If a function template f is called in a way that requires a default argument to be used, the dependent names are looked up, the semantics constraints are checked, and the instantiation of any template used in the default argument is done as if the default argument had been an initializer used in a function template specialization with the same scope, the same template parameters and the same access as that of the function template f used at that point.
A possibility, then, is that the closure type for a lambda expression in a default argument for a template function (or, presumably, a member function of a class template) is to be considered as having been declared in some block scope in the body of the fictional function template specialization.
Consider the following example:
namespace J { inline namespace K { template <typename T> int zap(const T &t) { foo(t); return 0; } template <typename T> void zip(int = zap([] { })) { } } template <typename T> void foo(const T &) { } } void bar() { J::K::zip<long>(); }
If zip were not a template, argument-dependent lookup successfully resolves the lookup for foo in all implementations tested; however, there is implementation variance in the handling of the example as written.
(See also issue 1690.)
Proposed resolution (September, 2013):
Change 14.7.1 [temp.inst] paragraph 13 as follows:
If a function template f is called in a way that requires a default argument to be used, the dependent names are looked up, the semantics constraints are checked, and the instantiation of any template used in the default argument is done as if the default argument had been an initializer used in a function template specialization with the same scope, the same template parameters and the same access as that of the function template f used at that point, except that the scope in which a closure type is declared (5.1.2 [expr.prim.lambda]) — and therefore its associated namespaces — remain as determined from the context of the definition for the default argument. This analysis is called default argument instantiation. The instantiated default argument is then used as the argument of f.
[Moved to DR at the February, 2014 meeting.]
It is not clear whether __func__ in the body of a lambda refers to the operator() of the closure class or to the containing function (if any). Since lambdas can appear in non-function scope, it would be preferable for them to refer to the closure class's operator().
Proposed resolution (September, 2013):
Change 5.1.2 [expr.prim.lambda] paragraph 7 as follows:
The lambda-expression's compound-statement yields the function-body... —end example] Further, a variable __func__ is implicitly defined at the beginning of the compound-statement of the lambda-expression, with semantics as described in 8.4.1 [dcl.fct.def.general].
[Moved to DR at the February, 2014 meeting.]
In a declaration like
T&& r = static_cast<T&&>(T());
it is not clear what the lifetime of the T temporary should be. According to 5.2.9 [expr.static.cast] paragraph 4, the static_cast is equivalent to a declaration of an invented temporary variable t. The lifetime of the temporary is extended to that of t, but it is not clear what that lifetime should be, nor if the subsequent binding of t to r would affect the lifetime of the original temporary. (See also issue 1568.)
Notes from the February, 2012 meeting:
The reference is bound to the xvalue result of the static_cast, so the lifetime of the temporary is not extended and this example results in a dangling reference.
Proposed resolution (February, 2012) [SUPERSEDED]:
Change 5.2.9 [expr.static.cast] paragraph 4 as follows:
Otherwise, an expression e can be explicitly converted to a type T using a static_cast of the form static_cast<T>(e) if the declaration T t(e); is well-formed, for some invented temporary variable t (8.5 [dcl.init]). The effect...
Proposed resolution (September, 2013):
Change 5.2.9 [expr.static.cast] paragraph 3 as follows:
A glvalue, class prvalue, or array prvalue of type “cv1 T1” can be cast to type “rvalue reference to cv2 T2” if “cv2 T2” is reference-compatible with “cv1 T1” (8.5.3 [dcl.init.ref]). If the glvalue value is not a bit-field, the result refers to the object or the specified base class subobject thereof; otherwise, the lvalue-to-rvalue conversion (4.1 [conv.lval]) is applied to the bit-field and the resulting prvalue is used as the expression of the static_cast for the remainder of this section. If T2 is an inaccessible (Clause 11 [class.access]) or ambiguous (10.2 [class.member.lookup]) base class of T1, a program that necessitates such a cast is ill-formed.
[Moved to DR at the February, 2014 meeting.]
The specification of conversion from integral or enumeration type in 5.2.9 [expr.static.cast] paragraph 10 uses the phrase “explicitly converted,” while the description of conversion from floating point to enumeration omits the word “explicitly.” Presumably these should be harmonized.
Proposed resolution (January, 2014):
Change 5.2.9 [expr.static.cast] paragraph 10 as follows:
...A value of floating-point type can also be explicitly converted to an enumeration type...
[Applied to WP at the February, 2014 meeting as part of document N3914.]
The strategy of merging allocations could turn a small memory leak into a big one. For example,
class P { int x; }; class Q { public: Q(){ throw 42; } private: int x[LARGE_NUMBER]; }; { P* p1 = new P(); Q* q1 = new Q(); // bang :-( // don't get here delete q1; delete p1; }
Instead of just leaking the first allocation, this could result in leaking the combined allocation.
Notes from the September, 2013 meeting:
EWG was not so concerned with memory leaks, but there are problems with the current wording dealing with lifetime issues. In particular, the existing wording did not guarantee that all of the merged allocations would, in fact, be executed. The intended direction is to relax the strict lifetime containment requirement from the current wording but to ensure that all of the allocations and frees will be executed, which requires that the allocation and initialization of later objects be non-throwing.
[Moved to DR at the February, 2014 meeting.]
According to 5.10 [expr.eq] paragraph 2, pointers to data members compare equal
if and only if they would refer to the same member of the same most derived object (1.8 [intro.object]) or the same subobject if indirection with a hypothetical object of the associated class type were performed.
This specification is overly constrained. For data members, most implementations simply compare the offsets of the members involved, violating the “only if” part of the specification. For example:
struct A {}; struct B : A { int x; }; struct C : A { int x; }; int A::*bx = (int(A::*))&B::x; int A::*cx = (int(A::*))&C::x; bool b1 = bx == cx;
The existing wording requires b1 to have the value false, even though the offsets of the members are the same. It would be better if the result of the comparison were unspecified unless the class containing the original member for the LHS is a base or derived class of the class containing the original member for the RHS.
Proposed resolution (January, 2014):
Change 5.10 [expr.eq] paragraph 3 as follows:
...Comparing pointers to members is defined as follows:
...
If either is a pointer to a virtual member function, the result is unspecified.
If one refers to a member of class C1 and the other refers to a member of a different class C2, where neither is a base class of the other, the result is unspecified. [Example:
struct A {}; struct B : A { int x; }; struct C : A { int x; }; int A::*bx = (int(A::*))&B::x; int A::*cx = (int(A::*))&C::x; bool b1 = (bx == cx); // unspecified
—end example]
Two Otherwise, two pointers to members compare equal if they would refer to the same member of the same most derived object (1.8 [intro.object]) or the same subobject if indirection with a hypothetical object of the associated class type were performed, otherwise they compare unequal. [Example:
struct B { int f(); }; struct L : B { }; struct R : B { }; struct D : L, R { }; int (B::*pb)() = &B::f; int (L::*pl)() = pb; int (R::*pr)() = pb; int (D::*pdl)() = pl; int (D::*pdr)() = pr; bool x = (pdl == pdr); // false bool y = (pb == pl); // true
—end example]
[Moved to DR at the February, 2014 meeting.]
The interaction of various issue resolutions has inadvertently resulted in the loss of the prohibition of defining types in conditions (6.4 [stmt.select] paragraph 1) and in a range-based for (6.5 [stmt.iter] paragraph 1). Issue 686 addressed a patchwork of restrictions by banning type definitions in type-specifier-seqs. Issue 948 then changed the definition of condition to use a decl-specifier-seq instead of a type-specifier-seq in order to permit the constexpr specifier. A similar change was made for range-based for statements by issue 1204.
The restrictions against type definitions in these contexts should be restored.
Proposed resolution (January, 2014):
Change 6.4 [stmt.select] paragraph 2 as follows:
The rules for conditions apply both to selection-statements and to the for and while statements (6.5 [stmt.iter]). The declarator shall not specify a function or an array. The decl-specifier-seq shall not define a class or enumeration. If the auto type-specifier appears in the decl-specifier-seq, the type of the identifier being declared is deduced from the initializer as described in 7.1.6.4 [dcl.spec.auto].
Change 6.5.4 [stmt.ranged] paragraph 2 as follows:
In the decl-specifier-seq of a for-range-declaration, each decl-specifier shall be either a type-specifier or constexpr. The decl-specifier-seq shall not define a class or enumeration.
[Moved to DR at the February, 2014 meeting.]
The description of the switch statement and case labels in 6.4.2 [stmt.switch] paragraph 2 apply integral promotions to the condition value and refer to the “promoted type” of the condition. However, the integral promotions (4.5 [conv.prom]) do not describe the result when they are applied to a scoped enumeration value.
Proposed resolution (September, 2013):
Change 6.4.2 [stmt.switch] paragraph 2 as follows:
The condition shall be of integral type, enumeration type, or class type. If of class type, the condition is contextually implicitly converted (Clause 4 [conv]) to an integral or enumeration type. Integral promotions are performed. If the (possibly converted) type is subject to integral promotions (4.5 [conv.prom]), the condition is converted to the promoted type. Any statement within the switch statement can be labeled with one or more case labels as follows:
case constant-expression :
where the constant-expression shall be a converted constant expression (5.19 [expr.const]) of the promoted adjusted type of the switch condition. No two of the case constants in the same switch shall have the same value after conversion to the promoted type of the switch condition.
[Moved to DR at the February, 2014 meeting.]
The grammar for elaborated-type-specifier in 7.1.6.3 [dcl.type.elab] reads, in part,
This allows use of the template keyword without a nested-name-specifier, e.g., struct template S<int>. This is inconsistent with other uses of the template keyword. It might be better to split the production in two and only allow the keyword following a nested-name-specifier, i.e.,
Proposed resolution (January, 2014):
Change the grammar in 7.1.6.3 [dcl.type.elab] as follows:
[Moved to DR at the February, 2014 meeting.]
Regarding the value of an enumerator whose enumeration's underlying type is not fixed, 7.2 [dcl.enum] paragraph 5 says,
the type of the initializing value is the same as the type of the initializing value of the preceding enumerator unless the incremented value is not representable in that type, in which case the type is an unspecified integral type sufficient to contain the incremented value.
It is not clear how this is to be applied when the preceding enumerator value is given by an enumerator whose value is the largest of its enumeration's values, and there is implementation variance on this point.
Proposed resolution (September, 2013):
Change 7.2 [dcl.enum] paragraph 5 as follows:
...If the underlying type is fixed, the type of each enumerator enumerator prior to the closing brace is the underlying type and the constant-expression in the enumerator-definition shall be a converted constant expression of the underlying type (5.19 [expr.const]); if the initializing value of an enumerator cannot be represented by the underlying type, the program is ill-formed. If the underlying type is not fixed, the type of each enumerator prior to the closing brace is the type of its initializing value determined as follows:
If an initializer is specified for an enumerator, the initializing value the constant-expression shall be an integral constant expression (5.19 [expr.const]). If the expression has unscoped enumeration type, the enumerator has the underlying type of that enumeration type, otherwise it has the same type as the expression and the constant-expression shall be an integral constant expression (5.19 [expr.const]).
If no initializer is specified for the first enumerator, the initializing value has its type is an unspecified integral type.
Otherwise the type of the initializing value enumerator is the same as the type that of the initializing value of the preceding enumerator unless the incremented value is not representable in that type, in which case the type is an unspecified integral type sufficient to contain the incremented value. If no such type exists, the program is ill-formed.
[Moved to DR at the February, 2014 meeting.]
Issue 1323 dealt with correcting the specification of the operand of alignas, which was originally given as the nonexistent term alignment-expression. It was corrected editorially to match the use of assignment-expression in 7.6.2 [dcl.align].
In 7.6.2 [dcl.align] paragraph 2, the expression is semantically constrained to be an integral constant expression. Since a constant-expression is syntactically a conditional-expression rather than an assignment-expression, it would probably make sense to change the syntactic nonterminal for the operand of alignas to be either a constant-expression or a conditional-expression.
Proposed resolution (November, 2013):
Change 7.6.1 [dcl.attr.grammar] paragraph 1 as follows:
Change 7.6.2 [dcl.align] paragraph 2 as follows:
When the alignment-specifier is of the form alignas( assignment-expression constant-expression ):
the assignment-expression constant-expression shall be an integral constant expression
[Moved to DR at the February, 2014 meeting.]
According to 8.3.6 [dcl.fct.default] paragraph 9,
Default arguments are evaluated each time the function is called.
Obviously, what was intended by this is that the default argument is evaluated only if no corresponding actual argument is provided, but this could be read as indicating that the default argument is evaluated and discarded by every function call.
Proposed resolution (January, 2014):
Change 8.3.6 [dcl.fct.default] paragraph 9 as follows:
Default arguments are A default argument is evaluated each time the function is called with no argument for the corresponding parameter. The order of evaluation...
[Moved to DR at the February, 2014 meeting.]
According to 8.4.2 [dcl.fct.def.default] paragraph 2,
An explicitly-defaulted function may be declared constexpr only if it would have been implicitly declared as constexpr, and may have an explicit exception-specification only if it is compatible (15.4 [except.spec]) with the exception-specification on the implicit declaration.
The requirement for exception-specifications has unfortunate consequences for the standard library component atomic, as described in LWG issue 2165: the component cannot be used with a T unless T is nothrow default constructible, even if the std::atomic<T> variable is never default initialized.
Proposed resolution (September, 2013):
Change 8.4.2 [dcl.fct.def.default] paragraphs 2 and 3 as follows:
An explicitly-defaulted function may be declared constexpr only if it would have been implicitly declared as constexpr, and may have an explicit exception-specification only if it is compatible (15.4 [except.spec]) with the exception-specification on the implicit declaration. If a function is explicitly defaulted on its first declaration,
it is implicitly considered to be constexpr if the implicit declaration would be, and,
it is implicitly considered to have the same exception-specification as if it had been implicitly declared (15.4 [except.spec]).
If a function that is explicitly defaulted has an explicit exception-specification that is not compatible (15.4 [except.spec]) with the exception-specification on the implicit declaration, then
if the function is explicitly defaulted on its first declaration, it is defined as deleted;
otherwise, the program is ill-formed.
[Example:
struct S { constexpr S() = default; // ill-formed: implicit S() is not constexpr S(int a = 0) = default; // ill-formed: default argument void operator=(const S&) = default; // ill-formed: non-matching return type ~S() throw(int) = default; // ill-formed deleted: exception specification does not match private: int i; S(S&); // OK: private copy constructor }; S::S(S&) = default; // OK: defines copy constructor—end example]
Additional note, January, 2014:
The proposed resolution appears to have the undesirable implication that a special member function could become deleted after the class is complete. For example, given
struct S { S() noexcept(false) = default; };
we need to check that the explicit exception specification is compatible with the one on the implicit declaration. After the resolution of issue 1330, the class is regarded as complete within exception-specifications, per 9.2 [class.mem] paragraph 2. This implies that the explicit exception-specification can only be checked once the class is complete.
The issue has been returned to "review" status to allow discussion of this concern.
[Moved to DR at the February, 2014 meeting.]
Bullet 2 sub-bullet 2 of 8.5.3 [dcl.init.ref] paragraph 5 says,
Otherwise, a temporary of type “cv1 T1” is created and initialized from the initializer expression using the rules for a non-reference copy-initialization (8.5 [dcl.init]). The reference is then bound to the temporary.
It is not clear what “using the rules for a non-reference copy-initialization” means. If it means that the temporary is initialized as if it were a standalone variable, the last bullet of 8.5 [dcl.init] paragraph 17 could apply:
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4 [over.match.copy], and the best one is chosen through overload resolution (13.3 [over.match]). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the cv-unqualified version of the destination type. The temporary is a prvalue. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization.
That is, for an example like
struct X { X(int) {} X(X const &) = delete; }; void f() { X const &x = 0; }
the specification requires creation of a temporary X(0), copying that tempoary to another temporary (subject to copy elision), and binding the reference to that second temporary. In the example above, because the copy constructor is deleted, the example is ill-formed, although current implementations fail to diagnose it as an error.
Is this intended?
Proposed resolution (September, 2013):
Change the last bullet of 8.5.3 [dcl.init.ref] paragraph 5, breaking it into sub-bullets, and the subsequent example as follows:
Otherwise:
If T1 is a class type, user-defined conversions are considered using the rules for copy-initialization of an object of type “cv1 T1” by user-defined conversion (8.5 [dcl.init], 13.3.1.4 [over.match.copy]); the program is ill-formed if the corresponding non-reference copy-initialization would be ill-formed. The result of the call to the conversion function, as described for the non-reference copy-initialization, is then used to direct-initialize the reference. The program is ill-formed if the direct-initialization does not result in a direct binding or if it involves a user-defined conversion.
If T1 is a non-class type, a temporary of type “cv1 T1” is created and copy-initialized (8.5 [dcl.init]) from the initializer expression using the rules for a non-reference copy-initialization (8.5 [dcl.init]). The reference is then bound to the temporary.
If T1 is reference-related to T2,:
cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2.; and
If T1 is reference-related to T2 and if the reference is an rvalue reference, the initializer expression shall not be an lvalue.
[Example:
struct Banana { }; struct Enigma { operator const Banana(); }; void enigmatic() { typedef const Banana ConstBanana; Banana &&banana1 = ConstBanana(); // ill-formed Banana &&banana2 = Enigma(); // ill-formed } const double& rcd2 = 2; // rcd2 refers to temporary with value 2.0 ...
Change 13.3.1.4 [over.match.copy] paragraph 1 as follows:
Under the conditions specified in 8.5 [dcl.init], as part of a copy-initialization of an object of class type, a user-defined conversion can be invoked to convert an initializer expression to the type of the object being initialized. Overload resolution is used to select the user-defined conversion to be invoked. [Note: The conversion performed for indirect binding to a reference to a possibly cv-qualified class type is determined in terms of a corresponding non-reference copy-initialization. —end note] Assuming that
[Moved to DR at the February, 2014 meeting.]
According to 9.2 [class.mem] paragraph 1,
Except when used to declare friends (11.3 [class.friend]) or to introduce the name of a member of a base class into a derived class (7.3.3 [namespace.udecl]), member-declarations declare members of the class, and each such member-declaration shall declare at least one member name of the class.
Unnamed bit-fields (9.6 [class.bit] paragraph 2) are described as not being members, and they obviously do not declare a member name; presumably they should therefore be included among the exceptions to this rule.
Additional note (October, 2013):
Curiously, the exemption for an unnamed bit-field not introducing names is in 7 [dcl.dcl] paragraph 3, referring to a simple-declaration. However, a simple-declaration is not a member-declaration and thus does not apply.
Proposed resolution (November, 2013):
Change 7 [dcl.dcl] paragraph 3 as follows:
...In such cases, and except for the declaration of an unnamed bit-field (9.6 [class.bit]), the decl-specifier-seq shall introduce one or more names into the program, or shall redeclare a name introduced by a previous declaration. [Example:...
Change 9.2 [class.mem] paragraph 1 as follows:
...Except when used to declare friends (11.3 [class.friend]), to declare an unamed bit-field (9.6 [class.bit]), or to introduce the name of a member of a base class into a derived class (7.3.3 [namespace.udecl]), member-declarations declare members of the class, and each such member-declaration shall declare at least one member name of the class. A member shall not...
[Moved to DR at the February, 2014 meeting.]
The grammar in 9.2 [class.mem] and 8.4.1 [dcl.fct.def.general] paragraph 1 are (in part):
This leads to the following curiosity:
struct A { void f1() = delete; // #1, ok void f2() = delete;; // #2, ok void f3() = delete;;; // #3, error, extraneous semicolon };
This could be addressed by moving the semicolon into the productions for function-body for the non-default/delete forms or by adding empty-declaration to the list of member-declaration productions, as is done with namespace-scope declarations.
Proposed resolution (November, 2013):
Change the grammar in 9.2 [class.mem] as follows:
Change 9.2 [class.mem] paragraph 1 as follows:
...Except when used to declare friends (11.3 [class.friend]) or to introduce the name of a member of a base class into a derived class (7.3.3 [namespace.udecl]), or when the declaration is an empty-declaration, member-declarations declare members of the class, and each such member-declaration shall declare at least one member name of the class.
[Moved to DR at the February, 2014 meeting.]
According to 7.3.3 [namespace.udecl] paragraph 15,
When a using-declaration brings names from a base class into a derived class scope, member functions and member function templates in the derived class override and/or hide member functions and member function templates with the same name, parameter-type-list (8.3.5 [dcl.fct]), cv-qualification, and ref-qualifier (if any) in a base class (rather than conflicting).
The algorithm for class-scope name lookup given in 10.2 [class.member.lookup], however, does not implement this requirement; there is nothing that removes a hidden base class member (replacing the using-declaration, per paragraph 3) from the result set.
Proposed resolution (September, 2013):
Change 10.2 [class.member.lookup] paragraph 3 as follows:
The lookup set for f in C, called S(f, C), consists of two component sets: the declaration set, a set of members named f; and the subobject set, a set of subobjects where declarations of these members (possibly including using-declarations) were found. In the declaration set, using-declarations are replaced by the members they designate set of designated members that are not hidden or overridden by members of the derived class (7.3.3 [namespace.udecl]), and type declarations (including injected-class-names) are replaced by the types they designate...
[Moved to DR at the February, 2014 meeting.]
Bullet 6 of 12.1 [class.ctor] paragraph 5 gives an abstract class a deleted default constructor when the virtual base has no default constructor, even though the abstract class's default constructor can never construct the virtual base class subobject. This seems parallel to the case described in issue 257 for mem-initializers. Should a similar accommodation be made to avoid deleted default constructors in abstract classes?
Notes from the April, 2013 meeting:
CWG agreed that a virtual base class should not cause an abstract class's default constructor to be defined as deleted.
Proposed resolution (August, 2013) [superseded]:
Change 12.1 [class.ctor] paragraph 4 as follows:
...A defaulted default constructor for class X is defined as deleted if:
...
any direct or virtual base class, or non-static data member with no brace-or-equal-initializer, or any direct base class, or, if X is not abstract, any virtual base class, has class type M (or array thereof) and either M has no default constructor or overload resolution (13.3 [over.match]) as applied to M's default constructor results in an ambiguity or in a function that is deleted or inaccessible from the defaulted default constructor, or
...
Proposed resolution (November, 2013):
This issue is resolved by the resolution of issue 1658.
[Moved to DR at the February, 2014 meeting.]
While reviewing the resolution of issue 1611, it was noticed that the final bullet of 12.1 [class.ctor] paragraph 4 has a similar issue:
...A defaulted default constructor for class X is defined as deleted if:
...
any direct or virtual base class or non-static data member has a type with a destructor that is deleted or inaccessible from the defaulted default constructor.
Presumably destructors for virtual bases of abstract classes should not be considered in making this determination.
A question was also raised regarding whether odr-use is correctly defined for destructors of virtual bases of abstract classes. 3.2 [basic.def.odr] paragraph 3 simply refers to 12.4 [class.dtor], where the relevant passage (paragraph 8) reads,
After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X's direct non-variant non-static data members, the destructors for X's direct base classes and, if X is the type of the most derived class (12.6.2 [class.base.init]), its destructor calls the destructors for X's virtual base classes.
It could be argued, particularly in light of the reference to 12.6.2 [class.base.init], that this is clear enough that the destructor for an abstract class does not invoke destructors for its virtual bases, but a note to that effect might be helpful.
Proposed resolution (November, 2013):
Add the following as a new paragraph at the end of 12 [special]:
For a class, its non-static data members, its non-virtual direct base classes, and, if the class is not abstract (10.4 [class.abstract]), its virtual base classes are called its potentially constructed subobjects.
Change 12.1 [class.ctor] paragraph 4 as follows:
...A defaulted default constructor for class X is defined as deleted if:
...
any direct or virtual base class, or non-static data member potentially constructed subobject, except for a non-static data member with no a brace-or-equal-initializer, has class type M (or array thereof) and either M has no default constructor or overload resolution (13.3 [over.match]) as applied to M's default constructor results in an ambiguity or in a function that is deleted or inaccessible from the defaulted default constructor, or
any direct or virtual base class or non-static data member potentially constructed subobject has a type with a destructor that is deleted or inaccessible from the defaulted default constructor.
Change 12.4 [class.dtor] paragraph 5 as follows:
A defaulted destructor for a class X is defined as deleted if:
X is a union-like class that has a variant member with a non-trivial destructor,
any of the non-static data members potentially constructed subobject has class type M (or array thereof) and M has a deleted destructor or a destructor that is inaccessible from the defaulted destructor,
any direct or virtual base class has a deleted destructor or a destructor that is inaccessible from the defaulted destructor,
Change 12.6.2 [class.base.init] paragraph 8 as follows:
In a non-delegating constructor, if a given non-static data member or base class potentially constructed subobject is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer) and the entity is not a virtual base class of an abstract class (10.4 [class.abstract]), then...
Change 12.6.2 [class.base.init] paragraph 10 as follows:
In a non-delegating constructor, the destructor for each direct or virtual base class and for each non-static data member potentially constructed subobject of class type is potentially invoked (12.4 [class.dtor]). [Note: This provision ensures that destructors can be called for fully-constructed sub-objects in case an exception is thrown (15.2 [except.ctor]). —end note]
Change 12.8 [class.copy] paragraph 8, replacing the bulleted list with a single sentence, as follows:
The implicitly-declared copy constructor for a class X will have the form
X::X(const X&)
if each potentially constructed subobject
each direct or virtual base class B of X has a copy constructor whose first parameter is of type const B& or const volatile B&, and
for all the non-static data members of X that are of a class type M (or array thereof), each such class type has a copy constructor whose first parameter is of type const M& or const volatile M&.121
Otherwise...
Change 12.8 [class.copy] paragraph 11 as follows:
An implicitly-declared copy/move constructor is an inline public member of its class. A defaulted copy/move constructor for a class X is defined as deleted (8.4.3 [dcl.fct.def.delete]) if X has:
a variant member with a non-trivial corresponding constructor and X is a union-like class,
a non-static data member potentially constructed subobject of class type M (or array thereof) that cannot be copied/moved because overload resolution (13.3 [over.match]), as applied to M's corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor,
a direct or virtual base class B that cannot be copied/moved because overload resolution (13.3 [over.match]), as applied to B's corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor,
any direct or virtual base class or non-static data member potentially constructed subobject of a type with a destructor that is deleted or inaccessible from the defaulted constructor, or,
...
Change 12.8 [class.copy] paragraph 14 as follows:
Before the defaulted copy/move constructor for a class is implicitly defined, all non-user-provided copy/move constructors for its direct and virtual base classes and its non-static data members potentially constructed subobjects shall have been implicitly defined. [Note: An implicitly-declared copy/move constructor has an exception-specification (15.4 [except.spec]). —end note]
Change 12.8 [class.copy] paragraph 23 as follows:
A defaulted copy/move assignment operator for class X is defined as deleted if X has:
...
a non-static data member potentially constructed subobject of class type M (or array thereof) that cannot be copied/moved because overload resolution (13.3 [over.match]), as applied to M's corresponding assignment operator, results in an ambiguity or a function that is deleted or inaccessible from the defaulted assignment operator, or.
a direct or virtual base class B that cannot be copied/moved because overload resolution (13.3 [over.match]), as applied to B's corresponding assignment operator, results in an ambiguity or a function that is deleted or inaccessible from the defaulted assignment operator.
This resolution also resolves issue 1611.
[Moved to DR at the February, 2014 meeting.]
If default arguments added in the out-of-class definition of a constructor make it a special member function, this can affect the overload resolution and thus the implicit exception specification and the triviality of an implicitly-declared special member function in a derived class.
See also issue 1496, which should also be addressed by the resolution of this issue.
Notes from the September, 2013 meeting:
It was decided to resolve this issue separately from issue 1496, which is now decoupled from this issue.
Proposed resolution (September, 2013):
Change 8.3.6 [dcl.fct.default] paragraph 6 as follows:
Except for member functions of class templates, the default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition; the program is ill-formed if a default constructor (12.1 [class.ctor]), copy or move constructor, or copy or move assignment operator (12.8 [class.copy]) is so declared. Default arguments for a member function of a class template shall be specified on the initial declaration of the member function within the class template. [Example:...
Delete the following from 12.8 [class.copy] paragraph 7:
...The latter case is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor. Thus, for the class definition
struct X { X(const X&, int); };
a copy constructor is implicitly-declared. If the user-declared constructor is later defined as
X::X(const X& x, int i =0) { /* ... */ }
then any use of X's copy constructor is ill-formed because of the ambiguity; no diagnostic is required.
[Moved to DR at the February, 2014 meeting.]
The decision in 12.8 [class.copy] paragraph 32 on whether or not a copy must be replaced by a move is phrased largely in terms of when copy elision can be performed, as described in the preceding paragraph. In particular, the fourth bullet of paragraph 31 applies
when the exception-declaration of an exception handler (Clause 15 [except]) declares an object of the same type (except for cv-qualification) as the exception object (15.1 [except.throw]), the copy/move operation can be omitted by treating the exception-declaration as an alias for the exception object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by the exception-declaration.
The determination of “when the meaning of the program will be unchanged” is, in the general case, not practical. Copy elision is optional, meaning that the compiler can simply choose not to perform it if the determination is too difficult. Choosing whether to do a copy or a move, however, is mandatory, even if the copy is elided, and such a broad criterion is not appropriate. Probably the best way to address this problem would be to eliminate exception-declarations as a possible target for move-construction.
(See also issue 1579.)
Proposed resolution (September, 2013):
Change 12.8 [class.copy] paragraphs 31 and 32 as follows:
...This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
...
when the exception-declaration of an exception handler (Clause 15 [except]) declares an object of the same type (except for cv-qualification) as the exception object (15.1 [except.throw]), the copy/move operation can be omitted by treating the exception-declaration as an alias for the exception object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by the exception-declaration. [Note: there cannot be a move from the exception object because it is always an lvalue. —end note]
[Example:...
When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If the first overload resolution fails or was not performed, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object's type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [Note:...
[Moved to DR at the February, 2014 meeting.]
Currently the conditions for moving from an object returned from a function are tied closely to the criteria for copy elision, which requires that the type of the object being returned be the same as the return type of the function. Another possibility that should be considered is to allow something like
optional<T> foo() { T t; ... return t; }
and allow optional<T>::optional(T&&) to be used for the initialization of the return type. Currently this can be achieved explicitly by use of std::move, but it would be nice not to have to remember to do so.
Similarly, the current rules apply only to complete objects; it could make sense to allow moving from subobjects of local objects.
(See also issue 1493 for other questions about the criteria for moving from a returned object.)
Rationale (April, 2013):
CWG felt that this suggestion should be considered in a broader context and was thus more appropriate for EWG.
Additional note (September, 2013):
Returned to "open" status in light of CD National Body comment.
Proposed resolution (September, 2013):
Change 12.8 [class.copy] paragraph 32 as follows:
When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, or when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload resolution fails...
[Moved to DR at the February, 2014 meeting.]
It is not clear whether it is permitted to explicitly instantiate or explicitly specialize specializations of inheriting constructor templates. Since inheriting constructors are considered to be implicitly declared (12.9 [class.inhctor] paragraph 1), it might be inferred that 14.7.2 [temp.explicit] paragraph 4 forbids their explicit instantiation:
If the declaration of the explicit instantiation names an implicitly-declared special member function (Clause 12 [special]), the program is ill-formed.
Similarly, an explicit specialization provides a definition for the specialized member, and 12 [special] paragraph 1 forbids defining an implicitly-declared special member function.
These inferences do not seem conclusive, however, so an explicit statement in 12.9 [class.inhctor] would be helpful.
(See also issue 1780.)
Proposed resolution (January, 2014):
Change 12.9 [class.inhctor] paragraph 4 as follows:
A constructor so declared has the same access as the corresponding constructor in X. It is deleted if the corresponding constructor in X is deleted (8.4 [dcl.fct.def]). An inheriting constructor shall not be explicitly instantiated (14.7.2 [temp.explicit]) or explicitly specialized (14.7.3 [temp.expl.spec]).
[Moved to DR at the February, 2014 meeting.]
Consider an example like:
struct Y { operator int*(); }; extern int *p; int *a = p + 100.0; // #1 int *b = Y() + 100.0; // #2
#1 is ill-formed because it violates the requirement of 5.7 [expr.add] that the non-pointer operand have integral or enumeration type. It appears that #2 is well-formed, however, because 13.3.1.2 [over.match.oper] paragraph 7 says,
If a built-in candidate is selected by overload resolution, the operands are converted to the types of the corresponding parameters of the selected operation function. Then the operator is treated as the corresponding built-in operator and interpreted according to Clause 5 [expr].
In this case, the selected operation function is
int *operator+(int *, std::ptrdiff_t)
100.0 is thus converted to std::ptrdiff_t before reaching 5.7 [expr.add].
This problem could be addressed by restricting the conversion to the class or enumeration operand rather than both operands.
Proposed resolution (January, 2014):
Change 13.3.1.2 [over.match.oper] paragraph 7 as follows:
If a built-in candidate is selected by overload resolution, the operands of class type are converted to the types of the corresponding parameters of the selected operation function, except that the second standard conversion sequence of a user-defined conversion sequence (13.3.3.1.2 [over.ics.user]) is not applied. Then the operator is treated as the corresponding built-in operator and interpreted according to Clause 5 [expr]. [Example:
struct X { operator double(); }; struct Y { operator int*(); }; int *a = Y() + 100.0; // error: pointer arithmetic requires integral operand int *b = Y() + X(); // error: pointer arithmetic requires integral operand
—end example]
[Moved to DR at the February, 2014 meeting as part of document N3914.]
Currently, 13.3.3.1 [over.best.ics] paragraph 4 reads,
However, when considering the argument of a constructor or user-defined conversion function that is a candidate by 13.3.1.3 [over.match.ctor] when invoked for the copying/moving of the temporary in the second step of a class copy-initialization, by 13.3.1.7 [over.match.list] when passing the initializer list as a single argument or when the initializer list has exactly one element and a conversion to some class X or reference to (possibly cv-qualified) X is considered for the first parameter of a constructor of X, or by 13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv], or 13.3.1.6 [over.match.ref] in all cases, only standard conversion sequences and ellipsis conversion sequences are considered.
This is cumbersome and hard to understand. A possible improvement might be:
However, only standard conversion sequences and ellipsis conversion sequences are considered if:
the parameter is the first parameter of a constructor of a class X, or
the parameter is the implicit object parameter of a user-defined conversion function, and
the constructor or user-defined conversion function is a candidate by:
13.3.1.3 [over.match.ctor] — when the argument is the temporary being copied/moved in the second step of a class copy-initialization.
13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv], or 13.3.1.6 [over.match.ref] — in all cases.
13.3.1.7 [over.match.list] — during phase two, when the argument was the only element in the initializer list, and the parameter is of type X or reference to (possibly cv-qualified) X.
(Note that this rewording removes the restriction that applies during phase one of 13.3.1.7 [over.match.list], as there is no longer any way to trigger it due to the fact that only initializer-list constructors are candidates. See this bug report for details.)
Proposed resolution (September, 2013) [SUPERSEDED]:
Change 13.3.3.1 [over.best.ics] paragraph 4 as follows:
However, when considering the argument of a constructor or user-defined conversion function that is a candidate by 13.3.1.3 [over.match.ctor] when invoked for the copying/moving of the temporary in the second step of a class copy-initialization, by 13.3.1.7 [over.match.list] when passing the initializer list as a single argument or when the initializer list has exactly one element and a conversion to some class X or reference to (possibly cv-qualified) X is considered for the first parameter of a constructor of X, or by 13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv], or 13.3.1.6 [over.match.ref] in all cases, only standard conversion sequences and ellipsis conversion sequences are considered. if the target is
the first parameter of a constructor of a class X or
the implicit object parameter of a user-defined conversion function,
and the constructor or user-defined conversion function is a candidate by
13.3.1.3 [over.match.ctor], when the argument is the temporary acting as the source in the second step of a class copy-initialization,
13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv], or 13.3.1.6 [over.match.ref] (in all cases), or
the second phase of 13.3.1.7 [over.match.list] when the initializer list has exactly one element, and the conversion is to X or reference to (possibly cv-qualified) X,
user-defined conversion sequences are not considered. [Example:
struct X { X(); }; struct B { operator X&(); }; B b; X x({b}); // error: B::operator X&() is not a candidate
—end example]
Additional note (October, 2013):
Questions have been raised about several of the bullets in the September, 2013 proposed resolution and whether a note would be preferable instead of or in addition to the example . The issue has been returned to "review" status to allow consideration of these questions.
Additional note (January, 2014):
It has also been observed that the proposed resolution would make the following example ill-formed by preventing the consideration of B's conversion function when initializing the first parameter of A's copy constructor:
struct A { A() {} A(const A &) {} }; struct B { operator A() { return A(); } } b; A a{b};
[Moved to DR at the February, 2014 meeting.]
The example in 13.5.8 [over.literal] paragraph 8 contains the line
float operator ""E(const char*); // OK
E does not begin with an underscore and thus is a reserved name.
Proposed resolution (September, 2013):
Change the example in 13.5.8 [over.literal] paragraph 8 as follows:
void operator "" _km(long double); // OK string operator "" _i18n(const char*, std::size_t); // OK template <char...> double operator "" _\u03C0(); // OK: UCN for lowercase pi float operator ""_E(const char*); // OK float operator ""E(const char*); // error: reserved identifier float operator " " B(const char*); // error: non-empty string-literal string operator "" 5X(const char*, std::size_t); // error: invalid literal suffix identifier double operator "" _miles(double); // error: invalid parameter-declaration-clause template <char...> int operator "" _j(const char*); // error: invalid parameter-declaration-clause
Rationale (February, 2014):
The specification was removed from the WP and moved into a Technical Specification.
[Moved to DR at the February, 2014 meeting.]
The term “address constant expression” was removed by paper N3652, but it is still referenced in the list of permissible nontype template arguments in 14.3.2 [temp.arg.nontype] paragraph 1:
an address constant expression of type std::nullptr_t.
Proposed resolution (November, 2013):
Change 14.3.2 [temp.arg.nontype] paragraph 1 as follows:
A template-argument for a non-type, non-template template-parameter shall be one of:
...
an address a constant expression of type std::nullptr_t.
[Moved to DR at the February, 2014 meeting.]
Since we don't deduce the return type of a function temploid until it is instantiated, it seems that a call to such a function as a member of the current instantiation must have a dependent type, but 14.6.2.2 [temp.dep.expr] doesn't appear to say that. For example:
template<typename T> struct X { typedef int type; }; template<typename T> struct S { auto f() { return 0; } void g() { X<decltype(f())>::type x; // typename presumably needed here } }
Presumably there should be a bullet in 14.6.2.1 [temp.dep.type] paragraph 8 for this case.
Proposed resolution (November, 2013):
Change 14.6.2.2 [temp.dep.expr] paragraph 3 as follows:
An id-expression is type-dependent if it contains
an identifier associated by name lookup with one or more declarations declared with a dependent type,
an identifier associated by name lookup with one or more declarations of member functions of the current instantiation declared with a return type that contains a placeholder type (7.1.6.4 [dcl.spec.auto]),
a template-id that is dependent,
a conversion-function-id that specifies a dependent type, or
a nested-name-specifier or a qualified-id that names a member of an unknown specialization;
or if it names a dependent member...
[Moved to DR at the February, 2014 meeting.]
According to 14.8.2.5 [temp.deduct.type] paragraph 17,
If, in the declaration of a function template with a non-type template-parameter, the non-type template-parameter is used in an expression in the function parameter-list and, if the corresponding template-argument is deduced, the template-argument type shall match the type of the template-parameter exactly, except that a template-argument deduced from an array bound may be of any integral type.
This does not cover return types, leaving the outcome of an example like the following unclear:
template <int N> struct A; template <short N> A<N> *foo(); void bar() { A<1> *(*fp)(void) = &foo; }
Proposed resolution (September, 2013):
Change 14.8.2.5 [temp.deduct.type] paragraph 17 as follows:
If, in the declaration of a function template with a non-type template-parameter, the non-type template-parameter is used in an expression in the function parameter-list and, if the corresponding template-argument is deduced, the template-argument type shall match the type of the template-parameter exactly, except that a template-argument deduced from an array bound may be of any integral type P has a form that contains <i>, and if the type of the corresponding value of A differs from the type of i, deduction fails. If P has a form that contains [i], and if the type of i is not an integral type, deduction fails.147 [Example:...
[Moved to DR at the February, 2014 meeting.]
In saying that the catch-clause parameter is copy-initialized from the exception object, 15.3 [except.handle] paragraph 16 leaves open the possibility that a converting constructor might be used for the initialization when the type of the exception-declaration is a base of the type of the exception object. It should be specified that the parameter is copy-constructed from the corresponding base class subobject in such cases.
Proposed resolution (September, 2013) [superseded]:
Change 15.3 [except.handle] paragraph 16 as follows:
If the exception-declaration specifies introduces a name, it declares a variable which is copy-initialized (8.5 [dcl.init]) from the exception object. If the exception-declaration denotes an object type but does not specify a name, a temporary (12.2 [class.temporary]) is copy-initialized (8.5 [dcl.init]) from the exception object.; otherwise, an unnamed variable is created. That variable, of type cv T or cv T&, is initialized from the exception object, of type E, as follows:
if T is a base class of E, the variable is copy-initialized from the corresponding base class subobject of the exception object;
otherwise, the variable is copy-initialized from the exception object.
The lifetime of the variable or temporary ends when the handler exits, after the destruction of any automatic objects initialized within the handler.
Additional note (October, 2013):
Additional discussion has pointed out that, although the unnamed handler parameter is no longer called a “temporary” in the proposed resolution, 12.2 [class.temporary] paragraph 1 still lists “entering a handler (15.3 [except.handle])” as one of the contexts in which a temporary is created. A question was also raised as to whether it is necessary to deal with named and unnamed handler parameters separately, since both are now referred to as “variables” in the revised wording. This issue has therefore been returned to "review" status to allow consideration of these points.
Proposed resolution (November, 2013):
Change 3 [basic] paragraph 6 as follows:
A variable is introduced by the declaration of a reference other than a non-static data member or of an object. The variable's name, if any, denotes the reference or object.
Change 12.2 [class.temporary] paragraph 1 as follows:
Temporaries of class type are created in various contexts: binding a reference to a prvalue (8.5.3 [dcl.init.ref]), returning a prvalue (6.6.3 [stmt.return]), a conversion that creates a prvalue (4.1 [conv.lval], 5.2.9 [expr.static.cast], 5.2.11 [expr.const.cast], 5.4 [expr.cast]), throwing an exception (15.1 [except.throw]), entering a handler (15.3 [except.handle]), and in some initializations (8.5 [dcl.init]). [Note:...
Change 15.1 [except.throw] paragraph 3 as follows:
Throwing an exception copy-initializes (8.5 [dcl.init], 12.8 [class.copy]) a temporary object, called the exception object. The temporary is an lvalue and is used to initialize the variable named declared in the matching handler (15.3 [except.handle]). If the type...
Change 15.3 [except.handle] paragraph 16 as follows:
If the exception-declaration specifies a name, it declares a variable which is copy-initialized (8.5 [dcl.init]) from the exception object. If the exception-declaration denotes an object type but does not specify a name, a temporary (12.2 [class.temporary]) is copy-initialized (8.5 [dcl.init]) from the exception object. The variable declared by the exception-declaration, of type cv T or cv T&, is initialized from the exception object, of type E, as follows:
if T is a base class of E, the variable is copy-initialized (8.5 [dcl.init]) from the corresponding base class subobject of the exception object;
otherwise, the variable is copy-initialized (8.5 [dcl.init]) from the exception object.
The lifetime of the variable or temporary ends when the handler exits, after the destruction of any automatic objects initialized within the handler.
[Moved to DR at the February, 2014 meeting.]
There is an ambiguity between a noexcept specifier's optional parenthesized constant-expression and an initializer:
void f() noexcept; void (*p)() noexcept (&f);
Here, we can just about make 8.2 [dcl.ambig.res] paragraph 1's rule fit, and say that the (&f) is part of the exception-specification rather than being an initializer. However, this case is much more problematic:
void (*fp2)() noexcept, (*fp)() noexcept (fp2 = 0);
The (fp = 0) here is unambiguously an initializer, because an assignment-expression cannot syntactically be a constant-expression, although current implementations treat it as an ill-formed part of the exception-specification.
Probably the best approach would be to change 15.4 [except.spec] to say that a ( following noexcept is always treated as being part of the noexcept-specification.
Proposed resolution (September, 2013):
Change 15.4 [except.spec] paragraph 1 as follows:
...In a noexcept-specification, the constant-expression, if supplied, shall be a constant expression (5.19 [expr.const]) that is contextually converted to bool (Clause 4 [conv]). A noexcept-specification noexcept is equivalent to noexcept( true). A ( token that follows noexcept is part of the noexcept-specification (and does not commence an initializer (8.5 [dcl.init]).
[Applied to WP at the February, 2014 meeting.]
Table 6 of 2.14.2 [lex.icon] paragraph 2 covers only decimal, octal, and hexadecimal literals. Binary literals should be treated like the latter two. (It would also be more consistent to refer to these as “literals” instead of “constants.”)
Proposed resolution (September, 2013):
Change the caption and header row of Table 6 in 2.14.2 [lex.icon] paragraph 2 as follows:
Table 6 — Types of integer constants literals |
Suffix | Decimal constants literal | Binary, Octal octal, or hexadecimal constant literal |
---|
[Applied to WP at the February, 2014 meeting.]
Should it be permitted for main to have a deduced return type?
(See also issue 1676.)
Proposed resolution (November, 2013):
Change 3.6.1 [basic.start.main] paragraph 2 as follows:
An implementation shall not predefine the main function. This function shall not be overloaded. It shall have a declared return type of type int, but otherwise its type is implementation-defined. All implementations An implementation shall allow both
a function of () returning int and
a function of (int, pointer to pointer to char) returning int
as the type...
[Applied to WP at the February, 2014 meeting.]
According to 5.1.2 [expr.prim.lambda] paragraph 10,
The identifier in a simple-capture is looked up using the usual rules for unqualified name lookup (3.4.1 [basic.lookup.unqual]); each such lookup shall find a variable with automatic storage duration declared in the reaching scope of the local lambda expression.
This does not permit a nested lambda to capture the non-static data member of the closure type introduced by an init-capture. A similar restriction applies to implicit capture in paragraph 12.
Presumably such captures should be allowed, capturing the non-static data member directly rather than the this pointer from the enclosing lambda's operator().
Proposed resolution (September, 2013):
This issue is resolved by the resolution of issue 1760.
[Applied to WP at the February, 2014 meeting.]
The access of the non-static data member corresponding to an init-capture is not specified. The question would be moot if the member were unnamed like the other non-static data members of the closure class.
Proposed resolution (September, 2013):
For every init-capture a non-static data member named by the identifier of the init-capture is declared in the closure type. This member is not a bit-field and not mutable. The type of that member corresponds to the type of a hypothetical An init-capture behaves as if it declares and explicitly captures a variable declaration of the form “auto init-capture ;” whose declarative region is the lambda-expression's compound-statement, except that the variable name (i.e., the identifier of the init-capture) is replaced by a unique identifier.:
if the capture is by copy (see below), the non-static data member declared for the capture and the variable are treated as two different ways of referring to the same object, which has the lifetime of the non-static data member, and no additional copy and destruction is performed, and
if the capture is by reference, the variable's lifetime ends when the closure object's lifetime ends.
[Note: This enables an init-capture like “x = std::move(x)”; the second “x” must bind to a declaration in the surrounding context. —end note] No entity is captured by an init-capture. Within the lambda-expressions lambda-declarator and compound-statement, the identifier in the init-capture hides any declaration of the same name in scopes enclosing the lambda-expression. [Example:...
Change 5.1.2 [expr.prim.lambda] paragraph 15 as follows:
An entity is captured by copy if it is implicitly captured and the capture-default is = or if it is explicitly captured with a capture that does not include an & is not of the form & identifier or & identifier initializer. For each entity...
Change 5.1.2 [expr.prim.lambda] paragraph 18 as follows:
Every id-expression within the compound-statement of a lambda-expression that is an odr-use (3.2 [basic.def.odr]) of an entity captured by copy is transformed into an access to the corresponding unnamed data member of the closure type. [Note:...
This resolution also resolves issue 1681.
[Applied to WP at the February, 2014 meeting.]
The current wording of 7.1.5 [dcl.constexpr] paragraph 8 is:
The constexpr specifier has no effect on the type of a constexpr function or a constexpr constructor. The class of which a constexpr function is a member shall be a literal type (3.9 [basic.types]).
The previous version of this wording made clear that the restriction on the class type applied only to non-static member functions; consequently, the new formulation has inadvertently banned static constexpr member functions of non-literal classes.
Proposed resolution (November, 2013):
Change 7.1.5 [dcl.constexpr] paragraph 8 as follows:
The constexpr specifier has no effect on the type of a constexpr function or a constexpr constructor. The class of which a constexpr function is a member shall be a literal type (3.9 [basic.types]). [Example:...
[Applied to WP at the February, 2014 meeting.]
The following example appears in 7.1.6.4 [dcl.spec.auto] paragraph 12:
template <class T> auto f(T t) { return t; } // return type deduced at instantiation time typedef decltype(f(1)) fint_t; // instantiates f<int> to deduce return type template<class T> auto f(T* t) { return *t; } void g() { int (*p)(int*) = &f; } // instantiates both fs to determine return types, // chooses second
This is the desired behavior, but the current wording does not achieve that effect. One possible way this could work would be:
auto is treated as a non-deduced context
in 14.8.2.2 [temp.deduct.funcaddr], the deduced A is produced by substituting the deduced template arguments into the original function template, rather than substituting them into P, and the deduced A is the type of the produced function declaration (triggering instantiation of a definition as needed to complete the type)
Proposed resolution (November, 2013):
Add the following as a new paragraph at the end of 14.8.2.2 [temp.deduct.funcaddr]:
A placeholder type (7.1.6.4 [dcl.spec.auto]) in the return type of a function template is a non-deduced context. If template argument deduction succeeds for such a function, the return type is determined from instantiation of the function body.
[Moved to DR at the September, 2013 meeting.]
The term “non-template function” is used in various places but never defined.
Proposed resolution (June, 2013):
Change 5.2.2 [expr.call] paragraph 1 as follows:
There are two kinds of function call: ordinary function call and member function [Footnote: A static member function (9.4 [class.static]) is an ordinary function. —end foot note] (9.3 [class.mfct]) call. A function call is a postfix expression followed by parentheses containing a possibly empty, comma-separated list of initializer-clauses which constitute the arguments to the function. The postfix expression shall have function type or pointer to function type. For an ordinary function a call to a non-member function or to a static member function, the postfix expression shall be either... For a member function call to a non-static member function, the postfix expression shall be...
Add a new paragraph before 8.3.5 [dcl.fct] paragraph 13:
A non-template function is a function that is not a function template specialization. [Note: A function template is not a function. —end note]
A declarator-id or abstract-declarator containing an ellipsis shall only be used...
Change the footnote in 13.3.1 [over.match.funcs] paragraph 7 as follows:
The process of argument deduction fully determines the parameter types of the function template specializations, i.e., the parameters of function template specializations contain no template parameter types. Therefore the, except where specified otherwise, function template specializations can be treated as normal (non-template) functions and non-template functions (8.3.5 [dcl.fct]) are treated equivalently for the remainder of overload resolution.
Change the final sub-bullet of 13.3.1.2 [over.match.oper] paragraph 3 as follows:
For a unary operator @...
...
For the operator ,, the unary operator &, or the operator ->, the built-in candidates set...
...
do not have the same parameter-type-list as any non-template non-member candidate that is not a function template specialization.
Change the indicated bullet of the second bulleted list of 13.3.3 [over.match.best] paragraph 1 as follows:
...
F1 is not a non-template function template specialization and F2 is a function template specialization, or, if not that,
...
Change 13.4 [over.over] paragraph 4 as follows:
If more than one function is selected, any function template specializations in the set are eliminated if the set also contains a non-template function that is not a function template specialization, and any given function template specialization F1 is eliminated
Change 14 [temp] paragraph 5 as follows:
A class template shall not have the same name as any other template, class, function, variable, enumeration, enumerator, namespace, or type in the same scope (3.3 [basic.scope]), except as specified in (14.5.5 [temp.class.spec]). Except that a function template can be overloaded either by (non-template) functions (8.3.5 [dcl.fct]) with the same name or by other function templates with the same name (14.8.3 [temp.over]), a template name declared in namespace scope or in class scope shall be unique in that scope.
Change 14.5.2 [temp.mem] paragraph 2 as follows:
A local class of non-closure type shall not have member templates. Access control rules (Clause 11 [class.access]) apply to member template names. A destructor shall not be a member template. A normal (non-template) member function (8.3.5 [dcl.fct]) with a given name and type and a member function template of the same name, which could be used to generate a specialization of the same type, can both be declared in a class. When both exist, a use of that name and type refers to the non-template member unless an explicit template argument list is supplied. [Example:
template <class T> struct A { void f(int); template <class T2> void f(T2); }; template <> void A<int>::f(int) { } // non-template member function template <> template <> void A<int>::f<>(int) { } // template member function template specialization int main() { A<char> ac; ac.f(1); // non-template ac.f('c'); // template ac.f<>(1); // template }—end example]
Change 14.5.4 [temp.friend] paragraph 1 as follows:
A friend of a class or class template can be a function template or class template, a specialization of a function template or class template, or an ordinary ( a non-template) function or class. For a friend function declaration that is not a template declaration:
if the name of the friend is a qualified or unqualified template-id, the friend declaration refers to a specialization of a function template, otherwise,
if the name of the friend is a qualified-id and a matching non-template function is found in the specified class or namespace, the friend declaration refers to that function, otherwise,
if the name of the friend is a qualified-id and a matching function template is found in the specified class or namespace, the friend declaration refers to the deduced specialization of that function template (14.8.2.6 [temp.deduct.decl]), otherwise,
the name shall be an unqualified-id that declares (or redeclares) an ordinary ( a non-template) function.
Change 14.5.4 [temp.friend] paragraph 4 as follows:
When a function is defined in a friend function declaration in a class template, the function is instantiated when the function is odr-used (3.2 [basic.def.odr]). The same restrictions...
Change 14.5.6 [temp.fct] paragraph 2 as follows:
A function template can be overloaded with other function templates and with normal (non-template) functions (8.3.5 [dcl.fct]). A normal non-template function is not related to a function template (i.e., it is never considered to be a specialization), even if it has the same name and type as a potentially generated function template specialization.144
Change 14.8.1 [temp.arg.explicit] paragraph 4 as follows:
[Note: An empty template argument list can be used to indicate that a given use refers to a specialization of a function template even when a normal (i.e., non-template) function (8.3.5 [dcl.fct]) is visible that would otherwise be used. For example:...
[Moved to DR at the September, 2013 meeting.]
Currently, 1.9 [intro.execution] paragraph 3 says,
Certain other aspects and operations of the abstract machine are described in this International Standard as unspecified (for example, order of evaluation of arguments to a function).
However, the order of evaluation of function arguments is no longer “unspecified;” instead, their value computations are unsequenced. A better example of unspecified behavior is needed.
Proposed resolution (April, 2013):
Change 1.9 [intro.execution] paragraph 3 as follows:
Certain other aspects and operations of the abstract machine are described in this International Standard as unspecified (for example, order of evaluation of arguments to a function evaluation of expressions in a new-initializer if the allocation function fails to allocate memory (5.3.4 [expr.new])). Where possible...
[Moved to DR at the September, 2013 meeting.]
According to 3.7.4.3 [basic.stc.dynamic.safety] paragraph 4,
an implementation may have strict pointer safety, in which case a pointer value that is not a safely-derived pointer value is an invalid pointer value unless the referenced complete object is of dynamic storage duration and has previously been declared reachable (20.7.4 [util.dynamic.safety]).
“Safely-derived pointer” is defined only with respect to dynamically-allocated storage. Presumably pointers to objects with automatic and static storage duration should also be considered valid.
Proposed resolution (April, 2013):
Change 3.7.4.3 [basic.stc.dynamic.safety] paragraph 4 as follows:
Alternatively, an implementation may have strict pointer safety, in which case a pointer value referring to an object with dynamic storage duration that is not a safely-derived pointer value is an invalid pointer value unless the referenced complete object is of dynamic storage duration and has previously been declared reachable (20.7.4 [util.dynamic.safety]). [Note:...
[Moved to DR at the September, 2013 meeting.]
According to 4.5 [conv.prom] paragraph 4,
A prvalue of an unscoped enumeration type whose underlying type is fixed (7.2 [dcl.enum]) can be converted to a prvalue of its underlying type. Moreover, if integral promotion can be applied to its underlying type, a prvalue of an unscoped enumeration type whose underlying type is fixed can also be converted to a prvalue of the promoted underlying type.
Because both of these conversions have the same rank, a call like the following is ambiguous, even though conversion to the underlying type might seem better than conversion to int:
enum E : char { e };
void f(char);
void f(int);
void g() {
f(e); // ambiguous
}
On the other hand, character types often have non-numeric semantics in programs, and programmers might use a character type just to set the size of the enumeration's object representation, not to imply character semantics for the enumeration. It might be better to leave the ambiguity in place in order to require programmers to make their intent explicit.
Proposed resolution (June, 2013):
Change 13.3.3.2 [over.ics.rank] paragraph 4 as follows:
...Two conversion sequences with the same rank are indistinguishable unless one of the following rules applies:
A conversion that does not convert a pointer, a pointer to member, or std::nullptr_t to bool is better than one that does.
A conversion that promotes an enumeration whose underlying type is fixed to its underlying type is better than one that promotes to the promoted underlying type, if the two are different.
If class B is derived...
[Moved to DR at the September, 2013 meeting.]
According to the current wording of 5 [expr] paragraph 11, the lvalue-to-rvalue conversion applies only to volatile lvalues in the listed contexts. Presumably it should apply to volatile xvalues as well.
Proposed resolution (June, 2013):
Change 5 [expr] paragraph 11 as follows:
...The lvalue-to-rvalue conversion (4.1 [conv.lval]) is applied if and only if the expression is an lvalue a glvalue of volatile-qualified type and it is one of the following:...
[Moved to DR at the September, 2013 meeting.]
According to 7.1.1 [dcl.stc] paragraph 4,
When thread_local is applied to a variable of block scope the storage-class-specifier static is implied if it does not appear explicitly.
This, presumably unintentionally, prohibits a declaration like
void f() { extern thread_local int n; }
Proposed resolution (April, 2013):
When thread_local is applied to a variable of block scope the storage-class-specifier static is implied if it does not appear explicitly no other storage-class-specifier appears in the decl-specifier-seq.
[Moved to DR at the September, 2013 meeting.]
After the resolution of issue 1359, one of the requirements for constexpr constructors is:
if the class is a non-empty union, or for each non-empty anonymous union member of a non-union class, exactly one non-static data member shall be initialized;
This wording does not appear to handle nested anonymous unions. For example:
struct S { union { union { int x = 1; float f; }; void *p = nullptr; }; };
Clearly here both S::x and S::p are initialized, but that does not appear to violate the new constraint.
Additional note (March, 2013):
It is not clear whether this example violates 9.5 [class.union] paragraph 5:
The member-specification of an anonymous union shall only define non-static data members. [Note: Nested types and functions cannot be declared within an anonymous union. —end note]
Is a nested anonymous union a “non-static data member” or a “nested type?”
See also issues 57, 1460, 1562, 1587, 1621, and 1623.
Proposed resolution (June, 2013):
Change 9.5 [class.union] paragraph 5 as follows:
...The member-specification of an anonymous union shall only define non-static data members. [Note: Nested types, anonymous unions, and functions cannot be declared within an anonymous union. —end note] The names of the members...
[Moved to DR at the September, 2013 meeting.]
According to 7.1.5 [dcl.constexpr] paragraph 5
For a constexpr constructor, if no argument values exist such that after function invocation substitution, every constructor call and full-expression in the mem-initializers would be a constant expression (including conversions), the program is ill-formed; no diagnostic required.
However, paragraph 4 also says,
every constructor involved in initializing non-static data members and base class sub-objects shall be a constexpr constructor;
violation of which requires a diagnostic. The question is whether a constructor call appearing in a mem-initializer expression is “involved in” the initialization of X::m. Given the “no diagnostic required” status of constructor calls in paragraph 5, the intent of the “involved in” phrasing would appear to be referring to constructors of members with class types and of base-class subobjects, but the wording should be clarified. For example, in a constructor definition like
constexpr X(): m(f(S())) { }
if S::S() is not constexpr, is a diagnostic required? For another example,
struct S { constexpr S() {} S(int); }; struct A { S s; }; struct C { A x; constexpr C(): x{ 1 } {} };
Is S::S(int) “involved?”
Proposed resolution (August, 2013):
Change 7.1.5 [dcl.constexpr] paragraph 4 as follows:
...In addition, either its function-body shall be = delete, or it shall satisfy the following constraints:
...
for a non-delegating constructor, every constructor involved in initializing selected to initialize non-static data members and base class sub-objects shall be a constexpr constructor.;
for a delegating constructor, the target constructor shall be a constexpr constructor.
[Moved to DR at the September, 2013 meeting.]
In an enumeration whose underlying type is not fixed, the type of the first enumerator is unspecified if it has no initializer, meaning that an implementation could choose either a signed or an unsigned type. As a result, the values of one and two in this example could be either -1 and 0 or very large unsigned numbers:
enum { zero, one = zero -1, two };
It would be better if 7.2 [dcl.enum] paragraph 5 specified the type of the first enumerator as a signed type.
Proposed resolution (August, 2013):
Change 7.2 [dcl.enum] paragraph 5 as follows:
...If the underlying type is not fixed, the type of each enumerator is the type of its initializing value:
If an initializer is specified for an enumerator...
If no initializer is specified for the first enumerator, the initializing value has an unspecified signed integral type.
Otherwise...
[Moved to DR at the September, 2013 meeting.]
There are at least a couple of problems with the current wording of 7.3.3 [namespace.udecl]. First is the use of the word “entity” to describe what the using-declaration represents. For example, in paragraph 1,
If a using-declaration names a constructor (3.4.3.1 [class.qual]), it implicitly declares a set of constructors in the class in which the using-declaration appears (12.9 [class.inhctor]); otherwise the name specified in a using-declaration is a synonym for the name of some entity declared elsewhere.
An overload set of functions and function templates is not an “entity,” according to 3 [basic] paragraph 3. A better phrasing might be something like, “...a synonym for a set of declarations in a different declarative region.”
The other problem is in paragraph 11:
The entity declared by a using-declaration shall be known in the context using it according to its definition at the point of the using-declaration. Definitions added to the namespace after the using-declaration are not considered when a use of the name is made.
This has the same problem with use of the term “entity,” and it also refers to “definitions” when presumably it means “declarations.”
Proposed resolution (October, 2012) [superseded]:
Add the following as a new paragraph after 3.3.2 [basic.scope.pdecl] paragraph 3:
The point of declaration for a class or class template first declared by a class-specifier is immediately after the identifier or simple-template-id (if any) in its class-head (Clause 9 [class]). The point of declaration for an enumeration is immediately after the identifier (if any) in either its enum-specifier (7.2 [dcl.enum]) or its first opaque-enum-declaration (7.2 [dcl.enum]), whichever comes first. The point of declaration of an alias or alias template immediately follows the type-id to which the alias refers.
The point of declaration of a using-declaration that does not name a constructor is immediately after the using-declaration (7.3.3 [namespace.udecl]).
Change 7.3.3 [namespace.udecl] paragraph 1 as follows:
...If a using-declaration names a constructor (3.4.3.1 [class.qual]), it implicitly declares a set of constructors in the class in which the using-declaration appears (12.9 [class.inhctor]); otherwise the name specified in a using-declaration is a synonym for the name of some entity declared elsewhere a set of declarations in another namespace or class.
Change 7.3.3 [namespace.udecl] paragraph 11 as follows:
The entity declared by a using-declaration shall be known in the context using it according to its definition at the point of the using-declaration. Definitions Declarations added to the namespace after the using-declaration are not considered when a use of the name is made. [Note: Thus, additional overloads and default arguments (8.3.6 [dcl.fct.default]) added after the using-declaration are ignored, but template specializations (14.5.5 [temp.class.spec], 14.7.3 [temp.expl.spec]) are considered. —end note] [Example:...
Additional note (October, 2012):
The note added by this resolution to 7.3.3 [namespace.udecl] paragraph 11 regarding the treatment of default arguments directly contradicts the normative statement of 8.3.6 [dcl.fct.default] paragraph 9:
When a declaration of a function is introduced by way of a using-declaration (7.3.3 [namespace.udecl]), any default argument information associated with the declaration is made known as well. If the function is redeclared thereafter in the namespace with additional default arguments, the additional arguments are also known at any point following the redeclaration where the using-declaration is in scope.
The issue has been returned to "review" status for reconciliation of these two passages.
Proposed resolution (June, 2013):
Insert a new paragraph following 3.3.2 [basic.scope.pdecl] paragraph 3, as follows
...whichever comes first. The point of declaration of an alias or alias template immediately follows the type-id to which the alias refers.
The point of declaration of a using-declaration that does not name a constructor is immediately after the using-declaration (7.3.3 [namespace.udecl]).
The point of declaration for an enumerator...
Change 7.3.3 [namespace.udecl] paragraph 1 as follows:
...If a using-declaration names a constructor (3.4.3.1 [class.qual]), it implicitly declares a set of constructors in the class in which the using-declaration appears (12.9 [class.inhctor]); otherwise the name specified in a using-declaration is a synonym for the name of some entity declared elsewhere a set of declarations in another namespace or class.
Change 7.3.3 [namespace.udecl] paragraph 11 as follows:
The entity declared by a using-declaration shall be known in the context using it according to its definition at the point of the using-declaration. Definitions Members added to the namespace after the using-declaration are not considered when a use of the name is made. [Note: Thus, additional overloads added after the using-declaration are ignored, but default function arguments (8.3.6 [dcl.fct.default]), default template arguments (14.1 [temp.param]), and template specializations (14.5.5 [temp.class.spec], 14.7.3 [temp.expl.spec]) are considered. —end note] [Example:
[Moved to DR at the September, 2013 meeting.]
It is not sufficiently clear from the existing wording that pointers and references to function types containing cv-qualifiers or a ref-qualifier are not permitted and thus would result in a deduction failure if created during template argument substitution. Normative wording to that effect should be added to, e.g., 8.3.5 [dcl.fct].
Proposed resolution (October, 2012) [SUPERSEDED]:
Change 8.3.1 [dcl.ptr] paragraph 4 as follows:
[Note: There are no pointers to references Forming a pointer to reference type is ill-formed; see 8.3.2 [dcl.ref]. Forming a pointer to a function type that has cv-qualifiers or a ref-qualifier is ill-formed; see 8.3.5 [dcl.fct]. Since the address of a bit-field (9.6 [class.bit]) cannot be taken, a pointer can never point to a bit-field. —end note]
Change 8.3.2 [dcl.ref] paragraph 6 as follows:
If a typedef typedef-name (7.1.3 [dcl.typedef]), a type template-parameter (14.3.1 [temp.arg.type]), 14.1 [temp.param]) or a decltype-specifier (7.1.6.2 [dcl.type.simple]) denotes a type TR that is a reference to a type T, an attempt to create...
Add the following as a new paragraph at the end of 8.3.2 [dcl.ref]:
[Note: Forming a reference to a function type that has cv-qualifiers or a ref-qualifier is ill-formed; see 8.3.5 [dcl.fct]. —end note]
Change 8.3.5 [dcl.fct] paragraph 6 as follows:
A function type with a cv-qualifier-seq or a ref-qualifier (including by typedef-name (7.1.3 [dcl.typedef], 14.1 [temp.param]) or decltype-specifier (7.1.6.2 [dcl.type.simple])) shall only be part of appear as:
...
the type-id of a template-argument for a type-parameter (14.2 [temp.names] 14.3.1 [temp.arg.type]).
[Example:
typedef int FIC(int) const; FIC f; // ill-formed: does not declare a member function struct S { FIC f; // OK }; FIC S::*pm = &S::f; // OK
—end example]
The effect of a cv-qualifier-seq in a function declarator...
Delete the following text from 8.3.5 [dcl.fct] paragraph 10 (the example is moved to paragraph 6, as indicated in the preceding change):
A typedef of a function type whose declarator includes a cv-qualifier-seq shall be used only to declare the function type for a non-static member function, to declare the function type to which a pointer to member refers, or to declare the top-level function type of another function typedef declaration. [Example: ... —end example]
Additional note (March, 2013):
The issue is being returned to "review" status out of concern that 13.6 [over.built] paragraph 11 requires forming a reference to a function type that might have a cv-qualifier or ref-qualifier.
Proposed resolution (April, 2013):
Change 8.3.1 [dcl.ptr] paragraph 4 as follows:
[Note: There are no pointers to references Forming a pointer to reference type is ill-formed; see 8.3.2 [dcl.ref]. Forming a pointer to function type is ill-formed if the function type has cv-qualifiers or a ref-qualifier; see 8.3.5 [dcl.fct]. Since the address of a bit-field (9.6 [class.bit]) cannot be taken, a pointer can never point to a bit-field. —end note]
Change 8.3.2 [dcl.ref] paragraph 6:
If a typedef typedef-name (7.1.3 [dcl.typedef]), a type template-parameter (14.3.1 [temp.arg.type]), 14.1 [temp.param]) or a decltype-specifier (7.1.6.2 [dcl.type.simple]) denotes a type TR that is a reference to a type T, an attempt to create the type “lvalue reference to cv TR” creates the type “lvalue reference to T”, while an attempt to create the type “rvalue reference to cv TR" creates the type TR. [Example:...
Add the following as a new paragraph at the end of 8.3.2 [dcl.ref]:
[Note: Forming a reference to function type is ill-formed if the function type has cv-qualifiers or a ref-qualifier; see 8.3.5 [dcl.fct]. —end note]
Change 8.3.5 [dcl.fct] paragraph 6 as follows:
A function type with a cv-qualifier-seq or a ref-qualifier (including a type named by typedef-name (7.1.3 [dcl.typedef], 14.1 [temp.param])) shall appear only be part of as:
the function type for a non-static member function,
the function type to which a pointer to member refers,
the top-level function type of a function typedef declaration or alias-declaration,
the type-id in the default argument of a type-parameter (14.1 [temp.param]), or
the type-id of a template-argument for a type-parameter (14.2 [temp.names] 14.3.1 [temp.arg.type]).
[Example:
typedef int FIC(int) const; FIC f; // ill-formed: does not declare a member function struct S { FIC f; // OK }; FIC S::*pm = &S::f; // OK
—end example] The effect of a cv-qualifier-seq...
Change 8.3.5 [dcl.fct] paragraph 10 as follows, moving the example to paragraph 6 as shown above:
...—end example] A typedef of a function type whose declarator includes a cv-qualifier-seq shall be used only to declare the function type for a non-static member function, to declare the function type to which a pointer to member refers, or to declare the top-level function type of another function typedef declaration. [Example: ... —end example]
Change 13.6 [over.built] paragraph 11 as follows:
For every quintuple (C1, C2, T, CV1, CV2), where C2 is a class type, C1 is the same type as C2 or is a derived class of C2, T is an object type or a function type, and CV1 and CV2 are cv-qualifier-seqs, there exist candidate operator functions of the form
CV12 T& operator->*(CV1 C1*, CV2 T C2::*);
where CV12 is the union of CV1 and CV2. The return type is shown for exposition only; see 5.5 [expr.mptr.oper] for the determination of the operator's result type.
[Moved to DR at the September, 2013 meeting.]
In 8.5.4 [dcl.init.list] paragraph 5, both the cases in which a reference can be bound to the result of a conversion function use the phrase “can be implicitly converted to...” This is confusing, as it could be read as excluding explicit conversion functions. However, that appears not to be the intent, as 13.3.1.6 [over.match.ref], which is cited in these cases, allows explicit conversion functions for direct-initialization.
Proposed resolution (August, 2011) [SUPERSEDED]:
Change the two indicated (not contiguous) sub-bullets of 8.5.3 [dcl.init.ref] paragraph 5 as follows:
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an lvalue of type “cv3 T3,”...
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an xvalue, class prvalue, or function lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3” (see 13.3.1.6 [over.match.ref]),
Additional note, January, 2012:
Questions have been raised regarding the consistency of the treatment of class prvalues in this resolution with other types . The issue is thus being returned to "review" status for additional discussion.
Proposed resolution (February, 2012) [SUPERSEDED]:
Change 8.5.3 [dcl.init.ref] paragraph 5 as follows:
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
If the reference is an lvalue reference and the initializer expression
is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2,” or
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an lvalue of type “cv3 T3,” where “cv1 T1” is reference-compatible with “cv3 T3”106 (this conversion is selected by enumerating the applicable conversion functions (13.3.1.6 [over.match.ref]) and choosing the best one through overload resolution (13.3 [over.match])),
then the reference is bound...
Otherwise, the reference shall be...
If the initializer expression
is an xvalue, class prvalue, array prvalue or function lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an xvalue, class prvalue, or function lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3” (see 13.3.1.6 [over.match.ref]),
then the reference is bound... [Example:
struct A { }; struct B : A { } b; extern B f(); const A& rca2 = f(); // bound to the A subobject of the B rvalue. A&& rra = f(); // same as above struct X { operator B(); operator int&(); } x; const A& r = x; // bound to the A subobject of the result of the conversion int i2 = 42; int&& rri = static_cast<int&&>(i2); // bound directly to i2 B&& rrb = x; // bound directly to the result of operator B int&& rri2 = X(); // error: lvalue-to-rvalue conversion applied to the // result of operator int&—end example]
Otherwise, a temporary... [Example:
const A& r = x; // r refers to a temporary B&& rrb = x; // rrb refers to a temporary const double& rcd2 = 2; // rcd2 refers to temporary with value 2.0 double&& rrd = 2; // rrd refers to temporary with value 2.0 ...
Change 13.3.1.6 [over.match.ref] paragraph 1 as follows:
Under the conditions specified in 8.5.3 [dcl.init.ref], a reference can be bound directly to a glvalue or class prvalue that is the result of applying a conversion function...
The conversion functions of S and its base classes are considered, except that for copy-initialization, only the non-explicit conversion functions are considered. Those that are not hidden within S and yield type “lvalue reference to cv2 T2” (when 8.5.3 [dcl.init.ref] requires an lvalue result) or “cv2 T2” or “rvalue reference to cv2 T2” (when 8.5.3 [dcl.init.ref] requires an rvalue result), where “cv1 T” is reference-compatible (8.5.3 [dcl.init.ref]) with “cv2 T2”, are candidate functions.
Note from the April, 2013 meeting:
Because of concerns about slicing and performance in the February, 2012 proposed resolution, CWG decided to return to the August, 2011 proposed resolution and split off the discussion about class prvalues into issue 1650.
Proposed resolution (April, 2013):
Change the two indicated (not contiguous) sub-bullets of 8.5.3 [dcl.init.ref] paragraph 5 as follows:
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an lvalue of type “cv3 T3,”...
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an xvalue, class prvalue, or function lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3” (see 13.3.1.6 [over.match.ref]),
[Moved to DR at the September, 2013 meeting.]
Can a constructor template ever be an initializer-list constructor without std::initializer_list (or an alias template specialization for it) appearing in the template signature? E.g., is there any way that the constructor in:
struct S { template<typename T> S(T); };
can be an initializer-list constructor?
Proposed resolution (October, 2012) [superseded]:
Modify 8.5.4 [dcl.init.list] paragraph 2 as follows:
A constructor is an initializer-list constructor if its first parameter is of type std::initializer_list<E> or reference to possibly cv-qualified std::initializer_list<E> for some type E, and either there are no other parameters or else all other parameters have default arguments (8.3.6 [dcl.fct.default]). [Note: Initializer-list constructors are favored over other constructors in list-initialization (13.3.1.7 [over.match.list]). Given a class C, a constructor template such as template<class T> C(T) is never instantiated to produce an initializer-list constructor, because an initializer list argument causes the corresponding parameter to be a non-deduced context (14.8.2.1 [temp.deduct.call]). —end note] The template std::initializer_list is not predefined;...
Additional note (January, 2013):
The wording of the new note needs to be adjusted, because such a constructor template might have a default template argument that is a specialization of std::initializer_list. For example:
struct D { template<typename T = std::initializer_list<int>> D(T); }; D d{{1, 2, 3}};
Proposed resolution (June, 2013):
Change the note in 8.5.4 [dcl.init.list] paragraph 2 as follows:
[Note: Initializer-list constructors are favored over other constructors in list-initialization (13.3.1.7 [over.match.list]). Passing an initializer list as the argument to the constructor template template<class T> C(T) of a class C does not create an initializer-list constructor, because an initializer list argument causes the corresponding parameter to be a non-deduced context (14.8.2.1 [temp.deduct.call]). —end note]
[Moved to DR at the September, 2013 meeting.]
When a similar question was raised in issue 413, the resolution was to remove the use of the term. The resolution of issue 1359 has now reintroduced the concept of an “empty” union, so there is once again the need to define it.
(See also issues 1562 and 1622.)
Proposed resolution (February, 2013) [superseded]:
Change 9.5 [class.union] paragraph 2 as follows:
...At most one non-static data member of a union may have a brace-or-equal-initializer. A union is an empty union if it has no non-static data members. [Note: If any...
Additional note (March, 2013):
The question was raised as to whether an example like
union A { union {}; union {}; constexpr A() {} }; A a = A();
is well-formed, which hinges on the question of whether A is an “empty union,” per 7.1.5 [dcl.constexpr] paragraph 4 bullet 5:
if the class is a non-empty union, or for each non-empty anonymous union member of a non-union class, exactly one non-static data member shall be initialized;
Must one of the empty anonymous union members be initialized for A's constructor to be constexpr?
The issue is being returned to "review" status for discussion of this point.
See also issues 1562, 1587, 1621, and 1623.
Proposed resolution (August, 2013):
Change 7.1.5 [dcl.constexpr] paragraph 4 as follows:
...In addition, either its function-body shall be = delete, or it shall satisfy the following constraints:
...
if the class is a non-empty union, or for each non-empty anonymous union member of a non-union class, exactly one non-static data member shall be initialized;
if the class is a union having variant members (9.5 [class.union]), exactly one of them shall be initialized;
if the class is a union-like class, but is not a union, for each of its anonymous union members having variant members, exactly one of them shall be initialized;
every constructor involved...
Change 9.5 [class.union] paragraph 2 as follows:
A union can have member functions (including constructors and destructors), but not virtual (10.3 [class.virtual]) functions. A union shall not have base classes. A union shall not be used as a base class. If a union contains a non-static data member of reference type the program is ill-formed. At most one non-static data member of a union may have a brace-or-equal-initializer. [Note: If any non-static data member...
Change 9.5 [class.union] paragraph 8 as follows:
A union-like class is a union or a class that has an anonymous union as a direct member. A union-like class X has a set of variant members. If X is a union its variant members are the non-static data members; otherwise, its variant members are the non-static data members of all anonymous unions that are members of X. If X is a union, a non-static data member of X that is not an anonymous union is a variant member of X. In addition, a non-static data member of an anonymous union that is a member of X is also a variant member of X. At most one variant member of a union may have a brace-or-equal-initializer. [Example:
union U { int x = 0; union { }; union { int z; int y = 1; // error: initialization for second variant member of U }; };
—end example]
Change 12.6.2 [class.base.init] paragraph 8 as follows:
In a non-delegating constructor, if a given non-static data member or base class is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer) and the entity is not a virtual base class of an abstract class (10.4 [class.abstract]), then
if the entity is a non-static data member that has a brace-or-equal-initializer and either
the constructor's class is a union (9.5 [class.union]), and no other variant member of that union is designated by a mem-initializer-id or
the constructor's class is not a union, and, if the entity is a member of an anonymous union, no other member of that union is designated by a mem-initializer-id,
the entity is initialized as specified in 8.5 [dcl.init];
otherwise, if the entity...
(This resolution also resolves issue 1562.)
[Moved to DR at the September, 2013 meeting.]
Consider an example like:
struct S {
typedef int T;
enum E : T {};
enum E : T {}; // #1
};
The declaration at #1 is ambiguous: it could either be an invalid redefinition of enum E or a zero-length bit-field of type enum E (since T{} is zero-valued constant expression). The current Standard does not appear to have a rule to disambiguate the two.
Proposed resolution (October, 2012) [superseded]:
Change 7.2 [dcl.enum] paragraph 1 as follows:
...the attributes in that attribute-specifier-seq are thereafter considered attributes of the enumeration whenever it is named. In a member-specification, an ambiguity can arise from the similarity between the declaration of an enumeration with an enum-base and the declaration of a zero-length unnamed bit-field of enumeration type. The ambiguity appears as a choice between an enum-specifier and a member-declaration for a bit-field. The resolution is that a : following enum identifier is parsed as part of an enum-base. [Example:
struct S { enum E : int {}; enum E : int {}; // error: redeclaration of enumeration };
—end example]
Notes from the April, 2013 meeting:
The ambiguity does not occur only with an empty set of braces but also when there is a single identifier that could be taken as the name of a constant in a containing scope or the declaration of an enumerator.
The resolution above sounds as if it is to be applied only if an ambiguity occurs; it should instead be always applied.
Proposed resolution (June, 2013):
Change 7.2 [dcl.enum] paragraph 1 as follows:
...the attributes in that attribute-specifier-seq are thereafter considered attributes of the enumeration whenever it is named. A : following “enum identifier” is parsed as part of an enum-base. [Note: This resolves a potential ambiguity between the declaration of an enumeration with an enum-base and the declaration of an unnamed bit-field of enumeration type. [Example:
struct S { enum E : int {}; enum E : int {}; // error: redeclaration of enumeration };
—end example] —end note]
[Moved to DR at the September, 2013 meeting.]
The following example is ill-formed:
union U { int a = 0; float f; U() {} // ok, a initialized to 0 U(float f) : f(f) {} // error, constructor initializes both a and f };
because of 12.6.2 [class.base.init] paragraph 8:
An attempt to initialize more than one non-static data member of a union renders the program ill-formed.
In non-union classes, a mem-initializer takes precedence over a non-static data member initializer, per paragraph 9:
If a given non-static data member has both a brace-or-equal-initializer and a mem-initializer, the initialization specified by the mem-initializer is performed, and the non-static data member's brace-or-equal-initializer is ignored.
It would be reasonable to treat union mem-initializers in an analogous fashion.
(See also issue 1460.)
Proposed resolution (March, 2013) [superseded]:
Change 12.6.2 [class.base.init] paragraph 8 as follows:
[Note: this wording was reviewed during the 2013-03-25 teleconference.]In a non-delegating constructor, if a given non-static data member or base class is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer) and the entity is not a virtual base class of an abstract class (10.4 [class.abstract]), then
if the entity is a non-static data member that has a brace-or-equal-initializer and, if the entity is a member of a union, no other non-static data member of that union is designated by a mem-initializer-id, the entity is initialized as specified in 8.5 [dcl.init];
...
See also issues 1460, 1587, 1621, and 1623.
Proposed resolution (August, 2013):
This issue is resolved by the resolution of issue 1460.
[Moved to DR at the September, 2013 meeting.]
The grammar in 12.6.2 [class.base.init] paragraph 1 for mem-initializer-list is incorrect:
The ellipsis in the second production should be on mem-initializer, not on mem-initializer-list.
Proposed resolution (August, 2013):
Change the grammar in 12.6.2 [class.base.init] paragraph 1 as follows:
[Moved to DR at the September, 2013 meeting.]
Paragraphs 12 and 25 of 12.8 [class.copy] both say that the function
is trivial if it is not user-provided, its declared parameter type is the same as if it had been implicitly declared, and...
However, a non-user-provided function might have more than one parameter if default arguments are used. The phrasing would be better as something like “its parameter-type-list is equivalent to the parameter-type-list of an implicit declaration.” (For consistency, the same phrasing should be used in 12.1 [class.ctor] paragraph 5.)
Proposed resolution (June, 2013):
Change 12.8 [class.copy] paragraph 12 as follows:
A copy/move constructor for class X is trivial if it is not user-provided, its declared parameter type is the same as if it had been implicitly declared parameter-type-list is equivalent to the parameter-type-list of an implicit declaration, and if...
Change 12.8 [class.copy] paragraph 25 as follows:
A copy/move assignment operator for class X is trivial if it is not user-provided, its declared parameter type is the same as if it had been implicitly declared parameter-type-list is equivalent to the parameter-type-list of an implicit declaration, and if...
[Moved to DR at the September, 2013 meeting.]
According to 12.9 [class.inhctor] paragraph 3,
For each non-template constructor in the candidate set of inherited constructors other than a constructor having no parameters or a copy/move constructor having a single parameter, a constructor is implicitly declared with the same constructor characteristics unless there is a user-declared constructor with the same signature in the class where the using-declaration appears.
It seems that this should be suppressing constructors that would be copy/move constructors in the derived class rather than copy/move constructors in the base class. For example:
struct B; struct A { A(const A&); A(const B&); A(int); }; struct B: A { using A::A; };
If B::B(const B&) is an inheriting constructor, other subobjects of B will not be copied. Also, if A::A(const A&) is not inherited, B objects cannot be constructed from an A object.
Proposed resolution (April, 2013):
Change 12.9 [class.inhctor] paragraph 3 as follows:
For each non-template constructor in the candidate set of inherited constructors other than a constructor having no parameters or a copy/move constructor having a single parameter, a constructor is implicitly declared with the same constructor characteristics unless there is a user-declared constructor with the same signature in the class where the using-declaration appears or the constructor would be a default, copy, or move constructor for that class.
[Moved to DR at the September, 2013 meeting.]
There is an unfortunate disparity between the treatment of an example like
struct S { int operator[](int); auto f() -> decltype(this->operator[](0)); };
(which is permitted, cf 5.1.1 [expr.prim.general] paragraph 3), and
struct S { int operator[](int); auto f() -> decltype((*this)[0]); };
which is not. The reason for rejecting the latter is 13.3.1.2 [over.match.oper] paragraph 3:
For a unary operator @ with an operand of a type whose cv-unqualified version is T1, and for a binary operator @ with a left operand of a type whose cv-unqualified version is T1 and a right operand of a type whose cv-unqualified version is T2, three sets of candidate functions, designated member candidates, non-member candidates and built-in candidates, are constructed as follows:
If T1 is a complete class type, the set of member candidates is the result of the qualified lookup of T1::operator@ (13.3.1.1.1 [over.call.func]); otherwise, the set of member candidates is empty.
...
It would be desirable to update the latter specification to allow lookup of preceding declarations in a class currently being defined, analogously with the lookup performed in the function-notation case.
Proposed resolution (April, 2013):
Change 13.3.1.2 [over.match.oper] paragraph 3 bullet 1 as follows:
If T1 is a complete class type or a class currently being defined, the set of member candidates is the result of the qualified lookup of T1::operator@ (13.3.1.1.1 [over.call.func]); otherwise, the set of member candidates is empty.
[Moved to DR at the September, 2013 meeting.]
In an example like
void f(int const(&)[2]); void f(int const(&)[3]); int main() { f({1, 2, 3}); }
the current overload resolution rules make no provision for different array sizes and thus treats the call as ambiguous, even though it seems obvious that the second f should be chosen in this case.
Rationale (August, 2011):
The implications of array temporaries for the language should be considered by the Evolution Working Group in a comprehensive fashion rather than on a case-by-case basis. See also issues 1300, 1326, and 1525.
Notes from the October, 2012 meeting:
CWG determined that this issue is unrelated to array temporaries and that a tiebreaker should be added for this case in overload resolution.
Proposed resolution, April, 2013:
Change 13.3.3.1.5 [over.ics.list] paragraph 2 as follows, adding a new paragraph (and moving the footnote to the new paragraph, as indicated):
[Drafting note: no other case in the remainder of the paragraph applies when the initializer list has more elements than the parameter array.]If the parameter type is std::initializer_list<X> or “array of X” [Footnote: Since there are no parameters of array type, this will only occur as the underlying type of a reference parameter. —end footnote] and all the elements of the initializer list can be implicitly converted to X, the implicit conversion sequence is the worst conversion necessary to convert an element of the list to X. This conversion can be a user-defined conversion even in the context of a call to an initializer-list constructor. [Example: ... —end example]
Otherwise, if the parameter type is “array of N X” [Footnote: Since there are no parameters of array type, this will only occur as the underlying type of a reference parameter. —end footnote], if the initializer list has exactly N elements or if it has fewer than N elements and X is default-constructible, and if all the elements of the initializer list can be implicitly converted to X, the implicit conversion sequence is the worst conversion necessary to convert an element of the list to X.
Change 13.3.3.2 [over.ics.rank] paragraph 3 as follows:
Two implicit conversion sequences of the same form are indistinguishable conversion sequences unless one of the following rules applies:
...
List-initialization sequence L1 is a better conversion sequence than list-initialization sequence L2 if L1 converts to std::initializer_list<X> for some X and L2 does not.
L1 converts to std::initializer_list<X> for some X and L2 does not, or, if not that,
L1 converts to type “array of N1 T,” L2 converts to type “array of N2 T”, and N1 is smaller than N2.
[Moved to DR at the September, 2013 meeting.]
According to 14.3.2 [temp.arg.nontype] paragraph 1, the argument for a non-type template parameter of pointer or reference type must be
a constant expression (5.19 [expr.const]) that designates the address of an object with static storage duration and external or internal linkage or a function with external or internal linkage, including function templates and function template-ids but excluding non-static class members, expressed (ignoring parentheses) as & id-expression, except that the & may be omitted if the name refers to a function or array and shall be omitted if the corresponding template-parameter is a reference
In C++03, the requirement for an id-expression eliminated the use of “addresses of array elements and names or addresses of non-static class members,” as noted in paragraph 3 of that section. With the advent of generalized constant expressions, however, it is possible to satisfy the requirements and still address these subobjects. For example:
extern constexpr int x[] = { 0, 1 }; constexpr const int *p1 = x + 1; const int &r = *p1; template <const int *> struct A; template <> struct A<&r> { };
If this is intentional, the note in 14.3.2 [temp.arg.nontype] paragraph 3 should be revised or removed; if not, the normative wording of paragraph 1 must be revised.
Notes from the April, 2013 meeting:
CWG did not favor extending the range of non-type template arguments to include subobjects, feeling that they should continue to be restricted to the address of a complete object.
Proposed resolution (June, 2013):
Change 14.3.2 [temp.arg.nontype] paragraph 1 as follows:
A template-argument for a non-type, non-template template-parameter shall be one of:
...
a constant expression (5.19 [expr.const]) that designates the address of an a complete object with static storage duration and external or internal linkage or a function with external or internal linkage, including function templates and function template-ids but excluding non-static class members, expressed (ignoring parentheses) as & id-expression, where the id-expression is the name of an object or function, except that the & may be omitted if the name refers to a function or array and shall be omitted if the corresponding template-parameter is a reference; or
...
[Moved to DR at the September, 2013 meeting.]
According to 14.3.3 [temp.arg.template] paragraph 3,
A template-argument matches a template template-parameter (call it P) when each of the template parameters in the template-parameter-list of the template-argument's corresponding class template or alias template (call it A) matches the corresponding template parameter in the template-parameter-list of P.
There does not appear to be a formal definition of the criteria for whether two template parameters “match,” however, and there is implementation variance in the treatment of an example like
struct A {
typedef int T1;
typedef int T2;
};
template<template<typename T, typename T::T1 N> class U>
struct B {
U<A, 0> u;
};
template<typename T, typename T::T2 N>
struct C {
};
B<C> b; // ok?
Proposed resolution (June, 2013):
Change 14.3.3 [temp.arg.template] paragraph 3 as follows:
A template-argument matches a template template-parameter (call it P) when each of the template parameters in the template-parameter-list of the template-argument's corresponding class template or alias template (call it A) matches the corresponding template parameter in the template-parameter-list of P. Two template parameters match if they are of the same kind (type, non-type, template), for non-type template-parameters, their types are equivalent (14.5.6.1 [temp.over.link]), and for template template-parameters, each of their corresponding template-parameters matches, recursively. When P's template-parameter-list contains a template parameter pack...
[Moved to DR at the September, 2013 meeting.]
According to 14.8.2.5 [temp.deduct.type] paragraph 5, one of the non-deduced contexts is
A function parameter pack that does not occur at the end of the parameter-declaration-clause.
This would make the following example ill-formed:
template <typename R, typename ...P> void foo(R (&)(P ..., ...)) { } void bar(int, ...) { } void zip() { foo(bar); }
It is not clear whether this is intentional; if the wording referred to parameter-declaration-list instead of parameter-declaration-clause, the example would be accepted.
Proposed resolution (June, 2013):
Change 14.8.2.5 [temp.deduct.type] paragraph 5 as follows
The non-deduced contexts are:
...
A function parameter pack that does not occur at the end of the parameter-declaration-clause parameter-declaration-list.
[Moved to DR at the September, 2013 meeting.]
The current specification does not appear to say whether an implementation is permitted/required/forbidden to complain when a sub-object's destructor is inaccessible. In particular, if there is no possibility for an exception to be thrown following a given sub-object's construction, should an implementation issue an error if that sub-object's destructor is inaccessible?
Proposed resolution (February, 2013):
Change 3.2 [basic.def.odr] paragraph 2 as follows:
...A destructor for a class is odr-used as specified in if it is potentially invoked (12.4 [class.dtor]).
Change 5.3.4 [expr.new] paragraph 17 as follows:
If the new-expression creates an object or an array of objects of class type, access and ambiguity control are done for the allocation function, the deallocation function (12.5 [class.free]), and the constructor (12.1 [class.ctor]). If the new expression new-expression creates an array of objects of class type, access and ambiguity control are done for the destructor is potentially invoked (12.4 [class.dtor]).
Change 12.4 [class.dtor] paragraph 11 as follows:
Destructors are A destructor is invoked implicitly
for a constructed objects with static storage duration (3.7.1 [basic.stc.static]) at program termination (3.6.3 [basic.start.term]),
for a constructed objects with thread storage duration (3.7.2 [basic.stc.thread]) at thread exit,
for a constructed objects with automatic storage duration (3.7.3 [basic.stc.auto]) when the block in which an object is created exits (6.7 [stmt.dcl]),
for a constructed temporary objects when the its lifetime of a temporary object ends (12.2 [class.temporary]),.
for constructed objects allocated by a new-expression (5.3.4 [expr.new]), through use of a delete-expression (5.3.5 [expr.delete]),
in several situations due to the handling of exceptions (15.3 [except.handle]).
In each case, the context of the invocation is the context of the construction of the object. A destructor is also invoked implicitly through use of a delete-expression (5.3.5 [expr.delete]) for a constructed object allocated by a new-expression (5.3.4 [expr.new]); the context of the invocation is the delete-expression. [Note: An array of class type contains several subobjects for each of which the destructor is invoked. —end note] A destructor can also be invoked explicitly. A destructor is potentially invoked if it is invoked or as specified in 5.3.4 [expr.new] and 12.6.2 [class.base.init]. A program is ill-formed if an object of class type or array thereof is declared and the destructor for the class is not accessible at the point of the declaration a destructor that is potentially invoked is deleted or not accessible from the context of the invocation. Destructors can also be invoked explicitly.
Add the following as a new paragraph following 12.6.2 [class.base.init] paragraph 9:
If a given non-static data member has both...
In a non-delegating constructor, the destructor for each direct or virtual base class and for each non-static data member of class type is potentially invoked (12.4 [class.dtor]). [Note: This provision ensures that destructors can be called for fully-constructed sub-objects in case an exception is thrown (15.2 [except.ctor]). —end note]
In a non-delegating constructor, initialization proceeds...
[Voted into the WP at the June, 2008 meeting.]
The C99 and C++ Standards disagree about the validity of two Cyrillic characters for use in identifiers. C++ (_N2691_.E [extendid]) says that 040d is valid in an identifier but that 040e is not; C99 (Annex D) says exactly the opposite. In fact, both characters should be accepted in identifiers; see the Unicode chart.
Proposed resolution (February, 2008):
The reference in paragraph 2 should be changed to ISO/IEC TR 10176:2003 and the table should be changed to conform to the one in that document (beginning on page 34).
[Voted into WP at April, 2007 meeting.]
Section 1.3 [intro.defs], definition of "signature" omits the function name as part of the signature. Since the name participates in overload resolution, shouldn't it be included in the definition? I didn't find a definition of signature in the ARM, but I might have missed it.
Fergus Henderson: I think so. In particular, 17.6.4.3.2 [global.names] reserves certain "function signatures" for use by the implementation, which would be wrong unless the signature includes the name.
-2- Each global function signature declared with external linkage in a header is reserved to the implementation to designate that function signature with external linkage.
-5- Each function signature from the Standard C library declared with external linkage is reserved to the implementation for use as a function signature with both extern "C" and extern "C++" linkage, or as a name of namespace scope in the global namespace.
Other uses of the term "function signature" in the description of the standard library also seem to assume that it includes the name.
James Widman:
Names don't participate in overload resolution; name lookup is separate from overload resolution. However, the word “signature” is not used in clause 13 [over]. It is used in linkage and declaration matching (e.g., 14.5.6.1 [temp.over.link]). This suggests that the name and scope of the function should be part of its signature.
Proposed resolution (October, 2006):
Replace 1.3 [intro.defs] “signature” with the following:
the name and the parameter-type-list (8.3.5 [dcl.fct]) of a function, as well as the class or namespace of which it is a member. If a function or function template is a class member its signature additionally includes the cv-qualifiers (if any) on the function or function template itself. The signature of a function template additionally includes its return type and its template parameter list. The signature of a function template specialization includes the signature of the template of which it is a specialization and its template arguments (whether explicitly specified or deduced). [Note: Signatures are used as a basis for name-mangling and linking. —end note]
Delete paragraph 3 and replace the first sentence of 14.5.6.1 [temp.over.link] as follows:
The signature of a function template specialization consists of the signature of the function template and of the actual template arguments (whether explicitly specified or deduced).
The signature of a function template consists of its function signature, its return type and its template parameter list is defined in 1.3 [intro.defs]. The names of the template parameters are significant...
(See also issue 537.)
[Voted into WP at April, 2007 meeting.]
The standard defines “signature” in two places: 1.3 [intro.defs] and 14.5.6.1 [temp.over.link] paragraphs 3-4. The former seems to be meant as a formal definition (I think it's the only place covering the nontemplate case), yet it lacks some bits mentioned in the latter (specifically, the notion of a “signature of a function template,” which is part of every signature of the associated function template specializations).
Also, I think the 1.3 [intro.defs] words “the information about a function that participates in overload resolution” isn't quite right either. Perhaps, “the information about a function that distinguishes it in a set of overloaded functions?”
Eric Gufford:
In 1.3 [intro.defs] the definition states that “Function signatures do not include return type, because that does not participate in overload resolution,” while 14.5.6.1 [temp.over.link] paragraph 4 states “The signature of a function template consists of its function signature, its return type and its template parameter list.” This seems inconsistent and potentially confusing. It also seems to imply that two identical function templates with different return types are distinct signatures, which is in direct violation of 13.3 [over.match]. 14.5.6.1 [temp.over.link] paragraph 4 should be amended to include verbiage relating to overload resolution.
Either return types are included in function signatures, or they're not, across the board. IMHO, they should be included as they are an integral part of the function declaration/definition irrespective of overloads. Then verbiage should be added about overload resolution to distinguish between signatures and overload rules. This would help clarify things, as it is commonly understood that overload resolution is based on function signature.
In short, the term “function signature” should be made consistent, and removed from its (implicit, explicit or otherwise) linkage to overload resolution as it is commonly understood.
James Widman:
The problem is that (a) if you say the return type is part of the signature of a non-template function, then you have overloading but not overload resolution on return types (i.e., what we have now with function templates). I don't think anyone wants to make the language uglier in that way. And (b) if you say that the return type is not part of the signature of a function template, you will break code. Given those alternatives, it's probably best to maintain the status quo (which the implementors appear to have rendered faithfully).
Proposed resolution (September, 2006):
This issue is resolved by the resolution of issue 357.
[Voted into WP at April, 2006 meeting.]
The standard uses “most derived object” in some places (for example, 1.3 [intro.defs] “dynamic type,” 5.3.5 [expr.delete]) to refer to objects of both class and non-class type. However, 1.8 [intro.object] only formally defines it for objects of class type.
Possible fix: Change the wording in 1.8 [intro.object] paragraph 4 from
an object of a most derived class type is called a most derived object
to
an object of a most derived class type, or of non-class type, is called a most derived object
Proposed resolution (October, 2005):
Add the indicated words to 1.8 [intro.object] paragraph 4:
If a complete object, a data member (9.2 [class.mem]), or an array element is of class type, its type is considered the most derived class, to distinguish it from the class type of any base class subobject; an object of a most derived class type, or of a non-class type, is called a most derived object.
[Voted into the WP at the September, 2008 meeting.]
In 1.9 [intro.execution] paragraph 16, the following expression is still listed as an example of undefined behavior:
i = ++i + 1;
However, it appears that the new sequencing rules make this expression well-defined:
The assignment side-effect is required to be sequenced after the value computations of both its LHS and RHS (5.17 [expr.ass] paragraph 1).
The LHS (i) is an lvalue, so its value computation involves computing the address of i.
In order to value-compute the RHS (++i + 1), it is necessary to first value-compute the lvalue expression ++i and then do an lvalue-to-rvalue conversion on the result. This guarantees that the incrementation side-effect is sequenced before the computation of the addition operation, which in turn is sequenced before the assignment side effect. In other words, it yields a well-defined order and final value for this expression.
It should be noted that a similar expression
i = i++ + 1;
is still not well-defined, since the incrementation side-effect remains unsequenced with respect to the assignment side-effect.
It's unclear whether making the expression in the example well-defined was intentional or just a coincidental byproduct of the new sequencing rules. In either case either the example should be fixed, or the rules should be changed.
Clark Nelson: In my opinion, the poster's argument is perfectly correct. The rules adopted reflect the CWG's desired outcome for issue 222. At the Portland meeting, I presented (and still sympathize with) Tom Plum's case that these rules go a little too far in nailing down required behavior; this is a consequence of that.
One way or another, a change needs to be made, and I think we should seriously consider weakening the resolution of issue 222 to keep this example as having undefined behavior. This could be done fairly simply by having the sequencing requirements for an assignment expression depend on whether it appears in an lvalue context.
James Widman: How's this for a possible re-wording?
In all cases, the side effect of the assignment expression is sequenced after the value computations of the right and left operands. Furthermore, if the assignment expression appears in a context where an lvalue is required, the side effect of the assignment expression is sequenced before its value computation.
Notes from the February, 2008 meeting:
There was no real support in the CWG for weakening the resolution of issue 222 and returning the example to having undefined behavior. No one knew of an implementation that doesn't already do the (newly) right thing for such an example, so there was little motivation to go out of our way to increase the domain of undefined behavior. So the proposed resolution is to change the example to one that definitely does have undependable behavior in existing practice, and undefined behavior under the new rules.
Also, the new formulation of the sequencing rules approved in Oxford contained the wording that by and large resolved issue 222, so with the resolution of this issue, we can also close issue 222.
Proposed resolution (March, 2008):
Change the example in 1.9 [intro.execution] paragraph 16 as follows:
i = v[i++]; // the behavior is undefined i = 7, i++, i++; // i becomes 9 i = ++i i++ + 1; // the behavior is undefined i = i + 1; // the value of i is incremented
This resolution also resolves issue 222.
[Voted into the WP at the September, 2008 meeting.]
Is the behavior undefined in the following example?
void f() { int n = 0; n = --n; }
1.9 [intro.execution] paragraph 16 says,
If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.
It's not clear to me whether the two side-effects in n=--n are “different.” As far as I can tell, it seems that both side-effects involve the assignment of -1 to n, which in a sense makes them non-“different.” But I don't know if that's the intent. Would it be better to say “another” instead of “a different?”
On a related note, can we include this example to illustrate?
void f( int, int ); void g( int a ) { f( a = -1, a = -1 ); } // Undefined?
Proposed resolution (March, 2008):
Change 1.9 [intro.execution] paragraph 16 as follows:
...If a side effect on a scalar object is unsequenced relative to either a different another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. [Example:
void f(int, int); void g(int i, int* v) { i = v[i++]; // the behavior is undefined i = 7, i++, i++; // i becomes 9 i = ++i + 1; // the behavior is undefined i = i + 1; // the value of i is incremented f(i = -1, i = -1); // the behavior is undefined }—end example] When calling...
[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.2 [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 specialized Explicit specializations and definitions of class template static data members have ordered initialization. Other class template static data members (i.e., implicitly or explicitly instantiated specializations) 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.
[Moved to DR at October 2007 meeting.]
C99 and C++ differ in their approach to universal character names (UCNs).
Issue 248 already covers the differences in UCNs allowed for identifiers, but a more fundamental issue is that of UCNs that correspond to codes reserved by ISO 10676 for surrogate pair forms.
Specifically, C99 does not allow UCNs whose short names are in the range 0xD800 to 0xDFFF. I think C++ should have the same constraint. If someone really wants to place such a code in a character or string literal, they should use a hexadecimal escape sequence instead, for example:
wchar_t w1 = L'\xD900'; // Okay. wchar_t w2 = L'\uD900'; // Error, not a valid character.
(Compare 6.4.3 paragraph 2 in ISO/IEC 9899/1999 with 2.3 [lex.charset] paragraph 2 in the C++ standard.)
Proposed resolution (October, 2007):
This issue is resolved by the adoption of paper J16/07-0030 = WG21 N2170.
[Voted into WP at the October, 2006 meeting.]
The current wording of 2.14.3 [lex.ccon] paragraph 3 states,
If the character following a backslash is not one of those specified, the behavior is undefined.
Paper J16/04-0167=WG21 N1727 suggests that such character escapes be ill-formed. In discussions at the Lillehammer meeting, however, the CWG felt that the newly-approved category of conditionally-supported behavior would be more appropriate.
Proposed resolution (April, 2006):
Change the next-to-last sentence of 2.14.3 [lex.ccon] paragraph 3 from:
If the character following a backslash is not one of those specified, the behavior is undefined.
to:
Escape sequences in which the character following the backslash is not listed in Table 6 are conditionally-supported, with implementation-defined semantics.
[Voted into the WP at the June, 2008 meeting.]
3 [basic] paragraph 8, while not incorrect, does not allow for linkage of operators and conversion functions. It says:
An identifier used in more than one translation unit can potentially refer to the same entity in these translation units depending on the linkage (3.5 [basic.link]) of the identifier specified in each translation unit.
Proposed Resolution (November, 2006):
This issue is resolved by the proposed resolution of issue 485.
[Voted into the WP at the June, 2008 meeting.]
Clause 3 [basic] paragraph 4 says:
A name is a use of an identifier (2.11 [lex.name]) that denotes an entity or label (6.6.4 [stmt.goto], 6.1 [stmt.label]).
Just three paragraphs later, it says
Two names are the same if
- they are identifiers composed of the same character sequence; or
- they are the names of overloaded operator functions formed with the same operator; or
- they are the names of user-defined conversion functions formed with the same type.
The last two bullets contradict the definition of name in paragraph 4 because they are not identifiers.
This definition affects other parts of the Standard, as well. For example, in 3.4.2 [basic.lookup.argdep] paragraph 1,
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.3 [class.friend]) not otherwise visible may be found.
With the current definition of name, argument-dependent lookup apparently does not apply to function-notation calls to overloaded operators.
Another related question is whether a template-id is a name or not and thus would trigger an argument-dependent lookup. Personally, I have always viewed a template-id as a name, just like operator+.
Proposed Resolution (November, 2006):
Change clause 3 [basic] paragraphs 3-8 as follows:
An entity is a value, object, subobject, base class subobject, array element, variable, reference, function, instance of a function, enumerator, type, class member, template, template specialization, namespace, or parameter pack.
A name is a use of an identifier identifier (2.11 [lex.name]), operator-function-id (13.5 [over.oper]), conversion-function-id (12.3.2 [class.conv.fct]), or template-id (14.2 [temp.names]) that denotes an entity or label (6.6.4 [stmt.goto], 6.1 [stmt.label]). A variable is introduced by the declaration of an object. The variable's name denotes the object.
Every name that denotes an entity is introduced by a declaration. Every name that denotes a label is introduced either by a goto statement (6.6.4 [stmt.goto]) or a labeled-statement (6.1 [stmt.label]).
A variable is introduced by the declaration of an object. The variable's name denotes the object.
Some names denote types, classes, enumerations, or templates. In general, it is necessary to determine whether or not a name denotes one of these entities before parsing the program that contains it. The process that determines this is called name lookup (3.4 [basic.lookup]).
Two names are the same if
they are identifiers identifiers composed of the same character sequence; or
they are the names of overloaded operator functions operator-function-ids formed with the same operator; or
they are the names of user-defined conversion functions conversion-function-ids formed with the same type., or
they are template-ids that refer to the same class or function (14.4 [temp.type]).
An identifier A name used in more than one translation unit can potentially refer to the same entity in these translation units depending on the linkage (3.5 [basic.link]) of the identifier name specified in each translation unit.
Change 3.3.7 [basic.scope.class] paragraph 1 item 5 as follows:
The potential scope of a declaration that extends to or past the end of a class definition also extends to the regions defined by its member definitions, even if the members are defined lexically outside the class (this includes static data member definitions, nested class definitions, member function definitions (including the member function body and any portion of the declarator part of such definitions which follows the identifier declarator-id, including a parameter-declaration-clause and any default arguments (8.3.6 [dcl.fct.default]).
[Drafting note: This last change is not really mandated by the issue, but it's another case of “identifier” confusion.]
(This proposed resolution also resolves issue 309.)
[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.2 [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.7 [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.7 [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.7 [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 declaration declarator, 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.2 [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).
[Voted into WP at the October, 2006 meeting.]
Is the following code well-formed?
namespace N { int i; extern int j; } int N::j = i;
The question here is whether the lookup for i in the initializer of N::j finds the declaration in namespace N or not. Implementations differ on this question.
If N::j were a static data member of a class, the answer would be clear: both 3.4.1 [basic.lookup.unqual] paragraph 12 and 8.5 [dcl.init] paragraph 11 say that the initializer “is in the scope of the member's class.” There is no such provision for namespace members defined outside the namespace, however.
The reasoning given in 3.4.1 [basic.lookup.unqual] may be instructive:
A name used in the definition of a static data member of class X (9.4.2 [class.static.data]) (after the qualified-id of the static member) is looked up as if the name was used in a member function of X.
It is certainly the case that a name used in a function that is a member of a namespace is looked up in that namespace (3.4.1 [basic.lookup.unqual] paragraph 6), regardless of whether the definition is inside or outside that namespace. Initializers for namespace members should probably be looked up the same way.
Proposed resolution (April, 2006):
Add a new paragraph following 3.4.1 [basic.lookup.unqual] paragraph 12:
If a variable member of a namespace is defined outside of the scope of its namespace then any name used in the definition of the variable member (after the declarator-id) is looked up as if the definition of the variable member occurred in its namespace. [Example:
namespace N { int i = 4; extern int j; } int i = 2; int N::j = i; // N::j == 4—end example]
[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.3 [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.3 [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.3 [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 namespaces and classes are 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 namespaces and classes associated with the argument types.
(See also issues 95, 136, 138, 139, 165, 166, and 218.)
[Voted into WP at April, 2007 meeting.]
The original intent of the Committee when Koenig lookup was added to the language was apparently something like the following:
This approach is not reflected in the current wording of the Standard. Instead, the following appears to be the status quo:
John Spicer: Argument-dependent lookup was created to solve the problem of looking up function names within templates where you don't know which namespace to use because it may depend on the template argument types (and was then expanded to permit use in nontemplates). The original intent only concerned functions. The safest and simplest change is to simply clarify the existing wording to that effect.
Bill Gibbons: I see no reason why non-function declarations should not be found. It would take a special rule to exclude "function objects", as well as pointers to functions, from consideration. There is no such rule in the standard and I see no need for one.
There is also a problem with the wording in 3.4.2 [basic.lookup.argdep] paragraph 2:
If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated namespaces and classes are not considered.
This implies that if the ordinary lookup of the name finds the declaration of a data member which is a pointer to function or function object, argument-dependent lookup is still done.
My guess is that this is a mistake based on the incorrect assumption that finding any member other than a member function would be an error. I would just change "class member function" to "class member" in the quoted sentence.
Mike Miller: In light of the issue of "short-circuiting" Koenig lookup when normal lookup finds a non-function, perhaps it should be written as "...finds the declaration of a class member, an object, or a reference, the associated namespaces..."?
Andy Koenig: I think I have to weigh in on the side of extending argument-dependent lookup to include function objects and pointers to functions. I am particularly concerned about [function objects], because I think that programmers should be able to replace functions by function objects without changing the behavior of their programs in fundamental ways.
Bjarne Stroustrup: I don't think we could seriously argue from first principles that [argument-dependent lookup should find only function declarations]. In general, C++ name lookup is designed to be independent of type: First we find the name(s), then, we consider its(their) meaning. 3.4 [basic.lookup] states "The name lookup rules apply uniformly to all names ..." That is an important principle.
Thus, I consider text that speaks of "function call" instead of plain "call" or "application of ()" in the context of koenig lookup an accident of history. I find it hard to understand how 5.2.2 [expr.call] doesn't either disallow all occurrences of x(y) where x is a class object (that's clearly not intended) or requires koenig lookup for x independently of its type (by reference from 3.4 [basic.lookup]). I suspect that a clarification of 5.2.2 [expr.call] to mention function objects is in order. If the left-hand operand of () is a name, it should be looked up using koenig lookup.
John Spicer: This approach causes otherwise well-formed programs to be ill-formed, and it does so by making names visible that might be completely unknown to the author of the program. Using-directives already do this, but argument-dependent lookup is different. You only get names from using-directives if you actually use using-directives. You get names from argument-dependent lookup whether you want them or not.
This basically breaks an important reason for having namespaces. You are not supposed to need any knowledge of the names used by a namespace.
But this example breaks if argument-dependent lookup finds non-functions and if the translation unit includes the <list> header somewhere.
namespace my_ns { struct A {}; void list(std::ostream&, A&); void f() { my_ns::A a; list(cout, a); } }
This really makes namespaces of questionable value if you still need to avoid using the same name as an entity in another namespace to avoid problems like this.
Erwin Unruh: Before we really decide on this topic, we should have more analysis on the impact on programs. I would also like to see a paper on the possibility to overload functions with function surrogates (no, I won't write one). Since such an extension is bound to wait until the next official update, we should not preclude any outcome of the discussion.
I would like to have a change right now, which leaves open several outcomes later. I would like to say that:
Koenig lookup will find non-functions as well. If it finds a variable, the program is ill-formed. If the primary lookup finds a variable, Koenig lookup is done. If the result contains both functions and variables, the program is ill-formed. [Note: A future standard will assign semantics to such a program.]
I myself are not comfortable with this as a long-time result, but it prepares the ground for any of the following long term solutions:
The note is there to prevent compiler vendors to put their own extensions in here.
(See also issues 113 and 143.)
Notes from 04/00 meeting:
Although many agreed that there were valid concerns motivating a desire for Koenig lookup to find non-function declarations, there was also concern that supporting this capability would be more dangerous than helpful in the absence of overload resolution for mixed function and non-function declarations.
A straw poll of the group revealed 8 in favor of Koenig lookup finding functions and function templates only, while 3 supported the broader result.
Notes from the 10/01 meeting:
There was unanimous agreement on one less controversial point: if the normal lookup of the identifier finds a non-function, argument-dependent lookup should not be done.
On the larger issue, the primary point of consensus is that making this change is an extension, and therefore it should wait until the point at which we are considering extensions (which could be very soon). There was also consensus on the fact that the standard as it stands is not clear: some introductory text suggests that argument-dependent lookup finds only functions, but the more detailed text that describes the lookup does not have any such restriction.
It was also noted that some existing implementations (e.g., g++) do find some non-functions in some cases.
The issue at this point is whether we should (1) make a small change to make the standard clear (presumably in the direction of not finding the non-functions in the lookup), and revisit the issue later as an extension, or (2) leave the standard alone for now and make any changes only as part of considering the extension. A straw vote favored option (1) by a strong majority.
Additional Notes (September, 2006):
Recent discussion of this issue has emphasized the following points:
The concept of finding function pointers and function objects as part of argument-dependent lookup is not currently under active discussion in the Evolution Working Group.
The major area of concern with argument-dependent lookup is finding functions in unintended namespaces. There are current proposals to deal with this concern either by changing the definition of “associated namespace” so that fewer namespaces are considered or to provide a mechanism for enabling or disabling ADL altogether. Although this concern is conceptually distinct from the question of whether ADL finds function pointers and function objects, it is related in the sense that the current rules are perceived as finding too many functions (because of searching too many namespaces), and allowing function pointers and function objects would also increase the number of entities found by ADL.
Any expansion of ADL to include function pointers and function objects must necessarily update the overloading rules to specify how they interact with functions and function templates in the overload set. Current implementation experience (g++) is not helpful in making this decision because, although it performs a uniform lookup and finds non-function entities, it diagnoses an error in overload resolution if non-function entities are in the overload set.
There is a possible problem if types are found by ADL: it is not clear that overloading between callable entities (functions, function templates, function pointers, and function objects) and types (where the postfix syntax means a cast or construction of a temporary) is reasonable or useful.
James Widman:
There is a larger debate here about whether ADL should find object names; the proposed wording below is only intended to answer the request for wording to clarify the status quo (option 1 above) and not to suggest the outcome of the larger debate.
Proposed Resolution (October, 2006):
Replace the normative text in 3.4.2 [basic.lookup.argdep] paragraph 3 with the following (leaving the text of the note and example unchanged):
Let X be the lookup set produced by unqualified lookup (3.4.1 [basic.lookup.unqual]) and let Y be the lookup set produced by argument dependent lookup (defined as follows). If X contains
- a declaration of a class member, or
- a block-scope function declaration that is not a using-declaration, or
- a declaration that is neither a function nor a function template
then Y is empty. Otherwise Y is the set of declarations found in the namespaces associated with the argument types as described below. The set of declarations found by the lookup of the name is the union of X and Y.
Change 3.4.1 [basic.lookup.unqual] paragraph 4 as indicated:
When considering an associated namespace, the lookup is the same as the lookup performed when the associated namespace is used as a qualifier (3.4.3.2 [namespace.qual]) except that:
- Any using-directives in the associated namespace are ignored.
- Any namespace-scope friend functions or friend function templates declared in associated classes are visible within their respective namespaces even if they are not visible during an ordinary lookup (11.3 [class.friend]).
- All names except those of (possibly overloaded) functions and function templates are ignored.
[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 the October, 2006 meeting.]
One might assume from 14.7.1 [temp.inst] paragraph 1 that argument-dependent lookup would require instantiation of any class template specializations used in argument types:
Unless a class template specialization has been explicitly instantiated (14.7.2 [temp.explicit]) or explicitly specialized (14.7.3 [temp.expl.spec]), the class template specialization is implicitly instantiated when the specialization is referenced in a context that requires a completely-defined object type or when the completeness of the class type affects the semantics of the program.
A complete class type is required to determine the associated classes and namespaces for the argument type (to determine the class's bases) and to determine the friend functions declared by the class, so the completeness of the class type certainly “affects the semantics of the program.”
This conclusion is reinforced by the second bullet of 3.4.2 [basic.lookup.argdep] paragraph 2:
If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the namespaces in which its associated classes are defined.
A class template specialization is a class type, so the second bullet would appear to apply, requiring the specialization to be instantiated in order to determine its base classes.
However, bullet 8 of that paragraph deals explicitly with class template specializations:
If T is a class template specialization 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 that the class template specialization itself is not listed as an associated class, unlike other class types, and there is no mention of base classes. If bullet 8 were intended as a supplement to the treatment of class types in bullet 2, one would expect phrasing along the lines of, “In addition to the associated namespaces and classes for all class types...” or some such; instead, bullet 8 reads like a self-contained and complete specification.
If argument-dependent lookup does not cause implicit instantiation, however, examples like the following fail:
template <typename T> class C { friend void f(C<T>*) { } }; void g(C<int>* p) { f(p); // found by ADL?? }
Implementations differ in whether this example works or not.
Proposed resolution (April, 2006):
Change bullet 2 of 3.4.2 [basic.lookup.argdep] paragraph 2 as indicated:
If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the namespaces in of which its associated classes are defined members. Furthermore, if T is a class template specialization, its associated namespaces and classes also include: the namespaces and classes associated with the types of the template arguments provided for template type parameters (excluding template template parameters); the namespaces of which any template template arguments are members; and the classes of which any member templates used as template template arguments are members. [Note: Non-type template arguments do not contribute to the set of associated namespaces. —end note]
Delete bullet 8 of 3.4.2 [basic.lookup.argdep] paragraph 2:
If T is a class template specialization 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. —end note]
[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 to be have that class type (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.1 [expr.prim.general] 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 used following the class-key in an elaborated-type-specifier (7.1.6.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.6.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.6.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-name or 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-specifieropt type-name class-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 used in 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, a A typedef-name that names a class shall not be used as the identifier class-name in the declarator declarator-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, a A typedef-name that names a class shall not be used as the identifier class-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 committee motion that adopted the proposed resolution The paper is N0905, and "option 1" from that paper was adopted at the Stockholm meeting in July of 1996. The resolution is that "using A::i" brings in everything named i from A.
3.4.3.2 [namespace.qual] paragraph 2 was modified to implement this resolution, but interestingly that only covers the namespace case and not the class case. I think the class case was overlooked when the wording was drafted. A core issue should be opened to make sure the class case is handled properly.
Notes from April 2003 meeting:
This is related to issue 11. 7.3.3 [namespace.udecl] paragraph 10 has an example for namespaces.
Proposed resolution (October 2003):
Add a bullet to the end of 3.4.3.1 [class.qual] paragraph 1:
Change the beginning of 7.3.3 [namespace.udecl] paragraph 4 from
A using-declaration used as a member-declaration shall refer to a member of a base class of the class being defined, shall refer to a member of an anonymous union that is a member of a base class of the class being defined, or shall refer to an enumerator for an enumeration type that is a member of a base class of the class being defined.
to
In a using-declaration used as a member-declaration, the nested-name-specifier shall name a base class of the class being defined. Such a using-declaration introduces the set of declarations found by member name lookup (10.2 [class.member.lookup], 3.4.3.1 [class.qual]).
[Voted into WP at April 2003 meeting.]
I have some concerns with the description of name lookup for elaborated type specifiers in 3.4.4 [basic.lookup.elab]:
Paragraph 2 has some parodoxical statements concerning looking up names that are simple identifers:
If the elaborated-type-specifier refers to an enum-name and this lookup does not find a previously declared enum-name, the elaborated-type-specifier is ill-formed. If the elaborated-type-specifier refers to an [sic] class-name and this lookup does not find a previously declared class-name... the elaborated-type-specifier is a declaration that introduces the class-name as described in 3.3.2 [basic.scope.pdecl]."
It is not clear how an elaborated-type-specifier can refer to an enum-name or class-name given that the lookup does not find such a name and that class-name and enum-name are not part of the syntax of an elaborated-type-specifier.
The second sentence quoted above seems to suggest that the name found will not be used if it is not a class name. typedef-name names are ill-formed due to the sentence preceding the quote. If lookup finds, for instance, an enum-name then a new declaration will be created. This differs from C, and from the enum case, and can have surprising effects:
struct S { enum E { one = 1 }; class E* p; // declares a global class E? };
Was this really the intent? If this is the case then some more work is needed on 3.4.4 [basic.lookup.elab]. Note that the section does not make finding a type template formal ill-formed, as is done in 7.1.6.3 [dcl.type.elab]. I don't see anything that makes a type template formal name a class-name. So the example in 7.1.6.3 [dcl.type.elab] of friend class T; where T is a template type formal would no longer be ill-formed with this interpretation because it would declare a new class T.
(See also issue 254.)
Notes from the 4/02 meeting:
This will be consolidated with the changes for issue 254. See also issue 298.
Proposed resolution (October 2002):
As given in N1376=02-0034. Note that the inserts and strikeouts in that document do not display correctly in all browsers; <del> --> <strike> and <ins> --> <b>, and the similar changes for the closing delimiters, seem to do the trick.
[Voted into WP at April 2003 meeting.]
The text in 3.4.4 [basic.lookup.elab] paragraph 2 twice refers to the possibility that an elaborated-type-specifier might have the form
class-key identifier ;
However, the grammar for elaborated-type-specifier does not include a semicolon.
In both 3.4.4 [basic.lookup.elab] and 7.1.6.3 [dcl.type.elab], the text asserts that an elaborated-type-specifier that refers to a typedef-name is ill-formed. However, it is permissible for the form of elaborated-type-specifier that begins with typename to refer to a typedef-name.
This problem is the result of adding the typename form to the elaborated-type-name grammar without changing the verbiage correspondingly. It could be fixed either by updating the verbiage or by moving the typename syntax into its own production and referring to both nonterminals when needed.
(See also issue 180. If this issue is resolved in favor of a separate nonterminal in the grammar for the typename forms, the wording in that issue's resolution must be changed accordingly.)
Notes from 04/01 meeting:
The consensus was in favor of moving the typename forms out of the elaborated-type-specifier grammar.
Notes from the 4/02 meeting:
This will be consolidated with the changes for issue 245.
Proposed resolution (October 2002):
As given in N1376=02-0034.
[Voted into the WP at the June, 2008 meeting.]
3.4.5 [basic.lookup.classref] paragraph 1 says,
In a class member access expression (5.2.5 [expr.ref] ), if the . or -> token is immediately followed by an identifier followed by a <, the identifier must be looked up to determine whether the < is the beginning of a template argument list (14.2 [temp.names] ) or a less-than operator. The identifier is first looked up in the class of the object expression. If the identifier is not found, it is then looked up in the context of the entire postfix-expression and shall name a class or function template.
There do not seem to be any circumstances in which use of a non-member template function would be well-formed as the id-expression of a class member access expression.
Proposed Resolution (November, 2006):
Change 3.4.5 [basic.lookup.classref] paragraph 1 as follows:
In a class member access expression (5.2.5 [expr.ref]), if the . or -> token is immediately followed by an identifier followed by a <, the identifier must be looked up to determine whether the < is the beginning of a template argument list (14.2 [temp.names]) or a less-than operator. The identifier is first looked up in the class of the object expression. If the identifier is not found, it is then looked up in the context of the entire postfix-expression and shall name a class or function template...
[Voted into WP at the October, 2006 meeting.]
I believe this program is invalid:
struct A { }; struct C { struct A {}; void f (); }; void C::f () { ::A *a; a->~A (); }The problem is that 3.4.5 [basic.lookup.classref] says that you have to look up A in both the context of the pointed-to-type (i.e., ::A), and in the context of the postfix-expression (i.e., the body of C::f), and that if the name is found in both places it must name the same type in both places.
The EDG front end does not issue an error about this program, though.
Am I reading the standardese incorrectly?
John Spicer: I think you are reading it correctly. I think I've been hoping that this would get changed. Unlike other dual lookup contexts, this is one in which the compiler already knows the right answer (the type must match that of the left hand of the -> operator). So I think that if either of the types found matches the one required, it should be sufficient. You can't say a->~::A(), which means you are forced to say a->::A::~A(), which disables the virtual mechanism. So you would have to do something like create a local typedef for the desired type.
See also issues 244, 399, and 466.
Proposed resolution (April, 2006):
Remove the indicated text from 3.4.5 [basic.lookup.classref] paragraph 2:
If the id-expression in a class member access (5.2.5 [expr.ref]) is an unqualified-id, and the type of the object expression is of a class type C (or of pointer to a class type C), the unqualified-id is looked up in the scope of class C...
Change 3.4.5 [basic.lookup.classref] paragraph 3 as indicated:
If the unqualified-id is ~type-name,
the type-name is looked up in the context of the entire
postfix-expression. and If the
type T of the object expression is of a class
type C (or of pointer to a class type C),
the type-name is also looked up in the context of the
entire postfix-expression and in the scope of
class C. The type-name shall refer to
a class-name. If type-name is found in both contexts,
the name shall refer to the same class type. If the type of the object
expression is of scalar type, the type-name is looked up in the
scope of the complete postfix-expression. At least one
of the lookups shall find a name that refers to (possibly
cv-qualified)
T. [Example:
struct A { };
struct B {
struct A { };
void f(::A* a);
};
void B::f(::A* a) {
a->~A(); // OK, lookup in *a finds the injected-class-name
}
—end example]
[Note: this change also resolves issue 414.]
[Voted into WP at October 2004 meeting.]
The example in 3.4.5 [basic.lookup.classref] paragraph 4 is wrong (see 11.2 [class.access.base] paragraph 5; the cast to the naming class can't be done) and needs to be corrected. This was noted when the final version of the algorithm for issue 39 was checked against it.
Proposed Resolution (October 2003):
Remove the entire note at the end of 3.4.5 [basic.lookup.classref] paragraph 4, including the entire example.
[Voted into WP at the October, 2006 meeting.]
By 3.4.5 [basic.lookup.classref] paragraph 3, the following is ill-formed because the two lookups of the destructor name (in the scope of the class of the object and in the surrounding context) find different Xs:
struct X {}; int main() { X x; struct X {}; x.~X(); // Error? }
This is silly, because the compiler knows what the type has to be, and one of the things found matches that. The lookup should require only that one of the lookups finds the required class type.
Proposed resolution (April, 2005):
This issue is resolved by the resolution of issue 305.
[Moved to DR at 10/01 meeting.]
3.5 [basic.link] paragraph 4 says (among other things):A name having namespace scope has external linkage if it is the name ofThat prohibits for example:
- [...]
- a named enumeration (7.2 [dcl.enum]), or an unnamed enumeration defined in a typedef declaration in which the enumeration has the typedef name for linkage purposes (7.1.3 [dcl.typedef])
typedef enum { e1 } *PE; void f(PE) {} // Cannot declare a function (with linkage) using a // type with no linkage.
However, the same prohibition was not made for class scope types. Indeed, 3.5 [basic.link] paragraph 5 says:
In addition, a member function, static data member, class or enumeration of class scope has external linkage if the name of the class has external linkage.
That allows for:
struct S { typedef enum { e1 } *MPE; void mf(MPE) {} };
My guess is that this is an unintentional consequence of 3.5 [basic.link] paragraph 5, but I would like confirmation on that.
Proposed resolution:
Change text in 3.5 [basic.link] paragraph 5 from:
In addition, a member function, static data member, class or enumeration of class scope has external linkage if the name of the class has external linkage.to:
In addition, a member function, a static data member, a named class or enumeration of class scope, or an unnamed class or enumeration defined in a class-scope typedef declaration such that the class or enumeration has the typedef name for linkage purposes (7.1.3 [dcl.typedef]), has external linkage if the name of the class has external linkage.
[Voted into WP at October 2004 meeting.]
According to 3.5 [basic.link] paragraph 8, "A name with no linkage ... shall not be used to declare an entity with linkage." This would appear to rule out code such as:
typedef struct { int i; } *PT; extern "C" void f(PT);[likewise]
static enum { a } e;which seems rather harmless to me.
See issue 132, which dealt with a closely related issue.
Andrei Iltchenko submitted the same issue via comp.std.c++ on 17 Dec 2001:
Paragraph 8 of Section 3.5 [basic.link] contains the following sentences: "A name with no linkage shall not be used to declare an entity with linkage. If a declaration uses a typedef name, it is the linkage of the type name to which the typedef refers that is considered."
The problem with this wording is that it doesn't cover cases where the type to which a typedef-name refers has no name. As a result it's not clear whether, for example, the following program is well-formed:
#include <vector> int main() { enum { sz = 6u }; typedef int (* aptr_type)[sz]; typedef struct data { int i, j; } * elem_type; std::vector<aptr_type> vec1; std::vector<elem_type> vec2; }
Suggested resolution:
My feeling is that the rules for whether or not a typedef-name used in a declaration shall be treated as having or not having linkage ought to be modelled after those for dependent types, which are explained in 14.6.2.1 [temp.dep.type].
Add the following text at the end of Paragraph 8 of Section 3.5 [basic.link] and replace the following example:
In case of the type referred to by a typedef declaration not having a name, the newly declared typedef-name has linkage if and only if its referred type comprises no names of no linkage excluding local names that are eligible for appearance in an integral constant-expression (5.19 [expr.const]). [Note: if the referred type contains a typedef-name that does not denote an unnamed class, the linkage of that name is established by the recursive application of this rule for the purposes of using typedef names in declarations.] [Example:void f() { struct A { int x; }; // no linkage extern A a; // ill-formed typedef A Bl extern B b; // ill-formed enum { sz = 6u }; typedef int (* C)[sz]; // C has linkage because sz can // appear in a constant expression }--end example.]
Additional issue (13 Jan 2002, from Andrei Iltchenko):
Paragraph 2 of Section 14.3.1 [temp.arg.type] is inaccurate and unnecessarily prohibits a few important cases; it says "A local type, a type with no linkage, an unnamed type or a type compounded from any of these types shall not be used as a template-argument for a template-parameter." The inaccuracy stems from the fact that it is not a type but its name that can have a linkage.
For example based on the current wording of 14.3.1 [temp.arg.type], the following example is ill-formed.
#include <vector> struct data { int i, j; }; int main() { enum { sz = 6u }; std::vector<int(*)[sz]> vec1; // The types 'int(*)[sz]' and 'data*' std::vector<data*> vec2; // have no names and are thus illegal // as template type arguments. }
Suggested resolution:
Replace the whole second paragraph of Section 14.3.1 [temp.arg.type] with the following wording:
A type whose name does not have a linkage or a type compounded from any such type shall not be used as a template-argument for a template-parameter. In case of a type T used as a template type argument not having a name, T constitutes a valid template type argument if and only if the name of an invented typedef declaration referring to T would have linkage; see 3.5. [Example:template <class T> class X { /* ... */ }; void f() { struct S { /* ... */ }; enum { sz = 6u }; X<S> x3; // error: a type name with no linkage // used as template-argument X<S*> x4; // error: pointer to a type name with // no linkage used as template-argument X<int(*)[sz]> x5; // OK: since the name of typedef int // (*pname)[sz] would have linkage }--end example] [Note: a template type argument may be an incomplete type (3.9 [basic.types]).]
Proposed resolution:
This is resolved by the changes for issue 389. The present issue was moved back to Review status in February 2004 because 389 was moved back to Review.
[Voted into WP at October 2004 meeting.]
3.5 [basic.link] paragraph 8 says (among other things):
A name with no linkage (notably, the name of a class or enumeration declared in a local scope (3.3.3 [basic.scope.block])) shall not be used to declare an entity with linkage. If a declaration uses a typedef name, it is the linkage of the type name to which the typedef refers that is considered.
I would expect this to catch situations such as the following:
// File 1: typedef struct {} *UP; void f(UP) {} // File 2: typedef struct {} *UP; // Or: typedef struct {} U, *UP; void f(UP);
The problem here is that most implementations must generate the same mangled name for "f" in two translation units. The quote from the standard above isn't quite clear, unfortunately: There is no type name to which the typedef refers.
A related situation is the following:
enum { no, yes } answer;The variable "answer" is declared as having external linkage, but it is declared with an unnamed type. Section 3.5 [basic.link] talks about the linkage of names, however, and does therefore not prohibit this. There is no implementation issue for most compilers because they do not ordinarily mangle variable names, but I believe the intent was to allow that implementation technique.
Finally, these problems are much less relevant when declaring names with internal linkage. For example, I would expect there to be few problems with:
typedef struct {} *UP; static void g(UP);
I recently tried to interpret 3.5 [basic.link] paragraph 8 with the assumption that types with no names have no linkage. Surprisingly, this resulted in many diagnostics on variable declarations (mostly like "answer" above).
I'm pretty sure the standard needs clarifying words in this matter, but which way should it go?
See also issue 319.
Notes from April 2003 meeting:
There was agreement that this check is not needed for variables and functions with extern "C" linkage, and a change there is desirable to allow use of legacy C headers. The check is also not needed for entities with internal linkage, but there was no strong sentiment for changing that case.
We also considered relaxing this requirement for extern "C++" variables but decided that we did not want to change that case.
We noted that if extern "C" functions are allowed an additional check is needed when such functions are used as arguments in calls of function templates. Deduction will put the type of the extern "C" function into the type of the template instance, i.e., there would be a need to mangle the name of an unnamed type. To plug that hole we need an additional requirement on the template created in such a case.
Proposed resolution (April 2003, revised slightly October 2003 and March 2004):
In 3.5 [basic.link] paragraph 8, change
A name with no linkage (notably, the name of a class or enumeration declared in a local scope (3.3.3 [basic.scope.block])) shall not be used to declare an entity with linkage. If a declaration uses a typedef name, it is the linkage of the type name to which the typedef refers that is considered.
to
A type is said to have linkage if and only ifA type without linkage shall not be used as the type of a variable or function with linkage, unless the variable or function has extern "C" linkage (7.5 [dcl.link]). [Note: in other words, a type without linkage contains a class or enumeration that cannot be named outside of its translation unit. An entity with external linkage declared using such a type could not correspond to any other entity in another translation unit of the program and is thus not permitted. Also note that classes with linkage may contain members whose types do not have linkage, and that typedef names are ignored in the determination of whether a type has linkage.]
- it is a class or enumeration type that is named (or has a name for linkage purposes (7.1.3 [dcl.typedef])) and the name has linkage; or
- it is a specialization of a class template (14 [temp]) [Footnote: a class template always has external linkage, and the requirements of 14.3.1 [temp.arg.type] and 14.3.2 [temp.arg.nontype] ensure that the template arguments will also have appropriate linkage]; or
- it is a fundamental type (3.9.1 [basic.fundamental]); or
- it is a compound type (3.9.2 [basic.compound]) other than a class or enumeration, compounded exclusively from types that have linkage; or
- it is a cv-qualified (3.9.3 [basic.type.qualifier]) version of a type that has linkage.
Change 14.3.1 [temp.arg.type] paragraph 2 from (note: this is the wording as updated by issue 62)
The following types shall not be used as a template-argument for a template type-parameter:
- a type whose name has no linkage
- an unnamed class or enumeration type that has no name for linkage purposes (7.1.3 [dcl.typedef])
- a cv-qualified version of one of the types in this list
- a type created by application of declarator operators to one of the types in this list
- a function type that uses one of the types in this list
to
A type without linkage (3.5 [basic.link]) shall not be used as a template-argument for a template type-parameter.
Once this issue is ready, issue 319 should be moved back to ready as well.
[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]
[Moved to DR at 4/02 meeting.]
The Standard does not appear to address how the rules for order of initialization apply to static data members of class templates.
Suggested resolution: Add the following verbiage to either 3.6.2 [basic.start.init] or 9.4.2 [class.static.data]:
Initialization of static data members of class templates shall be performed during the initialization of static data members for the first translation unit to have static initialization performed for which the template member has been instantiated. This requirement shall apply to both the static and dynamic phases of initialization.
Notes from 04/01 meeting:
Enforcing an order of initialization on static data members of class templates will result in substantial overhead on access to such variables. The problem is that the initialization be required as the result of instantiation in a function used in the initialization of a variable in another translation unit. In current systems, the order of initialization of static data data members of class templates is not predictable. The proposed resolution is to state that the order of initialization is undefined.
Proposed resolution (04/01, updated slightly 10/01):
Replace the following sentence 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.
with
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.
Note that this wording is further updated by issue 362.
Note (07/01):
Brian McNamara argues against the proposed resolution. The following excerpt captures the central point of a long message on comp.std.c++:
I have a class for representing linked lists which looks something liketemplate <class T> class List { ... static List<T>* sentinel; ... }; template <class T> List<T>* List<T>::sentinel( new List<T> ); // static member definitionThe sentinel list node is used to represent "nil" (the null pointer cannot be used with my implementation, for reasons which are immaterial to this discussion). All of the List's non-static member functions and constructors depend upon the value of the sentinel. Under the proposed resolution for issue #270, Lists cannot be safely instantiated before main() begins, as the sentinel's initialization is "unordered".
(Some readers may propose that I should use the "singleton pattern" in the List class. This is undesirable, for reasons I shall describe at the end of this post at the location marked "[*]". For the moment, indulge me by assuming that "singleton" is not an adequate solution.)
Though this is a particular example from my own experience, I believe it is representative of a general class of examples. It is common to use static data members of a class to represent the "distinguished values" which are important to instances of that class. It is imperative that these values be initialized before any instances of the class are created, as the instances depend on the values.
In a comp.std.c++ posting on 28 Jul 2001, Brian McNamara proposes the following alternative resolution:
Replace the following sentence 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.with
Objects with static storage duration defined in namespace scope shall be initialized in the order described below.and then after paragraph 1, add this text:
Dynamic initialization is either ordered or quasi-ordered. Explicit specializations of class template static data members have ordered initialization. Other class template static data member instances have quasi-ordered initialization. All other objects defined in namespace scope have ordered initialization. The order of initialization is specified as follows:along with a non-normative note along the lines of
- Objects that are defined within a single translation unit and that have ordered initialization shall be initialized in the order of their definitions in the translation unit.
- Objects that are defined only within a single translation unit and that have quasi-ordered initialization shall also be initialized in the order of their definitions in the translation unit -- that is, as though these objects had ordered initialization.
- Objects that are defined within multiple translation units (which, therefore, must have quasi-ordered initialization) shall be initialized as follows: in exactly one translation unit (which one is unspecified), the object shall be treated as though it has ordered initialization; in the other translation units which define the object, the object will be initialized before all other objects that have ordered initialization in those translation units.
- For any two objects, "X" and "Y", with static storage duration and defined in namespace scope, if the previous bullets do not imply a relationship for the initialization ordering between "X" and "Y", then the relative initialization order of these objects is unspecified.
[ Note: The intention is that translation units can each be compiled separately with no knowledge of what objects may be re-defined in other translation units. Each translation unit can contain a method which initializes all objects (both quasi-ordered and ordered) as though they were ordered. When these translation units are linked together to create an executable program, all of these objects can be initialized by simply calling the initialization methods (one from each translation unit) in any order. Quasi-ordered objects require some kind of guard to ensure that they are not initialized more than once (the first attempt to initialize such an object should succeed; any subsequent attempts should simply be ignored). ]
Erwin Unruh replies: There is a point which is not mentioned with this posting. It is the cost for implementing the scheme. It requires that each static template variable is instantiated in ALL translation units where it is used. There has to be a flag for each of these variables and this flag has to be checked in each TU where the instantiation took place.
I would reject this idea and stand with the proposed resolution of issue 270.
There just is no portable way to ensure the "right" ordering of construction.
Notes from 10/01 meeting:
The Core Working Group reaffirmed its previous decision.
[Voted into WP at April 2005 meeting.]
I have a couple of questions about 3.6.2 [basic.start.init], "Initialization of non-local objects." I believe I recall some discussion of related topics, but I can't find anything relevant in the issues list.
The first question arose when I discovered that different implementations treat reference initialization differently. Consider, for example, the following (namespace-scope) code:
int i; int& ir = i; int* ip = &i;Both initializers, "i" and "&i", are constant expressions, per 5.19 [expr.const] paragraph 4-5 (a reference constant expression and an address constant expression, respectively). Thus, both initializations are categorized as static initialization, according to 3.6.2 [basic.start.init] paragraph 1:
Zero-initialization and initialization with a constant expression are collectively called static initialization; all other initialization is dynamic initialization.
However, that does not mean that both ir and ip must be initialized at the same time:
Objects of POD types (3.9) with static storage duration initialized with constant expressions (5.19) shall be initialized before any dynamic initialization takes place.
Because "int&" is not a POD type, there is no requirement that it be initialized before dynamic initialization is performed, and implementations differ in this regard. Using a function called during dynamic initialization to print the values of "ip" and "&ir", I found that g++, Sun, HP, and Intel compilers initialize ir before dynamic initialization and the Microsoft compiler does not. All initialize ip before dynamic initialization. I believe this is conforming (albeit inconvenient :-) behavior.
So, my first question is whether it is intentional that a reference of static duration, initialized with a reference constant expression, need not be initialized before dynamic initialization takes place, and if so, why?
The second question is somewhat broader. As 3.6.2 [basic.start.init] is currently worded, it appears that there are no requirements on when ir is initialized. In fact, there is a whole category of objects -- non-POD objects initialized with a constant expression -- for which no ordering is specified. Because they are categorized as part of "static initialization," they are not subject to the requirement that they "shall be initialized in the order in which their definition appears in the translation unit." Because they are not POD types, they are not required to be initialized before dynamic initialization occurs. Am I reading this right?
My preference would be to change 3.6.2 [basic.start.init] paragraph 1 so that 1) references are treated like POD objects with respect to initialization, and 2) "static initialization" applies only to POD objects and references. Here's some sample wording to illustrate:
Suggested resolution:
Objects with static storage duration (3.7.1) shall be zero-initialized (8.5) before any other initialization takes place. Initializing a reference, or an object of POD type, of static storage duration with a constant expression (5.19) is called constant initialization. Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place. [Remainder unchanged.]
Proposed Resolution:
Change 3.6.2 [basic.start.init] paragraph 1 as follows:
Objects with static storage duration (3.7.1) shall be zero-initialized (8.5) before any other initialization takes place. Initializing a reference, or an object of POD type, of static storage duration with a constant expression (5.19) is called constant initialization. Together, zero-initialization and constant initialization are Zero-initialization and initialization with a constant expression are collectively called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed Objects of POD types (3.9) with static storage duration initialized with constant expressions (5.19) shall be initialized before any dynamic initialization takes place.
[Voted into the WP at the September, 2008 meeting (resolution in paper N2757).]
Given this literal type,
struct X { constexpr X() { } };
and this definition,
static X x;
the current specification does not require that x be statically initialized because it is not “initialized with a constant expression” (3.6.1 [basic.start.main] paragraph 1).
Lawrence Crowl:
This guarantee is essential for atomics.
Jens Maurer:
Suggestion:
A reference with static storage duration or an object of literal type with static storage duration can be initialized with a constant expression (5.19 [expr.const]) or with a constexpr constructor; this is called constant initialization.
(Not spelling out “default constructor” makes it easier to handle multiple-parameter constexpr constructors, where there isn't “a” constant expression but several.)
Peter Dimov:
In addition, there is a need to enforce static initialization for non-literal types: std::shared_ptr, std::once_flag, and std::atomic_* all have nontrivial copy constructors, making them non-literal types. However, we need a way to ensure that a constexpr constructor called with constant expressions will guarantee static initialization, regardless of the nontriviality of the copy constructor.
Proposed resolution (April, 2008):
Change 3.6.2 [basic.start.init] paragraph 1 as follows:
...A reference with static storage duration and an object of trivial or literal type with static storage duration can be initialized with a constant expression (5.19 [expr.const]); this If a reference with static storage duration is initialized with a constant expression (5.19 [expr.const]) or if the initialization of an object with static storage duration satisfies the requirements for the object being declared with constexpr (7.1.5 [dcl.constexpr]), that initialization is called constant initialization...
Change 6.7 [stmt.dcl] paragraph 4 as follows:
...A local object of trivial or literal type (3.9 [basic.types]) with static storage duration initialized with constant-expressions is initialized Constant initialization (3.6.2 [basic.start.init]) of a local entity with static storage duration is performed before its block is first entered...
Change 7.1.5 [dcl.constexpr] paragraph 7 as follows:
A constexpr specifier used in an object declaration declares the object as const. Such an object shall be initialized, and every expression that appears in its initializer (8.5 [dcl.init]) shall be a constant expression. Every implicit conversion used in converting the initializer expressions and every constructor call used for the initialization shall be one of those allowed in a constant expression (5.19 [expr.const])...
Replace 8.5.1 [dcl.init.aggr] paragraph 14 as follows:
When an aggregate with static storage duration is initialized with a brace-enclosed initializer-list, if all the member initializer expressions are constant expressions, and the aggregate is a trivial type, the initialization shall be done during the static phase of initialization (3.6.2 [basic.start.init]); otherwise, it is unspecified whether the initialization of members with constant expressions takes place during the static phase or during the dynamic phase of initialization. [Note: The order of initialization for aggregates with static storage duration is specified in 3.6.2 [basic.start.init] and 6.7 [stmt.dcl]. —end note]
(Note: the change to 3.6.2 [basic.start.init] paragraph 1 needs to be reconciled with the conflicting change in issue 684.)
[Voted into the WP at the June, 2008 meeting.]
The C++ standard has inherited the definition of the 'exit' function more or less unchanged from ISO C.
However, when the 'exit' function is called, objects of static extent which have been initialised, will be destructed if their types posses a destructor.
In addition, the C++ standard has inherited the definition of the 'signal' function and its handlers from ISO C, also pretty much unchanged.
The C standard says that the only standard library functions that may be called while a signal handler is executing, are the functions 'abort', 'signal' and 'exit'.
This introduces a bit of a nasty turn, as it is not at all unusual for the destruction of static objects to have fairly complex destruction semantics, often associated with resource release. These quite commonly involve apparently simple actions such as calling 'fclose' for a FILE handle.
Having observed some very strange behaviour in a program recently which in handling a SIGTERM signal, called the 'exit' function as indicated by the C standard.
But unknown to the programmer, a library static object performed some complicated resource deallocation activities, and the program crashed.
The C++ standard says nothing about the interaction between signals, exit and static objects. My observations, was that in effect, because the destructor called a standard library function other than 'abort', 'exit' or 'signal', while transitively in the execution context of the signal handler, it was in fact non-compliant, and the behaviour was undefined anyway.
This is I believe a plausible judgement, but given the prevalence of this common programming technique, it seems to me that we need to say something a lot more positive about this interaction.
Curiously enough, the C standard fails to say anything about the analogous interaction with functions registered with 'atexit' ;-)
Proposed Resolution (10/98):
The current Committee Draft of the next version of the ISO C standard specifies that the only standard library function that may be called while a signal handler is executing is 'abort'. This would solve the above problem.
[This issue should remain open until it has been decided that the next version of the C++ standard will use the next version of the C standard as the basis for the behavior of 'signal'.]
Notes (November, 2006):
C89 is slightly contradictory here: It allows any signal handler to terminate by calling abort, exit, longjmp, but (for asynchronous signals, i.e. not those produced by abort or raise) then makes calling any library function other than signal with the current signal undefined behavior (C89 7.7.1.1). For synchronous signals, C99 forbids calls to raise, but imposes no other restrictions. For asynchronous signals, C99 allows only calls to abort, _Exit, and signal with the current signal (C99 7.14.1.1). The current C++ WP refers to “plain old functions” and “conforming C programs” (18.10 [support.runtime] paragraph 6).
Proposed Resolution (November, 2006):
Change the footnote in 18.10 [support.runtime] paragraph 6 as follows:
In particular, a signal handler using exception handling is very likely to have problems. Also, invoking std::exit may cause destruction of objects, including those of the standard library implementation, which, in general, yields undefined behavior in a signal handler (see 1.9 [intro.execution]).
[Voted into WP at the October, 2006 meeting.]
According to 3.7.4.1 [basic.stc.dynamic.allocation] paragraph 3,
Any other allocation function that fails to allocate storage shall only indicate failure by throwing an exception of class std::bad_alloc (18.6.2.1 [bad.alloc]) or a class derived from std::bad_alloc.
Shouldn't this statement have the usual requirements for an unambiguous and accessible base class?
Proposed resolution (April, 2006):
Change the last sentence of 3.7.4.1 [basic.stc.dynamic.allocation] paragraph 3 as indicated:
Any other allocation function that fails to allocate storage shall only indicate failure only by throwing an exception of class std::bad_alloc (18.6.2.1 [bad.alloc]) or a class derived from std::bad_alloc a type that would match a handler (15.3 [except.handle]) of type std::bad_alloc (18.6.2.1 [bad.alloc]).
[Voted into the WP at the September, 2008 meeting (resolution in paper N2757).]
[Picked up by evolution group at October 2002 meeting.]
The default global operators delete are specified to not throw, but there is no requirement that replacement global, or class-specific, operators delete must not throw. That ought to be required.
In particular:
We already require that all versions of an allocator's deallocate() must not throw, so that part is okay.
Rationale (04/00):
Note (March, 2008):
The Evolution Working Group has accepted the intent of this issue and referred it to CWG for action for C++0x (see paper J16/07-0033 = WG21 N2173).
Proposed resolution (March, 2008):
Change 3.7.4.2 [basic.stc.dynamic.deallocation] paragraph 3 as follows:
A deallocation function shall not terminate by throwing an exception. The value of the first argument supplied to a deallocation function...
[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.6.1 [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.4.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.4.2 [basic.stc.dynamic.deallocation], or in 18.6.1.1 [new.delete.single] and 18.6.1.2 [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.6.1.1 [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.6.1.1 [new.delete.single] paragraphs 12 and 13, 18.6.1.2 [new.delete.array] paragraphs 11 and 12, and 3.7.4.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.4.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, if the value of the operand of delete is the null pointer the operation has no effect may be a null pointer value. If it is not a null pointer value, in In the 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.4.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:
The If 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]).
The If 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.4.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.4.2 [basic.stc.dynamic.deallocation] paragraph 3 as indicated:
The value of the first argument supplied to one of the a deallocation functions provided in the standard library may be a null pointer value; if so, and if the deallocation function is one supplied in the standard library, the call to the deallocation function has 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.]
[Moved to DR at 4/02 meeting.]
Jack Rouse: 3.8 [basic.life] paragraph 1 includes:
The lifetime of an object is a runtime property of the object. The lifetime of an object of type T begins when:Consider the code:
- storage with the proper alignment and size for type T is obtained, and
- if T is a class type with a non-trivial constructor (12.1 [class.ctor] ), the constructor call has completed.
struct B { B( int = 0 ); ~B(); }; struct S { B b1; }; int main() { S s = { 1 }; return 0; }In the code above, class S does have a non-trivial constructor, the default constructor generated by the compiler. According the text above, the lifetime of the auto s would never begin because a constructor for S is never called. I think the second case in the text needs to include aggregate initialization.
Mike Miller: I see a couple of ways of fixing the problem. One way would be to change "the constructor call has completed" to "the object's initialization is complete."
Another would be to add following "a class type with a non-trivial constructor" the phrase "that is not initialized with the brace notation (8.5.1 [dcl.init.aggr] )."
The first formulation treats aggregate initialization like a constructor call; even POD-type members of an aggregate could not be accessed before the aggregate initialization completed. The second is less restrictive; the POD-type members of the aggregate would be usable before the initialization, and the members with non-trivial constructors (the only way an aggregate can acquire a non-trivial constructor) would be protected by recursive application of the lifetime rule.
Proposed resolution (04/01):
In 3.8 [basic.life] paragraph 1, change
If T is a class type with a non-trivial constructor (12.1 [class.ctor]), the constructor call has completed.
to
If T is a class type with a non-trivial constructor (12.1 [class.ctor]), the initialization is complete. [Note: the initialization can be performed by a constructor call or, in the case of an aggregate with an implicitly-declared non-trivial default constructor, an aggregate initialization (8.5.1 [dcl.init.aggr]).]
[Voted into WP at April 2003 meeting.]
The wording in 3.8 [basic.life] paragraph 6 allows an lvalue designating an out-of-lifetime object to be used as the operand of a static_cast only if the conversion is ultimately to "char&" or "unsigned char&". This description excludes the possibility of using a cv-qualified version of these types for no apparent reason.
Notes on 04/01 meeting:
The wording should be changed to allow cv-qualified char types.
Proposed resolution (04/01):
In 3.8 [basic.life] paragraph 6 change the third bullet:
[Voted into WP at March 2004 meeting.]
3.8 [basic.life] paragraph 1 second bullet says:
if T is a class type with a non-trivial constructor (12.1), the constructor call has completed.
This is confusing; what was intended is probably something like
if T is a class type and the constructor invoked to create the object is non-trivial (12.1), the constructor call has completed.
Proposed Resolution (October 2003):
As given above.
[Voted into the WP at the September, 2008 meeting.]
In ISO/IEC 14882:2003, the second bullet of 3.8 [basic.life] paragraph 1 reads,
if T is a class type with a non-trivial constructor (12.1 [class.ctor]), the constructor call has completed.
Issue 119 pointed out that aggregate initialization can be used with some classes with a non-trivial implicitly-declared default constructor, and that in such cases there is no call to the object's constructor. The resolution for that issue was to change the previously-cited wording to read,
If T is a class type with a non-trivial constructor (12.1 [class.ctor], the initialization is complete.
Later (but before the WP was revised with the wording from the resolution of issue 119), issue 404 changed the 2003 wording to read,
If T is a class type and the constructor invoked to create the object is non-trivial (12.1 [class.ctor]), the constructor call has completed.
thus reversing the effect of issue 119, whose whole purpose was to cover objects with non-trivial constructors that are not invoked.
Through an editorial error, the post-Redmond draft (N1905) still contained the original 2003 wording that should have been replaced by the resolution of issue 119, in addition to the new wording from the resolution:
if T is a class type and the constructor invoked to create the object is non-trivial (12.1 [class.ctor]), the constructor call has completed. the initialization is complete.
Finally, during the application of the edits for delegating constructors (N1986), this editing error was “fixed” by retaining the original 2003 wording (which was needed for the application of the change specified in N1986), so that the current draft (N2009) reads,
if T is a class type and the constructor invoked to create the object is non-trivial (12.1 [class.ctor]), the principal constructor call 12.6.2 [class.base.init]) has completed.
Because the completion of the call to the principal constructor corresponds to the point at which the object is “fully constructed” (15.2 [except.ctor] paragraph 2), i.e., its initialization is complete, I believe that the exact wording of the issue 119 resolution would be correct and should be restored verbatim.
Proposed resolution (June, 2008):
Change 3.8 [basic.life] paragraph 1 as follows:
The lifetime of an object is a runtime property of the object. An object is said to have non-trivial initialization if it is of a class or aggregate type and it or one of its members is initialized by a constructor other than a trivial default constructor. [Note: Initialization by a trivial copy constructor is non-trivial initialization. —end note] The lifetime of an object of type T begins when:
storage with the proper alignment and size for type T is obtained, and
if T is a class type and the constructor invoked to create the object is non-trivial (12.1 [class.ctor]), the principal constructor call (12.6.2 [class.base.init]) has completed. [Note: the initialization can be performed by a constructor call or, in the case of an aggregate with an implicitly-declared non-trivial default constructor, an aggregate initialization 8.5.1 [dcl.init.aggr]. —end note] the object has non-trivial initialization, its initialization is complete.
The lifetime of an object of type T ends when...
[Voted into the WP at the June, 2008 meeting.]
The original proposed wording for 3.9 [basic.types] paragraph 11 required a constexpr constructor for a literal class only “if the class has at least one user-declared constructor.” This wording was dropped during the review by CWG out of a desire to ensure that literal types not have any uninitialized members. Thus, a class like
struct pixel { int x, y; };
is not a literal type. However, if an object of that type is aggregate-initialized or value-initialized, there can be no uninitialized members; the missing wording should be restored in order to permit use of expressions like pixel().x as constant expressions.
Proposed resolution (February, 2008):
Change 3.9 [basic.types] paragraph 10 as follows:
A type is a literal type if it is:
- a scalar type; or
- a class type (clause 9 [class]) with
- a trivial copy constructor,
- a trivial destructor,
- a trivial default constructor or at least one constexpr constructor other than the copy constructor,
- no virtual base classes, and
- all non-static data members and base classes of literal types; or
- an array of literal type.
[Moved to DR at 4/02 meeting.]
3.10 [basic.lval] paragraph 15 lists the types via which an lvalue can be used to access the stored value of an object; using an lvalue type that is not listed results in undefined behavior. It is permitted to add cv-qualification to the actual type of the object in this access, but only at the top level of the type ("a cv-qualified version of the dynamic type of the object").
However, 4.4 [conv.qual] paragraph 4 permits a "conversion [to] add cv-qualifiers at levels other than the first in multi-level pointers." The combination of these two rules allows creation of pointers that cannot be dereferenced without causing undefined behavior. For instance:
int* jp; const int * const * p1 = &jp; *p1; // undefined behavior!
The reason that *p1 results in undefined behavior is that the type of the lvalue is const int * const", which is not "a cv-qualified version of" int*.
Since the conversion is permitted, we must give it defined semantics, hence we need to fix the wording in 3.10 [basic.lval] to include all possible conversions of the type via 4.4 [conv.qual].
Proposed resolution (04/01):
Add a new bullet to 3.10 [basic.lval] paragraph 15, following "a cv-qualified version of the dynamic type of the object:"
[Voted into the WP at the September, 2008 meeting.]
The requirements on an implementation when presented with an alignment-specifier not supported by that implementation in that context are contradictory: 3.11 [basic.align] paragraph 9 says,
If a request for a specific extended alignment in a specific context is not supported by an implementation, the implementation may reject the request as ill-formed. The implementation may also silently ignore the requested alignment.
In contrast, 7.6.2 [dcl.align] paragraph 2, bullet 4 says simply,
- if the constant expression evaluates to an extended alignment and the implementation does not support that alignment in the context of the declaration, the program is ill-formed
with no provision to “silently ignore” the requested alignment. These two passages need to be reconciled.
If the outcome of the reconciliation is to grant implementations the license to accept and ignore extended alignment requests, the specification should be framed in terms of mechanisms that already exist in the Standard, such as undefined behavior and/or conditionally-supported constructs; “ill-formed” is a category that is defined by the Standard, not something that an implementation can decide.
Notes from the February, 2008 meeting:
The consensus was that such requests should be ill-formed and require a diagnostic. However, it was also observed that an implementation need not reject an ill-formed program; the only requirement is that it issue a diagnostic. It would thus be permissible for an implementation to “noisily ignore” (as opposed to “silently ignoring”) an unsupported alignment request.
Proposed resolution (June, 2008):
Change 3.11 [basic.align] paragraph 9 as follows:
If a request for a specific extended alignment in a specific context is not supported by an implementation, the implementation may reject the request as program is ill-formed. The implementation may also silently ignore the requested alignment. [Note: aAdditionally, a request for runtime allocation of dynamic memory storage for which the requested alignment cannot be honored may shall be treated as an allocation failure. —end note]
[Voted into WP at April, 2006 meeting.]
The C standard says in 6.3.2.3, paragraph 4:
Conversion of a null pointer to another pointer type yields a null pointer of that type. Any two null pointers shall compare equal.
C++ appears to be incompatible with the first sentence in only two areas:
A *a = 0; void *v = a;
C++ (4.10 [conv.ptr] paragraph 2) says nothing about the value of v.
void *v = 0; A *b = (A*)v; // aka static_cast<A*>(v)
C++ (5.2.9 [expr.static.cast] paragraph 10) says nothing about the value of b.
Suggested changes:
Add the following sentence to 4.10 [conv.ptr] paragraph 2:
The null pointer value is converted to the null pointer value of the destination type.
Add the following sentence to 5.2.9 [expr.static.cast] paragraph 10:
The null pointer value (4.10 [conv.ptr]) is converted to the null pointer value of the destination type.
Proposed resolution (October, 2005):
Add the indicated words to 4.10 [conv.ptr] paragraph 2:
An rvalue of type “pointer to cv T,” where T is an object type, can be converted to an rvalue of type “pointer to cv void”. The result of converting a “pointer to cv T” to a “pointer to cv void” points to the start of the storage location where the object of type T resides, as if the object is a most derived object (1.8 [intro.object]) of type T (that is, not a base class subobject). The null pointer value is converted to the null pointer value of the destination type.
Add the indicated words to 5.2.9 [expr.static.cast] paragraph 11:
An rvalue of type “pointer to cv1 void” can be converted to an rvalue of type “pointer to cv2 T,” where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. The null pointer value is converted to the null pointer value of the destination type. A value of type pointer to object converted to “pointer to cv void” and back, possibly with different cv-qualification, shall have its original value...
[Voted into the WP at the June, 2008 meeting as paper N2656.]
In the interest of promoting use of nullptr instead of the integer literal 0 as the null pointer constant, the proposal accepted by the Committee does not provide for converting a zero-valued integral constant to type std::nullptr_t. However, this omission reduces the utility of the feature for use in the library for smart pointers. In particular, the addition of that conversion (along with a converting constructor accepting a std::nullptr_t) would allow smart pointers to be used just like ordinary pointers in expressions like:
if (p == 0) { } if (0 == p) { } if (p != 0) { } if (0 != p) { } p = 0;
The existing use of the “unspecified bool type” idiom supports this usage, but being able to use std::nullptr_t instead would be simpler and more elegant.
Jason Merrill: I have another reason to support the conversion as well: it seems to me very odd for nullptr_t to be more restrictive than void*. Anything we can do with an arbitrary pointer, we ought to be able to do with nullptr_t as well. Specifically, since there is a standard conversion from literal 0 to void*, and there is a standard conversion from void* to bool, nullptr_t should support the same conversions.
This changes two of the example lines in the proposal as adopted:
if (nullptr) ; // error, no conversion to bool if (nullptr == 0) ; // error
become
if (nullptr) ; // evaluates to false if( nullptr == 0 ); // evaluates to true
And later,
char* ch3 = expr ? nullptr : nullptr; // ch3 is the null pointer value char* ch4 = expr ? 0 : nullptr; // ch4 is the null pointer value int n3 = expr ? nullptr : nullptr; // error, nullptr_t can't be converted to int int n4 = expr ? 0 : nullptr; // error, nullptr_t can't be converted to int
I would also allow reinterpret_cast from nullptr_t to integral type, with the same semantics as a reinterpret_cast from the null pointer value to integral type.
Basically, I would like nullptr_t to act like a void* which is constrained to always be (void*)0.
[Voted into WP at the October, 2006 meeting.]
When the Standard refers to a virtual base class, it should be understood to include base classes of virtual bases. However, the Standard doesn't actually say this anywhere, so when 4.11 [conv.mem] (for example) forbids casting to a derived class member pointer from a virtual base class member pointer, it could be read as meaning:
struct B {}; struct D : public B {}; struct D2 : virtual public D {}; int B::*p; int D::*q; void f() { static_cast<int D2::*>(p); // permitted static_cast<int D2::*>(q); // forbidden }
Proposed resolution (October, 2005):
Change 4.11 [conv.mem] paragraph 2 as indicated:
...If B is an inaccessible (clause 11 [class.access]), ambiguous (10.2 [class.member.lookup]) or virtual (10.1 [class.mi]) base class of D, or a base class of a virtual base class of D, a program that necessitates this conversion is ill-formed...
Change 5.2.9 [expr.static.cast] paragraph 2 as indicated:
...and B is not neither a virtual base class of D nor a base class of a virtual base class of D...
Change 5.2.9 [expr.static.cast] paragraph 9 as indicated:
...and B is not neither a virtual base class of D nor a base class of a virtual base class of D...
[Voted into the WP at the September, 2008 meeting.]
I believe that the committee has neglected to take into account one of the differences between C and C++ when defining sequence points. As an example, consider
(a += b) += c;
where a, b, and c all have type int. I believe that this expression has undefined behavior, even though it is well-formed. It is not well-formed in C, because += returns an rvalue there. The reason for the undefined behavior is that it modifies the value of `a' twice between sequence points.
Expressions such as this one are sometimes genuinely useful. Of course, we could write this particular example as
a += b; a += c;
but what about
void scale(double* p, int n, double x, double y) { for (int i = 0; i < n; ++i) { (p[i] *= x) += y; } }
All of the potential rewrites involve multiply-evaluating p[i] or unobvious circumlocations like creating references to the array element.
One way to deal with this issue would be to include built-in operators in the rule that puts a sequence point between evaluating a function's arguments and evaluating the function itself. However, that might be overkill: I see no reason to require that in
x[i++] = y;
the contents of `i' must be incremented before the assignment.
A less stringent alternative might be to say that when a built-in operator yields an lvalue, the implementation shall not subsequently change the value of that object as a consequence of that operator.
I find it hard to imagine an implementation that does not do this already. Am I wrong? Is there any implementation out there that does not `do the right thing' already for (a += b) += c?
5.17 [expr.ass] paragraph 1 says,
The result of the assignment operation is the value stored in the left operand after the assignment has taken place; the result is an lvalue.
What is the normative effect of the words "after the assignment has taken place"? I think that phrase ought to mean that in addition to whatever constraints the rules about sequence points might impose on the implementation, assignment operators on built-in types have the additional constraint that they must store the left-hand side's new value before returning a reference to that object as their result.
One could argue that as the C++ standard currently stands, the effect of x = y = 0; is undefined. The reason is that it both fetches and stores the value of y, and does not fetch the value of y in order to compute its new value.
I'm suggesting that the phrase "after the assignment has taken place" should be read as constraining the implementation to set y to 0 before yielding the value of y as the result of the subexpression y = 0.
Francis Glassborow:
My understanding is that for a single variable:
It is the 3) that is often ignored because in practice the compiler hardly ever codes for the read because it already has that value but in complicated evaluations with a shortage of registers, that is not always the case. Without getting too close to the hardware, I think we both know that a read too close to a write can be problematical on some hardware.
So, in x = y = 0;, the implementation must NOT fetch a value from y, instead it has to "know" what that value will be (easy because it has just computed that in order to know what it must, at some time, store in y). From this I deduce that computing the lvalue (to know where to store) and the rvalue to know what is stored are two entirely independent actions that can occur in any order commensurate with the overall requirements that both operands for an operator be evaluated before the operator is.
Erwin Unruh:
C distinguishes between the resulting value of an assignment and putting the value in store. So in C a compiler might implement the statement x=y=0; either as x=0;y=0; or as y=0;x=0; In C the statement (x += 5) += 7; is not allowed because the first += yields an rvalue which is not allowed as left operand to +=. So in C an assignment is not a sequence of write/read because the result is not really "read".
In C++ we decided to make the result of assignment an lvalue. In this case we do not have the option to specify the "value" of the result. That is just the variable itself (or its address in a different view). So in C++, strictly speaking, the statement x=y=0; must be implemented as y=0;x=y; which makes a big difference if y is declared volatile.
Furthermore, I think undefined behaviour should not be the result of a single mentioning of a variable within an expression. So the statement (x +=5) += 7; should NOT have undefined behaviour.
In my view the semantics could be:
Jerry Schwarz:
My recollection is different from Erwin's. I am confident that the intention when we decided to make assignments lvalues was not to change the semantics of evaluation of assignments. The semantics was supposed to remain the same as C's.
Ervin seems to assume that because assignments are lvalues, an assignment's value must be determined by a read of the location. But that was definitely not our intention. As he notes this has a significant impact on the semantics of assignment to a volatile variable. If Erwin's interpretation were correct we would have no way to write a volatile variable without also reading it.
Lawrence Crowl:
For x=y=0, lvalue semantics implies an lvalue to rvalue conversion on the result of y=0, which in turn implies a read. If y is volatile, lvalue semantics implies both a read and a write on y.
The standard apparently doesn't state whether there is a value dependence of the lvalue result on the completion of the assignment. Such a statement in the standard would solve the non-volatile C compatibility issue, and would be consistent with a user-implemented operator=.
Another possible approach is to state that primitive assignment operators have two results, an lvalue and a corresponding "after-store" rvalue. The rvalue result would be used when an rvalue is required, while the lvalue result would be used when an lvalue is required. However, this semantics is unsupportable for user-defined assignment operators, or at least inconsistent with all implementations that I know of. I would not enjoy trying to write such two-faced semantics.
Erwin Unruh:
The intent was for assignments to behave the same as in C. Unfortunately the change of the result to lvalue did not keep that. An "lvalue of type int" has no "int" value! So there is a difference between intent and the standard's wording.
So we have one of several choices:
I think the last one has the least impact on existing programs, but it is an ugly solution.
Andrew Koenig:
Whatever we may have intended, I do not think that there is any clean way of making
volatile int v; int i; i = v = 42;have the same semantics in C++ as it does in C. Like it or not, the subexpression v = 42 has the type ``reference to volatile int,'' so if this statement has any meaning at all, the meaning must be to store 42 in v and then fetch the value of v to assign it to i.
Indeed, if v is volatile, I cannot imagine a conscientious programmer writing a statement such as this one. Instead, I would expect to see
v = 42; i = v;if the intent is to store 42 in v and then fetch the (possibly changed) value of v, or
v = 42; i = 42;if the intent is to store 42 in both v and i.
What I do want is to ensure that expressions such as ``i = v = 42'' have well-defined semantics, as well as expressions such as (i = v) = 42 or, more realistically, (i += v) += 42 .
I wonder if the following resolution is sufficient:
Append to 5.17 [expr.ass] paragraph 1:
There is a sequence point between assigning the new value to the left operand and yielding the result of the assignment expression.
I believe that this proposal achieves my desired effect of not constraining when j is incremented in x[j++] = y, because I don't think there is a constraint on the relative order of incrementing j and executing the assignment. However, I do think it allows expressions such as (i += v) += 42, although with different semantics from C if v is volatile.
Notes on 10/01 meeting:
There was agreement that adding a sequence point is probably the right solution.
Notes from the 4/02 meeting:
The working group reaffirmed the sequence-point solution, but we will look for any counter-examples where efficiency would be harmed.
For drafting, we note that ++x is defined in 5.3.2 [expr.pre.incr] as equivalent to x+=1 and is therefore affected by this change. x++ is not affected. Also, we should update any list of all sequence points.
Notes from October 2004 meeting:
Discussion centered around whether a sequence point “between assigning the new value to the left operand and yielding the result of the expression” would require completion of all side effects of the operand expressions before the value of the assignment expression was used in another expression. The consensus opinion was that it would, that this is the definition of a sequence point. Jason Merrill pointed out that adding a sequence point after the assignment is essentially the same as rewriting
b += a
as
b += a, b
Clark Nelson expressed a desire for something like a “weak” sequence point that would force the assignment to occur but that would leave the side effects of the operands unconstrained. In support of this position, he cited the following expression:
j = (i = j++)
With the proposed addition of a full sequence point after the assignment to i, the net effect is no change to j. However, both g++ and MSVC++ behave differently: if the previous value of j is 5, the value of the expression is 5 but j gets the value 6.
Clark Nelson will investigate alternative approaches and report back to the working group.
Proposed resolution (March, 2008):
This issue is resolved by the adoption of the sequencing rules and the resolution of issue 637.
[Voted into WP at March 2004 meeting.]
I have found what looks like a bug in clause 5 [expr], paragraph 4:
Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored. The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full expression; otherwise the behavior is undefined. Example:i = v[i++]; // the behavior is unspecified i = 7, i++, i++; // i becomes 9 i = ++i + 1; // the behavior is unspecified i = i + 1; // the value of i is incremented--end example]
So which is it, unspecified or undefined?
Notes from October 2002 meeting:
We should find out what C99 says and do the same thing.
Proposed resolution (April 2003):
Change the example in clause 5 [expr], paragraph 4 from
[Example:i = v[i++]; // the behavior is unspecified i = 7, i++, i++; // i becomes 9 i = ++i + 1; // the behavior is unspecified i = i + 1; // the value of i is incremented--- end example]
to (changing "unspecified" to "undefined" twice)
[Example:i = v[i++]; // the behavior is undefined i = 7, i++, i++; // i becomes 9 i = ++i + 1; // the behavior is undefined i = i + 1; // the value of i is incremented--- end example]
[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 expression is a constant expression appears where an integral constant expression is required (5.19 [expr.const]), in which case the program is ill-formed.
[Moved to DR at 10/01 meeting.]
5.1.1 [expr.prim.general] paragraph 11 reads,
A template-id shall be used as an unqualified-id only as specified in 14.7.2 [temp.explicit] , 14.7 [temp.spec] , and 14.5.5 [temp.class.spec] .
What uses of template-ids as unqualified-ids is this supposed to prevent? And is the list of referenced sections correct/complete? For instance, what about 14.8.1 [temp.arg.explicit], "Explicit template argument specification?" Does its absence from the list in 5.1.1 [expr.prim.general] paragraph 11 mean that "f<int>()" is ill-formed?
This is even more confusing when you recall that unqualified-ids are contained in qualified-ids:
qualified-id: ::opt nested-name-specifier templateopt unqualified-id
Is the wording intending to say "used as an unqualified-id that is not part of a qualified-id?" Or something else?
Proposed resolution (10/00):
Remove the referenced sentence altogether.
[Voted into WP at March 2004 meeting.]
The example below is ambiguous.
struct A{ struct B{}; }; A::B C(); namespace B{ A C(); } struct Test { friend A::B ::C(); };Here, it is not clear whether the friend declaration denotes A B::C() or A::B C(), yet the standard does not resolve this ambiguity.
The ambiguity arises since both the simple-type-specifier (7.1.6.2 [dcl.type.simple] paragra 1) and an init-declararator (8 [dcl.decl] paragraph 1) contain an optional :: and an optional nested-name-specifier (5.1.1 [expr.prim.general] paragraph 1). Therefore, two different ways to analyse this code are possible:
simple-type-specifier = A::Bor
init-declarator = ::C()
simple-declaration = friend A::B ::C();
simple-type-specifier = ASince it is a friend declaration, the init-declarator may be qualified, and start with a global scope.
init-declarator = ::B::C()
simple-declaration = friend A ::B::C();
Suggested Resolution: In the definition of nested-name-specifier, add a sentence saying that a :: token immediately following a nested-name-specifier is always considered as part of the nested-name-specifier. Under this interpretation, the example is ill-formed, and should be corrected as either
friend A (::B::C)(); //or friend A::B (::C)();
An alternate suggestion — changing 7.1 [dcl.spec] to say that
The longest sequence of tokens that could possibly be a type name is taken as the decl-specifier-seq of a declaration.
— is undesirable because it would make the example well-formed rather than requiring the user to disambiguate the declaration explicitly.
Proposed resolution (04/01):
(See below for problem with this, from 10/01 meeting.)
In 5.1.1 [expr.prim.general] paragraph 7,
Before the grammar for qualified-id, start a new paragraph 7a with the text
A qualified-id is an id-expression that contains the scope resolution operator ::.
Following the grammar fragment, insert the following:
The longest sequence of tokens that could form a qualified-id constitutes a single qualified-id. [Example:
// classes C, D; functions F, G, namespace N; non-class type T friend C ::D::F(); // ill-formed, means friend (C::D::F)(); friend C (::D::F)(); // well-formed friend N::T ::G(); // ill-formed, means friend (N::T::G)(); friend N::T (::G)(); // well-formed—end example]
Start a new paragraph 7b following the example.
(This resolution depends on that of issue 215.)
Notes from 10/01 meeting:
It was pointed out that the proposed resolution does not deal with cases like X::Y where X is a type but not a class type. The working group reaffirmed its decision that the disambiguation should be syntactic only, i.e., it should depend only on whether or not the name is a type.
Jason Merrill :At the Seattle meeting, I suggested that a solution might be to change the class-or-namespace-name in the nested-name-specifier rule to just be "identifier"; there was some resistance to this idea. FWIW, I've tried this in g++. I had to revise the idea so that only the second and subsequent names were open to being any identifier, but that seems to work just fine.
So, instead of
it would be
Or some equivalent but right-associative formulation, if people feel that's important, but it seems irrelevant to me.
Clark Nelson :
Personally, I prefer the left-associative rule. I think it makes it easier to understand. I was thinking about this production a lot at the meeting, considering also some issues related to 301. My formulation was getting kind of ugly, but with a left-associative rule, it gets a lot nicer.
Your proposal isn't complete, however, as it doesn't allow template arguments without an explicit template keyword. You probably want to add an alternative for:
There is admittedly overlap between this alternative and
but I think they're both necessary.
Notes from the 4/02 meeting:
The changes look good. Clark Nelson will merge the two proposals to produce a single proposed resolution.
Proposed resolution (April 2003):
nested-name-specifier is currently defined in 5.1.1 [expr.prim.general] paragraph 7 as:
The proposed definition is instead:
Issue 215 is addressed by using type-name instead of class-name in the first alternative. Issue 125 (this issue) is addressed by using identifier instead of anything more specific in the third alternative. Using left association instead of right association helps eliminate the need for class-or-namespace-name (or type-or-namespace-name, as suggested for issue 215).
It should be noted that this formulation also rules out the possibility of A::template B::, i.e. using the template keyword without any template arguments. I think this is according to the purpose of the template keyword, and that the former rule allowed such a construct only because of the difficulty of formulation of a right-associative rule that would disallow it. But I wanted to be sure to point out this implication.
Notes from April 2003 meeting:
See also issue 96.
The proposed change resolves only part of issue 215.
[Moved to DR at 10/01 meeting.]
Christophe de Dinechin: In 5.2.2 [expr.call] , paragraph 2 reads:
If no declaration of the called function is visible from the scope of the call the program is ill-formed.I think nothing there or in the previous paragraph indicates that this does not apply to calls through pointer or virtual calls.
Mike Miller: "The called function" is unfortunate phraseology; it makes it sound as if it's referring to the function actually called, as opposed to the identifier in the postfix expression. It's wrong with respect to Koenig lookup, too (the declaration need not be visible if it can be found in a class or namespace associated with one or more of the arguments).
In fact, this paragraph should be a note. There's a general rule that says you have to find an unambiguous declaration of any name that is used (3.4 [basic.lookup] paragraph 1); the only reason this paragraph is here is to contrast with C's implicit declaration of called functions.
Proposed resolution:
Change section 5.2.2 [expr.call] paragraph 2 from:If no declaration of the called function is visible from the scope of the call the program is ill-formed.to:
[Note: if a function or member function name is used, and name lookup (3.4 [basic.lookup]) does not find a declaration of that name, the program is ill-formed. No function is implicitly declared by such a call. ]
(See also issue 218.)
[Voted into the WP at the June, 2008 meeting.]
Martin O'Riordan: Having gone through all the relevant references in the IS, it is not conclusive that a call via a pointer to a virtual member function is polymorphic at all, and could legitimately be interpreted as being static.
Consider 5.2.2 [expr.call] paragraph 1:
The function called in a member function call is normally selected according to the static type of the object expression (clause 10 [class.derived] ), but if that function is virtual and is not specified using a qualified-id then the function actually called will be the final overrider (10.3 [class.virtual] ) of the selected function in the dynamic type of the object expression.Here it is quite specific that you get the polymorphic call only if you use the unqualified syntax. But, the address of a member function is "always" taken using the qualified syntax, which by inference would indicate that call with a PMF is static and not polymorphic! Not what was intended.
Yet other references such as 5.5 [expr.mptr.oper] paragraph 4:
If the dynamic type of the object does not contain the member to which the pointer refers, the behavior is undefined.indicate that the opposite may have been intended, by stating that it is the dynamic type and not the static type that matters. Also, 5.5 [expr.mptr.oper] paragraph 6:
If the result of .* or ->* is a function, then that result can be used only as the operand for the function call operator (). [Example:which also implies that it is the object pointed to that determines both the validity of the expression (the static type of 'ptr_to_obj' may not have a compatible function) and the implicit (polymorphic) meaning. Note too, that this is stated in the non-normative example text.(ptr_to_obj->*ptr_to_mfct)(10);calls the member function denoted by ptr_to_mfct for the object pointed to by ptr_to_obj. ]
Andy Sawyer: Assuming the resolution is what I've assumed it is for the last umpteen years (i.e. it does the polymorphic thing), then the follow on to that is "Should there also be a way of selecting the non-polymorphic behaviour"?
Mike Miller: It might be argued that the current wording of 5.2.2 [expr.call] paragraph 1 does give polymorphic behavior to simple calls via pointers to members. (There is no qualified-id in obj.*pmf, and the IS says that if the function is not specified using a qualified-id, the final overrider will be called.) However, it clearly says the wrong thing when the pointer-to-member itself is specified using a qualified-id (obj.*X::pmf).
Bill Gibbons: The phrase qualified-id in 5.2.2 [expr.call] paragraph 1 refers to the id-expression and not to the "pointer-to-member expression" earlier in the paragraph:
For a member function call, the postfix expression shall be an implicit (9.3.1 [class.mfct.non-static] , 9.4 [class.static] ) or explicit class member access (5.2.5 [expr.ref] ) whose id-expression is a function member name, or a pointer-to-member expression (5.5 [expr.mptr.oper] ) selecting a function member.
Mike Miller: To be clear, here's an example:
struct S { virtual void f(); }; void (S::*pmf)(); void g(S* sp) { sp->f(); // 1: polymorphic sp->S::f(); // 2: non-polymorphic (sp->S::f)(); // 3: non-polymorphic (sp->*pmf)(); // 4: polymorphic (sp->*&S::f)(); // 5: polymorphic }
Notes from October 2002 meeting:
This was moved back to open for lack of a champion. Martin O'Riordan is not expected to be attending meetings.
Proposed resolution (February, 2008):
Change 5.2.2 [expr.call] paragraph 1 as follows:
... For a member function call, the postfix expression shall be an implicit (9.3.1 [class.mfct.non-static], 9.4 [class.static]) or explicit class member access (5.2.5 [expr.ref]) whose id-expression is a function member name, or a pointer-to-member expression (5.5 [expr.mptr.oper]) selecting a function member. The first expression in the postfix expression is then called the object expression, and; the call is as a member of the object pointed to or referred to by the object expression (5.2.5 [expr.ref], 5.5 [expr.mptr.oper]). In the case of an implicit class member access, the implied object is the one pointed to by this. [Note: a member function call of the form f() is interpreted as (*this).f() (see 9.3.1 [class.mfct.non-static]). —end note] If a function or member function name is used, the name can be overloaded (clause 13 [over]), in which case the appropriate function shall be selected according to the rules in 13.3 [over.match]. The function called in a member function call is normally selected according to the static type of the object expression (clause 10 [class.derived]), but if that function is virtual and is not specified using a qualified-id then the function actually called will be the final overrider (10.3 [class.virtual]) of the selected function in the dynamic type of the object expression If the selected function is non-virtual, or if the id-expression in the class member access expression is a qualified-id, that function is called. Otherwise, its final overrider (10.3 [class.virtual]) in the dynamic type of the object expression is called. ...
Change 5.5 [expr.mptr.oper] paragraph 4 as follows:
The first operand is called the object expression. If the dynamic type of the object expression does not contain the member to which the pointer refers, the behavior is undefined.
[Voted into WP at the October, 2006 meeting.]
The current wording of 5.2.2 [expr.call] paragraph 7 states:
When there is no parameter for a given argument, the argument is passed in such a way that the receiving function can obtain the value of the argument by invoking va_arg (18.10 [support.runtime]). The lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), and function-to-pointer (4.3 [conv.func]) standard conversions are performed on the argument expression. After these conversions, if the argument does not have arithmetic, enumeration, pointer, pointer to member, or class type, the program is ill-formed. If the argument has a non-POD class type (clause 9 [class]), the behavior is undefined.
Paper J16/04-0167=WG21 N1727 suggests that passing a non-POD object to ellipsis be ill-formed. In discussions at the Lillehammer meeting, however, the CWG felt that the newly-approved category of conditionally-supported behavior would be more appropriate.
Proposed resolution (October, 2005):
Change 5.2.2 [expr.call] paragraph 7 as indicated:
...After these conversions, if the argument does not have arithmetic, enumeration, pointer, pointer to member, or class type, the program is ill-formed. If the argument has a non-POD class type (clause 9), the behavior is undefined. Passing an argument of non-POD class type (clause 9) with no corresponding parameter is conditionally-supported, with implementation-defined semantics.
[Voted into the WP at the September, 2008 meeting.]
Issue 506 changed passing a non-POD class type to an ellipsis from undefined behavior to conditionally-supported behavior. As a result, an implementation could conceivably reject code like the following:
struct two {char _[2];}; template <class From, class To> struct is_convertible { private: static From f; template <class U> static char test(const U&); template <class U> static two test(...); public: static const bool value = sizeof(test<To>(f)) == 1; }; struct A { A(); }; int main() { const bool b = is_convertible<A,int>::value; // b == false }
This technique has become popular in template metaprogramming, and no non-POD object is actually passed at runtime. Concepts will eliminate much (perhaps not all) of the need for this kind of programming, but legacy code will persist.
Perhaps this technique should be officially supported by allowing implementations to reject passing a non-POD type to ellipsis only if it appears in a potentially-evaluated expression?
Notes from the July, 2007 meeting:
The CWG agreed with the suggestion to allow such calls in unevaluated contexts.
Proposed resolution (September, 2007):
Change 5.2.2 [expr.call] paragraph 7 as follows:
...Passing an a potentially-evaluated argument of non-trivial class type (clause 9 [class]) with no corresponding parameter is conditionally-supported, with implementation-defined semantics...
[Voted into WP at April, 2006 meeting.]
5.2.4 [expr.pseudo] paragraph 2 says both:
The type designated by the pseudo-destructor-name shall be the same as the object type.and also:
The cv-unqualified versions of the object type and of the type designated by the pseudo-destructor-name shall be the same type.Which is it? "The same" or "the same up to cv-qualifiers"? The second sentence is more generous than the first. Most compilers seem to implement the less restrictive form, so I guess that's what I think we should do.
Proposed resolution (October, 2005):
Change 5.2.4 [expr.pseudo] paragraph 2 as follows:
The left-hand side of the dot operator shall be of scalar type. The left-hand side of the arrow operator shall be of pointer to scalar type. This scalar type is the object type. The type designated by the pseudo-destructor-name shall be the same as the object type. The cv-unqualified versions of the object type and of the type designated by the pseudo-destructor-name shall be the same type. Furthermore, the two type-names in a pseudo-destructor-name of the form::opt nested-name-specifieropt type-name ::~ type-name
shall designate the same scalar type. The cv-unqualified versions of the object type and of the type designated by the pseudo-destructor-name shall be the same type.
[Voted into WP at March 2004 meeting.]
Consider
typedef struct { int a; } A; A f(void) { A a; return a; } int main(void) { int* p = &f().a; // #1 }
Should #1 be rejected? The standard is currently silent.
Mike Miller: I don't believe the Standard is silent on this. I will agree that the wording of 5.2.5 [expr.ref] paragraph 4 bullet 2 is unfortunate, as it is subject to misinterpretation. It reads,
If E1 is an lvalue, then E1.E2 is an lvalue.The intent is, "and not otherwise."
Notes from October 2003 meeting:
We agree the reference should be an rvalue, and a change along the lines of that recommended by Mike Miller is reasonable.
Proposed Resolution (October 2003):
Change the second bullet of 5.2.5 [expr.ref] paragraph 4 to read:
If E1 is an lvalue, then E1.E2 is an lvalue; otherwise, it is an rvalue.
[Voted into WP at April, 2006 meeting.]
There is an inconsistency between the normative text in section 5.2.8 [expr.typeid] and the example that follows.
Here is the relevant passage (starting with paragraph 4):
When typeid is applied to a type-id, the result refers to a std::type_info object representing the type of the type-id. If the type of the type-id is a reference type, the result of the typeid expression refers to a std::type_info object representing the referenced type.
The top-level cv-qualifiers of the lvalue expression or the type-id that is the operand of typeid are always ignored.
and the example:
typeid(D) == typeid(const D&); // yields true
The second paragraph above says the “type-id that is the operand”. This would be const D&. In this case, the const is not at the top-level (i.e., applied to the operand itself).
By a strict reading, the above should yield false.
My proposal is that the strict reading of the normative test is correct. The example is wrong. Different compilers here give different answers.
Proposed resolution (April, 2005):
Change the second sentence of 5.2.8 [expr.typeid] paragraph 4 as follows:
If the type of the type-id is a reference to a possibly cv-qualified type, the result of the typeid expression refers to a std::type_info object representing the cv-unqualified referenced type.
[Voted into WP at October 2004 meeting.]
Is it okay to use a static_cast to cast from a private base class to a derived class? That depends on what the words "valid standard conversion" in paragraph 8 mean — do they mean the conversion exists, or that it would not get an error if it were done? I think the former was intended — and therefore a static_cast from a private base to a derived class would be allowed.
Rationale (04/99): A static_cast from a private base to a derived class is not allowed outside a member from the derived class, because 4.10 [conv.ptr] paragraph 3 implies that the conversion is not valid. (Classic style casts work.)
Reopened September 2003:
Steve Adamczyk: It makes some sense to disallow the inverse conversion that is pointer-to-member of derived to pointer-to-member of private base. There's less justification for the pointer-to-private-base to pointer-to-derived case. EDG, g++ 3.2, and MSVC++ 7.1 allow the pointer case and disallow the pointer-to-member case. Sun disallows the pointer case as well.
struct B {}; struct D : private B {}; int main() { B *p = 0; static_cast<D *>(p); // Pointer case: should be allowed int D::*pm = 0; static_cast<int B::*>(pm); // Pointer-to-member case: should get error }
There's a tricky case with old-style casts: because the static_cast interpretation is tried first, you want a case like the above to be considered a static_cast, but then issue an error, not be rejected as not a static cast; if you did the latter, you would then try the cast as a reinterpret_cast.
Ambiguity and casting to a virtual base should likewise be errors after the static_cast interpretation is selected.
Notes from the October 2003 meeting:
There was lots of sentiment for making things symmetrical: the pointer case should be the same as the pointer-to-member case. g++ 3.3 now issues errors on both cases.
We decided an error should be issued on both cases. The access part of the check should be done later; by some definition of the word the static_cast is valid, and then later an access error is issued. This is similar to the way standard conversions work.
Proposed Resolution (October 2003):
Replace paragraph 5.2.9 [expr.static.cast]/6:
The inverse of any standard conversion sequence (clause 4 [conv]), other than the lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), function-to-pointer (4.3 [conv.func]), and boolean (4.12 [conv.bool]) conversions, can be performed explicitly using static_cast. The lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), and function-to-pointer (4.3 [conv.func]) conversions are applied to the operand. Such a static_cast is subject to the restriction that the explicit conversion does not cast away constness (5.2.11 [expr.const.cast]), and the following additional rules for specific cases:
with two paragraphs:
The inverse of any standard conversion sequence (clause 4 [conv]), other than the lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), function-to-pointer (4.3 [conv.func]), and boolean (4.12 [conv.bool]) conversions, can be performed explicitly using static_cast. A program is ill-formed if it uses static_cast to perform the inverse of an ill-formed standard conversion sequence.[Example:--- end example]struct B {}; struct D : private B {}; void f() { static_cast<D*>((B*)0); // Error: B is a private base of D. static_cast<int B::*>((int D::*)0); // Error: B is a private base of D. }
The lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), and function-to-pointer (4.3 [conv.func]) conversions are applied to the operand. Such a static_cast is subject to the restriction that the explicit conversion does not cast away constness (5.2.11 [expr.const.cast]), and the following additional rules for specific cases:
In addition, modify the second sentence of 5.4 [expr.cast]/5. The first two sentences of 5.4 [expr.cast]/5 presently read:
The conversions performed bycan be performed using the cast notation of explicit type conversion. The same semantic restrictions and behaviors apply.
- a const_cast (5.2.11),
- a static_cast (5.2.9),
- a static_cast followed by a const_cast,
- a reinterpret_cast (5.2.10), or
- a reinterpret_cast followed by a const_cast,
Change the second sentence to read:
The same semantic restrictions and behaviors apply, with the exception that in performing a static_cast in the following situations the conversion is valid even if the base class is inaccessible:
- a pointer to an object of derived class type or an lvalue of derived class type may be explicitly converted to a pointer or reference to an unambiguous base class type, respectively;
- a pointer to member of derived class type may be explicitly converted to a pointer to member of an unambiguous non-virtual base class type;
- a pointer to an object of an unambiguous non-virtual base class type, an lvalue of an unambiguous non-virtual base class type, or a pointer to member of an unambiguous non-virtual base class type may be explicitly converted to a pointer, a reference, or a pointer to member of a derived class type, respectively.
Remove paragraph 5.4 [expr.cast]/7, which presently reads:
In addition to those conversions, the following static_cast and reinterpret_cast operations (optionally followed by a const_cast operation) may be performed using the cast notation of explicit type conversion, even if the base class type is not accessible:
- a pointer to an object of derived class type or an lvalue of derived class type may be explicitly converted to a pointer or reference to an unambiguous base class type, respectively;
- a pointer to member of derived class type may be explicitly converted to a pointer to member of an unambiguous non-virtual base class type;
- a pointer to an object of non-virtual base class type, an lvalue of non-virtual base class type, or a pointer to member of non-virtual base class type may be explicitly converted to a pointer, a reference, or a pointer to member of a derived class type, respectively.
[Voted into WP at October 2004 meeting.]
Consider this code:
struct B {}; struct D : public B { D(const B&); }; extern B& b; void f() { static_cast<const D&>(b); }
The rules for static_cast permit the conversion to "const D&" in two ways:
The first alternative is 5.2.9 [expr.static.cast]/5; the second is 5.2.9 [expr.static.cast]/2.
Presumably the first alternative is better -- it's the "simpler" conversion. The standard does not seem to make that clear.
Steve Adamczyk: I take the "Otherwise" at the beginning of 5.2.9 [expr.static.cast]/3 as meaning that the paragraph 2 interpretation is used if available, which means in your example above interpretation 2 would be used. However, that's not what EDG's compiler does, and I agree that it's not the "simpler" conversion.
Proposed Resolution (October 2003):
Move paragraph 5.2.9/5:
An lvalue of type ``cv1 B'', where B is a class type, can be cast to type ``reference to cv2 D'', where D is a class derived (clause 10 [class.derived]) from B, if a valid standard conversion from ``pointer to D'' to ``pointer to B'' exists (4.10 [conv.ptr]), cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and B is not a virtual base class of D. The result is an lvalue of type ``cv2 D.'' If the lvalue of type ``cv1 B'' is actually a sub-object of an object of type D, the lvalue refers to the enclosing object of type D. Otherwise, the result of the cast is undefined. [Example:
struct B {}; struct D : public B {}; D d; B &br = d; static_cast<D&>(br); // produces lvalue to the original d object--- end example]
before paragraph 5.2.9 [expr.static.cast]/2.
Insert Otherwise, before the text of paragraph 5.2.9 [expr.static.cast]/2 (which will become 5.2.9 [expr.static.cast]/3 after the above insertion), so that it reads:
Otherwise, an expression e can be explicitly converted to a type T using a static_cast of the form static_cast<T>(e) if the declaration "T t(e);" is well-formed, for some invented temporary variable t (8.5 [dcl.init]). The effect of such an explicit conversion is the same as performing the declaration and initialization and then using the temporary variable as the result of the conversion. The result is an lvalue if T is a reference type (8.3.2 [dcl.ref]), and an rvalue otherwise. The expression e is used as an lvalue if and only if the initialization uses it as an lvalue.
[Voted into WP at April 2005 meeting.]
Paragraph 5.2.9 [expr.static.cast] paragraph 10 says that:
A value of type pointer to object converted to "pointer to cv void" and back to the original pointer type will have its original value.
That guarantee should be stronger. In particular, given:
T* p1 = new T; const T* p2 = static_cast<const T*>(static_cast<void *>(p1)); if (p1 != p2) abort ();there should be no call to "abort". The last sentence of Paragraph 5.2.9 [expr.static.cast] paragraph 10 should be changed to read:
A value of type pointer to object converted to "pointer to cv void" and back to the original pointer type (or a variant of the original pointer type that differs only in the cv-qualifiers applied to the object type) will have its original value. [Example:---end example.]T* p1 = new T; const T* p2 = static_cast<const T*>(static_cast<void *>(p1)); bool b = p1 == p2; // b will have the value true.
Proposed resolution:
Change 5.2.9 [expr.static.cast] paragraph 10 as indicated:
A value of type pointer to object converted to "pointer to
cv void" and back to the original pointer
type, possibly with different cv-qualification, will have
its original value. [Example:
T* p1 = new T;
const T* p2 = static_cast<const T*>(static_cast<void *>(p1));
bool b = p1 == p2; // b will have the value true.
---end example]
Rationale: The wording "possibly with different cv-qualification" was chosen over the suggested wording to allow for changes in cv-qualification at different levels in a multi-level pointer, rather than only at the object type level.
[Voted into the WP at the September, 2008 meeting.]
There appears to be no provision in the Standard for explicit conversion of a value of a scoped enumeration type to an integral type, even though the inverse conversion is permitted. That is,
enum class E { e }; static_cast<E>(0); // #1: OK static_cast<int>(E::e); // #2: error
This is because values of scope enumeration types (intentionally) cannot be implicitly converted to integral types (4.5 [conv.prom] and 4.7 [conv.integral]) and 5.2.9 [expr.static.cast] was not updated to permit #2, although #1 is covered by paragraph 8.
Proposed resolution (June, 2008):
Add the following as a new paragraph following 5.2.9 [expr.static.cast] paragraph 8:
A value of a scoped enumeration type (7.2 [dcl.enum]) can be explicitly converted to an integral type. The value is unchanged if the original value can be represented by the specified type. Otherwise, the resulting value is unspecified.
[Voted into WP at April 2005 meeting.]
It is currently not permitted to cast directly between a pointer to function type and a pointer to object type. This conversion is not listed in 5.2.9 [expr.static.cast] and 5.2.10 [expr.reinterpret.cast] and thus requires a diagnostic to be issued. However, if a sufficiently long integral type exists (as is the case in many implementations), it is permitted to cast between pointer to function types and pointer to object types using that integral type as an intermediary.
In C the cast results in undefined behavior and thus does not require a diagnostic, and Unix C compilers generally do not issue one. This fact is used in the definition of the standard Unix function dlsym, which is declared to return void* but in fact may return either a pointer to a function or a pointer to an object. The fact that C++ compilers are required to issue a diagnostic is viewed as a "competitive disadvantage" for the language.
Suggested resolution: Add wording to 5.2.10 [expr.reinterpret.cast] allowing conversions between pointer to function and pointer to object types, if the implementation has an integral data type that can be used as an intermediary.
Several points were raised in opposition to this suggestion:
Martin O'Riordan suggested an alternative approach:
The advantage of this approach is that it would permit writing portable, well-defined programs involving such conversions. However, it breaks the current degree of compatibility between old and new casts, and it adds functionality to dynamic_cast which is not obviously related to its current meaning.
Notes from 04/00 meeting:
Andrew Koenig suggested yet another approach: specify that "no diagnostic is required" if the implementation supports the conversion.
Later note:
It was observed that conversion between function and data pointers is listed as a "common extension" in C99.
Notes on the 10/01 meeting:
It was decided that we want the conversion defined in such a way that it always exists but is always undefined (as opposed to existing only when the size relationship is appropriate, and being implementation-defined in that case). This would allow an implementation to issue an error at compile time if the conversion does not make sense.
Bill Gibbons notes that the definitions of the other similar casts are inconsistent in this regard. Perhaps they should be updated as well.
Proposed resolution (April 2003):
After 5.2.10 [expr.reinterpret.cast] paragraph 6, insert:
A pointer to a function can be explicitly converted to a pointer to a function of a different type. The effect of calling a function through a pointer to a function type (8.3.5 [dcl.fct]) that is not the same as the type used in the definition of the function is undefined. Except that converting an rvalue of type ``pointer to T1'' to the type ``pointer to T2'' (where T1 and T2 are function types) and back to its original type yields the original pointer value, the result of such a pointer conversion is unspecified. [Note: see also 4.10 [conv.ptr] for more details of pointer conversions. ] It is implementation defined whether a conversion from pointer to object to pointer to function and/or a conversion from pointer to function to pointer to object exist.and in paragraph 10:
An lvalue expression of type T1 can be cast to the type ``reference to T2'' if T1 and T2 are object types and an expression of type ``pointer to T1'' can be explicitly converted to the type ``pointer to T2'' using a reinterpret_cast. That is, a reference cast reinterpret_cast< T& >(x) has the same effect as the conversion *reinterpret_cast< T* >(&x) with the built-in & and * operators. The result is an lvalue that refers to the same object as the source lvalue, but with a different type. No temporary is created, no copy is made, and constructors (12.1 [class.ctor]) or conversion functions (12.3 [class.conv]) are not called.
Drafting Note:
If either conversion exists, the implementation already has to define the behavior (paragraph 3).
Notes from April 2003 meeting:
The new consensus is that if the implementation allows this cast, pointer-to-function converted to pointer-to-object converted back to the original pointer-to-function should work; anything else is undefined behavior. If the implementation does not allow the cast, it should be ill-formed.
Tom Plum is investigating a new concept, that of a "conditionally-defined" feature, which may be applicable here.
Proposed Resolution (October, 2004):
(See paper J16/04-0067 = WG21 N1627 for background material and rationale for this resolution. The resolution proposed here differs only editorially from the one in the paper.)
Insert the following into 1.3 [intro.defs] and renumber all following definitions accordingly:
1.3.2 conditionally-supported behavior
behavior evoked by a program construct that is not a mandatory requirement of this International Standard. If a given implementation supports the construct, the behavior shall be as described herein; otherwise, the implementation shall document that the construct is not supported and shall treat a program containing an occurrence of the construct as ill-formed (1.3 [intro.defs]).
Add the indicated words to 1.4 [intro.compliance] paragraph 2, bullet 2:
If a program contains a violation of any diagnosable rule, or an occurrence of a construct described herein as “conditionally-supported” or as resulting in “conditionally-supported behavior” when the implementation does not in fact support that construct, a conforming implementation shall issue at least one diagnostic message, except that
Insert the following as a new paragraph following 5.2.10 [expr.reinterpret.cast] paragraph 7:
Converting a pointer to a function to a pointer to an object type or vice versa evokes conditionally-supported behavior. In any such conversion supported by an implementation, converting from an rvalue of one type to the other and back (possibly with different cv-qualification) shall yield the original pointer value; mappings between pointers to functions and pointers to objects are otherwise implementation-defined.
Change 7.4 [dcl.asm] paragraph 1 as indicated:
The meaning of an An asm declaration evokes conditionally-supported behavior. If supported, its meaning is implementation-defined.
Change 7.5 [dcl.link] paragraph 2 as indicated:
The string-literal indicates the required language linkage. The meaning of the string-literal is implementation-defined. A linkage-specification with a string that is unknown to the implementation is ill-formed. This International Standard specifies the semantics of C and C++ language linkage. Other values of the string-literal evoke conditionally-supported behavior, with implementation-defined semantics. [Note: Therefore, a linkage-specification with a string-literal that is unknown to the implementation requires a diagnostic. When the string-literal in a linkage-specification names a programming language, the spelling of the programming language's name is implementation-defined. [Note: It is recommended that the spelling be taken from the document defining that language, for example Ada (not ADA) and Fortran or FORTRAN (depending on the vintage). The semantics of a language linkage other than C++ or C are implementation-defined. ]
Change 14 [temp] paragraph 4 as indicated:
A template, a template explicit specialization (14.7.3 [temp.expl.spec]), or a class template partial specialization shall not have C linkage. If the linkage of one of these is something other than C or C++, the behavior is implementation-defined result is conditionally-supported behavior, with implementation-defined semantics.
[Voted into WP at April, 2006 meeting.]
Is reinterpret_cast<T*>(null_pointer_constant) guaranteed to yield the null pointer value of type T*?
I think a committee clarification is needed. Here's why: 5.2.10 [expr.reinterpret.cast] par. 8 talks of "null pointer value", not "null pointer constant", so it would seem that
reinterpret_cast<T*>(0)is a normal int->T* conversion, with an implementation-defined result.
However a little note to 5.2.10 [expr.reinterpret.cast] par. 5 says:
Converting an integral constant expression (5.19) with value zero always yields a null pointer (4.10), but converting other expressions that happen to have value zero need not yield a null pointer.Where is this supported in normative text? It seems that either the footnote or paragraph 8 doesn't reflect the intent.
SUGGESTED RESOLUTION: I think it would be better to drop the footnote #64 (and thus the special case for ICEs), for two reasons:
a) it's not normative anyway; so I doubt anyone is relying on the guarantee it hints at, unless that guarantee is given elsewhere in a normative part
b) users expect reinterpret_casts to be almost always implementation dependent, so this special case is a surprise. After all, if one wants a null pointer there's static_cast. And if one wants reinterpret_cast semantics the special case requires doing some explicit cheat, such as using a non-const variable as intermediary:
int v = 0; reinterpret_cast<T*>(v); // implementation defined reinterpret_cast<T*>(0); // null pointer value of type T* const int w = 0; reinterpret_cast<T*>(w); // null pointer value of type T*
It seems that not only that's providing a duplicate functionality, but also at the cost to hide what seems the more natural one.
Notes from October 2004 meeting:
This footnote was added in 1996, after the invention of reinterpret_cast, so the presumption must be that it was intentional. At this time, however, the CWG feels that there is no reason to require that reinterpret_cast<T*>(0) produce a null pointer value as its result.
Proposed resolution (April, 2005):
Delete the footnote in 5.2.10 [expr.reinterpret.cast] paragraph 5 reading,
Converting an integral constant expression (5.19 [expr.const]) with value zero always yields a null pointer (4.10 [conv.ptr]), but converting other expressions that happen to have value zero need not yield a null pointer.
Add the indicated note to 5.2.10 [expr.reinterpret.cast] paragraph 8:
The null pointer value (4.10 [conv.ptr]) is converted to the null pointer value of the destination type. [Note: A null pointer constant, which has integral type, is not necessarily converted to a null pointer value. —end note]
[Voted into WP at October 2003 meeting.]
An assignment returns an lvalue for its left operand. If that operand refers to a bit field, can the "&" operator be applied to the assignment? Can a reference be bound to it?
struct S { int a:3; int b:3; int c:3; }; void f() { struct S s; const int *p = &(s.b = 0); // (a) const int &r = (s.b = 0); // (b) int &r2 = (s.b = 0); // (c) }
Notes from the 4/02 meeting:
The working group agreed that this should be an error.
Proposed resolution (October 2002):
In 5.3.2 [expr.pre.incr] paragraph 1 (prefix "++" and "--" operators), change
The value is the new value of the operand; it is an lvalue.to
The result is the updated operand; it is an lvalue, and it is a bit-field if the operand is a bit-field.
In 5.16 [expr.cond] paragraph 4 ("?" operator), add the indicated text:
If the second and third operands are lvalues and have the same type, the result is of that type and is an lvalue and it is a bit-field if the second or the third operand is a bit-field, or if both are bit-fields.
In 5.17 [expr.ass] paragraph 1 (assignment operators) add the indicated text (the original text is as updated by issue 221, which is DR but not in TC1):
The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue with the type and value of the left operand after the assignment has taken place. The result in all cases is a bit-field if the left operand is a bit-field.
Note that issue 222 adds (non-conflicting) text at the end of this same paragraph (5.17 [expr.ass] paragraph 1).
In 5.18 [expr.comma] paragraph 1 (comma operator), change:
The type and value of the result are the type and value of the right operand; the result is an lvalue if its right operand is.to
The type and value of the result are the type and value of the right operand; the result is an lvalue if the right operand is an lvalue, and is a bit-field if the right operand is an lvalue and a bit-field.
Relevant related text (no changes required):
5.3.1 [expr.unary.op] paragraph 4:
The operand of & shall not be a bit-field.
8.5.3 [dcl.init.ref] paragraph 5, bullet 1, sub-bullet 1 (regarding binding a reference to an lvalue):
... is an lvalue (but is not a bit-field) ...
[Voted into the WP at the September, 2008 meeting.]
[Picked up by evolution group at October 2002 meeting.]
(See also issue 476.)
The size requested by an array allocation is computed by multiplying the number of elements requested by the size of each element and adding an implementation-specific amount for overhead. It is possible for this calculation to overflow. Is an implementation required to detect this situation and, for instance, throw std::bad_alloc?
On one hand, the maximum allocation size is one of the implementation limits specifically mentioned in Annex B [implimits], and, according to 1.4 [intro.compliance] paragraph 2, an implementation is only required to "accept and correctly execute" programs that do not violate its resource limits.
On the other hand, it is difficult or impossible for user code to detect such overflows in a portable fashion, especially given that the array allocation overhead is not fixed, and it would be a service to the user to handle this situation gracefully.
Rationale (04/01):
Each implementation is required to document the maximum size of an object (Annex B [implimits]). It is not difficult for a program to check array allocations to ensure that they are smaller than this quantity. Implementations can provide a mechanism in which users concerned with this problem can request extra checking before array allocations, just as some implementations provide checking for array index and pointer validity. However, it would not be appropriate to require this overhead for every array allocation in every program.
(See issue 624 for a request to reconsider this resolution.)
Note (March, 2008):
The Evolution Working Group has accepted the intent of this issue and referred it to CWG for action for C++0x (see paper J16/07-0033 = WG21 N2173).
Proposed resolution (September, 2008):
This issue is resolved by the resolution of issue 624, given in paper N2757.
[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 shall have be of integral type, or enumeration 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 a non-negative value. [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 shall have integral or enumeration type (3.9.1 [basic.fundamental]) with a non-negative value be 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 2004 meeting.]
What does this example do?
#include <stdio.h> #include <stdlib.h> struct A { void* operator new(size_t alloc_size, size_t dummy=0) { printf("A::operator new()\n"); return malloc(alloc_size); }; void operator delete(void* p, size_t s) { printf("A::delete %d\n", s); }; A() {printf("A constructing\n"); throw 2;}; }; int main() { try { A* ap = new A; delete ap; } catch(int) {printf("caught\n"); return 1;} }
The fundamental issue here is whether the deletion-on-throw is driven by the syntax of the new (placement or non-placement) or by signature matching. If the former, the operator delete would be called with the second argument equal to the size of the class. If the latter, it would be called with the second argument 0.
Core issue 127 (in TC1) dealt with this topic. It removed some wording in 15.2 [except.ctor] paragraph 2 that implied a syntax-based interpretation, leaving wording in 5.3.4 [expr.new] paragraph 19 that is signature-based. But there is no accompanying rationale to confirm an explicit choice of the signature-based approach.
EDG and g++ get 0 for the second argument, matching the presumed core issue 127 resolution. But maybe this should be revisited.
Notes from October 2003 meeting:
There was widespread agreement that the compiler shouldn't just silently call the delete with either of the possible values. In the end, we decided it's smarter to issue an error on this case and force the programmer to say what he means.
Mike Miller's analysis of the status quo: 3.7.4.2 [basic.stc.dynamic.deallocation] paragraph 2 says that "operator delete(void*, std::size_t)" is a "usual (non-placement) deallocation function" if the class does not declare "operator delete(void*)." 3.7.4.1 [basic.stc.dynamic.allocation] does not use the same terminology for allocation functions, but the most reasonable way to understand the uses of the term "placement allocation function" in the Standard is as an allocation function that has more than one parameter and thus can (but need not) be called using the "new-placement" syntax described in 5.3.4 [expr.new]. (In considering issue 127, the core group discussed and endorsed the position that, "If a placement allocation function has default arguments for all its parameters except the first, it can be called using non-placement syntax.")
5.3.4 [expr.new] paragraph 19 says that any non-placement deallocation function matches a non-placement allocation function, and that a placement deallocation function matches a placement allocation function with the same parameter types after the first -- i.e., a non-placement deallocation function cannot match a placement allocation function. This makes sense, because non-placement ("usual") deallocation functions expect to free memory obtained from the system heap, which might not be the case for storage resulting from calling a placement allocation function.
According to this analysis, the example shows a placement allocation function and a non-placement deallocation function, so the deallocation function should not be invoked at all, and the memory will just leak.
Proposed Resolution (October 2003):
Add the following text at the end of 5.3.4 [expr.new] paragraph 19:
If the lookup finds the two-parameter form of a usual deallocation function (3.7.4.2 [basic.stc.dynamic.deallocation]), and that function, considered as a placement deallocation function, would have been selected as a match for the allocation function, the program is ill-formed. [Example:--- end example]struct S { // Placement allocation function: static void* operator new(std::size_t, std::size_t); // Usual (non-placement) deallocation function: static void operator delete(void*, std::size_t); }; S* p = new (0) S; // ill-formed: non-placement deallocation function matches // placement allocation function
[Voted into the WP at the September, 2008 meeting (resolution in paper N2757).]
Issue 256 was closed without action, principally on the the grounds that an implementation could provide a means (command-line option, #pragma, etc.) for requesting that the allocation size be checked for validity, but that “it would not be appropriate to require this overhead for every array allocation in every program.”
This rationale may be giving too much weight to the overhead such a check would add, especially when compared to the likely cost of actually doing the storage allocation. In particular, the test essentially amounts to something like
if (max_allocation_size / sizeof(T) < num_elements) throw std::bad_alloc();
(noting that max_allocation_size/sizeof(T) is a compile-time constant). It might make more sense to turn the rationale around and require the check, assuming that implementations could provide a mechanism for suppressing it if needed.
Suggested resolution:
In 5.3.4 [expr.new] paragraph 7, add the following words before the example:
If the value of the expression is such that the size of the allocated object would exceed the implementation-defined limit, an exception of type std::bad_alloc is thrown and no storage is obtained.
Note (March, 2008):
The Evolution Working Group has accepted the intent of issue 256 and referred it to CWG for action for C++0x (see paper J16/07-0033 = WG21 N2173).
Proposed resolution (March, 2008):
As suggested.
Notes from the June, 2008 meeting:
The CWG felt that this situation should not be treated like an out-of-memory situation and thus an exception of type std::bad_alloc (or, alternatively, returning a null pointer for a throw() allocator) would not be appropriate.
Proposed resolution (June, 2008):
Change 5.3.4 [expr.new] paragraph 8 as follows:
If the value of the expression in a direct-new-declarator is such that the size of the allocated object would exceed the implementation-defined limit, no storage is obtained and the new-expression terminates by throwing an exception of a type that would match a handler (15.3 [except.handle]) of type std::length_error (19.2.4 [length.error]). Otherwise, if When the value of the that expression in a direct-new-declarator is zero, the allocation function is called to allocate an array with no elements.
[Drafting note: std::length_error is thrown by std::string and std::vector and thus appears to be the right choice for the exception to be thrown here.]
[Voted into the WP at the June, 2008 meeting.]
For delete expressions, 5.3.5 [expr.delete] paragraph 1 says
The operand shall have a pointer type, or a class type having a single conversion function to a pointer type.
However, paragraph 3 of that same section says:
if the static type of the operand is different from its dynamic type, the static type shall be a base class of the operand's dynamic type and the static type shall have a virtual destructor or the behavior is undefined.
Since the operand must be of pointer type, its static type is necessarily the same as its dynamic type. That clause is clearly referring to the object being pointed at, and not to the pointer operand itself.
Correcting the wording gets a little complicated, because dynamic and static types are attributes of expressions, not objects, and there's no sub-expression of a delete-expression which has the relevant types.
Suggested resolution:
then there is a static type and a dynamic type that the hypothetical expression (* const-expression) would have. If that static type is different from that dynamic type, then that static type shall be a base class of that dynamic type, and that static type shall have a virtual destructor, or the behavior is undefined.
There's precedent for such use of hypothetical constructs: see 5.10 [expr.eq] paragraph 2, and 8.1 [dcl.name] paragraph 1.
10.3 [class.virtual] paragraph 3 has a similar problem. It refers to
the type of the pointer or reference denoting the object (the static type).
The type of the pointer is different from the type of the reference, both of which are different from the static type of '*pointer', which is what I think was actually intended. Paragraph 6 contains the exact same wording, in need of the same correction. In this case, perhaps replacing "pointer or reference" with "expression" would be the best fix. In order for this fix to be sufficient, pointer->member must be considered equivalent to (*pointer).member, in which case the "expression" referred to would be (*pointer).
12.5 [class.free] paragraph 4 says thatif a delete-expression is used to deallocate a class object whose static type has...
This should be changed to
if a delete-expression is used to deallocate a class object through a pointer expression whose dereferenced static type would have...
The same problem occurs later, when it says that the
static and dynamic types of the object shall be identical
In this case you could replace "object" with "dereferenced pointer expression".
Footnote 104 says that
5.3.5 [expr.delete] requires that ... the static type of the delete-expression's operand be the same as its dynamic type.
This would need to be changed to
the delete-expression's dereferenced operand
Proposed resolution (December, 2006):
Change 5.3.5 [expr.delete] paragraph 3 as follows:
In the first alternative (delete object), if the static type of the operand object to be deleted is different from its dynamic type, the static type shall be a base class of the operand's dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined. In the second alternative (delete array) if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined.
Change the footnote in 12.5 [class.free] paragraph 4 as follows:
A similar provision is not needed for the array version of operator delete because 5.3.5 [expr.delete] requires that in this situation, the static type of the delete-expression's operand object to be deleted be the same as its dynamic type.
Change the footnote in 12.5 [class.free] paragraph 5 as follows:
If the static type in the delete-expression of the object to be deleted is different from the dynamic type and the destructor is not virtual the size might be incorrect, but that case is already undefined; see 5.3.5 [expr.delete].
[Drafting notes: No change is required for 10.3 [class.virtual] paragraph 7 because “the type of the pointer” includes the pointed-to type. No change is required for 12.5 [class.free] paragraph 4 because “...used to deallocate a class object whose static type...” already refers to the object (and not the operand expression).]
[Voted into WP at April 2003 meeting.]
In a couple of comp.std.c++ threads, people have asked whether the Standard guarantees that the deallocation function will be called in a delete-expression if the destructor throws an exception. Most/all people have expressed the opinion that the deallocation function must be called in this case, although no one has been able to cite wording in the Standard supporting that view.
#include <new.h> #include <stdio.h> #include <stdlib.h> static int flag = 0; inline void operator delete(void* p) throw() { if (flag) printf("in deallocation function\n"); free(p); } struct S { ~S() { throw 0; } }; void f() { try { delete new S; } catch(...) { } } int main() { flag=1; f(); flag=0; return 0; }
Proposed resolution (October 2002):
Add to 5.3.5 [expr.delete] paragraph 7 the highlighted text:
The delete-expression will call a deallocation function (3.7.4.2 [basic.stc.dynamic.deallocation]) [Note: The deallocation function is called regardless of whether the destructor for the object or some element of the array throws an exception. ]
[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.4.2 [basic.stc.dynamic.deallocation]), and if the value of the operand of the delete expression is not the a null pointer constant, 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 the WP at the September, 2008 meeting.]
The specification for the alignof operator (5.3.6 [expr.alignof]) does not forbid function types as operands, although it probably should.
Proposed resolution (March, 2008):
The issue, as described, is incorrect. The requirement in 5.3.6 [expr.alignof] is for “a complete object type,” so a function type is already forbidden. However, the existing text does have a problem in this requirement in that it does not allow a reference type, as anticipated by paragraph 3. Consequently, the proposal is to change 5.3.6 [expr.alignof] paragraph 1 as indicated:
An alignof expression yields the alignment requirement of its operand type. The operand shall be a type-id representing a complete object type or a reference to a complete object type.
[Voted into WP at April, 2007 meeting.]
5.4 [expr.cast] paragraph 6 says,
The operand of a cast using the cast notation can be an rvalue of type “pointer to incomplete class type”. The destination type of a cast using the cast notation can be “pointer to incomplete class type”. In such cases, even if there is a inheritance relationship between the source and destination classes, whether the static_cast or reinterpret_cast interpretation is used is unspecified.
The wording seems to allow the following:
casting from void pointer to incomplete type
struct A; struct B; void *v; A *a = (A*)v; // allowed to choose reinterpret_cast
variant application of static or reinterpret casting
B *b = (B*)a; // compiler can choose static_cast here A *aa = (A*)b; // compiler can choose reinterpret_cast here assert(aa == a); // might not hold
ability to somehow choose static_cast
It's not entirely clear how a compiler can
choose static_cast as 5.4 [expr.cast] paragraph 6
seems to allow. I believe the intent of 5.4 [expr.cast]
paragraph 6 is to force the use of reinterpret_cast when
either are incomplete class types and static_cast iff the
compiler knows both types and there is a non-ambiguous
hierarchy-traversal between that cast (or maybe not, core issue 242 talks about this). I cannot see any
other interpretation because it isn't intuitive, every compiler I've
tried agrees with me, and neither standard pointer conversions
(4.10 [conv.ptr] paragraph 3) nor static_cast
(5.2.9 [expr.static.cast] paragraph 5) talk about incomplete
class types. If the committee agrees with me, I would like to see
Proposed resolution (April, 2006):
Change 5.4 [expr.cast] paragraph 6 as indicated:
The operand of a cast using the cast notation can be an rvalue of type “pointer to incomplete class type.” The destination type of a cast using the cast notation can be “pointer to incomplete class type.” In such cases, even if there is a inheritance relationship between the source and destination classes, whether the static_cast or reinterpret_cast interpretation is used is unspecified. If both the operand and destination types are class types and one or both are incomplete, it is unspecified whether the static_cast or the reinterpret_cast interpretation is used, even if there is an inheritance relationship between the two classes. [Note: For example, if the classes were defined later in the translation unit, a multi-pass compiler would be permitted to interpret a cast between pointers to the classes as if the class types were complete at that point. —end note]
[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 the WP at the September, 2008 meeting as part of paper N2757.]
The current Standard leaves it implementation-defined whether integer division rounds the result toward 0 or toward negative infinity and thus whether the result of % may be negative. C99, apparently reflecting (nearly?) unanimous hardware practice, has adopted the rule that integer division rounds toward 0, thus requiring that the result of -1 % 5 be -1. Should the C++ Standard follow suit?
On a related note, does INT_MIN % -1 invoke undefined behavior? The % operator is defined in terms of the / operator, and INT_MIN / -1 overflows, which by 5 [expr] paragraph 5 causes undefined behavior; however, that is not the “result” of the % operation, so it's not clear. The wording of 5.6 [expr.mul] paragraph 4 appears to allow % to cause undefined behavior only when the second operand is 0.
Proposed resolution (August, 2008):
Change 5.6 [expr.mul] paragraph 4 as follows:
The binary / operator yields the quotient, and the binary % operator yields the remainder from the division of the first expression by the second. If the second operand of / or % is zero the behavior is undefined; otherwise (a/b)*b + a%b is equal to a. If both operands are nonnegative then the remainder is nonnegative; if not, the sign of the remainder is implementation-defined. [Footnote: According to work underway toward the revision of ISO C, the preferred algorithm for integer division follows the rules defined in the ISO Fortran standard, ISO/IEC 1539:1991, in which the quotient is always rounded toward zero. —end footnote]. For integral operands, the / operator yields the algebraic quotient with any fractional part discarded; [Footnote: This is often called “truncation towards zero.” —end footnote] if the quotient a/b is representable in the type of the result, (a/b)*b + a%b is equal to a.
[Drafting note: see C99 6.5.5 paragraph 6.]
[Voted into the WP at the June, 2008 meeting.]
The actual semantics of arithmetic comparison — e.g., whether 1 < 2 yields true or false — appear not to be specified anywhere in the Standard. The C Standard has a general statement that
Each of the operators < (less than), > (greater than), <= (less than or equal to), and >= (greater than or equal to) shall yield 1 if the specified relation is true and 0 if it is false.
There is no corresponding statement in the C++ Standard.
Proposed resolution (February, 2008):
Append the following paragraph to the end of 5.9 [expr.rel]:
If both operands (after conversions) are of arithmetic type, each of the operators shall yield true if the specified relation is true and false if it is false.
Append the following paragraph to the end of 5.10 [expr.eq]:
Each of the operators shall yield true if the specified relation is true and false if it is false.
[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 T2 that 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 the WP at the June, 2008 meeting as paper N2634.]
I've seen some pieces of code recently that put complex expressions involving overload resolution inside sizeof operations in constant expressions in templates.
5.19 [expr.const] paragraph 1 implies that some kinds of nonconstant expressions are allowed inside a sizeof in a constant expression, but it's not clear that this was intended to extend all the way to things like overload resolution. Allowing such things has some hidden costs. For example, name mangling has to be able to represent all operators, including calls, and not just the operators that can appear in constant expressions.
template <int I> struct A {}; char xxx(int); char xxx(float); template <class T> A<sizeof(xxx((T)0))> f(T){} int main() { f(1); }
If complex expressions are indeed allowed, it should be because of an explicit committee decision rather than because of some looseness in this section of the standard.
Notes from the 4/02 meeting:
Any argument for restricting such expressions must involve a cost/benefit ratio: a restriction would be palatable only if it causes minimum hardship for users and allows a substantial reduction in implementation cost. If we propose a restriction, it must be one that library writers can live with.
Lots of these cases fail with current compilers, so there can't be a lot of existing code using them. We plan to find out what cases there are in libraries like Loki and Boost.
We noted that in many cases one can move the code into a class to get the same result. The implementation problem comes up when the expression-in-sizeof is in a template deduction context or part of a template signature. The problem cases are ones where an error causes deduction to fail, as opposed to contexts where an error causes a diagnostic. The latter contexts are easier to handle; however, there are situations where "fail deduction" may be the desired behavior.
Notes from the April 2003 meeting:
Here is a better example:
extern "C" int printf(const char *, ...); char f(int); int f(...); // Approach 1 -- overload resolution in template class // No problem template <class T> struct conv_int { static const bool value = (sizeof(f(T())) == 1); }; // Approach 2 -- overload resolution in type deduction // Difficult template <int I> struct A { static const int value = I; }; template <class T> bool conv_int2(A<sizeof(f(T()))> p) { return p.value == 1; } template<typename T> A<sizeof(f(T()))> make_A() { return A<sizeof(f(T()))>(); } int main() { printf("short: %d\n", conv_int<short>::value); printf("int *: %d\n", conv_int<int *>::value); printf("short: %d\n", conv_int2<short>(make_A<short>())); printf("int *: %d\n", conv_int2<int *>(make_A<int*>())); }
The core working group liked the idea of a restriction that says that expressions inside sizeof in template signature contexts must be otherwise valid as nontype template argument expressions (i.e., integer operations only, limited casts). This of course is subject to whether users can live with that restriction. This topic was brought up in full committee, but there was limited feedback from other groups.
It was also noted that if typeof (whatever it is called) is added, there may be a similar issue there.
Note (March, 2005):
Dave Abrahams (quoting a Usenet posting by Vladimir Marko): The decltype and auto proposal (revision 3: N1607) presents
template <class T,class U> decltype((*(T*)0)+(*(U*)0)) add(const T& t,const U& u);
as a valid declaration (if the proposal is accepted). If [the restrictions in the April, 2003 note] really applied to decltype, the declaration above would be invalid. AFAICT every non-trivial use of decltype in a template function declaration would be invalid. And for me this would render my favorite proposal useless.
I would propose to allow any kind of expression inside sizeof (and decltype) and explicitly add sizeof (and decltype) expressions involving template-parameters to non-deduced contexts (add a bullet to 14.8.2.4 [temp.deduct.partial] paragraph 4).
Jaakko Jarvi: Just reinforcing that this is important and hope for insights. The topic is discussed a bit on page 10 of the latest revision of the proposal (N1705). Here's a quote from the proposal:
However, it is crucial that no restrictions are placed on what kinds of expressions are allowed inside decltype, and therefore also inside sizeof. We suggest that issue 339 is resolved to require the compiler to fail deduction (apply the SFINAE principle), and not produce an error, for as large set of invalid expressions in operands of sizeof or decltype as is possible to comfortably implement. We wish that implementors aid in classifying the kinds of expressions that should produce errors, and the kinds that should lead to failure of deduction.
Notes from the April, 2007 meeting:
The CWG is pursuing a compromise proposal, to which the EWG has tentatively agreed, which would allow arbitrary expressions in the return types of function templates but which would restrict the expressions that participate in the function signature (and thus in overload resolution) to those that can be used as non-type template arguments. During deduction and overload resolution, these complex return types would be ignored; that is, there would be no substitution of the deduced template arguments into the return type at this point. If such a function were selected by overload resolution, however, a substitution failure in the return type would produce a diagnostic rather than a deduction failure.
This approach works when doing overload resolution in the context of a function call, but additional tricks (still being defined) are needed in other contexts such as friend function declaration matching and taking the address of a function, in which the return type does play a part.
Notes from the July, 2007 meeting:
The problem is whether arbitrary expressions (for example, ones that include overload resolution) are allowed in template deduction contexts, and, if so, which expression errors are SFINAE failures and which are hard errors.
This issue deals with arbitrary expressions inside sizeof in deduction contexts. That's a fringe case right now (most compilers don't accept them). decltype makes the problem worse, because the standard use case is one that involves overload resolution. Generalized constant expressions make it worse yet, because they allow overload resolution and class types to show up in any constant expression in a deduction context.
Why is this an issue? Why don't we just say everything is allowed and be done with it?
At the April, 2007 meeting, we were headed toward a solution that imposed a restriction on expressions in deduction contexts, but such a restriction seems to really hamper uses of constexpr functions. So we're now proposing that fully general expressions be allowed, and that most errors in such expressions be treated as SFINAE failures rather than errors.
One issue with writing Standard wording for that is how to define “most.” There's a continuum of errors, some errors being clearly SFINAE failures, and some clearly “real” errors, with lots of unclear cases in between. We decided it's easier to write the definition by listing the errors that are not treated as SFINAE failures, and the list we came up with is as follows:
Everything else produces a SFINAE failure rather than a hard error.
There was broad consensus that this felt like a good solution, but that feeling was mixed with trepidation on several fronts:
We will be producing wording for the Working Draft for the October, 2007 meeting.
(See also issue 657.)
[Voted into WP at October 2003 meeting.]
According to 16.1 [cpp.cond] paragraph 1, the if-group
#if "Hello, world"
is well-formed, since it is an integral constant expression. Since that may not be obvious, here is why:
5.19 [expr.const] paragraph 1 says that an integral constant expression may involve literals (2.14 [lex.literal]); "Hello, world" is a literal. It restricts operators to not use certain type conversions; this expression does not use type conversions. It further disallows functions, class objects, pointers, ... - this expression is none of those, since it is an array.
However, 16.1 [cpp.cond] paragraph 6 does not explain what to do with this if-group, since the expression evaluates neither to false(zero) nor true(non-zero).
Proposed resolution (October 2002):
Change the beginning of the second sentence of 5.19 [expr.const] paragraph 1 which currently reads
An integral constant-expression can involve only literals (2.14 [lex.literal]), ...to say
An integral constant-expression can involve only literals of arithmetic types (2.14 [lex.literal], 3.9.1 [basic.fundamental]), ...
[Voted into WP at the October, 2006 meeting.]
The following translation unit appears to be well-formed.
int x[true?throw 4:5];
According to 5.19 [expr.const], this appears to be an integral constant expression: it is a conditional expression, involves only literals, and no assignment, increment, decrement, function-call, or comma operators. However, if this is well-formed, the standard gives no meaning to this declaration, since the array bound (8.3.4 [dcl.array] paragraph 1) cannot be computed.
I believe the defect is that throw expressions should also be banned from constant expressions.
Notes from October 2002 meeting:
We should also check on new and delete.
Notes from the April, 2005 meeting:
Although it could be argued that all three of these operators potentially involve function calls — throw to std::terminate, new and delete to the corresponding allocation and deallocation functions — and thus would already be excluded from constant expressions, this reasoning was considered to be too subtle to allow closing the issue with no change. A modification that explicitly clarifies the status of these operators will be drafted.
Proposed resolution (October, 2005):
Change the last sentence of 5.19 [expr.const] as indicated:
In particular, except in sizeof expressions, functions, class objects, pointers, or references shall not be used, and assignment, increment, decrement, function-call function call (including new-expressions and delete-expressions), or comma operators, or throw-expressions shall not be used.
Note: this sentence is also changed by the resolution of issue 530.
[Voted into WP at April 2005 meeting.]
I'm looking at 5.19 [expr.const]. I see:
An integral constant-expression can involve only ... const variables or static data members of integral or enumeration types initialized with constant expressions ...
Shouldn't that be "const non-volatile"?
It seems weird to say that:
const volatile int i = 3; int j[i];is valid.
Steve Adamczyk: See issue 76, which made the similar change to 7.1.6.1 [dcl.type.cv] paragraph 2, and probably should have changed this one as well.
Proposed resolution (October, 2004):
Change the first sentence in the second part of 5.19 [expr.const] paragraph 1 as follows:
An integral constant-expression can involve only literals of arithmetic types (2.14 [lex.literal], 3.9.1 [basic.fundamental]), enumerators, non-volatile const variables or static data members of integral or enumeration types initialized with constant expressions (8.5 [dcl.init]), non-type template parameters of integral or enumeration types, and sizeof expressions.
[Voted into the WP at the April, 2007 meeting as part of paper J16/07-0095 = WG21 N2235.]
Consider:
template <int* p> struct S { static const int I = 3; }; int i; int a[S<&i>::I];
Clearly this should be valid, but a pedantic reading of 5.19 [expr.const] would suggest that this is invalid because “&i” is not permitted in integral constant expressions.
Proposed resolution (October, 2005):
Change the last sentence of 5.19 [expr.const] paragraph 1 as indicated:
In particular, except in non-type template-arguments or sizeof expressions, functions, class objects, pointers, or references shall not be used, and assignment, increment, decrement, function-call, or comma operators shall not be used.
(Note: the same text is changed by the resolution of issue 367.)
Notes from April, 2006 meeting:
The proposed resolution could potentially be read as saying that the prohibited operations and operators would be permitted in integral constant expressions that are non-type template-arguments. John Spicer is investigating an alternate approach, to say that expressions in non-type template arguments are not part of the expression in which the template-id appears (in contrast to the operand of sizeof, which is part of the containing expression).
Additional note (May, 2008):
This issue is resolved by the rewrite of
[Voted into the WP at the September, 2008 meeting (resolution in paper N2757).]
The expressions that are excluded from being constant expressions in 5.19 [expr.const] paragraph 2 does not address an example like the following:
void f() {
int a;
constexpr int* p = &a; // should be ill-formed, currently isn't
}
Suggested resolution:
Add the following bullet to the list in 5.19 [expr.const] paragraph 2:
an id-expression that refers to a variable with automatic storage duration unless a permitted lvalue-to-rvalue conversion is applied (see above)
Proposed resolution (June, 2008):
Change 3.6.2 [basic.start.init] paragraph 1 as follows:
Objects with static storage duration (3.7.1 [basic.stc.static]) or thread storage duration (3.7.2) shall be zero-initialized (8.5 [dcl.init]) before any other initialization takes place. A reference with static or thread storage duration and an object of trivial or literal type with static or thread storage duration can be initialized with a constant expression (5.19 [expr.const]); this is called constant initialization. Constant initialization is performed:Together, zero-initialization and constant initialization...
if an object of trivial or literal type with static or thread storage duration is initialized with a constant expression (5.19 [expr.const]), or
if a reference with static or thread storage duration is initialized with a constant expression that is not an lvalue designating an object with thread or automatic storage duration.
Add the following in 5.19 [expr.const] paragraph 2:
an lvalue-to-rvalue conversion (4.1) unless it is applied to...
an array-to-pointer conversion (4.2 [conv.array]) that is applied to an lvalue that designates an object with thread or automatic storage duration
a unary operator & (5.3.1 [expr.unary.op]) that is applied to an lvalue that designates an object with thread or automatic storage duration
an id-expression that refers to a variable or data member of reference type;
...
(Note: the change to 3.6.2 [basic.start.init] paragraph 1 needs to be reconciled with the conflicting change in issue 688.)
[Voted into the WP at the June, 2008 meeting.]
According to 6.6 [stmt.jump] paragraph 2,
On exit from a scope (however accomplished), destructors (12.4 [class.dtor]) are called for all constructed objects with automatic storage duration (3.7.3 [basic.stc.auto]) (named objects or temporaries) that are declared in that scope, in the reverse order of their declaration.
This wording is problematic for temporaries and for parameters. First, temporaries are not "declared," so this requirement does not apply to them, in spite of the assertion in the quoted text that it does.
Second, although the parameters of a function are declared in the called function, they are constructed and destroyed in the calling context, and the order of evaluation of the arguments is unspecified (cf 5.2.2 [expr.call] paragraphs 4 and 8). The order of destruction of the parameters might, therefore, be different from the reverse order of their declaration.
Notes from 04/01 meeting:
Any resolution of this issue should be careful not to introduce requirements that are redundant or in conflict with those of other parts of the IS. This is especially true in light of the pending issues with respect to the destruction of temporaries (see issues 86, 124, 199, and 201). If possible, the wording of a resolution should simply reference the relevant sections.
It was also noted that the temporary for a return value is also destroyed "out of order."
Note that issue 378 picks a nit with the wording of this same paragraph.
Proposed Resolution (November, 2006):
Change 6.6 [stmt.jump] paragraph 2 as follows:
On exit from a scope (however accomplished), destructors (12.4 [class.dtor]) are called for all constructed objects with automatic storage duration (3.7.3 [basic.stc.auto]) (named objects or temporaries) that are declared in that scope, in the reverse order of their declaration. variables with automatic storage duration (3.7.3 [basic.stc.auto]) that have been constructed in that scope are destroyed in the reverse order of their construction. [Note: For temporaries, see 12.2 [class.temporary]. —end note] Transfer out of a loop...
Paragraph 6.6 [stmt.jump] paragraph 2 of the standard says:
On exit from a scope (however accomplished), destructors (12.4 [class.dtor]) are called for all constructed objects with automatic storage duration (3.7.3 [basic.stc.auto]) (named objects or temporaries) that are declared in that scope.
It refers to objects "that are declared" but the text in parenthesis also mentions temporaries, which cannot be declared. I think that text should be removed.
This is related to issue 276.
Proposed Resolution (November, 2006):
This issue is resolved by the resolution of issue 276.
[Moved to DR at October 2002 meeting.]
There is currently no restriction on the use of the inline specifier in friend declarations. That would mean that the following usage is permitted:
struct A { void f(); }; struct B { friend inline void A::f(); }; void A::f(){}
I believe this should be disallowed because a friend declaration in one class should not be able to change attributes of a member function of another class.
More generally, I think that the inline attribute should only be permitted in friend declarations that are definitions.
Notes from the 04/01 meeting:
The consensus agreed with the suggested resolution. This outcome would be similar to the resolution of issue 136.
Proposed resolution (10/01):
Add to the end of 7.1.2 [dcl.fct.spec] paragraph 3:
If the inline specifier is used in a friend declaration, that declaration shall be a definition or the function shall have previously been declared inline.
[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 March 2004 meeting.]
BTW, I noticed that the following note in 7.1.1 [dcl.stc] paragraph 2 doesn't seem to have made it onto the issues list or into the TR:
[Note: hence, the auto specifier is almost always redundant and not often used; one use of auto is to distinguish a declaration-statement from an expression-statement (stmt.ambig) explicitly. --- end note]
I thought that this was well known to be incorrect, because using auto does not disambiguate this. Writing:
auto int f();is still a declaration of a function f, just now with an error since the function's return type may not use an auto storage class specifier. I suppose an error is an improvement over a silent ambiguity going the wrong way, but it's still not a solution for the user who wants to express the other in a compilable way.
Proposed resolution: Replace that note with the following note:
[Note: hence, the auto specifier is always redundant and not often used. --- end note]
John Spicer: I support the proposed change, but I think the disambiguation case is not the one that you describe. An example of the supposed disambiguation is:
int i; int j; int main() { int(i); // declares i, not reference to ::i auto int(j); // declares j, not reference to ::j }
cfront would take "int(i)" as a cast of ::i, so the auto would force what it would otherwise treat as a statement to be considered a declaration (cfront 3.0 warned that this would change in the future).
In a conforming compiler the auto is always redundant (as you say) because anything that could be considered a valid declaration should be treated as one.
Proposed resolution (April 2003):
Replace 7.1.1 [dcl.stc] paragraph 2
[Note: hence, the auto specifier is almost always redundant and not often used; one use of auto is to distinguish a declaration-statement from an expression-statement (6.8 [stmt.ambig]) explicitly. --- end note]with
[Note: hence, the auto specifier is always redundant and not often used. One use of auto is to distinguish a declaration-statement from an expression-statement explicitly rather than relying on the disambiguation rules (6.8 [stmt.ambig]), which may aid readers. --- end note]
[Voted into WP at April, 2007 meeting.]
Are string literals from default arguments used in extern inlines supposed to have the same addresses across all translation units?
void f(const char* = "s") inline g() { f(); }
Must the "s" strings be the same in all copies of the inline function?
Steve Adamczyk: The totality of the standard's wisdom on this topic is (7.1.2 [dcl.fct.spec] paragraph 4):
A string literal in an extern inline function is the same object in different translation units.
I'd hazard a guess that a literal in a default argument expression is not "in" the extern inline function (it doesn't appear in the tokens of the function), and therefore it need not be the same in different translation units.
I don't know that users would expect such strings to have the same address, and an equally valid (and incompatible) expectation would be that the same string literal would be used for every expansion of a given default argument in a single translation unit.
Notes from April 2003 meeting:
The core working group feels that the address of a string literal should be guaranteed to be the same only if it actually appears textually within the body of the inline function. So a string in a default argument expression in a block extern declaration inside the body of a function would be the same in all instances of the function. On the other hand, a string in a default argument expression in the header of the function (i.e., outside of the body) would not be the same.
Proposed resolution (April 2003):
Change the last sentence and add the note to the end of 7.1.2 [dcl.fct.spec] paragraph 4:
A string literal in the body of an extern inline function is the same object in different translation units. [Note: A string literal that is encountered only in the context of a function call (in the default argument expression of the called function), is not “in” the extern inline function.]
Notes from October 2003 meeting:
We discussed ctor-initializer lists and decided that they are also part of the body. We've asked Clark Nelson to work on syntax changes to give us a syntax term for the body of a function so we can refer to it here. See also issue 452, which could use this term.
(October, 2005: moved to “review” status in concert with issue 452. With that resolution, the wording above needs no further changes.)
Proposed resolution (April, 2006):
Change the last sentence and add the note to the end of 7.1.2 [dcl.fct.spec] paragraph 4:
A string literal in the body of an extern inline function is the same object in different translation units. [Note: A string literal appearing in a default argument expression is not considered to be “in the body” of an inline function merely by virtue of the expression’s use in a function call from that inline function. —end note]
[Voted into WP at the October, 2006 meeting.]
I couldn't find wording that makes it invalid to say friend virtual... The closest seems to be 7.1.2 [dcl.fct.spec] paragraph 5, which says:
The virtual specifier shall only be used in declarations of nonstatic class member functions that appear within a member-specification of a class definition; see 10.3 [class.virtual].
I don't think that excludes a friend declaration (which is a valid member-specification by 9.2 [class.mem]).
John Spicer: I agree that virtual should not be allowed on friend declarations. I think the wording in 7.1.2 [dcl.fct.spec] is intended to be the declaration of a function within its class, although I think the wording should be improved to make it clearer.
Proposed resolution (October, 2005):
Change 7.1.2 [dcl.fct.spec] paragraphs 5-6 as indicated:
The virtual specifier shall only be used only in declarations the initial declaration of a non-static class member functions that appear within a member-specification of a class definition function; see
10.3 [class.virtual] .The explicit specifier shall be used only in declarations the declaration of constructors a constructor within a its class definition; see 12.3.1 [class.conv.ctor].
[Voted into WP at March 2004 meeting.]
I wonder if perhaps the core issue 56 change in 7.1.3 [dcl.typedef] paragraph 2 wasn't quite careful enough. The intent was to remove the allowance for:
struct S { typedef int I; typedef int I; };
but I think it also disallows the following:
class B { typedef struct A {} A; void f(struct B::A*p); };
See also issue 407.
Proposed resolution (October 2003):
At the end of 7.1.3 [dcl.typedef] paragraph 2, add the following:
In a given class scope, a typedef specifier can be used to redefine any class-name declared in that scope that is not also a typedef-name to refer to the type to which it already refers. [Example:struct S { typedef struct A {} A; // OK typedef struct B B; // OK typedef A A; // error };]
[Voted into the WP at the September, 2008 meeting.]
According to 7.1.5 [dcl.constexpr] paragraph 5,
If the instantiated template specialization of a constexpr function template would fail to satisfy the requirements for a constexpr function, the constexpr specifier is ignored and the specialization is not a constexpr function.
One would expect to see a similar provision for an instantiated constructor template (because the requirements for a constexpr function [paragraph 3] are different from the requirements for a constexpr constructor [paragraph 4]), but there is none; constexpr constructor templates are not mentioned.
Suggested resolution:
Change the wording of 7.1.5 [dcl.constexpr] paragraph 5 as indicated:
If the instantiated template specialization of a constexpr function template would fail to satisfy the requirements for a constexpr function or constexpr constructor, as appropriate to the function template, the constexpr specifier is ignored and the specialization is not a constexpr function or constexpr constructor.
Proposed resolution (June, 2008):
[Drafting note: This resolution goes beyond the problem described in the issue discussion, which is one aspect of the general failure of the existing wording to deal consistently with the distinctions between constexpr functions and constexpr constructors. The wording below attempts to rectify that problem systematically.]
Change 7.1.5 [dcl.constexpr] paragraph 2 as follows:
A constexpr specifier used in a function declaration the declaration of a function that is not a constructor declares that function to be a constexpr function. Similarly, a constexpr specifier used in a constructor declaration declares that constructor to be a constexpr constructor. Constexpr functions and constexpr constructors are implicitly inline (7.1.2 [dcl.fct.spec]). A constexpr function shall not be virtual (10.3).
Change 7.1.5 [dcl.constexpr] paragraph 3 as follows:
The definition of a constexpr function shall satisfy the following constraints:
it shall not be virtual (10.3 [class.virtual])
its return type shall be a literal type
each of its parameter types shall be a literal type
its function-body shall be a compound-statement of the form
{ return expression ; }
where expression is a potential constant expression (5.19 [expr.const])
every implicit conversion used in converting expression to the function return type (8.5 [dcl.init]) shall be one of those allowed in a constant expression (5.19 [expr.const]).
[Example:...
Change 7.1.5 [dcl.constexpr] paragraph 4 as follows:
The definition of a constexpr constructor shall satisfy the following constraints:
each of its parameter types shall be a literal type
its function-body shall not be a function-try-block
the compound-statement of its function-body shall be empty
every non-static data member and base class sub-object shall be initialized (12.6.2 [class.base.init])
every constructor involved in initializing non-static data members and base class sub-objects invoked by a mem-initializer shall be a constexpr constructor invoked with potential constant expression arguments, if any.
every constructor argument and full-expression in a mem-initializer shall be a potential constant expression
every implicit conversion used in converting a constructor argument to the corresponding parameter type and converting a full-expression to the corresponding member type shall be one of those allowed in a constant expression.
A trivial copy constructor is also a constexpr constructor. [Example: ...
Change 7.1.5 [dcl.constexpr] paragraph 5 as follows:
If the instantiated template specialization of a constexpr function template would fail to satisfy the requirements for a constexpr function or constexpr constructor, the constexpr specifier is ignored and the specialization is not a constexpr function.
Change 7.1.5 [dcl.constexpr] paragraph 6 as follows:
A constexpr specifier used in for a non-static member function definition that is not a constructor declares that member function to be const (9.3.1 [class.mfct.non-static]). [Note: ...
[Voted into the WP at the September, 2008 meeting.]
The current wording of 7.1.5 [dcl.constexpr] paragraph 7 seems not quite correct. It reads,
A constexpr specifier used in an object declaration declares the object as const. Such an object shall be initialized, and every expression that appears in its initializer (8.5 [dcl.init]) shall be a constant expression.
The phrase “every expression” is intended to cover multiple arguments to a constexpr constructor and multiple expressions in an aggregate initializer. However, it could be read (incorrectly) as saying that non-constant expressions cannot appear as subexpressions in such initializers, even in places where they do not render the full-expression non-constant (i.e., as unevaluated operands and in the unselected branches of &&, ||, and ?:). Perhaps this problem could be remedied by replacing “every expression” with “every full-expression?”
Proposed resolution (June, 2008):
Change 7.1.5 [dcl.constexpr] paragraph 7 as follows:
A constexpr specifier used in an object declaration declares the object as const. Such an object shall be initialized, and every expression that appears in its initializer (8.5) initialized. If it is initialized by a constructor call, the constructor shall be a constexpr constructor and every argument to the constructor shall be a constant expression. Otherwise, every full-expression that appears in its initializer shall be a constant expression. Every implicit conversion used...
[Voted into WP at April 2003 meeting.]
Although 14.1 [temp.param] paragraph 3 contains an assertion that
A type-parameter defines its identifier to be a type-name (if declared with class or typename)
the grammar in 7.1.6.2 [dcl.type.simple] paragraph 1 says that a type-name is either a class-name, an enum-name, or a typedef-name. The identifier in a template type-parameter is none of those. One possibility might be to equate the identifier with a typedef-name instead of directly with a type-name, which would have the advantage of not requiring parallel treatment of the two in situations where they are treated the same (e.g., in elaborated-type-specifiers, see issue 245). See also issue 215.
Proposed resolution (Clark Nelson, March 2002):
In 14.1 [temp.param] paragraph 3, change "A type-parameter defines its identifier to be a type-name" to "A type-parameter defines its identifier to be a typedef-name"
In 7.1.6.3 [dcl.type.elab] paragraph 2, change "If the identifier resolves to a typedef-name or a template type-parameter" to "If the identifier resolves to a typedef-name".
This has been consolidated with the edits for some other issues. See N1376=02-0034.
[Voted into WP at the October, 2006 meeting.]
7.1.6.2 [dcl.type.simple] paragraph 3 reads,
It is implementation-defined whether bit-fields and objects of char type are represented as signed or unsigned quantities. The signed specifier forces char objects and bit-fields to be signed; it is redundant with other integral types.
The last sentence in that quote is misleading w.r.t. bit-fields. The first sentence in that quote is correct but incomplete.
Proposed fix: change the two sentences to read:
It is implementation-defined whether objects of char type are represented as signed or unsigned quantities. The signed specifier forces char objects signed; it is redundant with other integral types except when declaring bit-fields (9.6 [class.bit]).
Proposed resolution (October, 2005):
Change 7.1.6.2 [dcl.type.simple] paragraph 3 as indicated:
When multiple simple-type-specifiers are allowed, they can be freely intermixed with other decl-specifiers in any order. [Note: It is implementation-defined whether bit-fields and objects of char type and certain bit-fields (9.6 [class.bit]) are represented as signed or unsigned quantities. The signed specifier forces bit-fields and char objects and bit-fields to be signed; it is redundant with other integral types in other contexts. —end note]
[Voted into the WP at the September, 2008 meeting.]
The second bullet of 7.1.6.2 [dcl.type.simple] paragraph 4 reads,
- otherwise, if e is a function call (5.2.2 [expr.call]) or an invocation of an overloaded operator (parentheses around e are ignored), decltype(e) is the return type of that function;
The reference to “that function” is imprecise; it is not the actual function called at runtime but the statically chosen function (ignoring covariant return types in virtual functions).
Also, the examples in this paragraph have errors:
The declaration of struct A should end with a semicolon.
The lines of the form decltype(...); are ill-formed; they need a declarator.
Proposed Resolution (October, 2007):
Change 7.1.6.2 [dcl.type.simple] paragraph 4 as follows:
The type denoted by decltype(e) is defined as follows:
if e is an id-expression or a class member access (5.2.5 [expr.ref]), decltype(e) is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;
otherwise, if e is a function call (5.2.2 [expr.call]) or an invocation of an overloaded operator (parentheses around e are ignored), decltype(e) is the return type of that the statically chosen function;
otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;
otherwise, decltype(e) is the type of e.
The operand of the decltype specifier is an unevaluated operand (clause 5 [expr]).
[Example:
const int&& foo(); int i; struct A { double x; }; const A* a = new A(); decltype(foo()) x1; // type is const int&& decltype(i) x2; // type is int decltype(a->x) x3; // type is double decltype((a->x)) x4; // type is const double&—end example]
[Voted into the WP at the February, 2008 meeting as paper J16/08-0056 = WG21 N2546.]
We've found an interesting parsing ambiguity with the new meaning of auto. Consider:
typedef int T; void f() { auto T = 42; // Valid or not? }
The question here is whether T should be a type specifier or a storage class? 7.1.6.4 [dcl.spec.auto] paragraph 1 says,
The auto type-specifier has two meanings depending on the context of its use. In a decl-specifier-seq that contains at least one type-specifier (in addition to auto) that is not a cv-qualifier, the auto type-specifier specifies that the object named in the declaration has automatic storage duration.
In this case, T is a type-specifier, so the declaration is ill-formed: there is no declarator-id. Many, however, would like to see auto work “just like int,” i.e., forcing T to be redeclared in the inner scope. Concerns cited included hijacking of the name in templates and inline function bodies over the course of time if a program revision introduces a type with that name in the surrounding context. Although it was pointed out that enclosing the name in parentheses in the inner declaration would prevent any such problems, this was viewed as unacceptably ugly.
Notes from the April, 2007 meeting:
The CWG wanted to avoid a rule like, “if auto can be a type-specifier, it is” (similar to the existing “if it can be a declaration, it is” rule) because of the lookahead and backtracking difficulties such an approach would pose for certain kinds of parsing techniques. It was noted that the difficult lookahead cases all involve parentheses, which would not be a problem if only the “=” form of initializer were permitted in auto declarations; only very limited lookahead is required in that case. It was also pointed out that the “if it can be a type-specifier, it is” approach results in a quiet change of meaning for cases like
typedef int T; int n = 3; void f() { auto T(n); }
This currently declares n to be an int variable in the inner scope but would, under the full lookahead approach, declare T to be a variable, quitely changing uses of n inside f() to refer to the outer variable.
The consensus of the CWG was to pursue the change to require the “=” form of initializer for auto.
Notes from the July, 2007 meeting:
See paper J16/07-0197 = WG21 N2337. There was no consensus among the CWG for either of the approaches recommended in the paper; additional input and direction is required.
[Moved to DR at October 2002 meeting.]
According to 7.2 [dcl.enum] paragraph 5, the underlying type of an enum is an unspecified integral type, which could potentially be unsigned int. The promotion rules in 4.5 [conv.prom] paragraph 2 say that such an enumeration value used in an expression will be promoted to unsigned int. This means that a conforming implementation could give the value false for the following code:
enum { zero }; -1 < zero; // might be falseThis is counterintuitive. Perhaps the description of the underlying type of an enumeration should say that an unsigned underlying type can be used only if the values of the enumerators cannot be represented in the corresponding signed type. This approach would be consistent with the treatment of integral promotion of bitfields (4.5 [conv.prom] paragraph 3).
On a related note, 7.2 [dcl.enum] paragraph 5 says,
the underlying type shall not be larger than int unless the value of an enumerator cannot fit in an int or unsigned int.
This specification does not allow for an enumeration like
enum { a = -1, b = UINT_MAX };
Since each enumerator can fit in an int or unsigned int, the underlying type is required to be no larger than int, even though there is no such type that can represent all the enumerators.
Proposed resolution (04/01; obsolete, see below):
Change 7.2 [dcl.enum] paragraph 5 as follows:
It is implementation-defined which integral type is used as the underlying type for an enumeration except that the underlying type shall not be larger than int unless the value of an enumerator cannot fit in an int or unsigned int neither int nor unsigned int can represent all the enumerator values. Furthermore, the underlying type shall not be an unsigned type if the corresponding signed type can represent all the enumerator values.
See also issue 58.
Notes from 04/01 meeting:
It was noted that 4.5 [conv.prom] promotes unsigned types smaller than int to (signed) int. The signedness chosen by an implementation for small underlying types is therefore unobservable, so the last sentence of the proposed resolution above should apply only to int and larger types. This observation also prompted discussion of an alternative approach to resolving the issue, in which the bmin and bmax of the enumeration would determine the promoted type rather than the underlying type.
Proposed resolution (10/01):
Change 4.5 [conv.prom] paragraph 2 from
An rvalue of type wchar_t (3.9.1 [basic.fundamental]) or an enumeration type (7.2 [dcl.enum]) can be converted to an rvalue of the first of the following types that can represent all the values of its underlying type: int, unsigned int, long, or unsigned long.to
An rvalue of type wchar_t (3.9.1 [basic.fundamental]) can be converted to an rvalue of the first of the following types that can represent all the values of its underlying type: int, unsigned int, long, or unsigned long. An rvalue of an enumeration type (7.2 [dcl.enum]) can be converted to an rvalue of the first of the following types that can represent all the values of the enumeration (i.e., the values in the range bmin to bmax as described in 7.2 [dcl.enum]): int, unsigned int, long, or unsigned long.
[Voted into WP at April 2003 meeting.]
7.2 [dcl.enum] defines the underlying type of an enumeration as an integral type "that can represent all the enumerator values defined in the enumeration".
What does the standard say about this code:
enum E { a = LONG_MIN, b = ULONG_MAX };
?
I think this should be ill-formed.
Proposed resolution:
In 7.2 [dcl.enum] paragraph 5 after
The underlying type of an enumeration is an integral type that can represent all the enumerator values defined in the enumeration.insert
If no integral type can represent all the enumerator values, the enumeration is ill-formed.
[Voted into WP at April, 2006 meeting.]
The C language (since C99), and some C++ compilers, accept:
enum { FOO, };
as syntactically valid. It would be useful
for machine generated code
for minimising changes when editing
to allow a distinction between the final item being intended as an ordinary item or as a limit:
enum { red, green, blue, num_colours }; // note no comma enum { fred, jim, sheila, }; // last is not special
This proposed change is to permit a trailing comma in enum by adding:
enum identifieropt { enumerator-list , }
as an alternative definition for the enum-specifier nonterminal
in
Proposed resolution (October, 2005):
Change the grammar in 7.2 [dcl.enum] paragraph 1 as indicated:
enum-specifier:enum identifieropt { enumerator-listopt }
enum identifieropt { enumerator-list , }
[Voted into the WP at the September, 2008 meeting.]
The current specification of scoped enumerations does not appear to forbid an example like the following, even though the enumerator e cannot be used:
enum class { e };
This might be covered by 7 [dcl.dcl] paragraph 3,
In a simple-declaration, the optional init-declarator-list can be omitted only when declaring a class (clause 9 [class]) or enumeration (7.2 [dcl.enum]), that is, when the decl-specifier-seq contains either a class-specifier, an elaborated-type-specifier with a class-key (9.1 [class.name]), or an enum-specifier. In these cases and whenever a class-specifier or enum-specifier is present in the decl-specifier-seq, the identifiers in these specifiers are among the names being declared by the declaration (as class-names, enum-names, or enumerators, depending on the syntax). In such cases, and except for the declaration of an unnamed bit-field (9.6 [class.bit]), the decl-specifier-seq shall introduce one or more names into the program, or shall redeclare a name introduced by a previous declaration.
which, when combined with paragraph 2,
A declaration occurs in a scope (3.3 [basic.scope]); the scope rules are summarized in 3.4 [basic.lookup]. A declaration that declares a function or defines a class, namespace, template, or function also has one or more scopes nested within it. These nested scopes, in turn, can have declarations nested within them. Unless otherwise stated, utterances in clause 7 [dcl.dcl] about components in, of, or contained by a declaration or subcomponent thereof refer only to those components of the declaration that are not nested within scopes nested within the declaration.
appears to rule out the similar class definition,
struct { int m; };
However, a scoped enumeration is not listed in paragraph 2 among the constructs containing a nested scope (although 3.3.8 [basic.scope.enum] does describe “enumeration scope”); furthermore, an enumerator-definition is not formally a “nested declaration.” If unusable scoped enumeration definitions are to be banned, these shortcomings in 7 [dcl.dcl] paragraph 2 must be addressed. (A note in 7.2 [dcl.enum] mentioning that unnamed scoped enumerations are not allowed would also be helpful.)
Notes from the February, 2008 meeting:
The consensus was to require that the identifier be present in an enum-specifier unless the enum-key is enum.
Proposed resolution (June, 2008):
Change 7.2 [dcl.enum] paragraph 2 as follows:
...The enum-keys enum class and enum struct are semantically equivalent; an enumeration type declared with one of these is a scoped enumeration, and its enumerators are scoped enumerators. The optional identifier shall not be omitted in the declaration of a scoped enumeration. The type-specifier-seq of an enum-base...
[Voted into the WP at the October, 2006 meeting as part of paper J16/06-0188 = WG21 N2118.]
The resolution of issue 106 specifies that an attempt to create a type “reference to cv1 T,” where T is a typedef or template parameter of the type “reference to cv2 S,” actually creates the type “reference to cv12 S,” where cv12 is the union of the two sets of cv-qualifiers.
One objection that has been raised to this resolution is that it is inconsistent with the treatment of cv-qualification and references specified in 8.3.2 [dcl.ref] paragraph 1, which says that cv-qualifiers applied to a typedef or template argument that is a reference type are ignored. For example:
typedef int& intref; const intref r1; // reference to int const intref& r2; // reference to const int
In fact, however, these two declarations are quite different. In the declaration of r1, const applies to a “top-level” reference, while in the declaration of t2, it occurs under a reference. In general, cv-qualifiers that appear under a reference are preserved, even if the type appears in a context in which top-level cv-qualification is removed, for example, in determining the type of a function from parameter types (8.3.5 [dcl.fct] paragraph 3) and in template argument deduction (14.8.2.1 [temp.deduct.call] paragraph 2).
Another objection to the resolution is that type composition gives different results in a single declaration than it does when separated into two declarations. For example:
template <class T> struct X { typedef T const T_const; typedef T_const& type1; typedef T const& type2; }; X<int&>::type1 t1; // int& X<int&>::type2 t2; // int const&
The initial motivation for the propagation of cv-qualification during reference-to-reference collapse was to prevent inadvertent loss of cv-qualifiers in contexts in which it could make a difference. For example, if the resolution were changed to discard, rather than propagate, embedded cv-qualification, overload resolution could surprisingly select a non-const version of a member function:
struct X { void g(); void g() const; }; template <typename T> struct S { static void f(const T& t) { t.g(); // const or non-const??? } }; X x; void q() { S<X>::f(x); // calls X::g() const S<X&>::f(x); // calls X::g() }
Another potentially-surprising outcome of dropping embedded cv-qualifiers would be:
template <typename T> struct A { void f(T&); // mutating version void f(const T&); // non-mutating version }; A<int&> ai; // Ill-formed: A<int&> declares f(int&) twice
On the other hand, those who would like to see the resolution changed to discard embedded cv-qualifiers observe that these examples are too simple to be representative of real-world code. In general, it is unrealistic to expect that a template written with non-reference type parameters in mind will automatically work correctly with reference type parameters as a result of applying the issue 106 resolution. Instead, template metaprogramming allows the template author to choose explicitly whether cv-qualifiers are propagated or dropped, according to the intended use of the template, and it is more important to respect the reasonable intuition that a declaration involving a template parameter will not change the type that the parameter represents.
As a sample of real-world code, tr1::tuple was examined. In both cases — the current resolution of issue 106 and one in which embedded cv-qualifiers were dropped — some metaprogramming was required to implement the intended interface, although the version reflecting the revised resolution was somewhat simpler.
Notes from the October, 2005 meeting:
The consensus of the CWG was that the resolution of issue 106 should be revised not to propagate embedded cv-qualification.
Note (February, 2006):
The wording included in the rvalue-reference paper, J16/06-0022 = WG21 N1952, incorporates changes intended to implement the October, 2005 consensus of the CWG.
[Voted into WP at March 2004 meeting.]
Issue 1:
The working paper is not clear about how the typename/template keywords interact with using-declarations:
template<class T> struct A { typedef int X; }; template<class T> void f() { typename A<T>::X a; // OK using typename A<T>::X; // OK typename X b; // ill-formed; X must be qualified X c; // is this OK? }When the rules for typename and the similar use of template were decided, we chose to require that they be used at every reference. The way to avoid typename at every use is to declare a typedef; then the typedef name itself is known to be a type. For using-declarations, we decided that they do not introduce new declarations but rather are aliases for existing declarations, like symbolic links. This makes it unclear whether the declaration "X c;" above should be well-formed, because there is no new name declared so there is no declaration with a "this is a type" attribute. (The same problem would occur with the template keyword when a member template of a dependent class is used). I think these are the main options:
The core WG already resolved this issue according to (1), but the wording does not seem to have been added to the standard. New wording needs to be drafted.
Issue 2:
Either way, one more point needs clarification. If the first option is adopted:
template<class T> struct A { struct X { }; }; template<class T> void g() { using typename A<T>::X; X c; // if this is OK, then X by itself is a type int X; // is this OK? }When "g" is instantiated, the two declarations of X are compatible (7.3.3 [namespace.udecl] paragraph 10). But there is no way to know this when the definition of "g" is compiled. I think this case should be ill-formed under the first option. (It cannot happen under the second option.) If the second option is adopted:
template<class T> struct A { struct X { }; }; template<class T> void g() { using A<T>::X; int X; // is this OK? }Again, the instantiation would work but there is no way to know that in the template definition. I think this case should be ill-formed under the second option. (It would already be ill-formed under the first option.)
From John Spicer:
The "not a new declaration" decision is more of a guiding principle than a hard and fast rule. For example, a name introduced in a using-declaration can have different access than the original declaration.Tentative Resolution:Like symbolic links, a using-declaration can be viewed as a declaration that declares an alias to another name, much like a typedef.
In my opinion, "X c;" is already well-formed. Why would we permit typename to be used in a using-declaration if not to permit this precise usage?
In my opinion, all that needs to be done is to clarify that the "typeness" or "templateness" attribute of the name referenced in the using-declaration is attached to the alias created by the using-declaration. This is solution #1.
The rules for multiple declarations with the same name in the same scope should treat a using-declaration which names a type as a typedef, just as a typedef of a class name is treated as a class declaration. This needs drafting work. Also see Core issue 36.
Rationale (04/99): Any semantics associated with the typename keyword in using-declarations should be considered an extension.
Notes from the April 2003 meeting:
This was reopened because we are now considering extensions again. We agreed that it is desirable for the typename to be "sticky" on a using-declaration, i.e., references to the name introduced by the using-declaration are known to be type names without the use of the typename keyword (which can't be specified on an unqualified name anyway, as of now). The related issue with the template keyword already has a separate issue 109.
Issue 2 deals with the "struct hack." There is an example in 7.3.3 [namespace.udecl] paragraph 10 that shows a use of using-declarations to import two names that coexist because of the "struct hack." After some deliberation, we decided that the template-dependent using-declaration case is different enough that we did not have to support the "struct hack" in that case. A name introduced in such a case is like a typedef, and no other hidden type can be accessed through an elaborated type specifier.
Proposed resolution (April 2003, revised October 2003):
Add a new paragraph to the bottom of 7.3.3 [namespace.udecl]:
If a using-declaration uses the keyword typename and specifies a dependent name (14.6.2 [temp.dep]), the name introduced by the using-declaration is treated as a typedef-name (7.1.3 [dcl.typedef]).
[Voted into WP at April 2003 meeting.]
According to 7.3.3 [namespace.udecl] paragraph 12,
When a using-declaration brings names from a base class into a derived class scope, member functions in the derived class override and/or hide member functions with the same name and parameter types in a base class (rather than conflicting).
Note that this description says nothing about the cv-qualification of the hiding and hidden member functions. This means, for instance, that a non-const member function in the derived class hides a const member function with the same name and parameter types instead of overloading it in the derived class scope. For example,
struct A { virtual int f() const; virtual int f(); }; struct B: A { B(); int f(); using A::f; }; const B cb; int i = cb.f(); // ill-formed: A::f() const hidden in B
The same terminology is used in 10.3 [class.virtual] paragraph 2:
If a virtual member function vf is declared in a class Base and in a class Derived, derived directly or indirectly from Base, a member function vf with the same name and same parameter list as Base::vf is declared, then Derived::vf is also virtual (whether or not it is so declared) and it overrides Base::vf.
Notes on the 04/01 meeting:
The hiding and overriding should be on the basis of the function signature, which includes any cv-qualification on the function.
Proposed resolution (04/02):
In 7.3.3 [namespace.udecl] paragraph 12 change:
When a using-declaration brings names from a base class into a derived class scope, member functions in the derived class override and/or hide member functions with the same name and parameter types in a base class (rather than conflicting).to read:
When a using-declaration brings names from a base class into a derived class scope, member functions and member function templates in the derived class override and/or hide member functions and member function templates with the same name, parameter-type-list (8.3.5 [dcl.fct]), and cv-qualification in a base class (rather than conflicting).
In 10.3 [class.virtual] paragraph 2 change:
If a virtual member function vf is declared in a class Base and in a class Derived, derived directly or indirectly from Base, a member function vf with the same name and same parameter list as Base::vf is declared, then Derived::vf is also virtual (whether or not it is so declared) and it overrides Base::vf.to read:
If a virtual member function vf is declared in a class Base and in a class Derived, derived directly or indirectly from Base, a member function vf with the same name, parameter-type-list (8.3.5 [dcl.fct]), and cv-qualification as Base::vf is declared, then Derived::vf is also virtual (whether or not it is so declared) and it overrides Base::vf.
See issue 140 for the definition of parameter-type-list.
[Voted into WP at April 2005 meeting.]
Can a using-declaration be used to import a namespace?
namespace my_namespace{ namespace my_namespace2 { int function_of_my_name_space(){ return 2;} } } int main (){ using ::my_namespace::my_namespace2; return my_namespace2::function_of_my_name_space(); }
Several popular compilers give an error on this, but there doesn't seem to be anything in 7.3.3 [namespace.udecl] that prohibits it. It should be noted that the user can get the same effect by using a namespace alias:
namespace my_namespace2 = ::my_namespace::my_namespace2;
Notes from the March 2004 meeting:
We agree that it should be an error.
Proposed resolution (October, 2004):
Add the following as a new paragraph after 7.3.3 [namespace.udecl] paragraph 5:
A using-declaration shall not name a namespace;
[Moved to DR at 4/01 meeting.]
7.5 [dcl.link] paragraph 6 says the following:
extern "C" { static void f(int) {} static void f(float) {} };Can a function with internal linkage "have C linkage" at all (assuming that phrase means "has extern "C" linkage"), for how can a function be extern "C" if it's not extern? The function type can have extern "C" linkage — but I think that's independent of the linkage of the function name. It should be perfectly reasonable to say, in the example above, that extern "C" applies only to the types of f(int) and f(float), not to the function names, and that the rule in 7.5 [dcl.link] paragraph 6 doesn't apply.
Suggested resolution: The extern "C" linkage specification applies only to the type of functions with internal linkage, and therefore some of the rules that have to do with name overloading don't apply.
Proposed Resolution:
The intent is to distingush implicit linkage from explicit linkage for both name linkage and language (function type) linkage. (It might be more clear to use the terms name linkage and type linkage to distinguish these concepts. A function can have a name with one kind of linkage and a type with a different kind of linkage. The function itself has no linkage: it has no name, only the declaration has a name. This becomes more obvious when you consider function pointers.)
The tentatively agreed proposal is to apply implicit linkage to names declared in brace-enclosed linkage specifications and to non-top-level names declared in simple linkage specifications; and to apply explicit linkage to top-level names declared in simple linkage specifications.
The language linkage of any function type formed through a function declarator is that of the nearest enclosing linkage-specification. For purposes of determining whether the declaration of a namespace-scope name matches a previous declaration, the language linkage portion of the type of a function declaration (that is, the language linkage of the function itself, not its parameters, return type or exception specification) is ignored.
For a linkage-specification using braces, i.e.
extern string-literal { declaration-seqopt }the linkage of any declaration of a namespace-scope name (including local externs) which is not contained in a nested linkage-specification, is not declared to have no linkage (static), and does not match a previous declaration is given the linkage specified in the string-literal. The language linkage of the type of any function declaration of a namespace-scope name (including local externs) which is not contained in a nested linkage-specification and which is declared with function declarator syntax is the same as that of a matching previous declaration, if any, else is specified by string-literal.
For a linkage-specification without braces, i.e.
extern string-literal declaration
the linkage of the names declared in the top-level declarators of declaration is specified by string-literal; if this conflicts with the linkage of any matching previous declarations, the program is ill-formed. The language linkage of the type of any top-level function declarator is specified by string-literal; if this conflicts with the language linkage of the type of any matching previous function declarations, the program is ill-formed. The effect of the linkage-specification on other (non top-level) names declared in declaration is the same as that of the brace-enclosed form.
Bill Gibbons: In particular, these should be well-formed:
extern "C" void f(void (*fp)()); // parameter type is pointer to // function with C language linkage extern "C++" void g(void (*fp)()); // parameter type is pointer to // function with C++ language linkage extern "C++" { // well-formed: the linkage of "f" void f(void(*fp)()); // and the function type used in the } // parameter still "C" extern "C" { // well-formed: the linkage of "g" void g(void(*fp)()); // and the function type used in the } // parameter still "C++"
but these should not:
extern "C++" void f(void(*fp)()); // error - linkage of "f" does not // match previous declaration // (linkage of function type used in // parameter is still "C" and is not // by itself ill-formed) extern "C" void g(void(*fp)()); // error - linkage of "g" does not // match previous declaration // (linkage of function type used in // parameter is still "C++" and is not // by itself ill-formed)
That is, non-top-level declarators get their linkage from matching declarations, if any, else from the nearest enclosing linkage specification. (As already described, top-level declarators in a brace-enclosed linkage specification get the linkage from matching declarations, if any, else from the linkage specifcation; while top-level declarators in direct linkage specifications get their linkage from that specification.)
Mike Miller: This is a pretty significant change from the current specification, which treats the two forms of language linkage similarly for most purposes. I don't understand why it's desirable to expand the differences.
It seems very unintuitive to me that you could have a top-level declaration in an extern "C" block that would not receive "C" linkage.
In the current standard, the statement in 7.5 [dcl.link] paragraph 4 that
the specified language linkage applies to the function types of all function declarators, function names, and variable names introduced by the declaration(s)
applies to both forms. I would thus expect that in
extern "C" void f(void(*)()); extern "C++" { void f(void(*)()); } extern "C++" f(void(*)());
both "C++" declarations would be well-formed, declaring an overloaded version of f that takes a pointer to a "C++" function as a parameter. I wouldn't expect that either declaration would be a redeclaration (valid or invalid) of the "C" version of f.
Bill Gibbons: The potential difficulty is the matching process and the handling of deliberate overloading based on language linkage. In the above examples, how are these two declarations matched:
extern "C" void f(void (*fp1)()); extern "C++" { void f(void(*fp2)()); }
given that the linkage that is part of fp1 is "C" while the linkage (prior to the matching process) that is part of fp2 is "C++"?
The proposal is that the linkage which is part of the parameter type is not determined until after the match is attempted. This almost always correct because you can't overload "C" and "C++" functions; so if the function names match, it is likely that the declarations are supposed to be the same.
Mike Miller: This seems like more trouble than it's worth. This comparison of function types ignoring linkage specifications is, as far as I know, not found anywhere in the current standard. Why do we need to invent it?
Bill Gibbons: It is possible to construct pathological cases where this fails, e.g.
extern "C" typedef void (*PFC)(); // pointer to "C" linkage function void f(PFC); // parameter is pointer to "C" function void f(void (*)()); // matching declaration or overload based on // difference in linkage type?
It is reasonable to require explicit typedefs in this case so that in the above example the second function declaration gets its parameter type function linkage from the first function declaration.
(In fact, I think you can't get into this situation without having already used typedefs to declare different language linkage for the top-level and parameter linkages.)
For example, if the intent is to overload based on linkage a typedef is needed:
extern "C" typedef void (*PFC)(); // pointer to "C" linkage function void f(PFC); // parameter is pointer to "C" function typedef void (*PFCPP)(); // pointer to "C++" linkage function void f(PFCPP); // parameter is pointer to "C++" function
In this case the two function declarations refer to different functions.
Mike Miller: This seems pretty strange to me. I think it would be simpler to determine the type of the parameter based on the containing linkage specification (implicitly "C++") and require a typedef if the user wants to override the default behavior. For example:
extern "C" { typedef void (*PFC)(); // pointer to "C" function void f(void(*)()); // takes pointer to "C" function } void f(void(*)()); // new overload of "f", taking // pointer to "C++" function void f(PFC); // redeclare extern "C" version
Notes from 04/00 meeting:
The following changes were tentatively approved, but because they do not completely implement the proposal above the issue is being kept for the moment in "drafting" status.
Notes from 10/00 meeting:
After further discussion, the core language working group determined that the more extensive proposal described above is not needed and that the following changes are sufficient.
Proposed resolution (04/01):
Change the first sentence of 7.5 [dcl.link] paragraph 1 from
All function types, function names, and variable names have a language linkage.
to
All function types, function names with external linkage, and variable names with external linkage have a language linkage.
In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names, and variable names introduced by the declaration(s).
to
In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names with external linkage, and variable names with external linkage declared within the linkage-specification.
Add at the end of the final example on 7.5 [dcl.link] paragraph 4:
extern "C" { static void f4(); // the name of the function f4 has // internal linkage (not C language // linkage) and the function's type // has C language linkage } extern "C" void f5() { extern void f4(); // Okay -- name linkage (internal) // and function type linkage (C // language linkage) gotten from // previous declaration. } extern void f4(); // Okay -- name linkage (internal) // and function type linkage (C // language linkage) gotten from // previous declaration. void f6() { extern void f4(); // Okay -- name linkage (internal) // and function type linkage (C // language linkage) gotten from // previous declaration. }
Change 7.5 [dcl.link] paragraph 7 from
Except for functions with internal linkage, a function first declared in a linkage-specification behaves as a function with external linkage. [Example:
extern "C" double f(); static double f(); // erroris ill-formed (7.1.1 [dcl.stc]). ] The form of linkage-specification that contains a braced-enclosed declaration-seq does not affect whether the contained declarations are definitions or not (3.1 [basic.def]); the form of linkage-specification directly containing a single declaration is treated as an extern specifier (7.1.1 [dcl.stc]) for the purpose of determining whether the contained declaration is a definition. [Example:
extern "C" int i; // declaration extern "C" { int i; // definition }—end example] A linkage-specification directly containing a single declaration shall not specify a storage class. [Example:
extern "C" static void f(); // error—end example]
to
A declaration directly contained in a linkage-specification is treated as if it contains the extern specifier (7.1.1 [dcl.stc]) for the purpose of determining the linkage of the declared name and whether it is a definition. Such a declaration shall not specify a storage class. [Example:extern "C" double f(); static double f(); // error extern "C" int i; // declaration extern "C" { int i; // definition } extern "C" static void g(); // error—end example]
[Moved to DR at October 2002 meeting. This was incorrectly marked as having DR status between 4/01 and 4/02. It was overlooked when issue 4 was moved to DR at the 4/01 meeting; this one should have been moved as well, because it's resolved by the changes there.]
Consider the following:
extern "C" void foo() { extern void bar(); bar(); }Does "bar()" have "C" language linkage?
The ARM is explicit and says
A linkage-specification for a function also applies to functions and objects declared within it.The DIS says
In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names, and variable names introduced by the declaration(s).Is the body of a function definition part of the declaration?
From Mike Miller:
Yes: from 7 [dcl.dcl] paragraph 1,
From Dag Brück:
Consider the following where extern "C" has been moved to a separate declaration:
extern "C" void foo(); void foo() { extern void bar(); bar(); }I think the ARM wording could possibly be interpreted such that bar() has "C" linkage in my example, but not the DIS wording.
As a side note, I have always wanted to think that placing extern "C" on a function definition or a separate declaration would produce identical programs.
Proposed Resolution (04/01):
See the proposed resolution for Core issue 4, which covers this case.
The ODR should also be checked to see whether it addresses name and type linkage.
[Voted into the WP at the September, 2008 meeting.]
The restrictions on declaring and/or defining classes inside type-specifier-seqs and type-ids are inconsistent throughout the Standard. This is probably due to the fact that nearly all of the sections that deal with them attempt to state the restriction afresh. There are three cases:
5.3.4 [expr.new], 6.4 [stmt.select], and 12.3.2 [class.conv.fct] prohibit “declarations” of classes and enumerations. That means that
while (struct C* p = 0) ;
is ill-formed unless a prior declaration of C has been seen. These appear to be cases that should have been fixed by issue 379, changing “class declaration” to “class definition,” but were overlooked.
5.1.2 [expr.prim.lambda], 7 [dcl.dcl], and 8.3.5 [dcl.fct] (late-specified return types) do not contain any restriction at all.
All the remaining cases prohibit “type definitions,” apparently referring to classes and enumerations.
Suggested resolution:
Add something like, “A class or enumeration shall not be defined in a type-specifier-seq or in a type-id,” to a single place in the Standard and remove all other mentions of that restriction (allowing declarations via elaborated-type-specifier).
Mike Miller:
An alias-declaration is just a different syntax for a typedef declaration, which allows definitions of a class in the type; I would expect the same to be true of an alias-declaration. I don't have any particularly strong attachment to allowing a class definition in an alias-declaration. My only concern is introducing an irregularity into what are currently exact-match semantics with typedefs.
There's a parallel restriction in many (but not all?) of these places on typedef declarations.
Jens Maurer:
Those are redundant, as typedef is not a type-specifier, and should be removed as well.
Proposed resolution (March, 2008):
Delete the indicated words from 5.2.7 [expr.dynamic.cast] paragraph 1:
...Types shall not be defined in a dynamic_cast....
Delete the indicated words from 5.2.8 [expr.typeid] paragraph 4:
...Types shall not be defined in the type-id....
Delete the indicated words from 5.2.9 [expr.static.cast] paragraph 1:
...Types shall not be defined in a static_cast....
Delete the indicated words from 5.2.10 [expr.reinterpret.cast] paragraph 1:
...Types shall not be defined in a reinterpret_cast....
Delete the indicated words from 5.2.11 [expr.const.cast] paragraph 1:
...Types shall not be defined in a const_cast....
Delete paragraph 5 of 5.3.3 [expr.sizeof]:
Types shall not be defined in a sizeof expression.
Delete paragraph 5 of 5.3.4 [expr.new]:
The type-specifier-seq shall not contain class declarations, or enumeration declarations.
Delete paragraph 4 of 5.3.6 [expr.alignof]:
A type shall not be defined in an alignof expression.
Delete paragraph 3 of 5.4 [expr.cast]:
Types shall not be defined in casts.
Delete the indicated words from 6.4 [stmt.select] paragraph 2:
...The type-specifier-seq shall not contain typedef and shall not declare a new class or enumeration....
Add the indicated words to 7.1.6 [dcl.type] paragraph 3:
At least one type-specifier that is not a cv-qualifier is required in a declaration unless it declares a constructor, destructor or conversion function. [Footnote: ... ] A type-specifier-seq shall not define a class or enumeration unless it appears in the type-id of an alias-declaration (7.1.3 [dcl.typedef]).
Delete the indicated words from 12.3.2 [class.conv.fct] paragraph 1:
...Classes, enumerations, and typedef-names shall not be declared in the type-specifier-seq....
Delete the indicated words from 15.3 [except.handle] paragraph 1:
...Types shall not be defined in an exception-declaration.
Delete paragraph 6 of 15.4 [except.spec]:
Types shall not be defined in exception-specifications.
[Drafting note: no changes are required to 5.1.2 [expr.prim.lambda], 7.1.3 [dcl.typedef], 7.6.2 [dcl.align], 7.2 [dcl.enum], 8.3.5 [dcl.fct], 14.1 [temp.param], or 14.2 [temp.names].]
[Moved to DR at 10/01 meeting.]
8.2 [dcl.ambig.res] paragraph 3 shows an example that includes <cstddef> with no using declarations or directives and refers to size_t without the std:: qualification.
Many references to size_t throughout the document omit the std:: namespace qualification.
This is a typical case. The use of std:: is inconsistent throughout the document.
In addition, the use of exception specifications should be examined for consistency.
(See also issue 282.)
Proposed resolution:
In 1.9 [intro.execution] paragraph 9, replace all two instances of "sig_atomic_t" by "std::sig_atomic_t".
In 3.1 [basic.def] paragraph 4, replace all three instances of "string" by "std::string" in the example and insert "#include <string>" at the beginning of the example code.
In 3.6.1 [basic.start.main] paragraph 4, replace
Calling the functionvoid exit(int);declared in <cstdlib>...
by
Calling the function std::exit(int) declared in <cstdlib>...
and also replace "exit" by "std::exit" in the last sentence of that paragraph.
In 3.6.1 [basic.start.main] first sentence of paragraph 5, replace "exit" by "std::exit".
In 3.6.2 [basic.start.init] paragraph 4, replace "terminate" by "std::terminate".
In 3.6.3 [basic.start.term] paragraph 1, replace "exit" by "std::exit" (see also issue 28).
In 3.6.3 [basic.start.term] paragraph 3, replace all three instances of "atexit" by "std::atexit" and both instances of "exit" by "std::exit" (see also issue 28).
In 3.6.3 [basic.start.term] paragraph 4, replace
Calling the functionvoid abort();declared in <cstdlib>...
by
Calling the function std::abort() declared in <cstdlib>...and "atexit" by "std::atexit" (see also issue 28).
In 3.7.4.1 [basic.stc.dynamic.allocation] paragraph 1 third sentence, replace "size_t" by "std::size_t".
In 3.7.4.1 [basic.stc.dynamic.allocation] paragraph 3, replace "new_handler" by "std::new_handler". Furthermore, replace "set_new_handler" by "std::set_new_handler" in the note.
In 3.7.4.1 [basic.stc.dynamic.allocation] paragraph 4, replace "type_info" by "std::type_info" in the note.
In 3.7.4.2 [basic.stc.dynamic.deallocation] paragraph 3, replace all four instances of "size_t" by "std::size_t".
In 3.8 [basic.life] paragraph 5, replace "malloc" by "std::malloc" in the example code and insert "#include <cstdlib>" at the beginning of the example code.
In 3.9 [basic.types] paragraph 2, replace "memcpy" by "std::memcpy" (the only instance in the footnote and both instances in the example) and replace "memmove" by "std::memmove" in the footnote (see also issue 43).
In 3.9 [basic.types] paragraph 3, replace "memcpy" by "std::memcpy", once in the normative text and once in the example (see also issue 43).
In 3.9.1 [basic.fundamental] paragraph 8 last sentence, replace "numeric_limits" by "std::numeric_limits".
In 5.2.7 [expr.dynamic.cast] paragraph 9 second sentence, replace "bad_cast" by "std::bad_cast".
In 5.2.8 [expr.typeid] paragraph 2, replace "type_info" by "std::type_info" and "bad_typeid" by "std::bad_typeid".
In 5.2.8 [expr.typeid] paragraph 3, replace "type_info" by "std::type_info".
In 5.2.8 [expr.typeid] paragraph 4, replace both instances of "type_info" by "std::type_info".
In 5.3.3 [expr.sizeof] paragraph 6, replace both instances of "size_t" by "std::size_t".
In 5.3.4 [expr.new] paragraph 11 last sentence, replace "size_t" by "std::size_t".
In 5.7 [expr.add] paragraph 6, replace both instances of "ptrdiff_t" by "std::ptrdiff_t".
In 5.7 [expr.add] paragraph 8, replace "ptrdiff_t" by "std::ptrdiff_t".
In 6.6 [stmt.jump] paragraph 2, replace "exit" by "std::exit" and "abort" by "std::abort" in the note.
In 8.2 [dcl.ambig.res] paragraph 3, replace "size_t" by "std::size_t" in the example.
In 8.4 [dcl.fct.def] paragraph 5, replace "printf" by "std::printf" in the note.
In 12.4 [class.dtor] paragraph 13, replace "size_t" by "std::size_t" in the example.
In 12.5 [class.free] paragraph 2, replace all four instances of "size_t" by "std::size_t" in the example.
In 12.5 [class.free] paragraph 6, replace both instances of "size_t" by "std::size_t" in the example.
In 12.5 [class.free] paragraph 7, replace all four instances of "size_t" by "std::size_t" in the two examples.
In 12.7 [class.cdtor] paragraph 4, replace "type_info" by "std::type_info".
In 13.6 [over.built] paragraph 13, replace all five instances of "ptrdiff_t" by "std::ptrdiff_t".
In 13.6 [over.built] paragraph 14, replace "ptrdiff_t" by "std::ptrdiff_t".
In 13.6 [over.built] paragraph 21, replace both instances of "ptrdiff_t" by "std::ptrdiff_t".
In 14.2 [temp.names] paragraph 4, replace both instances of "size_t" by "std::size_t" in the example. (The example is quoted in issue 96.)
In 14.3 [temp.arg] paragraph 1, replace "complex" by "std::complex", once in the example code and once in the comment.
In 14.7.3 [temp.expl.spec] paragraph 8, issue 24 has already corrected the example.
In 15.1 [except.throw] paragraph 6, replace "uncaught_exception" by "std::uncaught_exception".
In 15.1 [except.throw] paragraph 7, replace "terminate" by "std::terminate" and both instances of "unexpected" by "std::unexpected".
In 15.1 [except.throw] paragraph 8, replace "terminate" by "std::terminate".
In 15.2 [except.ctor] paragraph 3, replace "terminate" by "std::terminate".
In 15.3 [except.handle] paragraph 9, replace "terminate" by "std::terminate".
In 15.4 [except.spec] paragraph 8, replace "unexpected" by "std::unexpected".
In 15.4 [except.spec] paragraph 9, replace "unexpected" by "std::unexpected" and "terminate" by "std::terminate".
In 15.5 [except.special] paragraph 1, replace "terminate" by "std::terminate" and "unexpected" by "std::unexpected".
In the heading of 15.5.1 [except.terminate], replace "terminate" by "std::terminate".
In 15.5.1 [except.terminate] paragraph 1, footnote in the first bullet, replace "terminate" by "std::terminate". In the same paragraph, fifth bullet, replace "atexit" by "std::atexit". In the same paragraph, last bullet, replace "unexpected_handler" by "std::unexpected_handler".
In 15.5.1 [except.terminate] paragraph 2, replace
In such cases,void terminate();is called...
by
In such cases, std::terminate() is called...
and replace all three instances of "terminate" by "std::terminate".
In the heading of 15.5.2 [except.unexpected], replace "unexpected" by "std::unexpected".
In 15.5.2 [except.unexpected] paragraph 1, replace
...the functionvoid unexpected();is called...
by
...the function std::unexpected() is called....
In 15.5.2 [except.unexpected] paragraph 2, replace "unexpected" by "std::unexpected" and "terminate" by "std::terminate".
In 15.5.2 [except.unexpected] paragraph 3, replace "unexpected" by "std::unexpected".
In the heading of 15.5.3 [except.uncaught], replace "uncaught_exception" by "std::uncaught_exception".
In 15.5.3 [except.uncaught] paragraph 1, replace
The functionbool uncaught_exception()returns true...
by
The function std::uncaught_exception() returns true....
In the last sentence of the same paragraph, replace "uncaught_exception" by "std::uncaught_exception".
[Moved to DR at 10/01 meeting.]
Steve Clamage: Section 8.3.4 [dcl.array] paragraph 1 reads in part as follows:
Any type of the form "cv-qualifier-seq array of N T" is adjusted to "array of N cv-qualifier-seq T," and similarly for "array of unknown bound of T." [Example:The Note appears to contradict the sentence that precedes it.typedef int A[5], AA[2][3]; typedef const A CA; // type is "array of 5 const int" typedef const AA CAA; // type is "array of 2 array of 3 const int"—end example] [Note: an "array of N cv-qualifier-seq T" has cv-qualified type; such an array has internal linkage unless explicitly declared extern (7.1.6.1 [dcl.type.cv] ) and must be initialized as specified in 8.5 [dcl.init] . ]
Mike Miller: I disagree; all it says is that whether the qualification on the element type is direct ("const int x[5]") or indirect ("const A CA"), the array itself is qualified in the same way the elements are.
Steve Clamage: In addition, section 3.9.3 [basic.type.qualifier] paragraph 2 says:
A compound type (3.9.2 [basic.compound] ) is not cv-qualified by the cv-qualifiers (if any) of the types from which it is compounded. Any cv-qualifiers applied to an array type affect the array element type, not the array type (8.3.4 [dcl.array] )."The Note appears to contradict that section as well.
Mike Miller: Yes, but consider the last two sentences of 3.9.3 [basic.type.qualifier] paragraph 5:
Cv-qualifiers applied to an array type attach to the underlying element type, so the notation "cv T," where T is an array type, refers to an array whose elements are so-qualified. Such array types can be said to be more (or less) cv-qualified than other types based on the cv-qualification of the underlying element types.I think this says essentially the same thing as 8.3.4 [dcl.array] paragraph 1 and its note: the qualification of an array is (bidirectionally) equivalent to the qualification of its members.
Mike Ball: I find this a very far reach. The text in 8.3.4 [dcl.array] is essentially that which is in the C standard (and is a change from early versions of C++). I don't see any justification at all for the bidirectional equivalence. It seems to me that the note is left over from the earlier version of the language.
Steve Clamage: Finally, the Note seems to say that the declaration
volatile char greet[6] = "Hello";gives "greet" internal linkage, which makes no sense.
Have I missed something, or should that Note be entirely removed?
Mike Miller: At least the wording in the note should be repaired not to indicate that volatile-qualification gives an array internal linkage. Also, depending on how the discussion goes, either the wording in 3.9.3 [basic.type.qualifier] paragraph 2 or in paragraph 5 needs to be amended to be consistent regarding whether an array type is considered qualified by the qualification of its element type.
Steve Adamczyk pointed out that the current state of affairs resulted from the need to handle reference binding consistently. The wording is intended to define the question, "Is an array type cv-qualified?" as being equivalent to the question, "Is the element type of the array cv-qualified?"
Proposed resolution (10/00):
Replace the portion of the note in 8.3.4 [dcl.array] paragraph 1 reading
such an array has internal linkage unless explicitly declared extern (7.1.6.1 [dcl.type.cv]) and must be initialized as specified in 8.5 [dcl.init].
with
see 3.9.3 [basic.type.qualifier].
[Moved to DR at 10/01 meeting.]
8.3.5 [dcl.fct] paragraph 3 says,
All declarations for a function with a given parameter list shall agree exactly both in the type of the value returned and in the number and type of parameters.It is not clear what this requirement means with respect to a pair of declarations like the following:
int f(const int); int f(int x) { ... }Do they violate this requirement? Is x const in the body of the function declaration?
Tom Plum: I think the FDIS quotation means that the pair of decls are valid. But it doesn't clearly answer whether x is const inside the function definition. As to intent, I know the intent was that if the function definition wants to specify that x is const, the const must appear specifically in the defining decl, not just on some decl elsewhere. But I can't prove that intent from the drafted words.
Mike Miller: I think the intent was something along the following lines:
Two function declarations denote the same entity if the names are the same and the function signatures are the same. (Two function declarations with C language linkage denote the same entity if the names are the same.) All declarations of a given function shall agree exactly both in the type of the value returned and in the number and type of parameters; the presence or absence of the ellipsis is considered part of the signature.(See 3.5 [basic.link] paragraph 9. That paragraph talks about names in different scopes and says that function references are the same if the "types are identical for purposes of overloading," i.e., the signatures are the same. See also 7.5 [dcl.link] paragraph 6 regarding C language linkage, where only the name is required to be the same for declarations in different namespaces to denote the same function.)
According to this paragraph, the type of a parameter is determined by considering its decl-specifier-seq and declarator and then applying the array-to-pointer and function-to-pointer adjustments. The cv-qualifier and storage class adjustments are performed for the function type but not for the parameter types.
If my interpretation of the intent of the second sentence of the paragraph is correct, the two declarations in the example violate that restriction — the parameter types are not the same, even though the function types are. Since there's no dispensation mentioned for "no diagnostic required," an implementation presumably must issue a diagnostic in this case. (I think "no diagnostic required" should be stated if the declarations occur in different translation units — unless there's a blanket statement to that effect that I have forgotten?)
(I'd also note in passing that, if my interpretation is correct,
void f(int); void f(register int) { }is also an invalid pair of declarations.)
Proposed resolution (10/00):
In 1.3 [intro.defs] “signature,” change "the types of its parameters" to "its parameter-type-list (8.3.5 [dcl.fct])".
In the third bullet of 3.5 [basic.link] paragraph 9 change "the function types are identical for the purposes of overloading" to "the parameter-type-lists of the functions (8.3.5 [dcl.fct]) are identical."
In the sub-bullets of the third bullet of 5.2.5 [expr.ref] paragraph 4, change all four occurrences of "function of (parameter type list)" to "function of parameter-type-list."
In 8.3.5 [dcl.fct] paragraph 3, change
All declarations for a function with a given parameter list shall agree exactly both in the type of the value returned and in the number and type of parameters; the presence or absence of the ellipsis is considered part of the function type.to
All declarations for a function shall agree exactly in both the return type and the parameter-type-list.
In 8.3.5 [dcl.fct] paragraph 3, change
The resulting list of transformed parameter types is the function's parameter type list.to
The resulting list of transformed parameter types and the presence or absence of the ellipsis is the function's parameter-type-list.
In 8.3.5 [dcl.fct] paragraph 4, change "the parameter type list" to "the parameter-type-list."
In the second bullet of 13.1 [over.load] paragraph 2, change all occurrences of "parameter types" to "parameter-type-list."
In 13.3 [over.match] paragraph 1, change "the types of the parameters" to "the parameter-type-list."
In the last sub-bullet of the third bullet of 13.3.1.2 [over.match.oper] paragraph 3, change "parameter type list" to "parameter-type-list."
Note, 7 Sep 2001:
Editorial changes while putting in issue 147 brought up the fact that injected-class-name is not a syntax term and therefore perhaps shouldn't be written with hyphens. The same can be said of parameter-type-list.
[Voted into WP at April 2003 meeting.]
The interaction of default arguments and ellipsis is not clearly spelled out in the current wording of the Standard. 8.3.6 [dcl.fct.default] paragraph 4 says,
In a given function declaration, all parameters subsequent to a parameter with a default argument shall have default arguments supplied in this or previous declarations.
Strictly speaking, ellipsis isn't a parameter, but this could be clearer. Also, in 8.3.5 [dcl.fct] paragraph 2,
If the parameter-declaration-clause terminates with an ellipsis, the number of arguments shall be equal to or greater than the number of parameters specified.
This could be interpreted to refer to the number of arguments after the addition of default arguments to the argument list given in the call expression, but again it could be clearer.
Notes from 04/01 meeting:
The consensus opinion was that an ellipsis is not a parameter and that default arguments should be permitted preceding an ellipsis.
Proposed Resolution (4/02):
Change the following sentence in 8.3.5 [dcl.fct] paragraph 2 from
If the parameter-declaration-clause terminates with an ellipsis, the number of arguments shall be equal to or greater than the number of parameters specified.
to
If the parameter-declaration-clause terminates with an ellipsis, the number of arguments shall be equal to or greater than the number of parameters that do not have a default argument.
As noted in the defect, section 8.3.6 [dcl.fct.default] is correct but could be clearer.
In 8.3.6 [dcl.fct.default], add the following as the first line of the example in paragraph 4.
void g(int = 0, ...); // okay, ellipsis is not a parameter so it can follow // a parameter with a default argument
[Moved to DR at October 2002 meeting.]
This concerns the inconsistent treatment of cv qualifiers on reference types and function types. The problem originated with GCC bug report c++/2810. The bug report is available at http://gcc.gnu.org/cgi-bin/gnatsweb.pl?cmd=view&pr=2810&database=gcc
8.3.2 [dcl.ref] describes references. Of interest is the statement (my emphasis)
Cv-qualified references are ill-formed except when the cv-qualifiers are introduced through the use of a typedef or of a template type argument, in which case the cv-qualifiers are ignored.
Though it is strange to ignore 'volatile' here, that is not the point of this defect report. 8.3.5 [dcl.fct] describes function types. Paragraph 4 states,
In fact, if at any time in the determination of a type a cv-qualified function type is formed, the program is ill-formed.
No allowance for typedefs or template type parameters is made here, which is inconsistent with the equivalent reference case.
The GCC bug report was template code which attempted to do,
template <typename T> void foo (T const &); void baz (); ... foo (baz);
in the instantiation of foo, T is `void ()' and an attempt is made to const qualify that, which is ill-formed. This is a surprise.
Suggested resolution:
Replace the quoted sentence from paragraph 4 in 8.3.5 [dcl.fct] with
cv-qualified functions are ill-formed, except when the cv-qualifiers are introduced through the use of a typedef or of a template type argument, in which case the cv-qualifiers are ignored.
Adjust the example following to reflect this.
Proposed resolution (10/01):
In 8.3.5 [dcl.fct] paragraph 4, replace
The effect of a cv-qualifier-seq in a function declarator is not the same as adding cv-qualification on top of the function type, i.e., it does not create a cv-qualified function type. In fact, if at any time in the determination of a type a cv-qualified function type is formed, the program is ill-formed. [Example:bytypedef void F(); struct S { const F f; // ill-formed };-- end example]
The effect of a cv-qualifier-seq in a function declarator is not the same as adding cv-qualification on top of the function type. In the latter case, the cv-qualifiers are ignored. [Example:typedef void F(); struct S { const F f; // ok; equivalent to void f(); };-- end example]
Strike the last bulleted item in 14.8.2 [temp.deduct] paragraph 2, which reads
Attempting to create a cv-qualified function type.
Nathan Sidwell comments (18 Dec 2001 ): The proposed resolution simply states attempts to add cv qualification on top of a function type are ignored. There is no mention of whether the function type was introduced via a typedef or template type parameter. This would appear to allow
void (const *fptr) ();but, that is not permitted by the grammar. This is inconsistent with the wording of adding cv qualifiers to a reference type, which does mention typedefs and template parameters, even though
int &const ref;is also not allowed by the grammar.
Is this difference intentional? It seems needlessly confusing.
Notes from 4/02 meeting:
Yes, the difference is intentional. There is no way to add cv-qualifiers other than those cases.
Notes from April 2003 meeting:
Nathan Sidwell pointed out that some libraries use the inability to add const to a type T as a way of testing that T is a function type. He will get back to us if he has a proposal for a change.
[Voted into the WP at the September, 2008 meeting as part of paper N2757.]
The wording added to 8.3.5 [dcl.fct] for declarators with late-specified return types says,
In a declaration T D where D has the form
D1 ( parameter-declaration-clause ) cv-qualifier-seqopt ref-qualifieropt exception-specificationopt -> type-id
and the type of the contained declarator-id in the declaration T D1 is “derived-declarator-type-list T,” T shall be the single type-specifier auto and the derived-declarator-type-list shall be empty.
These restrictions were intended to ensure that the return type of the function is exactly the specified type-id following the ->, not modified by declarator operators and cv-qualification.
Unfortunately, the requirement for an empty derived-declarator-type-list does not achieve this goal but instead forbids declarations like
auto (*fp)() -> int; // pointer to function returning int
while allowing declarations like
auto *f() -> int; // function returning pointer to int
The reason for this is that, according to the grammar in 8 [dcl.decl] paragraph 4, the declarator *f() -> int is parsed as a ptr-operator applied to the direct-declarator f() -> int; that is, the declarator D1 seen in 8.3.5 [dcl.fct] is just f, and the derived-declarator-type-list is thus empty.
By contrast, the declarator (*fp)() -> int is parsed as the direct-declarator (*fp) followed by the parameter-declaration-clause, etc. In this case, D1 in 8.3.5 [dcl.fct] is (*fp) and the derived-declarator-type-list is “pointer to,” i.e., not empty.
My personal view is that there is no reason to forbid the (*fp)() -> int form, and that doing so is problematic. For example, this restriction would require users desiring the late-specified return type syntax to write function parameters as function types and rely on parameter type transformations rather than writing them as pointer-to-function types, as they will actually turn out to be:
void f(auto (*fp)() -> int); // ill-formed void f(auto fp() -> int); // OK (but icky)
It may be helpful in deciding whether to allow this form to consider the example of a function returning a pointer to a function. With the current restriction, only one of the three plausible forms is allowed:
auto (*f())() -> int; // Disallowed auto f() -> int (*)(); // Allowed auto f() -> auto (*)() -> int; // DisallowedSuggested resolution:
Delete the words “and the derived-declarator-type-list shall be empty” from 8.3.5 [dcl.fct] paragraph 2.
Add a new paragraph following 8 [dcl.decl] paragraph 4:
A ptr-operator shall not be applied, directly or indirectly, to a function declarator with a late-specified return type (8.3.5 [dcl.fct]).
Proposed resolution (June, 2008):
Change the grammar in 8 [dcl.decl] paragraph 4 as follows:
Change the grammar in 8.1 [dcl.name] paragraph 1 as follows:
Change 8.3.5 [dcl.fct] paragraph 2 as follows:
... T shall be the single type-specifier auto and the derived-declarator-type-list shall be empty. Then the type...
Change all occurrences of direct-new-declarator in 5.3.4 [expr.new] to noptr-new-declarator. These changes appear in the grammar in paragraph 1 and in the text of paragraphs 6-8, as follows:
...
new-declarator:
ptr-operator new-declaratoropt
direct-noptr-new-declarator
direct-noptr-new-declarator:
[ expression ]
...
direct-noptr-new-declarator [ constant-expression ]
When the allocated object is an array (that is, the direct-noptr-new-declarator syntax is used or the new-type-id or type-id denotes an array type), the new-expression yields a pointer to the initial element (if any) of the array. [Note: both new int and new int[10] have type int* and the type of new int[i][10] is int (*)[10] —end note]
Every constant-expression in a direct-noptr-new-declarator shall be an integral constant expression (5.19 [expr.const]) and evaluate to a strictly positive value. The expression in a direct-noptr-new-declarator shall be of integral type, enumeration type, or a class type for which a single non-explicit 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: given the definition int n = 42, new float[n][5] is well-formed (because n is the expression of a direct-noptr-new-declarator), but new float[5][n] is ill-formed (because n is not a constant expression). If n is negative, the effect of new float[n][5] is undefined. —end example]
When the value of the expression in a direct-noptr-new-declarator is zero, the allocation function is called to allocate an array with no elements.
[Moved to DR at 10/01 meeting.]
8.3.6 [dcl.fct.default] paragraph 4 says,
For non-template functions, default arguments can be added in later declarations of a function in the same scope. Declarations in different scopes have completely distinct sets of default arguments. That is, declarations in inner scopes do not acquire default arguments from declarations in outer scopes, and vice versa.It is unclear how this wording applies to friend function declarations. For example,
void f(int, int, int=0); // #1 class C { friend void f(int, int=0, int); // #2 }; void f(int=0, int, int); // #3Does the declaration at #2 acquire the default argument from #1, and does the one at #3 acquire the default arguments from #2?
There are several related questions involved with this issue:
Mike Miller: 8.3.6 [dcl.fct.default] paragraph 4 is speaking about the lexical location of the declaration... The friend declaration occurs in a different declarative region from the declaration at #1, so I would read [this paragraph] as saying that it starts out with a clean slate of default arguments.
Bill Gibbons: Yes. It occurs in a different region, although it declares a name in the same region (i.e. a redeclaration). This is the same as with local externs and is intended to work the same way. We decided that local extern declarations cannot add (beyond the enclosing block) new default arguments, and the same should apply to friend declarations.
John Spicer: The question is whether [this paragraph] does (or should) mean declarations that appear in the same lexical scope or declarations that declare names in the same scope. In my opinion, it really needs to be the latter. It seems somewhat paradoxical to say that a friend declaration declares a function in namespace scope yet the declaration in the class still has its own attributes. To make that work I think you'd have to make friends more like block externs that really do introduce a name into the scope in which the declaration is contained.
Bill Gibbons: In the absence of a declaration visible in class scope to which they could be attached, default arguments on friend declarations do not make sense. [They should be] ill-formed, to prevent surprises.
John Spicer: It is important that the following case work correctly:
class X { friend void f(X x, int i = 1){} }; int main() { X x; f(x); }
In other words, a function first declared in a friend declaration must be permitted to have default arguments and those default arguments must be usable when the function is found by argument dependent lookup. The reason that this is important is that it is common practice to define functions in friend declarations in templates, and that definition is the only place where the default arguments can be specified.
John Spicer: We want to avoid instantiation side effects. IMO, the way to do this would be to prohibit a friend declaration from providing default arguments if a declaration of that function is already visible. Once a function has had a default specified in a friend declaration it should not be possible to add defaults in another declaration be it a friend or normal declaration.
Mike Miller: The position that seems most reasonable to me is to allow default arguments in friend declarations to be used in Koenig lookup, but to say that they are completely unrelated to default arguments in declarations in the surrounding scope; and to forbid use of a default argument in a call if more than one declaration in the overload set has such a default, as in the proposed resolution for issue 1.
Notes from 10/99 meeting:
Four possible outcomes were identified:
The core group eliminated the first and fourth options from consideration, but split fairly evenly between the remaining two.
A straw poll of the full committee yielded the following results (given as number favoring/could live with/"over my dead body"):
Additional discussion is recorded in the "Record of Discussion" for the meeting, J16/99-0036 = WG21 N1212. See also paper J16/00-0040 = WG21 N1263.
Proposed resolution (10/00):
In 8.3.6 [dcl.fct.default], add following paragraph 4:
If a friend declaration specifies a default argument expression, that declaration must be a definition and shall be the only declaration of the function or function template in the translation unit.
[Moved to DR at 4/01 meeting.]
The description of copy-initialization in 8.5 [dcl.init] paragraph 14 says:
struct A { A(A&); }; struct B : A { }; struct C { operator B&(); }; C c; const A a = c; // allowed?
The temporary created with the conversion function is an lvalue of type B. If the temporary must have the cv-qualifiers of the destination type (i.e. const) then the copy-constructor for A cannot be called to create the object of type A from the lvalue of type const B. If the temporary has the cv-qualifiers of the result type of the conversion function, then the copy-constructor for A can be called to create the object of type A from the lvalue of type const B. This last outcome seems more appropriate.
Steve Adamczyk:
Because of late changes to this area, the relevant text is now the third sub-bullet of the fourth bullet of 8.5 [dcl.init] paragraph 14:
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated... The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the destination type. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization.
The issue still remains whether the wording should refer to "the cv-unqualified version of the destination type." I think it should.
Notes from 10/00 meeting:
The original example does not illustrate the remaining problem. The following example does:
struct C { }; C c; struct A { A(const A&); A(const C&); }; const volatile A a = c; // Okay
Proposed Resolution (04/01):
In 8.5 [dcl.init], paragraph 14, bullet 4, sub-bullet 3, change
if the function is a constructor, the call initializes a temporary of the destination type.
to
if the function is a constructor, the call initializes a temporary of the cv-unqualified version of the destination type.
Paragraph 9 of 8.5 [dcl.init] says:
If no initializer is specified for an object, and the object is of (possibly cv-qualified) non-POD class type (or array thereof), the object shall be default-initialized; if the object is of const-qualified type, the underlying class type shall have a user-declared default constructor. Otherwise, if no initializer is specified for an object, the object and its subobjects, if any, have an indeterminate initial value; if the object or any of its subobjects are of const-qualified type, the program is ill-formed.It should be made clear that this paragraph does not apply to static objects.
Proposed resolution (10/00): In 8.5 [dcl.init] paragraph 9, replace
Otherwise, if no initializer is specified for an object..."with
Otherwise, if no initializer is specified for a non-static object...
[Moved to DR at 4/02 meeting.]
Is the temporary created during copy-initialization of a class object treated as an lvalue or an rvalue? That is, is the following example well-formed or not?
struct B { }; struct A { A(A&); // not const A(const B&); }; B b; A a = b;
According to 8.5 [dcl.init] paragraph 14, the initialization of a is performed in two steps. First, a temporary of type A is created using A::A(const B&). Second, the resulting temporary is used to direct-initialize a using A::A(A&).
The second step requires binding a reference to non-const to the temporary resulting from the first step. However, 8.5.3 [dcl.init.ref] paragraph 5 requires that such a reference be bound only to lvalues.
It is not clear from 3.10 [basic.lval] whether the temporary created in the process of copy-initialization should be treated as an lvalue or an rvalue. If it is an lvalue, the example is well-formed, otherwise it is ill-formed.
Proposed resolution (04/01):
In 8.5 [dcl.init] paragraph 14, insert the following after "the call initializes a temporary of the destination type":
The temporary is an rvalue.
In 15.1 [except.throw] paragraph 3, replace
The temporary is used to initialize the variable...
with
The temporary is an lvalue and is used to initialize the variable...
(See also issue 84.)
[Moved to DR at 10/01 meeting.]
The intent of 8.5 [dcl.init] paragraph 5 is that pointers that are zero-initialized will contain a null pointer value. Unfortunately, the wording used,
...set to the value of 0 (zero) converted to T
does not match the requirements for creating a null pointer value given in 4.10 [conv.ptr] paragraph 1:
A null pointer constant is an integral constant expression (5.19 [expr.const]) rvalue of integer type that evaluates to zero. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type...
The problem is that the "value of 0" in the description of zero-initialization is not specified to be an integral constant expression. Nonconstant expressions can also have the value 0, and converting a nonconst 0 to pointer type need not result in a null pointer value.
Proposed resolution (04/01):
In 8.5 [dcl.init] paragraph 5, change
...set to the value 0 (zero) converted to T;
to
...set to the value 0 (zero), taken as an integral constant expression, converted to T; [footnote: as specified in 4.10 [conv.ptr], converting an integral constant expression whose value is 0 to a pointer type results in a null pointer value.]
[Moved to DR at October 2002 meeting.]
We've been looking at implementing value-initialization. At one point, some years back, I remember Bjarne saying that something like X() in an expression should produce an X object with the same value one would get if one created a static X object, i.e., the uninitialized members would be zero-initialized because the whole object is initialized at program startup, before the constructor is called.
The formulation for default-initialization that made it into TC1 (in 8.5 [dcl.init]) is written a little differently (see issue 178), but I had always assumed that it would still be a valid implementation to zero the whole object and then call the default constructor for the troublesome "non-POD but no user-written constructor" cases.
That almost works correctly, but I found a problem case:
struct A { A(); ~A(); }; struct B { // B is a non-POD with no user-written constructor. // It has a nontrivial generated constructor. const int i; A a; }; int main () { // Value-initializing a "B" doesn't call the default constructor for // "B"; it value-initializes the members of B. Therefore it shouldn't // cause an error on generation of the default constructor for the // following: new B(); }
If the definition of the B::B() constructor is generated, an error is issued because the const member "i" is not initialized. But the definition of value-initialization doesn't require calling the constructor, and therefore it doesn't require generating it, and therefore the error shouldn't be detected.
So this is a case where zero-initializing and then calling the constructor is not equivalent to value-initializing, because one case generates an error and the other doesn't.
This is sort of unfortunate, because one doesn't want to generate all the required initializations at the point where the "()" initialization occurs. One would like those initializations to be packaged in a function, and the default constructor is pretty much the function one wants.
I see several implementation choices:
Personally, I find option 1 the least objectionable.
Proposed resolution (10/01):
Add the indicated wording to the third-to-last sentence of 3.2 [basic.def.odr] pararaph 2:
A default constructor for a class is used by default initialization or value initialization as specified in 8.5 [dcl.init].
Add a footnote to the indicated bullet in 8.5 [dcl.init] paragraph 5:
Add the indicated wording to the first sentence of 12.1 [class.ctor] paragraph 7:
An implicitly-declared default constructor for a class is implicitly defined when it is used (3.2 [basic.def.odr]) to create an object of its class type (1.8 [intro.object]).
[Voted into the WP at the September, 2008 meeting (resolution in paper N2762).]
The definition of default initialization (8.5 [dcl.init] paragraph 5) is:
if T is a non-POD class type (clause 9 [class]), the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
if T is an array type, each element is default-initialized;
otherwise, the object is zero-initialized.
However, default initialization is invoked only for non-POD class types and arrays thereof (5.3.4 [expr.new] paragraph 15 for new-expressions, 8.5 [dcl.init] paragraph 10 for top-level objects, and 12.6.2 [class.base.init] paragraph 4 for member and base class subobjects — but see issue 510). Consequently, all cases that invoke default initialization are handled by the first two bullets; the third bullet can never be reached. Its presence is misleading, so it should be removed.
Notes from the September, 2008 meeting:
The approach adopted in the resolution in paper N2762 was different from the suggestion above: it changes the definition of default initialization to include POD types and changes the third bullet to specify that “no initialization is performed.”
[Voted into the WP at the September, 2008 meeting (resolution in paper N2762).]
The wording resulting from the resolution of issue 302 does not quite implement the intent of the issue. The revised wording of 3.2 [basic.def.odr] paragraph 2 is:
A default constructor for a class is used by default initialization or value initialization as specified in 8.5 [dcl.init].
This sounds as if 8.5 [dcl.init] specifies how and under what circumstances value initialization uses a default constructor (which was, in fact, the case for default initialization in the original wording). However, the normative text there makes it plain that value initialization does not call the default constructor (the permission granted to implementations to call the default constructor for value initialization is in a non-normative footnote).
The example that occasioned this observation raises an additional question. Consider:
struct POD { const int x; }; POD data = POD();
According to the (revised) resolution of issue 302, this code is ill-formed because the implicitly-declared default constructor will be implicitly defined as a result of being used by value initialization (12.1 [class.ctor] paragraph 7), and the implicitly-defined constructor fails to initialize a const-qualified member (12.6.2 [class.base.init] paragraph 4). This seems unfortunate, because the (trivial) default constructor of a POD class is otherwise not used — default initialization applies only to non-PODs — and it is not actually needed in value initialization. Perhaps value initialization should be defined to “use” the default constructor only for non-POD classes? If so, both of these problems would be resolved by rewording the above-referenced sentence of 3.2 [basic.def.odr] paragraph 2 as:
A default constructor for a non-POD class is used by default initialization or value initialization as specified in (8.5 [dcl.init]).
Notes from the April, 2006 meeting:
The approach favored by the CWG was to leave 3.2 [basic.def.odr] unchanged and to add normative wording to 8.5 [dcl.init] indicating that it is unspecified whether the default constructor is called.
Notes from the October, 2006 meeting:
The CWG now prefers that it should not be left unspecified whether programs of this sort are well- or ill-formed; instead, the Standard should require that the default constructor be defined in such cases. Three possibilities of implementing this decision were discussed:
Change 3.2 [basic.def.odr] to state flatly that the default constructor is used by value initialization (removing the implication that 8.5 [dcl.init] determines the conditions under which it is used).
Change 8.5 [dcl.init] to specify that non-union class objects with no user-declared constructor are value-initialized by first zero-initializing the object and then calling the (implicitly-defined) default constructor, replacing the current specification of value-initializing each of its sub-objects.
Add a normative statement to 8.5 [dcl.init] that value-initialization causes the implicitly-declared default constructor to be implicitly defined, even if it is not called.
Proposed resolution (June, 2008):
Change the second bullet of the value-initialization definition in 8.5 [dcl.init] paragraph 5 as follows:
if T is a non-union class type without a user-provided constructor, then every non-static data member and base-class component of T is value-initialized; [Footnote: Value-initialization for such a class object may be implemented by zero-initializing the object and then calling the default constructor. —end footnote] the object is zero-initialized and the implicitly-defined default constructor is called;
Notes from the September, 2008 meeting:
The resolution supplied in paper N2762 differs from the June, 2008 proposed resolution in that the implicitly-declared default constructor is only called (and thus defined) if it is non-trivial, making the struct POD example above well-formed.
[Voted into the WP at the April, 2007 meeting as part of paper J16/07-0099 = WG21 N2239.]
A recent GCC bug report ( http://gcc.gnu.org/bugzilla/show_bug.cgi?id=11633) asks about the validity of
int count = 23; int foo[] = { count++, count++, count++ };is this undefined or unspecified or something else? I can find nothing in 8.5.1 [dcl.init.aggr] that indicates whether the components of an initializer-list are evaluated in order or not, or whether they have sequence points between them.
6.7.8/23 of the C99 std has this to say
The order in which any side effects occur among the initialization list expressions is unspecified.I think similar wording is needed in 8.5.1 [dcl.init.aggr]
Steve Adamczyk: I believe the standard is clear that each initializer expression in the above is a full-expression (1.9 [intro.execution]/12-13; see also issue 392) and therefore there is a sequence point after each expression (1.9 [intro.execution]/16). I agree that the standard does not seem to dictate the order in which the expressions are evaluated, and perhaps it should. Does anyone know of a compiler that would not evaluate the expressions left to right?
Mike Simons: Actually there is one, that does not do left to right: gcc/C++. None of the post increment operations take effect until after the statement finishes. So in the sample code gcc stores 23 into all positions in the array. The commercial vendor C++ compilers for AIX, Solaris, Tru64, HPUX (parisc and ia64), and Windows, all do sequence points at each ',' in the initializer list.
[Voted into WP at April, 2007 meeting.]
The current wording of 8.5.1 [dcl.init.aggr] paragraph 8 requires that
An initializer for an aggregate member that is an empty class shall have the form of an empty initializer-list {}.
This is overly constraining. There is no reason that the following should be ill-formed:
struct S { }; S s; S arr[1] = { s };
Mike Miller: The wording of 8.5.1 [dcl.init.aggr] paragraph 8 is unclear. “An aggregate member” would most naturally mean “a member of an aggregate.” In context, however, I think it must mean “a member [of an aggregate] that is an aggregate”, that is, a subaggregate. Members of aggregates need not themselves be aggregates (cf paragraph 13 and 12.6.1 [class.expl.init]); it cannot be the case that an object of an empty class with a user-declared constructor must be initialized with {} when it is a member of an aggregate. This wording should be clarified, regardless of the decision on Nathan's point.
Proposed resolution (October, 2005):
This issue is resolved by the resolution of issue 413.
[Voted into the WP at the June, 2008 meeting as part of paper N2672.]
C (both C90 and C99) appear to allow a declaration of the form
struct S { int i; } s = { { 5 } };
in which the initializer of a scalar member of an aggregate can itself be brace-enclosed. The relevant wording from the C99 Standard is found in 6.7.8 paragraph 11:
The initializer for a scalar shall be a single expression, optionally enclosed in braces.
and paragraph 16:
Otherwise, the initializer for an object that has aggregate or union type shall be a brace-enclosed list of initializers for the elements or named members.
The “list of initializers” in paragraph 16 must be a recursive reference to paragraph 11 (that's the only place that describes how an initialized item gets its value from the initializer expression), which would thus make the “brace-enclosed” part of paragraph 11 apply to each of the initializers in the list in paragraph 16 as well.
This appears to be an incompatibility between C and C++: 8.5.1 [dcl.init.aggr] paragraph 11 says,
If the initializer-list begins with a left brace, then the succeeding comma-separated list of initializer-clauses initializes the members of a subaggregate....
which clearly leaves the impression that only a subaggregate may be initialized by a brace-enclosed initializer-clause.
Either the specification in 8.5.1 [dcl.init.aggr] should be changed to allow a brace-enclosed initializer of a scalar member of an aggregate, as in C, or this incompatibility should be listed in Appendix C [diff].
Notes from the July, 2007 meeting:
It was noted that implementations differ in their handling of this construct; however, the issue is long-standing and fairly obscure.
Notes from the October, 2007 meeting:
The initializer-list proposal will resolve this issue when it is adopted.
[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 temporary via 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):The constructor that would be used to make the copy shall be callable whether or not the copy is actually done. [Example:
- 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 Either bound to 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—end example]
[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 type or cv-qualified class type — also refer to objects.
[Moved to DR at 10/01 meeting.]
With class name injection, when a base class name is used in a derived class, the name found is the injected name in the base class, not the name of the class in the scope containing the base class. Consequently, if the base class name is not accessible (e.g., because is is in a private base class), the base class name cannot be used unless a qualified name is used to name the class in the class or namespace of which it is a member.
Without class name injection the following example is valid. With class name injection, A is inaccessible in class C.
class A { }; class B: private A { }; class C: public B { A* p; // error: A inaccessible };
At the least, the standard should be more explicit that this is, in fact, ill-formed.
(See paper J16/99-0010 = WG21 N1187.)
Proposed resolution (04/01):
Add to the end of 11.1 [class.access.spec] paragraph 3:
[Note: In a derived class, the lookup of a base class name will find the injected-class-name instead of the name of the base class in the scope in which it was declared. The injected-class-name might be less accessible than the name of the base class in the scope in which it was declared.] [Example:
class A { }; class B : private A { }; class C : public B { A* p; // error: injected-class-name A is inaccessible ::A* q; // OK };—end example]
[Moved to DR at October 2002 meeting.]
I think that the definition of a POD class in the current version of the Standard is overly permissive in that it allows for POD classes for which a user-defined operator function operator& may be defined. Given that the idea behind POD classes was to achieve compatibility with C structs and unions, this makes 'Plain old' structs and unions behave not quite as one would expect them to.
In the C language, if x and y are variables of struct or union type S that has a member m, the following expression are allowed: &x, x.m, x = y. While the C++ standard guarantees that if x and y are objects of a POD class type S, the expressions x.m, x = y will have the same effect as they would in C, it is still possible for the expression &x to be interpreted differently, subject to the programmer supplying an appropriate version of a user-defined operator function operator& either as a member function or as a non-member function.
This may result in surprising effects. Consider:
// POD_bomb is a POD-struct. It has no non-static non-public data members, // no virtual functions, no base classes, no constructors, no user-defined // destructor, no user-defined copy assignment operator, no non-static data // members of type pointer to member, reference, non-POD-struct, or // non-POD-union. struct POD_bomb { int m_value1; int m_value2; int operator&() { return m_value1++; } int operator&() const { return m_value1 + m_value2; } };
3.9 [basic.types] paragraph 2 states:
For any complete POD object type T, whether or not the object holds a valid value of type T, the underlying bytes (1.7 [intro.memory]) making up the object can be copied into an array of char or unsigned char [footnote: By using, for example, the library functions (17.6.1.2 [headers]) memcpy or memmove]. If the content of the array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value. [Example:#define N sizeof(T) char buf[N]; T obj; // obj initialized to its original value memcpy(buf, &obj, N); // between these two calls to memcpy, // obj might be modified memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type // holds its original value—end example]
Now, supposing that the complete POD object type T in the example above is POD_bomb, and we cannot any more count on the assertions made in the comments to the example. Given a standard conforming implementation, the code will not even compile. And I see no legal way of copying the contents of an object of a complete object type POD_bomb into an array of char or unsigned char with memcpy or memmove without making use of the unary & operator. Except, of course, by means of an ugly construct like:
struct POD_without_ampersand { POD_bomb a_bomb; } obj; #define N sizeof(POD_bomb) char buf[N]; memcpy(buf, &obj, N); memcpy(&obj, buf, N);
The fact that the definition of a POD class allows for POD classes for which a user-defined operator& is defined, may also present major obstacles to implementers of the offsetof macro from <cstddef>
18.2 [support.types] paragraph 5 says:
The macro offsetof accepts a restricted set of type arguments in this International Standard. type shall be a POD structure or a POD union (clause 9 [class]). The result of applying the offsetof macro to a field that is a static data member or a function is undefined."
Consider a well-formed C++ program below:
#include <cstddef> #include <iostream> struct POD_bomb { int m_value1; int m_value2; int operator&() { return m_value1++; } int operator&() const { return m_value1 + m_value2; } }; // POD_struct is a yet another example of a POD-struct. struct POD_struct { POD_bomb m_nonstatic_bomb1; POD_bomb m_nonstatic_bomb2; }; int main() { std::cout << "offset of m_nonstatic_bomb2: " << offsetof(POD_struct, m_nonstatic_bomb2) << '\n'; return 0; }
See Jens Maurer's paper 01-0038=N1324 for an analysis of this issue.
Notes from 10/01 meeting:
A consensus was forming around the idea of disallowing operator& in POD classes when it was noticed that it is permitted to declare global-scope operator& functions, which cause the same problems. After more discussion, it was decided that such functions should not be prohibited in POD classes, and implementors should simply be required to "get the right answer" in constructs such as offsetof and va_start that are conventionally implemented using macros that use the "&" operator. It was noted that one can cast the original operand to char & to de-type it, after which one can use the built-in "&" safely.
Proposed resolution:
[Footnote: Note that offsetof is required to work as specified even if unary operator& is overloaded for any of the types involved.]
[Footnote: Note that va_start is required to work as specified even if unary operator& is overloaded for the type of parmN.]
[Moved to DR at October 2002 meeting.]
Although 8.3 [dcl.meaning] requires that a declaration of a qualified-id refer to a member of the specified namespace or class and that the member not have been introduced by a using-declaration, it applies only to names declared in a declarator. It is not clear whether there is existing wording enforcing the same restriction for qualified-ids in class-specifiers and elaborated-type-specifiers or whether additional wording is required. Once such wording is found/created, the proposed resolution of issue 275 must be modified accordingly.
Notes from 10/01 meeting:
The sentiment was that this should be required on class definitions, but not on elaborated type specifiers in general (which are references, not declarations). We should also make sure we consider explicit instantiations, explicit specializations, and friend declarations.
Proposed resolution (10/01):
Add after the end of 9.1 [class.name] paragraph 3:
When a nested-name-specifier is specified in a class-head or in an elaborated-type-specifier, the resulting qualified name shall refer to a previously declared member of the class or namespace to which the nested-name-specifier refers, and the member shall not have been introduced by a using-declaration in the scope of the class or namespace nominated by the nested-name-specifier.
[Voted into WP at April, 2007 meeting.]
In 9 [class] paragraph 4, the first sentence says "A structure is a class definition defined with the class-key struct". As far as I know, there is no such thing as a structure in C++; it certainly isn't listed as one of the possible compound types in 3.9.2 [basic.compound]. And defining structures opens the question of whether a forward declaration is a structure or not. The parallel here with union (which follows immediately) suggests that structures and classes are really different things, since the same wording is used, and classes and unions do have some real differences, which manifest themselves outside of the definition. It also suggests that since one can't forward declare union with class and vice versa, the same should hold for struct and class -- I believe that the intent was that one could use struct and class interchangeably in forward declaration.
Suggested resolution:
I suggest something like the following:
If a class is defined with the class-key class, its members and base classes are private by default. If a class is defined with the class-key struct, its members and base classes are public by default. If a class is defined with the class-key union, its members are public by default, and it holds only one data member at a time. Such classes are called unions, and obey a number of additional restrictions, see 9.5 [class.union].
Proposed resolution (April, 2006):
This issue is resolved by the resolution of issue 538.
[Voted into WP at March 2004 meeting.]
The ARM used the term "class declaration" to refer to what would usually be termed the definition of the class. The standard now often uses "class definition", but there are some surviving uses of "class declaration" with the old meaning. They should be found and changed.
Proposed resolution (April 2003):
Replace in 3.1 [basic.def] paragraph 2
A declaration is a definition unless it declares a function without specifying the function's body (8.4 [dcl.fct.def]), it contains the extern specifier (7.1.1 [dcl.stc]) or a linkage-specification [Footnote: Appearing inside the braced-enclosed declaration-seq in a linkage-specification does not affect whether a declaration is a definition. --- end footnote] (7.5 [dcl.link]) and neither an initializer nor a function-body, it declares a static data member in a class declaration definition (9.4 [class.static]), it is a class name declaration (9.1 [class.name]), or it is a typedef declaration (7.1.3 [dcl.typedef]), a using-declaration (7.3.3 [namespace.udecl]), or a using-directive (7.3.4 [namespace.udir]).
Replace in 7.1.2 [dcl.fct.spec] paragraphs 5 and 6
The virtual specifier shall only be used in declarations of nonstatic class member functions that appear within a member-specification of a class declaration definition; see 10.3 [class.virtual].
The explicit specifier shall be used only in declarations of constructors within a class declaration definition; see 12.3.1 [class.conv.ctor].
Replace in 7.1.3 [dcl.typedef] paragraph 4
A typedef-name that names a class is a class-name (9.1 [class.name]). If a typedef-name is used following the class-key in an elaborated-type-specifier (7.1.6.3 [dcl.type.elab]) or in the class-head of a class declaration definition (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]), the program is ill-formed.
Replace in 7.3.1.2 [namespace.memdef] paragraph 3
The name of the friend is not found by simple name lookup until a matching declaration is provided in that namespace scope (either before or after the class declaration definition granting friendship).
Replace in 8.3.2 [dcl.ref] paragraph 4
There shall be no references to references, no arrays of references, and no pointers to references. The declaration of a reference shall contain an initializer (8.5.3 [dcl.init.ref]) except when the declaration contains an explicit extern specifier (7.1.1 [dcl.stc]), is a class member (9.2 [class.mem]) declaration within a class declaration definition, or is the declaration of a parameter or a return type (8.3.5 [dcl.fct]); see 3.1 [basic.def].
Replace in 8.5.3 [dcl.init.ref] paragraph 3
The initializer can be omitted for a reference only in a parameter declaration (8.3.5 [dcl.fct]), in the declaration of a function return type, in the declaration of a class member within its class declaration definition (9.2 [class.mem]), and where the extern specifier is explicitly used.
Replace in 9.1 [class.name] paragraph 2
A class definition declaration introduces the class name into the scope where it is defined declared and hides any class, object, function, or other declaration of that name in an enclosing scope (3.3 [basic.scope]). If a class name is declared in a scope where an object, function, or enumerator of the same name is also declared, then when both declarations are in scope, the class can be referred to only using an elaborated-type-specifier (3.4.4 [basic.lookup.elab]).
Replace in 9.4 [class.static] paragraph 4
Static members obey the usual class member access rules (clause 11 [class.access]). When used in the declaration of a class member, the static specifier shall only be used in the member declarations that appear within the member-specification of the class declaration definition.
Replace in 9.7 [class.nest] paragraph 1
A class can be defined declared within another class. A class defined declared within another is called a nested class. The name of a nested class is local to its enclosing class. The nested class is in the scope of its enclosing class. Except by using explicit pointers, references, and object names, declarations in a nested class can use only type names, static members, and enumerators from the enclosing class.
Replace in 9.8 [class.local] paragraph 1
A class can be defined declared within a function definition; such a class is called a local class. The name of a local class is local to its enclosing scope. The local class is in the scope of the enclosing scope, and has the same access to names outside the function as does the enclosing function. Declarations in a local class can use only type names, static variables, extern variables and functions, and enumerators from the enclosing scope.
Replace in 10 [class.derived] paragraph 1
... The class-name in a base-specifier shall not be an incompletely defined class (clause 9 [class]); this class is called a direct base class for the class being declared defined. During the lookup for a base class name, non-type names are ignored (3.3.10 [basic.scope.hiding]). If the name found is not a class-name, the program is ill-formed. A class B is a base class of a class D if it is a direct base class of D or a direct base class of one of D's base classes. A class is an indirect base class of another if it is a base class but not a direct base class. A class is said to be (directly or indirectly) derived from its (direct or indirect) base classes. [Note: See clause 11 [class.access] for the meaning of access-specifier.] Unless redefined redeclared in the derived class, members of a base class are also considered to be members of the derived class. The base class members are said to be inherited by the derived class. Inherited members can be referred to in expressions in the same manner as other members of the derived class, unless their names are hidden or ambiguous (10.2 [class.member.lookup]). [Note: the scope resolution operator :: (5.1.1 [expr.prim.general]) can be used to refer to a direct or indirect base member explicitly. This allows access to a name that has been redefined redeclared in the derived class. A derived class can itself serve as a base class subject to access control; see 11.2 [class.access.base]. A pointer to a derived class can be implicitly converted to a pointer to an accessible unambiguous base class (4.10 [conv.ptr]). An lvalue of a derived class type can be bound to a reference to an accessible unambiguous base class (8.5.3 [dcl.init.ref]).]
Replace in 10.1 [class.mi] paragraph 5
For another example,for an object c of class type C, a single subobject of type V is shared by every base subobject of c that is declared to have has a virtual base class of type V.class V { /* ... */ }; class A : virtual public V { /* ... */ }; class B : virtual public V { /* ... */ }; class C : public A, public B { /* ... */ };
Replace in the example in 10.2 [class.member.lookup] paragraph 6 (the whole paragraph was turned into a note by the resolution of core issue 39)
The names defined declared in V and the left hand instance of W are hidden by those in B, but the names defined declared in the right hand instance of W are not hidden at all.
Replace in 10.4 [class.abstract] paragraph 2
... A virtual function is specified pure by using a pure-specifier (9.2 [class.mem]) in the function declaration in the class declaration definition. ...
Replace in the footnote at the end of 11.2 [class.access.base] paragraph 1
[Footnote: As specified previously in clause 11 [class.access], private members of a base class remain inaccessible even to derived classes unless friend declarations within the base class declaration definition are used to grant access explicitly.]
Replace in _N3225_.11.3 [class.access.dcl] paragraph 1
The access of a member of a base class can be changed in the derived class by mentioning its qualified-id in the derived class declaration definition. Such mention is called an access declaration. ...
Replace in the example in 13.4 [over.over] paragraph 5
The initialization of pfe is ill-formed because no f() with type int(...) has been defined declared, and not because of any ambiguity.
Replace in C.1.6 [diff.dcl] paragraph 1
Rationale: Storage class specifiers don't have any meaning when associated with a type. In C++, class members can be defined declared with the static storage class specifier. Allowing storage class specifiers on type declarations could render the code confusing for users.
Replace in C.1.8 [diff.class] paragraph 3
In C++, a typedef name may not be redefined redeclared in a class declaration definition after being used in the declaration that definitionDrafting notes:
The resolution of core issue 45 (DR) deletes 11.7 [class.access.nest] paragraph 2.
The following occurrences of "class declaration" are not changed:
[Voted into WP at March 2004 meeting.]
The standard (9 [class] par. 4) says that "A POD-struct is an aggregate class that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-defined copy assignment operator and no user-defined destructor."
Note that it says 'user-defined', not 'user-declared'. Is it the intent that if e.g. a copy assignment operator is declared but not defined, this does not (per se) prevent the class to be a POD-struct?
Proposed resolution (April 2003):
Replace in 9 [class] paragraph 4
A POD-struct is an aggregate class that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-defined declared copy assignment operator and no user-defined declared destructor. Similarly, a POD-union is an aggregate union that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-defined declared copy assignment operator and no user-defined declared destructor.
Drafting note: The changes are shown relative to TC1, incorporating the changes from the resolution of core issue 148.
[Voted into WP at April, 2007 meeting.]
The proposal says that value is true if "T is an empty class (10)". Clause 10 doesn't define an empty class, although it has a note that says a base class may "be of zero size (clause 9)" 9/3 says "Complete objects and member subobjects of class type shall have nonzero size." This has a footnote, which says "Base class subobjects are not so constrained."
The standard uses the term "empty class" in two places (8.5.1 [dcl.init.aggr]), but neither of those places defines it. It's also listed in the index, which refers to the page that opens clause 9, i.e. the nonzero size stuff cited above.
So, what's the definition of "empty class" that determines whether the predicate is_empty is true?
The one place where it's used is 8.5.1 [dcl.init.aggr] paragraph 8, which says (roughly paraphrased) that an aggregate initializer for an empty class must be "{}", and when such an initializer is used for an aggregate that is not an empty class the members are default-initialized. In this context it's pretty clear what's meant. In the type traits proposal it's not as clear, and it was probably intended to have a different meaning. The boost implementation, after it eliminates non-class types, determines whether the trait is true by comparing the size of a class derived from T to the size of an otherwise-identical class that is not derived from T.
Howard Hinnant: is_empty was created to find out whether a type could be derived from and have the empty base class optimization successfully applied. It was created in part to support compressed_pair which attempts to optimize away the space for one of its members in an attempt to reduce spatial overhead. An example use is:
template <class T, class Compare = std::less<T> > class SortedVec { public: ... private: T* data_; compressed_pair<Compare, size_type> comp_; Compare& comp() {return comp_.first();} const Compare& comp() const {return comp_.first();} size_type& sz() {return comp_.second();} size_type sz() const {return comp_.second();} };
Here the compare function is optimized away via the empty base optimization if Compare turns out to be an "empty" class. If Compare turns out to be a non-empty class, or a function pointer, the space is not optimized away. is_empty is key to making this work.
This work built on Nathan's article: http://www.cantrip.org/emptyopt.html.
Clark Nelson: I've been looking at issue 413, and I've reached the conclusion that there are two different kinds of empty class. A class containing only one or more anonymous bit-field members is empty for purposes of aggregate initialization, but not (necessarily) empty for purposes of empty base-class optimization.
Of course we need to add a definition of emptiness for purposes of aggregate initialization. Beyond that, there are a couple of questions:
Notes from the October, 2005 meeting:
There are only two places in the Standard where the phrase “empty class” appears, both in 8.5.1 [dcl.init.aggr] paragraph 8. Because it is not clear whether the definition of “empty for initialization purposes” is suitable for use in defining the is_empty predicate, it would be better just to avoid using the phrase in the language clauses. The requirements of 8.5.1 [dcl.init.aggr] paragraph 8 appear to be redundant; paragraph 6 says that an initializer-list must have no more initializers than the number of elements to initialize, so an empty class already requires an empty initializer-list, and using an empty initializer-list with a non-empty class is covered adequately by paragraph 7's description of the handling of an initializer-list with fewer initializers than the number of members to initialize.
Proposed resolution (October, 2005):
Change
Static data members and anonymous bit fields are not considered members of the class for purposes of aggregate initialization. [Example:
struct A { int i; static int s; int j; int :17; int k; } a = { 1 , 2 , 3 };Here, the second initializer 2 initializes a.j and not the static data member A::s, and the third initializer 3 initializes a.k and not the padding before it. —end example]
Delete 8.5.1 [dcl.init.aggr] paragraph 8:
An initializer for an aggregate member that is an empty class shall have the form of an empty initializer-list {}. [Example:
struct S { }; struct A { S s; int i; } a = { { } , 3 };—end example] An empty initializer-list can be used to initialize any aggregate. If the aggregate is not an empty class, then each member of the aggregate shall be initialized with a value of the form T() (5.2.3 [expr.type.conv]), where T represents the type of the uninitialized member.
This resolution also resolves issue 491.
Additional note (October, 2005):
Deleting 8.5.1 [dcl.init.aggr] paragraph 8 altogether may not be a good idea. It would appear that, in its absence, the initializer elision rules of paragraph 11 would allow the initializer for a in the preceding example to be written { 3 } (because the empty-class member s would consume no initializers from the list).
Proposed resolution (October, 2006):
(Drafting note: this resolution also cleans up incorrect references to syntactic non-terminals in the nearby paragraphs.)
Change 8.5.1 [dcl.init.aggr] paragraph 4 as indicated:
An array of unknown size initialized with a brace-enclosed initializer-list containing n initializers initializer-clauses, where n shall be greater than zero... An empty initializer list {} shall not be used as the initializer initializer-clause for an array of unknown bound.
Change
Static data members and anonymous bit fields are not considered members of the class for purposes of aggregate initialization. [Example:
struct A { int i; static int s; int j; int :17; int k; } a = { 1 , 2 , 3 };Here, the second initializer 2 initializes a.j and not the static data member A::s, and the third initializer 3 initializes a.k and not the anonymous bit field before it. —end example]
Change 8.5.1 [dcl.init.aggr] paragraph 6 as indicated:
An initializer-list is ill-formed if the number of initializers initializer-clauses exceeds the number of members...
Change 8.5.1 [dcl.init.aggr] paragraph 7 as indicated:
If there are fewer initializers initializer-clauses in the list than there are members...
Replace 8.5.1 [dcl.init.aggr] paragraph 8:
An initializer for an aggregate member that is an empty class shall have the form of an empty initializer-list {}. [Example:
struct S { }; struct A { S s; int i; } a = { { } , 3 };—end example] An empty initializer-list can be used to initialize any aggregate. If the aggregate is not an empty class, then each member of the aggregate shall be initialized with a value of the form T() (5.2.3 [expr.type.conv]), where T represents the type of the uninitialized member.
with:
If an aggregate class C contains a subaggregate member m that has no members for purposes of aggregate initialization, the initializer-clause for m shall not be omitted from an initializer-list for an object of type C unless the initializer-clauses for all members of C following m are also omitted. [Example:
struct S { } s; struct A { S s1; int i1; S s2; int i2; S s3; int i3; } a = { { }, // Required initialization 0, s, // Required initialization 0 }; // Initialization not required for A::s3 because A::i3 is also not initialized—end example]
Change 8.5.1 [dcl.init.aggr] paragraph 10 as indicated:
When initializing a multi-dimensional array, the initializers initializer-clauses initialize the elements...
Change 8.5.1 [dcl.init.aggr] paragraph 11 as indicated:
Braces can be elided in an initializer-list as follows. If the initializer-list begins with a left brace, then the succeeding comma-separated list of initializers initializer-clauses initializes the members of a subaggregate; it is erroneous for there to be more initializers initializer-clauses than members. If, however, the initializer-list for a subaggregate does not begin with a left brace, then only enough initializers initializer-clauses from the list are taken to initialize the members of the subaggregate; any remaining initializers initializer-clauses are left to initialize the next member of the aggregate of which the current subaggregate is a member. [Example:...
Change 8.5.1 [dcl.init.aggr] paragraph 12 as indicated:
All implicit type conversions (clause 4 [conv]) are considered when initializing the aggregate member with an initializer from an initializer-list assignment-expression. If the initializer assignment-expression can initialize a member, the member is initialized. Otherwise, if the member is itself a non-empty subaggregate, brace elision is assumed and the initializer assignment-expression is considered for the initialization of the first member of the subaggregate. [Note: As specified above, brace elision cannot apply to subaggregates with no members for purposes of aggregate initialization; an initializer-clause for the entire subobject is required. —end note] [Example:... Braces are elided around the initializer initializer-clause for b.a1.i...
Change 8.5.1 [dcl.init.aggr] paragraph 15 as indicated:
When a union is initialized with a brace-enclosed initializer, the braces shall only contain an initializer initializer-clause for the first member of the union...
Change 8.5.1 [dcl.init.aggr] paragraph 16 as indicated:
[Note: as As described above, the braces around the initializer initializer-clause for a union member can be omitted if the union is a member of another aggregate. —end note]
(Note: this resolution also resolves issue 491.)
[Voted into WP at April, 2007 meeting.]
There are several problems with the terms defined in 9 [class] paragraph 4:
A structure is a class defined with the class-key struct; its members and base classes (clause 10 [class.derived]) are public by default (clause 11 [class.access]). A union is a class defined with the class-key union; its members are public by default and it holds only one data member at a time (9.5 [class.union]). [Note: aggregates of class type are described in 8.5.1 [dcl.init.aggr]. —end note] A POD-struct is an aggregate class that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-declared copy assignment operator and no user-declared destructor. Similarly, a POD-union is an aggregate union that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-declared copy assignment operator and no user-declared destructor. A POD class is a class that is either a POD-struct or a POD-union.
Although the term structure is defined here, it is used only infrequently throughout the Standard, often apparently inadvertently and consequently incorrectly:
5.2.5 [expr.ref] paragraph 4: the use is in a note and is arguably correct and helpful.
9.2 [class.mem] paragraph 11: the term is used (three times) in an example. There appears to be no reason to use it instead of “class,” but its use is not problematic.
17.3 [definitions] “iostream class templates:” the traits argument to the iostream class templates is (presumably unintentionally) constrained to be a structure, i.e., to use the struct keyword and not the class keyword in its definition.
B [implimits] paragraph 2: the minimum number of declarator operators is given for structures and unions but not for classes defined using the class keyword.
B [implimits] paragraph 2: class, structure, and union are used as disjoint terms in describing nesting levels. (The nonexistent nonterminal struct-declaration-list is used, as well.)
There does not appear to be a reason for defining the term structure. The one reference where it is arguably useful, in the note in 5.2.5 [expr.ref], could be rewritten as something like, “'class objects' may be defined using the class, struct, or union class-keys; see clause 9 [class].”
Based on its usage later in the paragraph and elsewhere, “POD-struct” appears to be intended to exclude unions. However, the definition of “aggregate class” in 8.5.1 [dcl.init.aggr] paragraph 1 includes unions. Furthermore, the name itself is confusing, leading to the question of whether it was intended that only classes defined using struct could be POD-structs or if class-classes are included. The definition should probably be rewritten as, “A POD-struct is an aggregate class defined with the class-key struct or the class-key class that has no...
In most references outside clause 9 [class], POD-struct and POD-union are mentioned together and treated identically. These references should be changed to refer to the unified term, “POD class.”
Noted in passing: 18.2 [support.types] paragraph 4 refers to the undefined terms “POD structure” and (unhyphenated) “POD union;” the pair should be replaced by a single reference to “POD class.”
Proposed resolution (April, 2006):
Change 9 [class] paragraph 4 as indicated:
A structure is a class defined with the class-key struct; its members and base classes (clause 10 [class.derived]) are public by default (clause 11 [class.access]). A union is a class defined with the class-key union; its members are public by default and it holds only one data member at a time (9.5 [class.union]). [Note: aggregates of class type are described in 8.5.1 [dcl.init.aggr]. —end note] A POD-struct is an aggregate class that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-declared copy assignment operator and no user-declared destructor. Similarly, a POD-union is an aggregate union that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-declared copy assignment operator and no user-declared destructor. A POD class is a class that is either a POD-struct or a POD-union. A POD class is an aggregate class that has no non-static data members of non-POD type (or array of such a type) or reference, and has no user-declared copy assignment operator and no user-declared destructor. A POD-struct is a POD class defined with the class-key struct or the class-key class. A POD-union is a POD class defined with the class-key union.
Change 11.2 [class.access.base] paragraph 2 as indicated:
In the absence of an access-specifier for a base class, public is assumed when the derived class is declared defined with the class-key struct and private is assumed when the class is declared defined with the class-key class. [Example:...
Delete the note in 5.2.5 [expr.ref] paragraph 4:
[Note: “class objects” can be structures (9.2 [class.mem]) and unions (9.5 [class.union]). Classes are discussed in clause 9 [class]. —end note]
Change the commentary in the example in 9.2 [class.mem] paragraph 11 as indicated:
...an integer, and two pointers to similar structures objects of the same type. Once this definition...
...the count member of the structure object to which sp points; s.left refers to the left subtree pointer of the structure object s; and...
Change 17.3 [definitions] “iostream class templates” as indicated:
...the argument traits is a structure class which defines additional characteristics...
Change 18.6 [support.dynamic] paragraph 4 as indicated:
If type is not a POD structure or a POD union POD class (clause 9), the results are undefined.
Change the third bullet of B [implimits] paragraph 2 as indicated:
Pointer, array, and function declarators (in any combination) modifying an a class, arithmetic, structure, union, or incomplete type in a declaration [256].
Change the nineteenth bullet of B [implimits] paragraph 2 as indicated:
Data members in a single class, structure, or union [16 384].
Change the twenty-first bullet of B [implimits] paragraph 2 as indicated:
Levels of nested class, structure, or union definitions in a single struct-declaration-list member-specification [256].
Change C.4 [diff.library] paragraph 6 as indicated:
The C++ Standard library provides 2 standard structures structs from the C library, as shown in Table 126.
Change the last sentence of 3.9 [basic.types] paragraph 10 as indicated:
Scalar types, POD-struct types, POD-union types POD classes (clause 9 [class]), arrays of such types and cv-qualified versions of these types (3.9.3 [basic.type.qualifier]) are collectively called POD types.
Drafting note: Do not change 3.9 [basic.types] paragraph 11, because it's a note and the definition of “layout-compatible” is separate for POD-struct and POD-union in 9.2 [class.mem].
(This resolution also resolves issue 327.)
[Voted into the WP at the July, 2007 meeting as part of paper J16/07-0202 = WG21 N2342.]
A POD struct (9 [class] paragraph 4) is “an aggregate class that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types), or reference, and that has no user-defined copy assignment operator and no user-defined destructor.” Meanwhile, an aggregate class (8.5.1 [dcl.init.aggr] paragraph 1) must have “no user-declared constructors, no private or protecte non-static data members, no base classes, and no virtual functions.”
This is too strict. The whole reason we define the notion of POD is for the layout compatibility guarantees in 9.2 [class.mem] paragraphs 14-17 and the byte-for-byte copying guarantees of 3.9 [basic.types] paragraph 2. None of those guarantees should be affected by the presence of ordinary constructors, any more than they're affected by the presence of any other member function. It’s silly for the standard to make layout and memcpy guarantees for this class:
struct A { int n; };
but not for this one:
struct B { int n; B(n_) : n(n_) { } };
With either A or B, it ought to be possible to save an array of those objects to disk with a single call to Unix’s write(2) system call or the equivalent. At present the standard says that it’s legal for A but not B, and there isn’t any good reason for that distinction.
Suggested resolution:
The following doesn’t fix all problems (in particular it still doesn’t let us treat pair<int, int> as a POD), but at least it goes a long way toward fixing the problem: in 8.5.1 [dcl.init.aggr] paragraph 1, change “no user-declared constructors” to “no nontrivial default constructor and no user-declared copy constructor.”
(Yes, I’m aware that this proposed change would also allow brace initialization for some types that don't currently allow it. I consider this to be a feature, not a bug.)
Mike Miller: I agree that something needs to be done about “POD,” but I’m not sure that this is it. My own take is that “POD” is used for too many different things — things that are related but not identical — and the concept should be split. The current definition is useful, as is, for issues regarding initialization and lifetime. For example, I wouldn’t want to relax the prohibition of jumping over a constructor call in 6.7 [stmt.dcl] (which is currently phrased in terms of POD types). On the other hand, I agree that the presence of a user-declared constructor says nothing about layout and bitwise copying. This needs (IMHO) a non-trivial amount of further study to determine how many categories we need (instead of just POD versus non-POD), which guarantees and prohibitions go with which category, the interaction of “memcpy initialization” (for want of a better term) with object lifetime, etc.
(See paper J16/06-0172 = WG21 N2102.)
Proposed resolution (April, 2007):
Adoption of the POD proposal (currently J16/07-0090 = WG21 N2230) will resolve this issue.
[Voted into WP at October 2004 meeting.]
We had a user complain that our compiler was allowing the following code:
struct B { struct S; }; struct D : B { }; struct D::S { };
We took one look at the code and made the reasonable (I would claim) assumption that this was indeed a bug in our compiler. Especially as we had just fixed a very similar issue with the definition of static data members.
Imagine our surprise when code like this showed up in Boost and that every other compiler we tested accepts this code. So is this indeed legal (it seems like it must be) and if so is there any justification for this beyond 3.4.3.1 [class.qual]?
John Spicer: The equivalent case for a member function is covered by the declarator rules in 8.3 [dcl.meaning] paragraph 1. The committee has previously run into cases where a restriction should apply to both classes and non-classes, but fails to do so because there is no equivalent of 8.3 [dcl.meaning] paragraph 1 for classes.
Given that, by the letter of the standard, I would say that this case is allowed.
Notes from October 2003 meeting:
We feel this case should get an error.
Proposed Resolution (October 2003):
Note that the change here interacts with issue 432.
Add the following as a new paragraph immediately following 3.3.2 [basic.scope.pdecl] paragraph 2:
The point of declaration for a class first declared by a class-specifier is immediately after the identifier or template-id (if any) in its class-head (Clause 9 [class]). The point of declaration for an enumeration is immediately after the identifier (if any) in its enum-specifier (7.2 [dcl.enum]).
Change point 1 of 3.3.7 [basic.scope.class] paragraph 1 to read:
The potential scope of a name declared in a class consists not only of the declarative region following the name's declarator point of declaration, but also of all function bodies, default arguments, and constructor ctor-initializers in that class (including such things in nested classes).
[Note that the preceding change duplicates one of the changes in the proposed resolution of issue 432.]
Change 14.7.2 [temp.explicit] paragraph 2 to read:
If the explicit instantiation is for a member function, a member class or a static data member of a class template specialization, the name of the class template specialization in the qualified-id for the member declarator name shall be a template-id.
Add the following as paragraph 5 of Clause 9 [class]:
If a class-head contains a nested-name-specifier, the class-specifier shall refer to a class that was previously declared directly in the class or namespace to which the nested-name-specifier refers (i.e., neither inherited nor introduced by a using-declaration), and the class-specifier shall appear in a namespace enclosing the previous declaration.
Delete 9.1 [class.name] paragraph 4 (this was added by issue 284):
When a nested-name-specifier is specified in a class-head or in an elaborated-type-specifier, the resulting qualified name shall refer to a previously declared member of the class or namespace to which the nested-name-specifier refers, and the member shall not have been introduced by a using-declaration in the scope of the class or namespace nominated by the nested-name-specifier.
[Voted into WP at March 2004 meeting.]
Is it legal to use an incomplete type (3.9 [basic.types] paragraph 6) as a class member, if no object of such class is ever created ?
And as a class template member, even if the template is instantiated, but no object of the instantiated class is created?
The consensus seems to be NO, but no wording was found in the standard which explicitly disallows it.
The problem seems to be that most of the restrictions on incomplete types are on their use in objects, but class members are not objects.
A possible resolution, if this is considered a defect, is to add to 3.2 [basic.def.odr] paragraph 4, (situations when T must be complete), the use of T as a member of a class or instantiated class template.
The thread on comp.std.c++ which brought up the issue was "Compiler differences: which is correct?", started 2001 11 30. <[email protected]>
Proposed Resolution (April 2002, revised April 2003):
Change the first bullet of the note in 3.2 [basic.def.odr] paragraph 4 and add two new bullets following it, as follows:
Replace 9.2 [class.mem] paragraph 8 by:
Non-static (9.4 [class.static]) data members shall not have incomplete types. In particular, a class C shall not contain a non-static member of class C, but it can contain a pointer or reference to an object of class C.
See also 3.9 [basic.types] paragraph 6, which is relevant but not changed by the Proposed Resolution.
[Voted into WP at April 2005 meeting.]
I've encountered a C++ program in which a member function wants to declare that it may throw an object of its own class, e.g.:
class Foo { private: int val; public: Foo( int &initval ) { val = initval; }; void throwit() throw(Foo) { throw (*this); }; };
The compiler is complaining that Foo is an incomplete type, and can't be used in the exception specification.
My reading of the standard [basic.types] is inconclusive. Although it does state that the class declaration is considered complete when the closing brace is read, I believe it also intends that the member function declarations should not be semantically validated until the class has been completely declared.
If this isn't allowed, I don't know how else a member function could be declared to throw an object of its own class.
John Spicer: The type is considered complete within function bodies, but not in their declaration (see 9.2 [class.mem] paragraph 2).
Proposed Resolution:
Change 9.2 [class.mem] paragraph 2 as follows:
Within the class member-specification, the class is regarded as complete within function bodies, default arguments, exception-specifications, and constructor ctor-initializers (including such things in nested classes).
Rationale: Taken with 8.3.5 [dcl.fct] paragraph 6, the exception-specification is the only part of a function declaration/definition in which the class name cannot be used because of its putative incompleteness. There is no justification for singling out exception specifications this way; both in the function body and in a catch clause, the class type will be complete, so there is no harm in allowing the class name to be used in the exception-specification.
[Voted into WP at April, 2007 meeting.]
According to 9.2 [class.mem] paragraph 9, the name of a non-static data member can only be used with an object reference (explicit or implied by the this pointer of a non-static member function) or to form a pointer to member. This restriction applies even in the operand of sizeof, although the operand is not evaluated and thus no object is needed to perform the operation. Consequently, determining the size of a non-static class member often requires a circumlocution like
sizeof ((C*) 0)->m
instead of the simpler and more obvious (but incorrect)
sizeof (C::m)
The CWG considered this question as part of issue 198 and decided at that time to retain the restriction on consistency grounds: the rule was viewed as applying uniformly to expressions, and making an exception for sizeof would require introducing a special-purpose “wart.”
The issue has recently resurfaced, in part due to the fact that the restriction would also apply to the decltype operator. Like the unary & operator to form a pointer to member, sizeof and decltype need neither an lvalue nor an rvalue, requiring solely the declarative information of the named operand. One possible approach would be to define the concept of “unevaluated operand” or the like, exempt unevaluated operands from the requirement for an object reference in 9.2 [class.mem] paragraph 9, and then define the operands of these operators as “unevaluated.”
Proposed resolution (April, 2007):
The wording is given in paper J16/07-0113 = WG21 N2253.
[Voted into the WP at the July, 2007 meeting as part of paper J16/07-0202 = WG21 N2342.]
It should be made clear in 9.2 [class.mem] paragraph 15,
Two POD-struct (clause 9 [class]) types are layout-compatible if they have the same number of non-static data members, and corresponding non-static data members (in order) have layout-compatible types (3.9 [basic.types]).
that “corresponding... (in order)” refers to declaration order and not the order in which the members are laid out in memory.
However, this raises the point that, in cases where an access-specifier is involved, the declaration and layout order can be different (see paragraph 12). Thus, for two POD-struct classes A and B,
struct A { char c; int i; } struct B { char c; public: int i; };
a compiler could move B::i before B::c, but A::c must precede A::i. It does not seem reasonable that these two POD-structs would be considered layout-compatible, even though they satisfy the requirement that corresponding members in declaration order are layout-compatible.
One possibility would be to require that neither POD-struct have an access-specifier in order to be considered layout-compatible. (It's not sufficient to require that they have the same access-specifiers, because the compiler is not required to lay out the storage the same way for different classes.)
8.5.1 [dcl.init.aggr] paragraph 2 should also be clarified to make explicit that “increasing... member order” refers to declaration order.
Proposed resolution (April, 2007):
This issue will be resolved by the adoption of the POD proposal (currently J16/07-0090 = WG21 N2230). That paper does not propose a change to the wording of 8.5.1 [dcl.init.aggr] paragraph 2, but the CWG feels that the intent of that paragraph (that the initializers are used in declaration order) is clear enough not to require revision.
[Voted into WP at July, 2007 meeting.]
9.3.2 [class.this] paragraph 1, which specifies the meaning of the keyword 'this', seems to limit its usage to the *body* of non-static member functions. However 'this' is also usable in ctor-initializers which, according to the grammar in 8.4 [dcl.fct.def] par. 1, are not part of the body.
Proposed resolution: Changing the first part of 9.3.2 [class.this] par. 1 to:
In the body of a nonstatic (9.3) member function or in a ctor-initializer (12.6.2), the keyword this is a non-lvalue expression whose value is the address of the object for which the function is called.
NOTE: I'm talking of constructors as functions that are "called"; there have been discussions on c.l.c++.m as to whether constructors are "functions" and to whether this terminology is correct or not; I think it is both intuitive and in agreement with the standard wording.
Steve Adamczyk: See also issue 397, which is defining a new syntax term for the body of a function including the ctor-initializers.
Notes from the March 2004 meeting:
This will be resolved when issue 397 is resolved.
Proposed resolution (October, 2005):
Change 8.4 [dcl.fct.def] paragraph 1 as indicated:
Function definitions have the form
function-definition:
decl-specifier-seqopt declarator ctor-initializeropt function-body
decl-specifier-seqopt declarator function-try-block
function-body:ctor-initializeropt compound-statement
function-try-block
An informal reference to the body of a function should be interpreted as a reference to the nonterminal function-body.
Change the definition of function-try-block in 15 [except] paragraph 1:
function-try-block:
try ctor-initializeropt function-body compound-statement handler-seq
Change 3.3.7 [basic.scope.class] paragraph 1, point 1, as indicated:
The potential scope of a name declared in a class consists not only of the declarative region following the name's point of declaration, but also of all function bodies, bodies and default arguments, and constructor ctor-initializers in that class (including such things in nested classes).
Change 3.3.7 [basic.scope.class] paragraph 1, point 5, as indicated:
The potential scope of a declaration that extends to or past the end of a class definition also extends to the regions defined by its member definitions, even if the members are defined lexically outside the class (this includes static data member definitions, nested class definitions, member function definitions (including the member function body and, for constructor functions (12.1 [class.ctor]), the ctor-initializer (12.6.2 [class.base.init] )) and any portion of the declarator part of such definitions which follows the identifier, including a parameter-declaration-clause and any default arguments (8.3.6 [dcl.fct.default]). [Example:...
Change footnote 32 in 3.4.1 [basic.lookup.unqual] paragraph 8 as indicated:
That is, an unqualified name that occurs, for instance, in a type or default argument expression in the parameter-declaration-clause, parameter-declaration-clause or in the function body, or in an expression of a mem-initializer in a constructor definition.
Change 5.1.1 [expr.prim.general] paragraph 3 as indicated:
...The keyword this shall be used only inside a non-static class member function body (9.3 [class.mfct]) or in a constructor mem-initializer (12.6.2 [class.base.init])...
Change 9.2 [class.mem] paragraph 2 as indicated:
...Within the class member-specification, the class is regarded as complete within function bodies, default arguments, and exception-specifications, and constructor ctor-initializers (including such things in nested classes)...
Change 9.2 [class.mem] paragraph 9 as indicated:
Each occurrence in an expression of the name of a non-static data member or non-static member function of a class shall be expressed as a class member access (5.2.5 [expr.ref]), except when it appears in the formation of a pointer to member (5.3.1 [expr.unary.op]), or or when it appears in the body of a non-static member function of its class or of a class derived from its class (9.3.1 [class.mfct.non-static]), or when it appears in a mem-initializer for a constructor for its class or for a class derived from its class (12.6.2 [class.base.init]).
Change the note in 9.3 [class.mfct] paragraph 5 as indicated:
[Note: a name used in a member function definition (that is, in the parameter-declaration-clause including the default arguments (8.3.6 [dcl.fct.default]), or or in the member function body, or, for a constructor function (12.1 [class.ctor]), in a mem-initializer expression (12.6.2 [class.base.init])) is looked up as described in 3.4 [basic.lookup]. —end note]
Change 9.3.1 [class.mfct.non-static] paragraph 1 as indicated:
...A non-static member function may also be called directly using the function call syntax (5.2.2 [expr.call], 13.3.1.1 [over.match.call]) from within the body of a member function of its class or of a class derived from its class.
- from within the body of a member function of its class or of a class derived from its class, or
- from a mem-initializer (12.6.2 [class.base.init]) for a constructor for its class or for a class derived from its class.
Change 9.3.1 [class.mfct.non-static] paragraph 3 as indicated:
When an id-expression (5.1.1 [expr.prim.general]) that is not part of a class member access syntax (5.2.5 [expr.ref]) and not used to form a pointer to member (5.3.1 [expr.unary.op]) is used in the body of a non-static member function of class X or used in the mem-initializer for a constructor of class X, if name lookup (3.4.1 [basic.lookup.unqual]) resolves the name in the id-expression to a non-static non-type member of class X or of a base class of X, the id-expression is transformed into a class member access expression (5.2.5 [expr.ref]) using (*this) (9.3.2 [class.this]) as the postfix-expression to the left of the . operator...
Change 12.1 [class.ctor] paragraph 7 as indicated:
...The implicitly-defined default constructor performs the set of initializations of the class that would be performed by a user-written default constructor for that class with an empty mem-initializer-list no ctor-initializer (12.6.2 [class.base.init]) and an empty function body compound-statement...
Change 12.6.2 [class.base.init] paragraph 4 as indicated:
...After the call to a constructor for class X has completed, if a member of X is neither specified in the constructor's mem-initializers, nor default-initialized, nor value-initialized, nor given a value during execution of the compound-statement of the body of the constructor, the member has indeterminate value.
Change the last bullet of 12.6.2 [class.base.init] paragraph 5 as indicated:
Finally, the body compound-statement of the constructor body is executed.
Change 15 [except] paragraph 4 as indicated:
A function-try-block associates a handler-seq with the ctor-initializer, if present, and the function-body compound-statement. An exception thrown during the execution of the initializer expressions in the ctor-initializer or during the execution of the function-body compound-statement transfers control to a handler in a function-try-block in the same way as an exception thrown during the execution of a try-block transfers control to other handlers. [Example:
int f(int); class C { int i; double d; public: C(int, double); }; C::C(int ii, double id) try : i(f(ii)), d(id) { // constructor function body statements } catch (...) { // handles exceptions thrown from the ctor-initializer // and from the constructor function body statements }—end example]
Change 15.2 [except.ctor] paragraph 2 as indicated:
When an exception is thrown, control is transferred to the nearest handler with a matching type (15.3 [except.handle]); “nearest” means the handler for which the compound-statement, compound-statement or ctor-initializer, or function-body following the try keyword was most recently entered by the thread of control and not yet exited.
[Voted into WP at March 2004 meeting.]
The following test program is claimed to be a negative C++ test case for "Unnamed classes shall not contain static data members", c.f. ISO/IEC 14882 section 9.4.2 [class.static.data] paragraph 5.
struct B { typedef struct { static int i; // Is this legal C++ ? } A; }; int B::A::i = 47; // Is this legal C++ ?
We are not quite sure about what an "unnamed class" is. There is no exact definition in ISO/IEC 14882; the closest we can come to a hint is the wording of section 7.1.3 [dcl.typedef] paragraph 5, where it seems to be understood that a class-specifier with no identifier between "class" and "{" is unnamed. The identifier provided after "}" ( "A" in the test case above) is there for "linkage purposes" only.
To us, class B::A in the test program above seems "named" enough, and there is certainly a mechanism to provide the definition for B::A::i (in contrast to the note in section 9.4.2 [class.static.data] paragraph 5).
Our position is therefore that the above test program is indeed legal C++. Can you confirm or reject this claim?
Herb Sutter replied to the submitter as follows: Here are my notes based on a grep for "unnamed class" in the standard:
a named class (clause class), or an unnamed class defined in a typedef declaration in which the class has the typedef name for linkage purposes (7.1.3 [dcl.typedef]);Likewise in your example, you have an unnamed class defined in a typedef declaration.
So yes, an unnamed class is one where there is no identifier (class name) between the class-key and the {. This is also in harmony with the production for class-name in 9 [class] paragraph 1:
Notes from the October 2003 meeting:
We agree that the example is not valid; this is an unnamed class. We will add wording to define an unnamed class. The note in 9.4.2 [class.static.data] paragraph 5 should be corrected or deleted.
Proposed Resolution (October 2003):
At the end of clause 9 [class], paragraph 1, add the following:
A class-specifier where the class-head omits the optional identifier defines an unnamed class.
Delete the following from 9.4.2 [class.static.data] paragraph 5:
[ Note: this is because there is no mechanism to provide the definitions for such static data members. ]
[Voted into WP at the October, 2006 meeting.]
As a result of the resolution of core issue 48, the current C++ standard is not in sync with existing practice and with user expectations as far as definitions of static data members having const integral or const enumeration type are concerned. Basically what current implementations do is to require a definition only if the address of the constant is taken. Example:
void f() { std::string s; ... // current implementations don't require a definition if (s.find('a', 3) == std::string::npos) { ... }
To the letter of the standard, though, the above requires a definition of npos, since the expression std::string::npos is potentially evaluated. I think this problem would be easily solved with simple changes to 9.4.2 [class.static.data] paragraph 4, 9.4.2 [class.static.data] paragraph 5 and 3.2 [basic.def.odr] paragraph 3.
Suggested resolution:
Replace 9.4.2 [class.static.data] paragraph 4 with:
If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be [note1] an integral constant expression (5.19). In that case, the member can appear in integral constant expressions. No definition of the member is required, unless an lvalue expression that designates it is potentially evaluated and either used as operand to the built-in unary & operator [note 2] or directly bound to a reference.
If a definition exists, it shall be at namespace scope and shall not contain an initializer.
In 9.4.2 [class.static.data] paragraph 5 change
There shall be exactly one definition of a static data member that is used in a program; no diagnostic is required; see 3.2.
to
Except as allowed by 9.4.2 par. 4, there shall be exactly one definition of a static data member that is potentially evaluated (3.2) in a program; no diagnostic is required.
In 3.2 [basic.def.odr] paragraph 3 add, at the beginning:
Except for the omission allowed by 9.4.2, par. 4, ...
[note 1] Actually it shall be a "= followed by a constant-expression". This could probably be an editorial fix, rather than a separate DR.
[note 2] Note that this is the case when reinterpret_cast-ing to a reference, like in
struct X { static const int value = 0; }; const char & c = reinterpret_cast<const char&>(X::value);See 5.2.10 [expr.reinterpret.cast]/10
More information, in response to a question about why issue 48 does not resolve the problem:
The problem is that the issue was settled in a way that solves much less than it was supposed to solve; that's why I decided to file, so to speak, a DR on a DR.
I understand this may seem a little 'audacious' on my part, but please keep reading. Quoting from the text of DR 48 (emphasis mine):
Originally, all static data members still had to be defined outside the class whether they were used or not.
But that restriction was supposed to be lifted [...]
In particular, if an integral/enum const static data member is initialized within the class, and its address is never taken, we agreed that no namespace-scope definition was required.
The corresponding resolution doesn't reflect this intent, with the definition being still required in most situations anyway: it's enough that the constant appears outside a place where constants are required (ignoring the obvious cases of sizeof and typeid) and you have to provide a definition. For instance:
struct X { static const int c = 1; }; void f(int n) { if (n == X::c) // <-- potentially evaluated ... }
<start digression>
Most usages of non-enum BOOST_STATIC_COSTANTs, for instance, are (or were, last time I checked) non-conforming. If you recall, Paul Mensonides pointed out that the following template
// map_integral template<class T, T V> struct map_integral : identity<T> { static const T value = V; }; template<class T, T V> const T map_integral<T, V>::value;
whose main goal is to map the same couples (type, value) to the same storage, also solves the definition problem. In this usage it is an excellent hack (if your compiler is good enough), but IMHO still a hack on a language defect.
<end digression>
What I propose is to solve the issue according to the original intent, which is also what users expect and all compilers that I know of already do. Or, in practice, we would have a rule that exists only as words in a standard document.
PS: I've sent a copy of this to Mr. Adamczyk to clarify an important doubt that occurred to me while writing this reply:
if no definition is provided for an integral static const data member is that member an object? Paragraph 1.8/1 seems to say no, and in fact it's difficult to think it is an object without assuming/pretending that a region of storage exists for it (an object *is* a region of storage according to the standard).
I would think that when no definition is required we have to assume that it could be a non-object. In that case there's nothing in 3.2 which says what 'used' means for such an entity and the current wording would thus be defective. Also, since the name of the member is an lvalue and 3.10/2 says an lvalue refers to an object we would have another problem.
OTOH the standard could pretend it is always an object (though the compiler can optimize it away) and in this case it should probably make a special case for it in 3.2/2.
Notes from the March 2004 meeting:
We sort of like this proposal, but we don't feel it has very high priority. We're not going to spend time discussing it, but if we get drafting for wording we'll review it.
Proposed resolution (October, 2005):
Change the first two sentences of 3.2 [basic.def.odr] paragraph 2 from:
An expression is potentially evaluated unless it appears where an integral constant expression is required (see 5.19 [expr.const]), is the operand of the sizeof operator (5.3.3 [expr.sizeof]), or is the operand of the typeid operator and the expression does not designate an lvalue of polymorphic class type (5.2.8 [expr.typeid]). An object or non-overloaded function is used if its name appears in a potentially-evaluated expression.
to
An expression that is the operand of the sizeof operator (5.3.3 [expr.sizeof]) is unevaluated, as is an expression that is the operand of the typeid operator if it is not an lvalue of a polymorphic class type (5.2.8 [expr.typeid]); all other expressions are potentially evaluated. An object or non-overloaded function whose name appears as a potentially-evaluated expression is used, unless it is an object that satisfies the requirements for appearing in an integral constant expression (5.19 [expr.const]) and the lvalue-to-rvalue conversion (4.1 [conv.lval]) is immediately applied.
Change the first sentence of 9.4.2 [class.static.data] paragraph 2 as indicated:
If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which whose constant-expression shall be an integral constant expression (5.19 [expr.const]).
[Voted into WP at the October, 2006 meeting.]
Section 9.6 [class.bit] paragraph 4 needs to be more specific about the signedness of bit fields of enum type. How much leeway does an implementation have in choosing the signedness of a bit field? In particular, does the phrase "large enough to hold all the values of the enumeration" mean "the implementation decides on the signedness, and then we see whether all the values will fit in the bit field", or does it require the implementation to make the bit field signed or unsigned if that's what it takes to make it "large enough"?
(See also issue 172.)
Note (March, 2005): Clark Nelson observed that there is variation among implementations on this point.
Notes from April, 2005 meeting:
Although implementations enjoy a great deal of latitude in handling bit-fields, it was deemed more user-friendly to ensure that the example in paragraph 4 will work by requiring implementations to use an unsigned underlying type if the enumeration type has no negative values. (If the implementation is allowed to choose a signed representation for such bit-fields, the comparison against TRUE will be false.)
In addition, it was observed that there is an apparent circularity between 7.2 [dcl.enum] paragraph 7 and 9.6 [class.bit] paragraph 4 that should be resolved.
Proposed resolution (April, 2006):
Replace 7.2 [dcl.enum] paragraph 7, deleting the embedded footnote 85, with the following:
For an enumeration where emin is the smallest enumerator and emax is the largest, the values of the enumeration are the values in the range bmin to bmax, defined as follows: Let K be 1 for a two's complement representation and 0 for a one's complement or sign-magnitude representation. bmax is the smallest value greater than or equal to max(|emin|-K,|emax|) and equal to 2M-1, where M is a non-negative integer. bmin is zero if emin is non-negative and -(bmax+K) otherwise. The size of the smallest bit-field large enough to hold all the values of the enumeration type is max(M,1) if bmin is zero and M+1 otherwise. It is possible to define an enumeration that has values not defined by any of its enumerators.
Add the indicated text to the second sentence of 9.6 [class.bit] paragraph 4:
If the value of an enumerator is stored into a bit-field of the same enumeration type and the number of bits in the bit-field is large enough to hold all the values of that enumeration type (7.2 [dcl.enum]), the original enumerator value and the value of the bit-field shall compare equal.
[Voted into WP at October 2004 meeting.]
It looks like the example on 9.6 [class.bit] paragraph 4 has both the enum and function contributing the identifier "f" for the same scope.
enum BOOL { f=0, t=1 }; struct A { BOOL b:1; }; A a; void f() { a.b = t; if (a.b == t) // shall yield true { /* ... */ } }
Proposed resolution:
Change the example at the end of 9.6 [class.bit]/4 from:
enum BOOL { f=0, t=1 }; struct A { BOOL b:1; }; A a; void f() { a.b = t; if (a.b == t) // shall yield true { /* ... */ } }
To:
enum BOOL { FALSE=0, TRUE=1 }; struct A { BOOL b:1; }; A a; void f() { a.b = TRUE; if (a.b == TRUE) // shall yield true { /* ... */ } }
[Voted into WP at April 2003 meeting.]
9.8 [class.local] paragraph 1 says,
Declarations in a local class can use only type names, static variables, extern variables and functions, and enumerators from the enclosing scope.The definition of when an object or function is "used" is found in 3.2 [basic.def.odr] paragraph 2 and essentially says that the operands of sizeof and non-polymorphic typeid operators are not used. (The resolution for issue 48 will add contexts in which integral constant expressions are required to the list of non-uses.)
This definition of "use" would presumably allow code like
void foo() { int i; struct S { int a[sizeof(i)]; }; };which is required for C compatibility.
However, the restrictions on nested classes in 9.7 [class.nest] paragraph 1 are very similar to those for local classes, and the example there explicitly states that a reference in a sizeof expression is a forbidden use (abbreviated for exposition):
class enclose { public: int x; class inner { void f(int i) { int a = sizeof(x); // error: refers to enclose::x } }; };
[As a personal note, I have seen real-world code that was exactly like this; it was hard to persuade the author that the required writearound, sizeof(((enclose*) 0)->x), was an improvement over sizeof(x). —wmm]
Similarly, 9.2 [class.mem] paragraph 9 would appear to prohibit examples like the following:
struct B { char x[10]; }; struct D: B { char y[sizeof(x)]; };
Suggested resolution: Add cross-references to 3.2 [basic.def.odr] following the word "use" in both 9.7 [class.nest] and 9.8 [class.local] , and change the example in 9.7 [class.nest] to indicate that a reference in a sizeof expression is permitted. In 9.2 [class.mem] paragraph 9, "referred to" should be changed to "used" with a cross_reference to 3.2 [basic.def.odr].
Notes from 10/01 meeting:
It was noted that the suggested resolution did not make the sizeof() example in 9.7 [class.nest] valid. Although the reference to the argument of sizeof() is not regarded as a use, the right syntax must be used nonetheless to reference a non-static member from the enclosing class. The use of the member name by itself is not valid. The consensus within the core working group was that nothing should be done about this case. It was later discovered that 9.4 [class.static] paragraph 3 states that
The definition of a static member shall not use directly the names of the nonstatic members of its class or of a base class of its class (including as operands of the sizeof operator). The definition of a static member may only refer to these members to form pointer to members (5.3.1 [expr.unary.op]) or with the class member access syntax (5.2.5 [expr.ref]).
This seems to reinforce the decision of the working group.
The use of "use" should still be cross-referenced. The statements in 9.7 [class.nest] and 9.8 [class.local] should also be rewritten to state the requirement positively rather than negatively as the list of "can't"s is already missing some cases such as template parameters.
Notes from the 4/02 meeting:
We backed away from "use" in the technical sense, because the requirements on the form of reference are the same whether or not the reference occurs inside a sizeof.
Proposed Resolution (revised October 2002):
In 9.2 [class.mem] paragraph 9, replace
Except when used to form a pointer to member (5.3.1 [expr.unary.op]), when used in the body of a nonstatic member function of its class or of a class derived from its class (9.3.1 [class.mfct.non-static]), or when used in a mem-initializer for a constructor for its class or for a class derived from its class (12.6.2 [class.base.init]), a nonstatic data or function member of a class shall only be referred to with the class member access syntax (5.2.5 [expr.ref]).
with the following paragraph
Each occurrence in an expression of the name of a nonstatic data member or nonstatic member function of a class shall be expressed as a class member access (5.2.5 [expr.ref]), except when it appears in the formation of a pointer to member (5.3.1 [expr.unary.op]), when it appears in the body of a nonstatic member function of its class or of a class derived from its class (9.3.1 [class.mfct.non-static]), or when it appears in a mem-initializer for a constructor for its class or for a class derived from its class (12.6.2 [class.base.init]).
In 9.7 [class.nest] paragraph 1, replace the last sentence,
Except by using explicit pointers, references, and object names, declarations in a nested class can use only type names, static members, and enumerators from the enclosing class.
with the following
[Note: In accordance with 9.2 [class.mem], except by using explicit pointers, references, and object names, declarations in a nested class shall not use nonstatic data members or nonstatic member functions from the enclosing class. This restriction applies in all constructs including the operands of the sizeof operator.]
In the example following 9.7 [class.nest] paragraph 1, change the comment on the first statement of function f to emphasize that sizeof(x) is an error. The example reads in full:
int x; int y; class enclose { public: int x; static int s; class inner { void f(int i) { int a = sizeof(x); // error: direct use of enclose::x even in sizeof x = i; // error: assign to enclose::x s = i; // OK: assign to enclose::s ::x = i; // OK: assign to global x y = i; // OK: assign to global y } void g(enclose* p, int i) { p->x = i; // OK: assign to enclose::x } }; }; inner* p = 0; // error: inner not in scope
[Voted into WP at the October, 2006 meeting.]
Issue 298, recently approved, affirms that cv-qualified class types can be used as nested-name-specifiers. Should the same be true for base-specifiers?
Rationale (April, 2005):
The resolution of issue 298 added new text to 9.1 [class.name] paragraph 5 making it clear that a typedef that names a cv-qualified class type is a class-name. Because the definition of base-specifier simply refers to class-name, it is already the case that cv-qualified class types are permitted as base-specifiers.
Additional notes (June, 2005):
It's not completely clear what it means to have a cv-qualified type as a base-specifier. The original proposed resolution for issue 298 said that “the cv-qualifiers are ignored,” but that wording is not in the resolution that was ultimately approved.
If the cv-qualifiers are not ignored, does that mean that the base-class subobject should be treated as always similarly cv-qualified, regardless of the cv-qualification of the derived-class lvalue used to access the base-class subobject? For instance:
typedef struct B { void f(); void f() const; int i; } const CB; struct D: CB { }; void g(D* dp) { dp->f(); // which B::f? dp->i = 3; // permitted? }
Proposed resolution (October, 2005):
Change 9.1 [class.name] paragraph 5 as indicated:
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 class-name. If a typedef-name that names a cv-qualified class type is used where a class-name is required, the cv-qualifiers are ignored. A typedef-name shall not be used as the identifier in a class-head.
Delete 7.1.3 [dcl.typedef] paragraph 8:
[Note: if the typedef-name is used where a class-name (or enum-name) is required, the program is ill-formed. For example,
typedef struct { S(); // error: requires a return type because S is // an ordinary member function, not a constructor } S;—end note]
[Voted into WP at April 2005 meeting.]
The ambiguity text in 10.2 [class.member.lookup] may not say what we intended. It makes the following example ill-formed:
struct A { int x(int); }; struct B: A { using A::x; float x(float); }; int f(B* b) { b->x(3); // ambiguous }This is a name lookup ambiguity because of 10.2 [class.member.lookup] paragraph 2:
... Each of these declarations that was introduced by a using-declaration is considered to be from each sub-object of C that is of the type containing the declaration designated by the using-declaration. If the resulting set of declarations are not all from sub-objects of the same type, or the set has a nonstatic member and includes members from distinct sub-objects, there is an ambiguity and the program is ill-formed.This contradicts the text and example in paragraph 12 of 7.3.3 [namespace.udecl] .
Proposed Resolution (10/00):
Replace the two cited sentences from 10.2 [class.member.lookup] paragraph 2 with the following:
The resulting set of declarations shall all be from sub-objects of the same type, or there shall be a set of declarations from sub-objects of a single type that contains using-declarations for the declarations found in all other sub-object types. Furthermore, for nonstatic members, the resulting set of declarations shall all be from a single sub-object, or there shall be a set of declarations from a single sub-object that contains using-declarations for the declarations found in all other sub-objects. Otherwise, there is an ambiguity and the program is ill-formed.
Replace the examples in 10.2 [class.member.lookup] paragraph 3 with the following:
struct A { int x(int); static int y(int); }; struct V { int z(int); }; struct B: A, virtual V { using A::x; float x(float); using A::y; static float y(float); using V::z; float z(float); }; struct C: B, A, virtual V { }; void f(C* c) { c->x(3); // ambiguous -- more than one sub-object A c->y(3); // not ambiguous c->z(3); // not ambiguous }
Notes from 04/01 meeting:
The following example should be accepted but is rejected by the wording above:
struct A { static void f(); }; struct B1: virtual A { using A::f; }; struct B2: virtual A { using A::f; }; struct C: B1, B2 { }; void g() { C::f(); // OK, calls A::f() }
Notes from 10/01 meeting (Jason Merrill):
The example in the issues list:
struct A { int x(int); }; struct B: A { using A::x; float x(float); }; int f(B* b) { b->x(3); // ambiguous }Is broken under the existing wording:
... Each of these declarations that was introduced by a using-declaration is considered to be from each sub-object of C that is of the type containing the declaration designated by the using-declaration. If the resulting set of declarations are not all from sub-objects of the same type, or the set has a nonstatic member and includes members from distinct sub-objects, there is an ambiguity and the program is ill-formed.Since the two x's are considered to be "from" different objects, looking up x produces a set including declarations "from" different objects, and the program is ill-formed. Clearly this is wrong. The problem with the existing wording is that it fails to consider lookup context.
The first proposed solution:
The resulting set of declarations shall all be from sub-objects of the same type, or there shall be a set of declarations from sub-objects of a single type that contains using-declarations for the declarations found in all other sub-object types. Furthermore, for nonstatic members, the resulting set of declarations shall all be from a single sub-object, or there shall be a set of declarations from a single sub-object that contains using-declarations for the declarations found in all other sub-objects. Otherwise, there is an ambiguity and the program is ill-formed.breaks this testcase:
struct A { static void f(); }; struct B1: virtual A { using A::f; }; struct B2: virtual A { using A::f; }; struct C: B1, B2 { }; void g() { C::f(); // OK, calls A::f() }because it considers the lookup context, but not the definition context; under this definition of "from", the two declarations found are the using-declarations, which are "from" B1 and B2.
The solution is to separate the notions of lookup and definition context. I have taken an algorithmic approach to describing the strategy.
Incidentally, the earlier proposal allows one base to have a superset of the declarations in another base; that was an extension, and my proposal does not do that. One algorithmic benefit of this limitation is to simplify the case of a virtual base being hidden along one arm and not another ("domination"); if we allowed supersets, we would need to remember which subobjects had which declarations, while under the following resolution we need only keep two lists, of subobjects and declarations.
Proposed resolution (October 2002):
Replace 10.2 [class.member.lookup] paragraph 2 with:
The following steps define the result of name lookup for a member name f in a class scope C.
The lookup set for f in C, called S(f,C), consists of two component sets: the declaration set, a set of members named f; and the subobject set, a set of subobjects where declarations of these members (possibly including using-declarations) were found. In the declaration set, using-declarations are replaced by the members they designate, and type declarations (including injected-class-names) are replaced by the types they designate. S(f,C) is calculated as follows.
If C contains a declaration of the name f, the declaration set contains every declaration of f in C (excluding bases), the subobject set contains C itself, and calculation is complete.
Otherwise, S(f,C) is initially empty. If C has base classes, calculate the lookup set for f in each direct base class subjobject Bi, and merge each such lookup set S(f,Bi) in turn into S(f,C).
The following steps define the result of merging lookup set S(f,Bi) into the intermediate S(f,C):
- If each of the subobject members of S(f,Bi) is a base class subobject of at least one of the subobject members of S(f,C), S(f,C) is unchanged and the merge is complete. Conversely, if each of the subobject members of S(f,C) is a base class subobject of at least one of the subobject members of S(f,Bi), the new S(f,C) is a copy of S(f,Bi).
- Otherwise, if the declaration sets of S(f,Bi) and S(f,C) differ, the merge is ambiguous: the new S(f,C) is a lookup set with an invalid declaration set and the union of the subobject sets. In subsequent merges, an invalid declaration set is considered different from any other.
- Otherwise, consider each declaration d in the set, where d is a member of class A. If d is a nonstatic member, compare the A base class subobjects of the subobject members of S(f,Bi) and S(f,C). If they do not match, the merge is ambiguous, as in the previous step. [Note: It is not necessary to remember which A subobject each member comes from, since using-declarations don't disambiguate. ]
- Otherwise, the new S(f,C) is a lookup set with the shared set of declarations and the union of the subobject sets.
The result of name lookup for f in C is the declaration set of S(f,C). If it is an invalid set, the program is ill-formed.
[Example:
struct A { int x; }; // S(x,A) = {{ A::x }, { A }} struct B { float x; }; // S(x,B) = {{ B::x }, { B }} struct C: public A, public B { }; // S(x,C) = { invalid, { A in C, B in C }} struct D: public virtual C { }; // S(x,D) = S(x,C) struct E: public virtual C { char x; }; // S(x,E) = {{ E::x }, { E }} struct F: public D, public E { }; // S(x,F) = S(x,E) int main() { F f; f.x = 0; // OK, lookup finds { E::x } }S(x,F) is unambiguous because the A and B base subobjects of D are also base subobjects of E, so S(x,D) is discarded in the first merge step. --end example]
Turn 10.2 [class.member.lookup] paragraphs 5 and 6 into notes.
Notes from October 2003 meeting:
Mike Miller raised some new issues in N1543, and we adjusted the proposed resolution as indicated in that paper.
Further information from Mike Miller (January 2004):
Unfortunately, I've become aware of a minor glitch in the proposed resolution for issue 39 in N1543, so I'd like to suggest a change that we can discuss in Sydney.
A brief review and background of the problem: the major change we agreed on in Kona was to remove detection of multiple-subobject ambiguity from class lookup (10.2 [class.member.lookup]) and instead handle it as part of the class member access expression. It was pointed out in Kona that 11.2 [class.access.base]/5 has this effect:
If a class member access operator, including an implicit "this->," is used to access a nonstatic data member or nonstatic member function, the reference is ill-formed if the left operand (considered as a pointer in the "." operator case) cannot be implicitly converted to a pointer to the naming class of the right operand.
After the meeting, however, I realized that this requirement is not sufficient to handle all the cases. Consider, for instance,
struct B { int i; }; struct I1: B { }; struct I2: B { }; struct D: I1, I2 { void f() { i = 0; // not ill-formed per 11.2p5 } };
Here, both the object expression ("this") and the naming class are "D", so the reference to "i" satisfies the requirement in 11.2 [class.access.base]/5, even though it involves a multiple-subobject ambiguity.
In order to address this problem, I proposed in N1543 to add a paragraph following 5.2.5 [expr.ref]/4:
If E2 is a non-static data member or a non-static member function, the program is ill-formed if the class of E1 cannot be unambiguously converted (10.2) to the class of which E2 is directly a member.
That's not quite right. It does diagnose the case above as written; however, it breaks the case where qualification is used to circumvent the ambiguity:
struct D2: I1, I2 { void f() { I2::i = 0; // ill-formed per proposal } };
In my proposed wording, the class of "this" can't be converted to "B" (the qualifier is ignored), so the access is ill-formed. Oops.
I think the following is a correct formulation, so the proposed resolution we discuss in Sydney should contain the following paragraph instead of the one in N1543:
If E2 is a nonstatic data member or a non-static member function, the program is ill-formed if the naming class (11.2) of E2 cannot be unambiguously converted (10.2) to the class of which E2 is directly a member.
This reformulation also has the advantage of pointing readers to 11.2 [class.access.base], where the the convertibility requirement from the class of E1 to the naming class is located and which might otherwise be overlooked.
Notes from the March 2004 meeting:
We discussed this further and agreed with these latest recommendations. Mike Miller has produced a paper N1626 that gives just the final collected set of changes.
(This resolution also resolves isssue 306.)
[Voted into WP at April 2005 meeting.]
Is the following well-formed?
struct A { struct B { }; }; struct C : public A, public A::B { B *p; };The lookup of B finds both the struct B in A and the injected B from the A::B base class. Are they the same thing? Does the standard say so?
What if a struct is found along one path and a typedef to that struct is found along another path? That should probably be valid, but does the standard say so?
This is resolved by issue 39
February 2004: Moved back to "Review" status because issue 39 was moved back to "Review".
[Voted into WP at March 2004 meeting.]
In clause 10.4 [class.abstract] paragraph 2, it reads:
A pure virtual function need be defined only if explicitly called with the qualified-id syntax (5.1.1 [expr.prim.general]).
This is IMHO incomplete. A dtor is a function (well, a "special member function", but this also makes it a function, right?) but it is called implicitly and thus without a qualified-id syntax. Another alternative is that the pure virtual function is called directly or indirectly from the ctor. Thus the above sentence which specifies when a pure virtual function need be defined ("...only if...") needs to be extended:
A pure virtual function need be defined only if explicitly called with the qualified-id syntax (5.1.1 [expr.prim.general]) or if implicitly called (12.4 [class.dtor] or 12.7 [class.cdtor]).
Proposed resolution:
Change 10.4 [class.abstract] paragraph 2 from
A pure virtual function need be defined only if explicitly called with the qualified-id syntax (5.1.1 [expr.prim.general]).
to
A pure virtual function need be defined only if explicitly called with, or as if with (12.4 [class.dtor]), the qualified-id syntax (5.1.1 [expr.prim.general]).
Note: 12.4 [class.dtor] paragraph 6 defines the "as if" cited.
[Moved to DR at 4/01 meeting.]
Consider the following example:
class A { class A1{}; static void func(A1, int); static void func(float, int); static const int garbconst = 3; public: template < class T, int i, void (*f)(T, int) > class int_temp {}; template<> class int_temp<A1, 5, func> { void func1() }; friend int_temp<A1, 5, func>::func1(); int_temp<A1, 5, func>* func2(); }; A::int_temp<A::A1, A::garbconst + 2, &A::func>* A::func2() {...}ISSUE 1:
In 11 [class.access] paragraph 5 we have:
A::int_temp A::A1 A::garbconst (part of an expression) A::func (after overloading is done)I suspect that member templates were not really considered when this was written, and that it might have been written rather differently if they had been. Note that access to the template arguments is only legal because the class has been declared a friend, which is probably not what most programmers would expect.
Rationale:
Not a defect. This behavior is as intended.
ISSUE 2:
Now consider void A::int_temp<A::A1, A::garbconst + 2, &A::func>::func1() {...} By my reading of 11.7 [class.access.nest] , the references to A::A1, A::garbconst and A::func are now illegal, and there is no way to define this function outside of the class. Is there any need to do anything about either of these Issues?
Proposed resolution (04/01):
The resolution for this issue is contained in the resolution for issue 45.
[Voted into WP at the October, 2006 meeting.]
The proposed resolution for issue 45 inserts the following sentence after 11 [class.access] paragraph 1:
A member of a class can also access all names as the class of which it is a member.
I don't think that this is correctly constructed English. I see two possibilities:
This is a typo, and the correct change is:
A member of a class can also access all names of the class of which it is a member.
The intent is something more like:
A member of a nested class can also access all names accessible by any other member of the class of which it is a member.
[Note: this was editorially corrected at the time defect resolutions were being incorporated into the Working Paper to read, “...can also access all the names declared in the class of which it is a member,” which is essentially the same as the preceding option 1.]
I would prefer to use the language proposed for 11.7 [class.access.nest]:
A nested class is a member and as such has the same access rights as any other member.
A second problem is with the text in 11.3 [class.friend] paragraph 2:
[Note: this means that access to private and protected names is also granted to member functions of the friend class (as if the functions were each friends) and to the static data member definitions of the friend class. This also means that private and protected type names from the class granting friendship can be used in the base-clause of a nested class of the friend class. However, the declarations of members of classes nested within the friend class cannot access the names of private and protected members from the class granting friendship. Also, because the base-clause of the friend class is not part of its member declarations, the base-clause of the friend class cannot access the names of the private and protected members from the class granting friendship. For example,class A { class B { }; friend class X; }; class X : A::B { // ill-formed: A::B cannot be accessed // in the base-clause for X A::B mx; // OK: A::B used to declare member of X class Y: A::B { // OK: A::B used to declare member of X A::B my; // ill-formed: A::B cannot be accessed // to declare members of nested class of X }; };—end note]
This seems to be an oversight. The proposed change to 11.7 [class.access.nest] paragraph 1 would appear to have eliminated the restrictions on nested class access. However, at least one compiler (gcc 3.4.3) doesn't appear to take my view, and continues with the restrictions on access by classes within a friend class, while implementing the rest of the resolution of issue 45.
Note (March, 2005):
Andreas Hommel: I think issue 45 requires an additional change in 9.7 [class.nest] paragraph 4:
Like a member function, a friend function (11.3 [class.friend]) defined within a nested class is in the lexical scope of that class; it obeys the same rules for name binding as a static member function of that class (9.4 [class.static]) and has no special access rights to members of an enclosing class.
I believe the “no special access rights” language should be removed.
Proposed resolution (October, 2005):
This issue is resolved by the resolution of issue 372.
[Moved to DR at 4/01 meeting.]
11.2 [class.access.base] paragraph 4 says:
A base class is said to be accessible if an invented public member of the base class is accessible. If a base class is accessible, one can implicitly convert a pointer to a derived class to a pointer to that base class.Given the above, is the following well-formed?
class D; class B { protected: int b1; friend void foo( D* pd ); }; class D : protected B { }; void foo( D* pd ) { if ( pd->b1 > 0 ); // Is 'b1' accessible? }Can you access the protected member b1 of B in foo? Can you convert a D* to a B* in foo?
1st interpretation:
A public member of B is accessible within foo (since foo is a friend), therefore foo can refer to b1 and convert a D* to a B*.
2nd interpretation:
B is a protected base class of D, and a public member of B is a protected member of D and can only be accessed within members of D and friends of D. Therefore foo cannot refer to b1 and cannot convert a D* to a B*.
(See J16/99-0042 = WG21 N1218.)
Proposed Resolution (04/01):
A base class B of N is accessible at R, if
- an invented public member of B would be a public member of N, or
- R occurs in a member or friend of class N, and an invented public member of B would be a private or protected member of N, or
- R occurs in a member or friend of a class P derived from N, and an invented public member of B would be a private or protected member of P, or
- there exists a class S such that B is a base class of S accessible at R and S is a base class of N accessible at R. [Example:
class B { public: int m; }; class S: private B { friend class N; }; class N: private S { void f() { B* p = this; // OK because class S satisfies the // fourth condition above: B is a base // class of N accessible in f() because // B is an accessible base class of S // and S is an accessible base class of N. } };—end example]
A base class is said to be accessible if an invented public member of the base class is accessible.
A member m is accessible at the point R when named in class N if
- m as a member of N is public, or
- m as a member of N is private, and R occurs in a member or friend of class N, or
- m as a member of N is protected, and R occurs in a member or friend of class N, or in a member or friend of a class P derived from N, where m as a member of P is private or protected, or
- there exists a base class B of N that is accessible at R, and m is accessible at R when named in class B. [Example:...
The resolution for issue 207 modifies this wording slightly.
[Moved to DR at 4/01 meeting.]
The text in 11.2 [class.access.base] paragraph 4 does not seem to handle the following cases:
class D; class B { private: int i; friend class D; }; class C : private B { }; class D : private C { void f() { B::i; //1: well-formed? i; //2: well-formed? } };The member i is not a member of D and cannot be accessed in the scope of D. What is the naming class of the member i on line //1 and line //2?
Proposed Resolution (04/01): The resolution for this issue is contained in the resolution for issue 9..
[Moved to DR at 10/01 meeting.]
Consider the following example:
class A { protected: static void f() {}; }; class B : A { public: using A::f; void g() { A::f(); } };
The standard says in 11.2 [class.access.base] paragraph 4 that the call to A::f is ill-formed:
A member m is accessible when named in class N if
- m as a member of N is public, or
- m as a member of N is private, and the reference occurs in a member or friend of class N, or
- m as a member of N is protected, and the reference occurs in a member or friend of class N, or in a member or friend of a class P derived from N, where m as a member of P is private or protected, or
- there exists a base class B of N that is accessible at the point of reference, and m is accessible when named in class B.
Here, m is A::f and N is A.
It seems clear to me that the third bullet should say "public, private or protected".
Steve Adamczyk:The words were written before using-declarations existed, and therefore didn't anticipate this case.
Proposed resolution (04/01):
Modify the third bullet of the third change ("A member m is accessible...") in the resolution of issue 9 to read "public, private, or protected" instead of "private or protected."
[Moved to DR at 4/02 meeting.]
The definition of "friend" in 11.3 [class.friend] says:
A friend of a class is a function or class that is not a member of the class but is permitted to use the private and protected member names from the class. ...A nested class, i.e. INNER in the example below, is a member of class OUTER. The sentence above states that it cannot be a friend. I think this is a mistake.
class OUTER { class INNER; friend class INNER; class INNER {}; };
Proposed resolution (04/01):
Change the first sentence of 11.3 [class.friend] as follows:
A friend of a class is a function or class that is not a member of the class but is allowed given permission to use the private and protected member names from the class. The name of a friend is not in the scope of the class, and the friend is not called with the member access operators (5.2.5 [expr.ref]) unless it is a member of another class. A class specifies its friends, if any, by way of friend declarations. Such declarations give special access rights to the friends, but they do not make the nominated friends members of the befriending class.
[Voted into WP at the October, 2006 meeting.]
I don't know the reason for this distinction, but it seems to be surprising that Base::A is legal and D is illegal in this example:
class D; class Base { class A; class B; friend class D; }; class Base::B { }; class Base::A : public Base::B // OK because of issue 45 { }; class D : public Base::B // illegal because of 11.4p4 { };
Shouldn't this be consistent (either way)?
Notes from the April, 2005 meeting:
In discussing issue 372, the CWG decided that access in the base-specifiers of a class should be the same as for its members, and that resolution will apply to friend declarations, as well.
Proposed resolution (October, 2005):
This issue is resolved by the resolution of issue 372.
[Voted into WP at October 2004 meeting.]
We consider it not unreasonable to do the following
class A { protected: void g(); }; class B : public A { public: using A::g; // B::g is a public synonym for A::g }; class C: public A { void foo(); }; void C::foo() { B b; b.g(); }
However the EDG front-end does not like and gives the error
#410-D: protected function "A::g" is not accessible through a "B" pointer or object b.g(); ^
Steve Adamczyk: The error in this case is due to 11.4 [class.protected] of the standard, which is an additional check on top of the other access checking. When that section says "a protected nonstatic member function ... of a base class" it doesn't indicate whether the fact that there is a using-declaration is relevant. I'd say the current wording taken at face value would suggest that the error is correct -- the function is protected, even if the using-declaration for it makes it accessible as a public function. But I'm quite sure the wording in 11.4 [class.protected] was written before using-declarations were invented and has not been reviewed since for consistency with that addition.
Notes from April 2003 meeting:
We agreed that the example should be allowed.
Proposed resolution (April 2003, revised October 2003):
Change 11.4 [class.protected] paragraph 1 from
When a friend or a member function of a derived class references a protected nonstatic member function or protected nonstatic data member of a base class, an access check applies in addition to those described earlier in clause 11 [class.access]. [Footnote: This additional check does not apply to other members, e.g. static data members or enumerator member constants.] Except when forming a pointer to member (5.3.1 [expr.unary.op]), the access must be through a pointer to, reference to, or object of the derived class itself (or any class derived from that class (5.2.5 [expr.ref]). If the access is to form a pointer to member, the nested-name-specifier shall name the derived class (or any class derived from that class).
to
An additional access check beyond those described earlier in clause 11 [class.access] is applied when a nonstatic data member or nonstatic member function is a protected member of its naming class (11.2 [class.access.base]). [Footnote: This additional check does not apply to other members, e.g., static data members or enumerator member constants.] As described earlier, access to a protected member is granted because the reference occurs in a friend or member of some class C. If the access is to form a pointer to member (5.3.1 [expr.unary.op]), the nested-name-specifier shall name C or a class derived from C. All other accesses involve a (possibly implicit) object expression (5.2.5 [expr.ref]). In this case, the class of the object expression shall be C or a class derived from C.
Additional discussion (September, 2004):
Steve Adamczyk: I wonder if this wording is incorrect. Consider:
class A { public: int p; }; class B : protected A { // p is a protected member of B }; class C : public B { friend void fr(); }; void fr() { B *pb = new B; pb->p = 1; // Access okay? Naming class is B, p is a protected member of B, // the "C" of the issue 385 wording is C, but access is not via // an object of type C or a derived class thereof. }
I think the formulation that the member is a protected member of its naming class is not what we want. I think we intended that the member is protected in the declaration that is found, where the declaration found might be a using-declaration.
Mike Miller: I think the proposed wording makes the access pb->p ill-formed, and I think that's the right thing to do.
First, protected inheritance of A by B means that B intends the public and protected members of A to be part of B's implementation, available to B's descendants only. (That's why there's a restriction on converting from B* to A*, to enforce B's intention on the use of members of A.) Consequently, I see no difference in access policy between your example and
class B { protected: int p; };
Second, the reason we have this rule is that C's use of inherited protected members might be different from their use in a sibling class, say D. Thus members and friends of C can only use B::p in a manner consistent with C's usage, i.e., in C or derived-from-C objects. If we rewrote your example slightly,
class D: public B { }; void fr(B* pb) { pb->p = 1; } void g() { fr(new D); }
it's clear that the intent of this rule is broken — fr would be accessing B::p assuming C's policies when the object in question actually required D's policies.
(See also issues 471 and 472.)
[Moved to DR at 4/01 meeting.]
Paragraph 1 says: "The members of a nested class have no special access to members of an enclosing class..."
This prevents a member of a nested class from being defined outside of its class definition. i.e. Should the following be well-formed?
class D { class E { static E* m; }; }; D::E* D::E::m = 1; // ill-formedThis is because the nested class does not have access to the member E in D. 11 [class.access] paragraph 5 says that access to D::E is checked with member access to class E, but unfortunately that doesn't give access to D::E. 11 [class.access] paragraph 6 covers the access for D::E::m, but it doesn't affect the D::E access. Are there any implementations that are standard compliant that support this?
Here is another example:
class C { class B { C::B *t; //2 error, C::B is inaccessible }; };This causes trouble for member functions declared outside of the class member list. For example:
class C { class B { B& operator= (const B&); }; }; C::B& C::B::operator= (const B&) { } //3If the return type (i.e. C::B) is access checked in the scope of class B (as implied by 11 [class.access] paragraph 5) as a qualified name, then the return type is an error just like referring to C::B in the member list of class B above (i.e. //2) is ill-formed.
Proposed resolution (04/01):
The resolution for this issue is incorporated into the resolution for issue 45.
[Moved to DR at 4/01 meeting.]
Example:
#include <iostream.h> class C { // entire body is private struct Parent { Parent() { cout << "C::Parent::Parent()\n"; } }; struct Derived : Parent { Derived() { cout << "C::Derived::Derived()\n"; } }; Derived d; }; int main() { C c; // Prints message from both nested classes return 0; }How legal/illegal is this? Paragraphs that seem to apply here are:
11 [class.access] paragraph 1:
A member of a class can beand 11.7 [class.access.nest] paragraph 1:
- private; that is, its name can be used only by members and friends of the class in which it is declared. [...]
The members of a nested class have no special access to members of an enclosing class, nor to classes or functions that have granted friendship to an enclosing class; the usual access rules (clause 11 [class.access] ) shall be obeyed. [...]This makes me think that the ': Parent' part is OK by itself, but that the implicit call of 'Parent::Parent()' by 'Derived::Derived()' is not.
From Mike Miller:
I think it is completely legal, by the reasoning given in the (non-normative) 11.7 [class.access.nest] paragraph 2. The use of a private nested class as a base of another nested class is explicitly declared to be acceptable there. I think the rationale in the comments in the example ("// OK because of injection of name A in A") presupposes that public members of the base class will be public members in a (publicly-derived) derived class, regardless of the access of the base class, so the constructor invocation should be okay as well.
I can't find anything normative that explicitly says that, though.
(See also papers J16/99-0009 = WG21 N1186, J16/00-0031 = WG21 N1254, and J16/00-0045 = WG21 N1268.)
Proposed Resolution (04/01):
Insert the following as a new paragraph following 11 [class.access] paragraph 1:
A member of a class can also access all names as the class of which it is a member. A local class of a member function may access the same names that the member function itself may access. [Footnote: Access permissions are thus transitive and cumulative to nested and local classes.]
Delete 11 [class.access] paragraph 6.
In 11.7 [class.access.nest] paragraph 1, change
The members of a nested class have no special access to members of an enclosing class, nor to classes or functions that have granted friendship to an enclosing class; the usual access rules (clause 11 [class.access]) shall be obeyed.
to
A nested class is a member and as such has the same access rights as any other member.
Change
B b; // error: E::B is private
to
B b; // Okay, E::I can access E::B
Change
p->x = i; // error: E::x is private
to
p->x = i; // Okay, E::I can access E::x
Delete 11.7 [class.access.nest] paragraph 2.
(This resolution also resolves issues 8 and 10.
[Voted into WP at April 2003 meeting.]
According to 12.1 [class.ctor] paragraph 1, a declaration of a constructor has a special limited syntax, in which only function-specifiers are allowed. A friend specifier is not a function-specifier, so one interpretation is that a constructor cannot be declared in a friend declaration.
(It should also be noted, however, that neither friend nor function-specifier is part of the declarator syntax, so it's not clear that anything conclusive can be derived from the wording of 12.1 [class.ctor].)
Notes from 04/01 meeting:
The consensus of the core language working group was that it should be permitted to declare constructors as friends.
Proposed Resolution (revised October 2002):
Change paragraph 1a in 3.4.3.1 [class.qual] (added by the resolution of issue 147) as follows:
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. Such a constructor name shall be used only in the declarator-id of a constructor definition declaration that appears outside of the class definition names a constructor....
Note: the above does not allow qualified names to be used for in-class declarations; see 8.3 [dcl.meaning] paragraph 1. Also note that issue 318 updates the same paragraph.
Change the example in 11.3 [class.friend], paragraph 4 as follows:
class Y { friend char* X::foo(int); friend X::X(char); // constructors can be friends friend X::~X(); // destructors can be friends //... };
[Voted into WP at October 2003 meeting.]
In 12.1 [class.ctor] paragraph 5, the standard says "A constructor is trivial if [...]", and goes on to define a trivial default constructor. Taken literally, this would mean that a copy constructor can't be trivial (contrary to 12.8 [class.copy] paragraph 6). I suggest changing this to "A default constructor is trivial if [...]". (I think the change is purely editorial.)
Proposed Resolution (revised October 2002):
Change 12.1 [class.ctor] paragraph 5-6 as follows:
A default constructor for a class X is a constructor of class X that can be called without an argument. If there is no user-declared user-declared constructor for class X, a default constructor is implicitly declared. An implicitly-declared implicitly-declared default constructor is an inline public member of its class. A default constructor is trivial if it is an implicitly-declared default constructor and if:
- its class has no virtual functions (10.3 [class.virtual]) and no virtual base classes (10.1 [class.mi]), and
- all the direct base classes of its class have trivial default constructors, and
- for all the nonstatic data members of its class that are of class type (or array thereof), each such class has a trivial default constructor.
Otherwise, the default constructor is non-trivial.
Change 12.4 [class.dtor] paragraphs 3-4 as follows (the main changes are removing italics):
If a class has no user-declared user-declared destructor, a destructor is declared implicitly. An implicitly-declared implicitly-declared destructor is an inline public member of its class. A destructor is trivial if it is an implicitly-declared destructor and if:
- all of the direct base classes of its class have trivial destructors and
- for all of the non-static data members of its class that are of class type (or array thereof), each such class has a trivial destructor.
Otherwise, the destructor is non-trivial non-trivial.
In 9.5 [class.union] paragraph 1, change "trivial constructor" to "trivial default constructor".
In 12.2 [class.temporary] paragraph 3, add to the reference to 12.1 [class.ctor] a second reference, to 12.8 [class.copy].
[Voted into WP at October 2003 meeting.]
12.1 [class.ctor] paragraph 10 states
A copy constructor for a class X is a constructor with a first parameter of type X & or of type const X &. [Note: see 12.8 [class.copy] for more information on copy constructors.]
No mention is made of constructors with first parameters of types volatile X & or const volatile X &. This statement seems to be in contradiction with 12.8 [class.copy] paragraph 2 which states
A non-template constructor for class X is a copy constructor if its first parameter is of type X &, const X &, volatile X & or const volatile X &, ...
12.8 [class.copy] paragraph 5 also mentions the volatile versions of the copy constructor, and the comparable paragraphs for copy assignment (12.8 [class.copy] paragraphs 9 and 10) all allow volatile versions, so it seems that 12.1 [class.ctor] is at fault.
Proposed resolution (October 2002):
Change 12.1 [class.ctor] paragraph 10 from
A copy constructor for a class X is a constructor with a first parameter of type X& or of type const X&. [Note: see 12.8 [class.copy] for more information on copy constructors. ]to (note that the dropping of italics is intentional):
A copy constructor (12.8 [class.copy]) is used to copy objects of class type.
[Voted into WP at April, 2006 meeting.]
In 12.2 [class.temporary] paragraph 5, should binding a reference to the result of a "?" operation, each of whose branches is a temporary, extend both temporaries?
Here's an example:
const SFileName &C = noDir ? SFileName("abc") : SFileName("bcd");
Do the temporaries created by the SFileName conversions survive the end of the full expression?
Notes from 10/00 meeting:
Other problematic examples include cases where the temporary from one branch is a base class of the temporary from the other (i.e., where the implementation must remember which type of temporary must be destroyed), or where one branch is a temporary and the other is not. Similar questions also apply to the comma operator. The sense of the core language working group was that implementations should be required to support these kinds of code.
Notes from the March 2004 meeting:
We decided that the cleanest model is one in which any "?" operation that returns a class rvalue always copies one of its operands to a temporary and returns the temporary as the result of the operation. (Note that this may involve slicing.) An implementation would be free to optimize this using the rules in 12.8 [class.copy] paragraph 15, and in fact we would expect that in many cases compilers would do such optimizations. For example, the compiler could construct both rvalues in the above example into a single temporary, and thus avoid a copy.
See also issue 446.
Proposed resolution (October, 2004):
This issue is resolved by the resolutions of issue 446.
Note (October, 2005):
This issue was overlooked when issue 446 was moved to “ready” status and was thus inadvertently omitted from the list of issues accepted as Defect Reports at the October, 2005 meeting.
[Moved to DR at 4/01 meeting.]
Jack Rouse: 12.2 [class.temporary] states that temporary objects will normally be destroyed at the end of the full expression in which they are created. This can create some unique code generation requirements when initializing a class array with a default constructor that uses a default argument. Consider the code:
struct T { int i; T( int ); ~T(); }; struct S { S( int = T(0).i ); ~S(); }; S* f( int n ) { return new S[n]; }The full expression allocating the array in f(int) includes the default constructor for S. Therefore according to 1.9 [intro.execution] paragraph 14, it includes the default argument expression for S(int). So evaluation of the full expression should include evaluating the default argument "n" times and creating "n" temporaries of type T. But the destruction of the temporaries must be delayed until the end of the full expression so this requires allocating space at runtime for "n" distinct temporaries. It is unclear how these temporaries are supposed to be allocated and deallocated. They cannot readily be autos because a variable allocation is required.
I believe that many existing implementations will destroy the temporaries needed by the default constructor after each array element is initialized. But I can't find anything in the standard that allows the temporaries to be destroyed early in this case.
I think the standard should allow the early destruction of temporaries used in the default initialization of class array elements. I believe early destruction is the status quo, and I don't think the users of existing C++ compilers have been adversely impacted by it.
Proposed resolution (04/01):
The proposed resolution is contained in the proposal for issue 201.
[Voted into the WP at the April, 2007 meeting as part of paper J16/07-0099 = WG21 N2239.]
12.2 [class.temporary] paragraph 3 simply states the requirement that temporaries created during the evaluation of an expression
are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created.There is nothing said about the relative order in which these temporaries are destroyed.
Paragraph 5, dealing with temporaries bound to references, says
the temporaries created during the evaluation of the expression initializing the reference, except the temporary to which the reference is bound, are destroyed at the end of the full-expression in which they are created and in the reverse order of the completion of their construction.Is this difference intentional? May temporaries in expressions other than those initializing references be deleted in non-LIFO order?
Notes from 04/00 meeting:
Steve Adamczyk expressed concern about constraining implementations that are capable of fine-grained parallelism -- they may be unable to determine the order of construction without adding undesirable overhead.
Proposed resolution (April, 2007):
As specified in paper J16/07-0099 = WG21 N2239.
[Moved to DR at 4/01 meeting.]
According to 12.2 [class.temporary] paragraph 4, an expression appearing as the initializer in an object definition constitutes a context "in which temporaries are destroyed at a different point than the end of the full-expression." It goes on to say that the temporary containing the value of the expression persists until after the initialization is complete (see also issue 117). This seems to presume that the end of the full-expression is a point earlier than the completion of the initialization.
However, according to 1.9 [intro.execution] paragraphs 12-13, the full-expression in such cases is, in fact, the entire initialization. If this is the case, the behavior described for temporaries in an initializer expression is simply the normal behavior of temporaries in any expression, and treating it as an exception to the general rule is both incorrect and confusing.
Proposed resolution (04/01):
[Note: this proposal also addresses issue 124.]
Add to the end of 1.9 [intro.execution] paragraph 12:
If the initializer for an object or sub-object is a full-expression, the initialization of the object or sub-object (e.g., by calling a constructor or copying an expression value) is considered to be part of the full-expression.
Replace 12.2 [class.temporary] paragraph 4 with:
There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context is when a default constructor is called to initialize an element of an array. If the constructor has one or more default arguments, any temporaries created in the default argument expressions are destroyed immediately after return from the constructor.
[Voted into WP at April 2005 meeting.]
Section 12.2 [class.temporary] paragraph 2, abridged:
X f(X); void g() { X a; a = f(a); }a=f(a) requires a temporary for either the argument a or the result of f(a) to avoid undesired aliasing of a.
The note seems to imply that an implementation is allowed to omit copying "a" to f's formal argument, or to omit using a temporary for the return value of f. I don't find that license in normative text.
Function f returns an X by value, and in the expression the value is assigned (not copy-constructed) to "a". I don't see how that temporary can be omitted. (See also 12.8 [class.copy] p 15)
Since "a" is an lvalue and not a temporary, I don't see how copying "a" to f's formal parameter can be avoided.
Am I missing something, or is 12.2 [class.temporary] p 2 misleading?
Proposed resolution (October, 2004):
In 12.2 [class.temporary] paragraph 2, change the last sentence as indicated:
On the other hand, the expression a=f(a) requires a temporary for either the argument a or the result of f(a) to avoid undesired aliasing of a the result of f(a), which is then assigned to a.
[Voted into WP at March 2004 meeting.]
class C { public: C(); ~C(); int& get() { return p; } // reference return private: int p; }; int main () { if ( C().get() ) // OK? }
Section 12.2 [class.temporary] paragraph 3 says a temp is destroyed as the last step in evaluating the full expression. But the expression C().get() has a reference type. Does 12.2 [class.temporary] paragraph 3 require that the dereference to get a boolean result occur before the destructor runs, making the code valid? Or does the code have undefined behavior?
Bill Gibbons: It has undefined behavior, though clearly this wasn't intended. The lvalue-to-rvalue conversion that occurs in the "if" statement is not currently part of the full-expression.
From section 12.2 [class.temporary] paragraph 3:
Temporary objects are destroyed as the last step in evaluating the full-expression (1.9 [intro.execution]) that (lexically) contains the point where they were created.
From section 1.9 [intro.execution] paragraph 12:
A full-expression is an expression that is not a subexpression of another expression. If a language construct is defined to produce an implicit call of a function, a use of the language construct is considered to be an expression for the purposes of this definition.
The note in section 1.9 [intro.execution] paragraph 12 goes on to explain that this covers expressions used as initializers, but it does not discuss lvalues within temporaries.
It is a small point but it is probably worth correcting 1.9 [intro.execution] paragraph 12. Instead of the "implicit call of a function" wording, it might be better to just say that a full-expression includes any implicit use of the expression value in the enclosing language construct, and include a note giving implicit calls and lvalue-to-rvalue conversions as examples.
Offhand the places where this matters include: initialization (including member initializers), selection statements, iteration statements, return, throw
Proposed resolution (April 2003):
Change 1.9 [intro.execution] paragraph 12-13 to read:
A full-expression is an expression that is not a subexpression of another expression. If a language construct is defined to produce an implicit call of a function, a use of the language construct is considered to be an expression for the purposes of this definition. Conversions applied to the result of an expression in order to satisfy the requirements of the language construct in which the expression appears are also considered to be part of the full-expression.
[Note: certain contexts in C++ cause the evaluation of a full-expression that results from a syntactic construct other than expression (5.18 [expr.comma]). For example, in 8.5 [dcl.init] one syntax for initializer is
( expression-list )
but the resulting construct is a function call upon a constructor function with expression-list as an argument list; such a function call is a full-expression. For example, in 8.5 [dcl.init], another syntax for initializer is= initializer-clause
but again the resulting construct might be a function call upon a constructor function with one assignment-expression as an argument; again, the function call is a full-expression. ] [Example:struct S { S(int i): I(i) { } int& v() { return I; } private: int I; }; S s1(1); // full-expression is call of S::S(int) S s2 = 2; // full-expression is call of S::S(int) void f() { if (S(3).v()) // full-expression includes lvalue-to-rvalue and // int to bool conversions, performed before // temporary is deleted at end of full-expression { } }
—end example]
[Voted into WP at April 2005 meeting.]
There seems to be a typo in 12.2 [class.temporary]/5, which says "The temporary to which the reference is bound or the temporary that is the complete object TO a subobject OF which the TEMPORARY is bound persists for the lifetime of the reference except as specified below."
I think this should be "The temporary to which the reference is bound or the temporary that is the complete object OF a subobject TO which the REFERENCE is bound persists for the lifetime of the reference except as specified below."
I used upper-case letters for the parts I think need to be changed.
Proposed resolution (October, 2004):
Change 12.2 [class.temporary] paragraph 5 as indicated:
The temporary to which the reference is bound or the temporary that is the complete object to of a subobject of to which the temporary reference is bound persists for the lifetime of the reference except as specified below.
[Voted into WP at April, 2006 meeting.]
Section 12.2 [class.temporary] paragraph 5 ends with this "rule":
For the temporary to be destroyed after obj2 is destroyed, when obj2 has static storage, I would say that the reference to the temporary should also have static storage, but that is IMHO not clear from the paragraph.
Example:
void f () { const T1& ref = T1(); static T2 obj2; ... }
Here the temporary would be destoyed before obj2, contrary to the rule above.
Steve Adamczyk: I agree there's a minor issue here. I think the clause quoted above meant for obj1 and obj2 to have the same storage duration. Replacing "obj2 is an object with static or automatic storage duration" by "obj2 is an object with the same storage duration as obj1" would, I believe, fix the problem.
Notes from October 2004 meeting:
We agreed with Steve Adamczyk's suggestion.
Proposed resolution (October, 2005):
Change 12.2 [class.temporary] paragraph 5 as follows:
... In addition, the destruction of temporaries bound to references shall take into account the ordering of destruction of objects with static or automatic storage duration (3.7.1 [basic.stc.static], 3.7.3 [basic.stc.auto]); that is, if obj1 is an object with static or automatic storage duration created before the temporary is created with the same storage duration as the temporary, the temporary shall be destroyed before obj1 is destroyed; if obj2 is an object with static or automatic storage duration created after the temporary is created with the same storage duration as the temporary, the temporary shall be destroyed after obj2 is destroyed...
[Moved to DR at October 2002 meeting.]
May user-defined conversion functions be static? That is, should this compile?
class Widget { public: static operator bool() { return true; } };
All my compilers hate it. I hate it, too. However, I don't see anything in 12.3.2 [class.conv.fct] that makes it illegal. Is this a prohibition that arises from the grammar, i.e., the grammar doesn't allow "static" to be followed by a conversion-function-id in a member function declaration? Or am I just overlooking something obvious that forbids static conversion functions?
Proposed Resolution (4/02):
Add to 12.3.2 [class.conv.fct] as a new paragraph 7:
Conversion functions cannot be declared static.
[Moved to DR at October 2002 meeting.]
12.4 [class.dtor] contains this example:
struct B { virtual ~B() { } }; struct D : B { ~D() { } }; D D_object; typedef B B_alias; B* B_ptr = &D_object; void f() { D_object.B::~B(); // calls B's destructor B_ptr->~B(); // calls D's destructor B_ptr->~B_alias(); // calls D's destructor B_ptr->B_alias::~B(); // calls B's destructor B_ptr->B_alias::~B_alias(); // error, no B_alias in class B }
On the other hand, 3.4.3 [basic.lookup.qual] contains this example:
struct C { typedef int I; }; typedef int I1, I2; extern int* p; extern int* q; p->C::I::~I(); // I is looked up in the scope of C q->I1::~I2(); // I2 is looked up in the scope of // the postfix-expression struct A { ~A(); }; typedef A AB; int main() { AB *p; p->AB::~AB(); // explicitly calls the destructor for A }
Note that
B_ptr->B_alias::~B_alias();
is claimed to be an error, while the equivalent
p->AB::~AB();
is claimed to be well-formed.
I believe that clause 3 is correct and that clause 12 is in error. We worked hard to get the destructor lookup rules in clause 3 to be right, and I think we failed to notice that a change was also needed in clause 12.
Mike Miller:
Unfortunately, I don't believe 3.4.3 [basic.lookup.qual] covers the case of p->AB::~AB(). It's clearly intended to do so, as evidenced by 3.4.3.1 [class.qual] paragraph 1 ("a destructor name is looked up as specified in 3.4.3 [basic.lookup.qual]"), but I don't think the language there does so.
The relevant paragraph is 3.4.3 [basic.lookup.qual] paragraph 5. (None of the other paragraphs in that section deal with this topic at all.) It has two parts. The first is
If a pseudo-destructor-name (5.2.4 [expr.pseudo]) contains a nested-name-specifier, the type-names are looked up as types in the scope designated by the nested-name-specifier.
This sentence doesn't apply, because ~AB isn't a pseudo-destructor-name. 5.2.4 [expr.pseudo] makes clear that this syntactic production (5.2 [expr.post] paragraph 1) only applies to cases where the type-name is not a class-name. p->AB::~AB is covered by the production using id-expression.
The second part of 3.4.3 [basic.lookup.qual] paragraph 5 says
In a qualified-id of the form:
::opt nested-name-specifier ~ class-name
where the nested-name-specifier designates a namespace name, and in a qualified-id of the form:
::opt nested-name-specifier class-name :: ~ class-name
the class-names are looked up as types in the scope designated by the nested-name-specifier.
This wording doesn't apply, either. The first one doesn't because the nested-name-specifier is a class-name, not a namespace name. The second doesn't because there's only one layer of qualification.
As far as I can tell, there's no normative text that specifies how the ~AB is looked up in p->AB::~AB(). 3.4.3.1 [class.qual], where all the other class member qualified lookups are handled, defers to 3.4.3 [basic.lookup.qual], and 3.4.3 [basic.lookup.qual] doesn't cover the case.
See also issue 305.
Jason Merrill: My thoughts on the subject were that the name we use in a destructor call is really meaningless; as soon as we see the ~ we know what the user means, all we're doing from that point is testing their ability to name the destructor in a conformant way. I think that everyone will agree that
anything::B::~B()should be well-formed, regardless of the origins of the name "B". I believe that the rule about looking up the second "B" in the same context as the first was intended to provide this behavior, but to me this seems much more heavyweight than necessary. We don't need a whole new type of lookup to be able to use the same name before and after the ~; we can just say that if the two names match, the call is well-formed. This is significantly simpler to express, both in the standard and in an implementation.
Anyone writing two different names here is either deliberately writing obfuscated code, trying to call the destructor of a nested class, or fighting an ornery compiler (i.e. one that still wants to see B_alias::~B()). I think we can ignore the first case. The third would be handled by reverting to the old rule (look up the name after ~ in the normal way) with the lexical matching exception described above -- or we could decide to break such code, do no lookup at all, and only accept a matching name. In a good implementation, the second should probably get an error message telling them to write Outer::Inner::~Inner instead.
We discussed this at the meetings, but I don't remember if we came to any sort of consensus on a direction. I see three options:
My order of preference is 2, 3, 1.
Incidentally, it seems to me oddly inconsistent to allow Namespace::~Class, but not Outer::~Inner. Prohibiting the latter makes sense from the standpoint of avoiding ambiguity, but what was the rationale for allowing the former?
John Spicer: I agree that allowing Namespace::~Class is odd. I'm not sure where this came from. If we eliminated that special case, then I believe the #1 rule would just be that in A::B1::~B2 you look up B1 and B2 in the same place in all cases.
I don't like #2. I don't think the "old" rules represent a deliberate design choice, just an error in the way the lookup was described. The usage that rule permits p->X::~Y (where Y is a typedef to X defined in X), but I doubt people really do that. In other words, I think that #1 a more useful special case than #2 does, not that I think either special case is very important.
One problem with the name matching rule is handling cases like:
A<int> *aip; aip->A<int>::~A<int>(); // should work aip->A<int>::~A<char>(); // should notI would favor #1, while eliminating the special case of Namespace::~Class.
Proposed resolution (10/01):
Replace the normative text of 3.4.3 [basic.lookup.qual] paragraph 5 after the first sentence with:
Similarly, in a qualified-id of the form:
::opt nested-name-specifieropt class-name :: ~ class-namethe second class-name is looked up in the same scope as the first.
In 12.4 [class.dtor] paragraph 12, change the example to
D D_object; typedef B B_alias; B* B_ptr = &D_object; void f() { D_object.B::~B(); // calls B's destructor B_ptr->~B(); // calls D's destructor B_ptr->~B_alias(); // calls D's destructor B_ptr->B_alias::~B(); // calls B's destructor B_ptr->B_alias::~B_alias(); // calls B's destructor }
April 2003: See issue 399.
[Moved to DR at 10/01 meeting.]
There is a mismatch between 12.4 [class.dtor] paragraph 11 and 12.5 [class.free] paragraph 4 regarding the lookup of deallocation functions in virtual destructors. 12.4 [class.dtor] says,
At the point of definition of a virtual destructor (including an implicit definition (12.8 [class.copy])), non-placement operator delete shall be looked up in the scope of the destructor's class (3.4.1 [basic.lookup.unqual]) and if found shall be accessible and unambiguous. [Note: this assures that an operator delete corresponding to the dynamic type of an object is available for the delete-expression (12.5 [class.free]). ]
The salient features to note from this description are:
On the other hand, 12.5 [class.free] says,
If a delete-expression begins with a unary :: operator, the deallocation function's name is looked up in global scope. Otherwise, if the delete-expression is used to deallocate a class object whose static type has a virtual destructor, the deallocation function is the one found by the lookup in the definition of the dynamic type's virtual destructor (12.4 [class.dtor]). Otherwise, if the delete-expression is used to deallocate an object of class T or array thereof, the static and dynamic types of the object shall be identical and the deallocation function's name is looked up in the scope of T. If this lookup fails to find the name, the name is looked up in the global scope. If the result of the lookup is ambiguous or inaccessible, or if the lookup selects a placement deallocation function, the program is ill-formed.
Points of interest in this description include:
Suggested resolution: Change the description of the lookup in 12.4 [class.dtor] paragraph 11 to match the one in 12.5 [class.free] paragraph 4.
Proposed resolution (10/00):
Replace 12.4 [class.dtor] paragraph 11 with the following:
At the point of definition of a virtual destructor (including an implicit definition), the non-array deallocation function is looked up in the scope of the destructor's class (10.2 [class.member.lookup]), and, if no declaration is found, the function is looked up in the global scope. If the result of this lookup is ambiguous or inaccessible, or if the lookup selects a placement deallocation function, the program is ill-formed. [Note: this assures that a deallocation function corresponding to the dynamic type of an object is available for the delete-expression (12.5 [class.free]).]
In 12.5 [class.free] paragraph 4, change
...the deallocation function is the one found by the lookup in the definition of the dynamic type's virtual destructor (12.4 [class.dtor]).
to
...the deallocation function is the one selected at the point of definition of the dynamic type's virtual destructor (12.4 [class.dtor]).
[Moved to DR at 10/01 meeting.]
12.4 [class.dtor] paragraph 12 contains the following note:an explicit destructor call must always be written using a member access operator (5.2.5 [expr.ref]); in particular, the unary-expression ~X() in a member function is not an explicit destructor call (5.3.1 [expr.unary.op]).
This note is incorrect, as an explicit destructor call can be written as a qualified-id, e.g., X::~X(), which does not use a member access operator.
Proposed resolution (04/01):
Change 12.4 [class.dtor] paragraph 12 as follows:
[Note: an explicit destructor call must always be written using a member access operator (5.2.5 [expr.ref]) or a qualified-id (5.1.1 [expr.prim.general]); in particular, the unary-expression ~X() in a member function is not an explicit destructor call (5.3.1 [expr.unary.op]).]
[Voted into the WP at the September, 2008 meeting.]
Deallocation functions can't be virtual because they are static member functions; however, according to 12.5 [class.free] paragraph 7, they behave like virtual functions when the class's destructor is virtual:
Since member allocation and deallocation functions are static they cannot be virtual. [Note: however, when the cast-expression of a delete-expression refers to an object of class type, because the deallocation function actually called is looked up in the scope of the class that is the dynamic type of the object, if the destructor is virtual, the effect is the same.
Because the intent is to make any use of a deleted function diagnosable at compile time, a virtual deleted function can neither override nor be overridden by a non-deleted function, as described in 10.3 [class.virtual] paragraph 14:
A function with a deleted definition (8.4 [dcl.fct.def]) shall not override a function that does not have a deleted definition. Likewise, a function that does not have a deleted definition shall not override a function with a deleted definition.
One would assume that a similar kind of prohibition is needed for deallocation functions in a class hierarchy with virtual destructors, but it's not clear that the current specification says that. 8.4 [dcl.fct.def] paragraph 10 says,
A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed.
Furthermore, the deallocation function is looked up at the point of definition of a virtual destructor (12.4 [class.dtor] paragraph 11), and the function found by this lookup is considered to be “used” (3.2 [basic.def.odr] paragraph 2). However, it's not completely clear that this “use” constitutes a “reference” in the sense of 8.4 [dcl.fct.def] paragraph 10, especially in a program in which an object of a type that would call that deallocation function is never deleted.
Suggested resolution:Augment the list of lookup results from a virtual destructor that render a program ill-formed in 12.4 [class.dtor] paragraph 10 to include a deleted function:
If the result of this lookup is ambiguous or inaccessible, or if the lookup selects a placement deallocation function or a function with a deleted definition (8.4 [dcl.fct.def]), the program is ill-formed.
Proposed resolution (June, 2008):
Change 12.4 [class.dtor] paragraph 10 as follows:
If the result of this lookup is ambiguous or inaccessible, or if the lookup selects a placement deallocation function or a function with a deleted definition (8.4 [dcl.fct.def]), the program is ill-formed.
[Voted into WP at April, 2006 meeting.]
8.5 [dcl.init] paragraph 10 makes it clear that non-static POD class objects with no initializer are left uninitialized and have an indeterminate initial value:
If no initializer is specified for an object, and the object is of (possibly cv-qualified) non-POD class type (or array thereof), the object shall be default-initialized; if the object is of const-qualified type, the underlying class type shall have a user-declared default constructor. Otherwise, if no initializer is specified for a non-static object, the object and its subobjects, if any, have an indeterminate initial value; if the object or any of its subobjects are of const-qualified type, the program is ill-formed.
12.6 [class.init] paragraph 1, however, implies that all class objects without initializers, whether POD or not, are default-initialized:
When no initializer is specified for an object of (possibly cv-qualified) class type (or array thereof), or the initializer has the form (), the object is initialized as specified in 8.5 [dcl.init]. The object is default-initialized if there is no initializer, or value-initialized if the initializer is ().
Proposed resolution (October, 2005):
Remove the indicated words from 12.6 [class.init] paragraph 1:
When no initializer is specified for an object of (possibly cv-qualified) class type (or array thereof), or the initializer has the form (), the object is initialized as specified in 8.5 [dcl.init]. The object is default-initialized if there is no initializer, or value-initialized if the initializer is ().
[Voted into the WP at the September, 2008 meeting (resolution in paper N2757).]
Part of the decision regarding whether a class has a trivial special function (copy constructor, copy assignment operator, default constructor) is whether its base and member subobjects have corresponding trivial member functions. However, with the advent of defaulted functions, it is now possible for a single class to have both trivial and nontrivial overloads for those functions. For example,
struct B { B(B&) = default; // trivial B(const B&); // non-trivial, because user-provided }; struct D : B { };
Although B has a trivial copy constructor and thus satisfies the requirements in 12.8 [class.copy] paragraph 6, the copy constructor in B that would be called by the implicitly-declared copy constructor in D is not trivial. This could be fixed either by requiring that all the subobject's copy constructors (or copy assignment operators, or default constructors) be trivial or that the one that would be selected by overload resolution be trivial.
Proposed resolution (July, 2008):
Change 8.4 [dcl.fct.def] paragraph 9 as follows:
... A special member function that would be implicitly defined as deleted shall not be explicitly defaulted. If a special member function for a class X is defaulted on its first declaration, no other special member function of the same kind (default constructor, copy constructor, or copy assignment operator) shall be declared in class X. A special member function is user-provided...
Notes from the September, 2008 meeting:
The resolution adopted as part of paper N2757 differs from the July, 2008 proposed resolution by allowing defaulted and user-provided special member functions to coexist. Instead, a trivial class is defined as having no non-trivial copy constructors or copy assignment operators, and a trivial copy constructor or assignment operator is defined as invoking only trivial copy operations for base and member subobjects.
[Moved to DR at October 2002 meeting.]
13.3.1.1 [over.match.call] paragraph 3 says that when a call of the form
(&C::f)()is written, the set of overloaded functions named by C::f must not contain any nonstatic member functions. A footnote gives the rationale: if a member of C::f is a nonstatic member function, &C::f is a pointer to member constant, and therefore the call is invalid.
This is clear, it's implementable, and it doesn't directly contradict anything else in the standard. However, I'm not sure it's consistent with some similar cases.
In 13.4 [over.over] paragraph 5, second example, it is made amply clear that when &C::f is used as the address of a function, e.g.,
int (*pf)(int) = &C::f;the overload set can contain both static and nonstatic member functions. The function with the matching signature is selected, and if it is nonstatic &C::f is a pointer to member function, and otherwise &C::f is a normal pointer to function.
Similarly, 13.3.1.1.1 [over.call.func] paragraph 3 makes it clear that
C::f();is a valid call even if the overload set contains both static and nonstatic member functions. Overload resolution is done, and if a nonstatic member function is selected, an implicit this-> is added, if that is possible.
Those paragraphs seem to suggest the general rule that you do overload resolution first and then you interpret the construct you have according to the function selected. The fact that there are static and nonstatic functions in the overload set is irrelevant; it's only necessary that the chosen function be static or nonstatic to match the context.
Given that, I think it would be more consistent if the (&C::f)() case would also do overload resolution first. If a nonstatic member is chosen, the program would be ill-formed.
Proposed resolution (04/01):
Change the indicated text in 13.3.1.1 [over.match.call] paragraph 3:
The fourth case arises from a postfix-expression of the form &F, where F names a set of overloaded functions. In the context of a function call, the set of functions named by F shall contain only non-member functions and static member functions. [Footnote: If F names a non-static member function, &F is a pointer-to-member, which cannot be used with the function call syntax.] And in this context using &F behaves the same as using &F is treated the same as the name F by itself. Thus, (&F)(expression-listopt) is simply (F)(expression-listopt), which is discussed in 13.3.1.1.1 [over.call.func]. If the function selected by overload resolution according to 13.3.1.1.1 [over.call.func] is a nonstatic member function, the program is ill-formed. [Footnote: When F is a nonstatic member function, a reference of the form &A::F is a pointer-to-member, which cannot be used with the function-call syntax, and a reference of the form &F is an invalid use of the "&" operator on a nonstatic member function.] (The resolution of &F in other contexts is described in 13.4 [over.over].)
[Moved to DR at 4/01 meeting.]
In describing non-member functions in an overload set, footnote 116 (13.3.1.1.1 [over.call.func]) says,Because of the usual name hiding rules, these will be introduced by declarations or by using-directives all found in the same block or all found at namespace scope.
At least in terms of the current state of the Standard, this is not correct: a block extern declaration does not prevent Koenig lookup from occurring. For example,
enum E { zero }; void f(E); void g() { void f(int); f(zero); }
In this example, the overload set will include declarations from both namespace and block scope.
(See also issue 12.)
Proposed resolution (04/01):
In 3.4.2 [basic.lookup.argdep] paragraph 2, change
If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated namespaces and classes are not considered.
to
If the ordinary unqualified lookup of the name finds the declaration of a class member function, or a block-scope function declaration that is not a using-declaration, the associated namespaces and classes are not considered.
and change the example to:
namespace NS { class T { }; void f(T); void g(T, int); } NS::T parm; void g(NS::T, float); int main() { f(parm); // OK: calls NS::f extern void g(NS::T, float); g(parm, 1); // OK: calls g(NS::T, float) }
In 13.3.1.1.1 [over.call.func] paragraph 3 from:
If the name resolves to a non-member function declaration, that function and its overloaded declarations constitute the set of candidate functions.
to
If the name resolves to a set of non-member function declarations, that set of functions constitutes the set of candidate functions.
Note that this text is also edited by issue 364. Also, remove the associated footnote 116.
[Voted into WP at October 2003 meeting.]
Consider this program:
struct S { static void f (int); void f (char); }; void g () { S::f ('a'); }
G++ 3.1 rejects it, saying:
test.C:7: cannot call member function `void S::f(char)' without object
Mark Mitchell: It looks to me like G++ is correct, given 13.3.1.1.1 [over.call.func]. This case is the "unqualified function call" case described in paragraph 3 of that section. ("Unqualified" here means that there is no "x->" or "x." in front of the call, not that the name is unqualified.)
That paragraph says that you first do name lookup. It then asks you to look at what declaration is returned. (That's a bit confusing; you presumably get a set of declarations. Or maybe not; the name lookup section says that if name lookup finds a non-static member in a context like this the program is in error. But surely this program is not erroneous. Hmm.)
Anyhow, you have -- at least -- "S::f(char)" as the result of the lookup.
The keyword "this" is not in scope, so "all overloaded declarations of the function name in T become candidate functions and a contrived object of type T becomes the implied object argument." That means we get both versions of "f" at this point. Then, "the call is ill-formed, however, if overload resolution selects one of the non-static members of T in this case." Since, in this case, "S::f(char)" is the winner, the program is ill-formed.
Steve Adamczyk: This result is surprising, because we've selected a function that we cannot call, when there is another function that can be called. This should either be ambiguous, or it should select the static member function. See also 13.3.1 [over.match.funcs] paragraph 2: "Similarly, when appropriate, the context can construct an argument list that contains an implied object argument..."
Notes from October 2002 meeting:
We agreed that g++ has it right, but the standard needs to be clearer.
Proposed resolution (October 2002, revised April 2003):
Change 13.3.1.1.1 [over.call.func] paragraphs 2 and 3 as follows:
In qualified function calls, the name to be resolved is an id-expression and is preceded by an -> or . operator. Since the construct A->B is generally equivalent to (*A).B, the rest of clause 13 [over] assumes, without loss of generality, that all member function calls have been normalized to the form that uses an object and the . operator. Furthermore, clause 13 [over] assumes that the postfix-expression that is the left operand of the . operator has type ``cv T'' where T denotes a class. [Footnote: Note that cv-qualifiers on the type of objects are significant in overload resolution for both lvalue and class rvalue objects. --- end footnote] Under this assumption, the id-expression in the call is looked up as a member function of T following the rules for looking up names in classes (10.2 [class.member.lookup]). If a member function is found, that function and its overloaded declarations The function declarations found by that lookup constitute the set of candidate functions. The argument list is the expression-list in the call augmented by the addition of the left operand of the . operator in the normalized member function call as the implied object argument (13.3.1 [over.match.funcs]).
In unqualified function calls, the name is not qualified by an -> or . operator and has the more general form of a primary-expression. The name is looked up in the context of the function call following the normal rules for name lookup in function calls (3.4.2 [basic.lookup.argdep] 3.4 [basic.lookup]). If the name resolves to a non-member function declaration, that function and its overloaded declarations The function declarations found by that lookup constitute the set of candidate functions. [Footnote: Because of the usual name hiding rules, these will be introduced by declarations or by using-directives all found in the same block or all found at namespace scope. --- end footnote] Because of the rules for name lookup, the set of candidate functions consists (1) entirely of non-member functions or (2) entirely of member functions of some class T. In case (1), tThe argument list is the same as the expression-list in the call. If the name resolves to a nonstatic member function, then the function call is actually a member function call. In case (2), the argument list is the expression-list in the call augmented by the addition of an implied object argument as in a qualified function call. If the keyword this (9.3.2 [class.this]) is in scope and refers to the class T of that member function, or a derived class thereof of T, then the function call is transformed into a normalized qualified function call using implied object argument is(*this) as the postfix-expression to the left of the . operator. The candidate functions and argument list are as described for qualified function calls above. If the keyword this is not in scope or refers to another class, then name resolution found a static member of some class T. In this case, all overloaded declarations of the function name in T become candidate functions and a contrived object of type T becomes the implied object argument. [Footnote: An implied object argument must be contrived to correspond to the implicit object parameter attributed to member functions during overload resolution. It is not used in the call to the selected function. Since the member functions all have the same implicit object parameter, the contrived object will not be the cause to select or reject a function. --- end footnote] If the argument list is augmented by a contrived object and The call is ill-formed, however, if overload resolution selects one of the non-static member functions of T, the call is ill-formed in this case.
Note that issue 239 also edits paragraph 3.
[Voted into WP at October 2003 meeting.]
According to 13.3.1.1.2 [over.call.object] paragraph 2, when the primary-expression E in the function call syntax evaluates to a class object of type "cv T", a surrogate call function corresponding to an appropriate conversion function declared in a direct or indirect base class B of T is included or not included in the set of candidate functions based on class B being accessible.
For instance in the following code sample, as per the paragraph in question, the expression c(3) calls f2, instead of the construct being ill-formed due to the conversion function A::operator fp1 being inaccessible and its corresponding surrogate call function providing a better match than the surrogate call function corresponding to C::operator fp2:
void f1(int) { } void f2(float) { } typedef void (* fp1)(int); typedef void (* fp2)(float); struct A { operator fp1() { return f1; } }; struct B : private A { }; struct C : B { operator fp2() { return f2; } }; int main() { C c; c(3); // f2 is called, instead of the construct being ill-formed. return 0; }
The fact that the accessibility of a base class influences the overload resolution process contradicts the fundamental language rule (3.4 [basic.lookup] paragraph 1, and 13.3 [over.match] paragraph 2) that access checks are applied only once name lookup and function overload resolution (if applicable) have succeeded.
Notes from 4/02 meeting:
There was some concern about whether 10.2 [class.member.lookup] (or anything else, for that matter) actually defines "ambiguous base class". See issue 39. See also issue 156.
Notes from October 2002 meeting:
It was suggested that the ambiguity check is done as part of the call of the conversion function.
Proposed resolution (revised October 2002):
In 13.3.1.1.2 [over.call.object] paragraph 2, replace the last sentence
Similarly, surrogate call functions are added to the set of candidate functions for each conversion function declared in an accessible base class provided the function is not hidden within T by another intervening declaration.
with
Similarly, surrogate call functions are added to the set of candidate functions for each conversion function declared in a base class of T provided the function is not hidden within T by another intervening declaration.
Replace 13.3.1.1.2 [over.call.object] paragraph 3
If such a surrogate call function is selected by overload resolution, its body, as defined above, will be executed to convert E to the appropriate function and then to invoke that function with the arguments of the call.by
If such a surrogate call function is selected by overload resolution, the corresponding conversion function will be called to convert E to the appropriate function pointer or reference, and the function will then be invoked with the arguments of the call. If the conversion function cannot be called (e.g., because of an ambiguity), the program is ill-formed.
[Voted into WP at October 2004 meeting.]
Normally reference semantics allow incomplete types in certain contexts, but isn't this:
class A; A& operator<<(A& a, const char* msg); void foo(A& a) { a << "Hello"; }
required to be diagnosed because of the op<<? The reason being that the class may actually have an op<<(const char *) in it.
What is it? un- or ill-something? Diagnosable? No problem at all?
Steve Adamczyk: I don't know of any requirement in the standard that the class be complete. There is a rule that will instantiate a class template in order to be able to see whether it has any operators. But I wouldn't think one wants to outlaw the above example merely because the user might have an operator<< in the class; if he doesn't, he would not be pleased that the above is considered invalid.
Mike Miller: Hmm, interesting question. My initial reaction is that it just uses ::operator<<; any A::operator<< simply won't be considered in overload resolution. I can't find anything in the Standard that would say any different.
The closest analogy to this situation, I'd guess, would be deleting a pointer to an incomplete class; 5.3.5 [expr.delete] paragraph 5 says that that's undefined behavior if the complete type has a non-trivial destructor or an operator delete. However, I tend to think that that's because it deals with storage and resource management, not just because it might have called a different function. Generally, overload resolution that goes one way when it might have gone another with more declarations in scope is considered to be not an error, cf 7.3.3 [namespace.udecl] paragraph 9, 14.6.3 [temp.nondep] paragraph 1, etc.
So my bottom line take on it would be that it's okay, it's up to the programmer to ensure that all necessary declarations are in scope for overload resolution. Worst case, it would be like the operator delete in an incomplete class -- undefined behavior, and thus not required to be diagnosed.
13.3.1.2 [over.match.oper] paragraph 3, bullet 1, says, "If T1 is a class type, the set of member candidates is the result of the qualified lookup of T1::operator@ (13.3.1.1.1 [over.call.func])." Obviously, that lookup is not possible if T1 is incomplete. Should 13.3.1.2 [over.match.oper] paragraph 3, bullet 1, say "complete class type"? Or does the inability to perform the lookup mean that the program is ill-formed? 3.2 [basic.def.odr] paragraph 4 doesn't apply, I don't think, because you don't know whether you'll be applying a class member access operator until you know whether the operator involved is a member or not.
Notes from October 2003 meeting:
We noticed that the title of this issue did not match the body. We checked the original source and then corrected the title (so it no longer mentions templates).
We decided that this is similar to other cases like deleting a pointer to an incomplete class, and it should not be necessary to have a complete class. There is no undefined behavior.
Proposed Resolution (October 2003):
Change the first bullet of 13.3.1.2 [over.match.oper] paragraph 3 to read:
If T1 is a complete class type, the set of member candidates is the result of the qualified lookup of T1::operator@ (13.3.1.1.1 [over.call.func]); otherwise, the set of member candidates is empty.
[Moved to DR at October 2002 meeting.]
Does dropping a cv-qualifier on a reference binding prevent the binding as far as overload resolution is concerned? Paragraph 4 says "Other restrictions on binding a reference to a particular argument do not affect the formation of a conversion sequence." This was intended to refer to things like access checking, but some readers have taken that to mean that any aspects of reference binding not mentioned in this section do not preclude the binding.
Proposed resolution (10/01):
In 13.3.3.1.4 [over.ics.ref] paragraph 4 add the indicated text:
Other restrictions on binding a reference to a particular argument that are not based on the types of the reference and the argument do not affect the formation of a standard conversion sequence, however.
[Voted into WP at October 2003 meeting.]
template <class T> void f(T); template <class T> void g(T); template <class T> void g(T,T); int main() { (&f<int>); (&g<int>); }The question is whether &f<int> identifies a unique function. &g<int> is clearly ambiguous.
13.4 [over.over] paragraph 1 says that a function template name is considered to name a set of overloaded functions. I believe it should be expanded to say that a function template name with an explicit template argument list is also considered to name a set of overloaded functions.
In the general case, you need to have a destination type in order to identify a unique function. While it is possible to permit this, I don't think it is a good idea because such code depends on there only being one template of that name that is visible.
The EDG front end issues an error on this use of "f". egcs 1.1.1 allows it, but the most current snapshot of egcs that I have also issues an error on it.
It has been pointed out that when dealing with nontemplates, the rules for taking the address of a single function differ from the rules for an overload set, but this asymmetry is needed for C compatibility. This need does not exist for the template case.
My feeling is that a general rule is better than a general rule plus an exception. The general rule is that you need a destination type to be sure that the operation will succeed. The exception is when there is only one template in the set and only then when you provide values for all of the template arguments.
It is true that in some cases you can provide a shorthand, but only if you encourage a fragile coding style (that will cause programs to break when additional templates are added).
I think the standard needs to specify one way or the other how this case should be handled. My recommendation would be that it is ill-formed.
Nico Josuttis: Consider the following example:
template <int VAL> int add (int elem) { return elem + VAL; } std::transform(coll.begin(), coll.end(), coll.begin(), add<10>);
If John's recommendation is adopted, this code will become ill-formed. I bet there will be a lot of explanation for users necessary why this fails and that they have to change add<10> to something like (int (*)(int))add<10>.
This example code is probably common practice because this use of the STL is typical and is accepted in many current implementations. I strongly urge that this issue be resolved in favor of keeping this code valid.
Bill Gibbons: I find this rather surprising. Shouldn't a template-id which specifies all of the template arguments be treated like a declaration-only explicit instantiation, producing a set of ordinary function declarations? And when that set happens to contain only one function, shouldn't the example code work?
(See also issue 250.)
Notes from 04/01 meeting:
The consensus of the group was that the add example should not be an error.
Proposed resolution (October 2002):
In 13.4 add to the end of paragraph 2:
[Note: As described in 14.8.1 [temp.arg.explicit], if deduction fails and the function template name is followed by an explicit template argument list, the template-id is then examined to see whether it identifies a single function template specialization. If it does, the template-id is considered to be an lvalue for that function template specialization. The target type is not used in that determination.]
In 14.8.1 [temp.arg.explicit] paragraph 2 insert before the first example:
In contexts where deduction is done and fails, or in contexts where deduction is not done, if a template argument list is specified and it, along with any default template arguments, identifies a single function template specialization, then the template-id is an lvalue for the function template specialization.
Change the first example of 14.8.1 [temp.arg.explicit] paragraph 2:
template<class X, class Y> X f(Y); void g() { int i = f<int>(5.6); // Y is deduced to be double int j = f(5.6); // ill-formed: X cannot be deduced }to read:
template<class X, class Y> X f(Y); void g() { int i = f<int>(5.6); // Y is deduced to be double int j = f(5.6); // ill-formed: X cannot be deduced f<void>(f<int, bool>); // Y for outer f deduced to be // int (*)(bool) f<void>(f<int>); // ill-formed: f<int> does not denote a // single template function specialization }
Note: This interacts with the resolution of issue 226 (default template arguments for function templates).
[Moved to DR at 4/01 meeting.]
Is the intent of 13.5.3 [over.ass] paragraph 1 that all assignment operators be non-static member functions (including operator+=, operator*=, etc.) or only simple assignment operators (operator=)?
Notes from 04/00 meeting:
Nearly all references to "assignment operator" in the IS mean operator= and not the compound assignment operators. The ARM was specific that this restriction applied only to operator=. If it did apply to compound assignment operators, it would be impossible to overload these operators for bool operands.
Proposed resolution (04/01):
Change the title of 5.17 [expr.ass] from "Assignment operators" to "Assignment and compound assignment operators."
Change the first sentence of 5.17 [expr.ass] paragraph 1 from
There are several assignment operators, all of which group right-to-left. All require a modifiable lvalue as their left operand, and the type of an assignment expression is that of its left operand. The result of the assignment operation is the value stored in the left operand after the assignment has taken place; the result is an lvalue.
to
The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue with the type and value of the left operand after the assignment has taken place.
Additional note (10/00): Paragraphs 2-6 of 5.17 [expr.ass] should all be understood to apply to simple assignment only and not to compound assignment operators.
[Voted into WP at April, 2006 meeting.]
Lets start with the proposed solution. In 13.5.6 [over.ref], replace line ...
postfix-expression -> id-expression.... with the lines ...
postfix-expression -> templateopt id-expression(This then is a copy of the two lines in 5.2 [expr.post] covering "->dtor")
postfix-expression -> pseudo-destructor-name
Alternatively remove the sentence "It implements class member access using ->" and the syntax line following.
Reasons:
Currently stdc++ is inconsistent when handling expressions of the form "postfixexpression->scalar_type_dtor()": If "postfixexpression" is a pointer to the scalar type, it is OK, but if "postfixexpression" refers to any smart pointer class (e.g. iterator or allocator::pointer) with class specific CLASS::operator->() returning pointer to the scalar type, then it is ill-formed; so while c++98 does allow CLASS::operator->() returning pointer to scalar type, c++98 prohibits any '->'-expression involving this overloaded operator function.
Not only is this behaviour inconsistent, but also when comparing the corresponding chapters of c++pl2 and stdc++98 it looks like an oversight and unintended result. Mapping between stdc++98 and c++pl2:
c++pl2.r.5.2 -> 5.2 [expr.post]For the single line of c++pl2.r.5.2 covering "->dtor", 5.2 [expr.post] has two lines. Analogously c++pl2.r.5.2.4 has been doubled to 5.2.4 [expr.pseudo] and 5.2.5 [expr.ref]. From 13.5.6 [over.ref], the sentence forbiding CLASS::operator->() returning pointer to scalar type has been removed. Only the single line of c++pl2.r.13.4.6 (<-> c++pl2.r.5.2's single line) has not gotten its 2nd line when converted into 13.5.6 [over.ref].
c++pl2.r.5.2.4 -> 5.2.4 [expr.pseudo] + 5.2.5 [expr.ref]
c++pl2.r.13.4 -> 13.3.1.2 [over.match.oper]
c++pl2.r.13.4.6 -> 13.5.6 [over.ref]
Additionally GCC32 does is right (but against 13.5.6 [over.ref]).
AFAICS this would not break old code except compilers like VC7x and Comeau4301.
It does not add new functionality, cause any expression class_type->scalar_type_dtor() even today can be substituted through (*class_type).scalar_type_dtor().
Without this fix, template functions like some_allocator<T>::destroy(p) must use "(*p).~T()" or "(*p).T::~T()" when calling the destructor, otherwise the simpler versions "p->~T()" or "p->T::~T()" could be used.
Sample code, compiled with GCC32, VC7[1] and Comeau4301:
struct A {};//any class template <class T> struct PTR { T& operator* () const; T* operator-> () const; }; template <class T> void f () { { T* p ; p = new T ; (*p).T::~T() ;//OK p = new T ; (*p).~T() ;//OK p = new T ; p->T::~T() ;//OK p = new T ; p->~T() ;//OK } { PTR<T> p = PTR<T>() ; (*p).T::~T() ;//OK (*p).~T() ;//OK p.operator->() ;//OK !!! p->T::~T() ;//GCC32: OK; VC7x,Com4301: OK for A; ERROR w/ int p->~T() ;//GCC32: OK; VC7x,Com4301: OK for A; ERROR w/ int } } void test () { f <A> (); f <int>(); }
Proposed resolution (April, 2005):
Change 13.5.6 [over.ref] paragraph 1 as indicated:
operator-> shall be a non-static member function taking no parameters. It implements the class member access using syntax that uses ->
postfix-expression -> templateopt id-expression
postfix-expression -> pseudo-destructor-nameAn expression x->m is interpreted as (x.operator->())->m for a class object x of type T if T::operator->() exists and if the operator is selected as the best match function by the overload resolution mechanism (13.3 [over.match]).
[Voted into WP at March 2004 meeting.]
During a discussion over at the boost mailing list (www.boost.org), we came across the following "puzzle":
struct A { template< typename T > operator T() const; } a; template<> A::operator float() const { return 1.0f; } int main() { float f = 1.0f * a; }
The code is compiled without errors or warnings from EDG-based compilers (Comeau, Intel), but rejected from others (GCC, MSVC [7.1]). The question: Who is correct? Where should I file the bug report?
To explain the problem: The EDG seems to see 1.0f*a as a call to the unambiguous operator*(float,float) and thus casts 'a' to 'float'. The other compilers have several operators (float*float, float*double, float*int, ...) available and thus can't decide which cast is appropriate. I think the latter is the correct behaviour, but I'd like to hear some comments from the language lawyers about the standard's point of view on this problem.
Andreas Hommel: Our compiler also rejects this code:
Error : function call 'operator*(float, {lval} A)' is ambiguous 'operator*(float, unsigned long long)' 'operator*(float, int)' 'operator*(float, unsigned int)' 'operator*(float, long)' 'operator*(float, unsigned long)' 'operator*(float, float)' 'operator*(float, double)' 'operator*(float, long double)' 'operator*(float, long long)' Test.cp line 12 float f = 1.0f * a;
Is this example really legal? It was my understanding that all candidates from 13.6 [over.built] participate in overload resolution.
Daveed Vandevoorde: I believe the EDG-based compiler is right. Note that the built-in operator* requires "usual arithmetic conversions" (see 5.6 [expr.mul] paragraph 2 and 5 [expr] paragaph 9). This means that there is no candidate taking (float, double) arguments: Only (float, float) or (double, double).
Since your first argument is of type float, the (float, float) case is preferred over the (double, double) case (the latter would require a floating-point promotion).
Stave Adamczyk: Daveed's statement is wrong; as Andreas says, the prototypes in 13.6 [over.built] paragraph 12 have pairs of types, not the same type twice. However, the list of possibilities considered in Andreas' message is wrong also: 13.6 [over.built] paragraph 12 calls for pairs of promoted arithmetic types, and float is not a promoted type (it promotes to double -- see 4.6 [conv.fpprom]).
Nevertheless, the example is ambiguous. Let's look at the overload resolution costs. The right operand is always going to have a user-defined-conversion cost (the template conversion function will convert directly to the const version of the second parameter of the prototype). The left operand is always going to have a promotion (float --> double) or a standard conversion (anything else). So the cases with promotions are better than the others. However, there are several of those cases, with second parameters of type int, unsigned int, long, unsigned long, double, and long double, and all of those are equally good. Therefore the example is ambiguous.
Here's a reduced version that should be equivalent:
struct A { template <typename T> operator T() const; } a; void f(double, int); void f(double, unsigned int); void f(double, long); void f(double, unsigned long); void f(double, double); void f(double, long double); int main() { f(1.0f, a); // Ambiguous }
Personally, I think this is evidence that 13.6 [over.built] doesn't really do quite what it should. But the standard is clear, if possibly flawed.
Andreas Hommel: You are right, "float" is not a promoted arithmetic type, this is a bug in our compiler.
However, the usual arithmetic conversions (5 [expr] paragraph 9) do not promote the floating point types, so
float operator+(float, float);is a legal built-in operator function, so I wonder if it shouldn't be included in the candidate list.
Steve Adamczyk: Hmm, the definition of the term in 13.6 [over.built] paragraph 2 is highly ambiguous:
Similarly, the term promoted arithmetic type refers to promoted integral types plus floating types.I can't tell if that's "promoted integral types plus (all) floating types" or "promoted integral types plus (promoted) floating types". I thought the latter was intended, but indeed the usual arithmetic conversions could give you "float + float", so it makes sense that float would be one of the possibilities. We should discuss this to make sure everyone has the same interpretation.
Proposed Resolution (October 2003):
Change the second sentence of 13.6 paragraph 2 as follows:
Similarly, the term promoted arithmetic type refers to promoted integral types plus floating types floating types plus promoted integral types.
[Voted into WP at April 2003 meeting.]
14 [temp] paragraph 7 allows class templates to be declared exported, including member classes and member class templates (implicitly by virtue of exporting the containing template class). However, paragraph 8 does not exclude exported class templates from the statement that
An exported template need only be declared (and not necessarily defined) in a translation unit in which it is instantiated.This is an incorrect implication; however, it is also not dispelled in 14.7.1 [temp.inst] paragraph 6:
If an implicit instantiation of a class template specialization is required and the template is declared but not defined, the program is ill-formed.This wording says nothing about the translation unit in which the definition must be provided. Contrast this with 14.7.2 [temp.explicit] paragraph 3:
A definition of a class template or a class member template shall be in scope at the point of the explicit instantiation of the class template or class member template.
Suggested resolution:
(See also issue 212.)
Notes from 04/00 meeting:
John Spicer opined that even though 14 [temp] paragraph 7 speaks of "declaring a class template exported," that does not mean that the class template is "an exported template" in the sense of paragraph 8. He suggested clarifying paragraph 7 to that effect instead of the change to paragraph 8 suggested above, and questioned the need for a change to 14.7.1 [temp.inst].
Notes from the 4/02 meeting:
This is resolved by the proposed changes for issue 323.
[Voted into WP at April 2003 meeting.]
The standard doesn't seem to describe whether the keyword export should appear on exported template declarations that are not used or defined in that particular translation unit.
For example:
// File 1: template<typename T> void f(); // export omitted // File 2: export template<typename T> void f() {} int main() { f<int>(); }
Another example is:
// File 1: struct S { template<typename T> void m(); }; // File 2: struct S { template<typename T> void m(); }; export template<typename T> void S::m() {} int main() { S s; S.m<int>(); }
I think both examples should be clarified to be invalid. If a template is exported in one translation unit, it should be declared export in all translation units in which it appears.
With the current wording, it seems that even the following is valid:
// File 1: export template<typename T> void f(); // export effectively ignored // File 2: template<typename T> void f() {} // Inclusion model void g() { f<int>(); } // File 3: void g(); template<typename T> void f() {} // Inclusion model int main() { g(); f<int>(); }
In fact, I think the declaration in "File 1" could be a definition and this would still satisfy the the requirements of the standard, which definitely seems wrong.
Proposed Resolution (revised October 2002):
Replace 14 [temp] paragraphs 6, 7, and 8 by the following text:
A template-declaration may be preceded by the export keyword. Such a template is said to be exported. Declaring exported a class template is equivalent to declaring exported all of its non-inline member functions, static data members, member classes, member class templates, and non-inline member function templates.
If a template is exported in one translation unit, it shall be exported in all translation units in which it appears; no diagnostic is required. A declaration of an exported template shall appear with the export keyword before any point of instantiation (14.6.4.1 [temp.point]) of that template in that translation unit. In addition, the first declaration of an exported template containing the export keyword must not follow the definition of that template. The export keyword shall not be used in a friend declaration.
Templates defined in an unnamed namespace, inline functions, and inline function templates shall not be exported. An exported non-class template shall be defined only once in a program; no diagnostic is required. An exported non-class template need only be declared (and not necessarily defined) in a translation unit in which it is instantiated.
A non-exported non-class template must be defined in every translation unit in which it is implicitly instantiated (14.7.1 [temp.inst]), unless the corresponding specialization is explicitly instantiated (14.7.2 [temp.explicit]) in some translation unit; no diagnostic is required.
Note: This change also resolves issues 204 and 335.
[Voted into WP at April 2003 meeting.]
The syntax for "export" permits it only on template declarations. Clause 14 [temp] paragraph 6 further restricts "export" to appear only on namespace scope declarations. This means that you can't export a member template of a non-template class, as in:
class A { template <class T> void f(T); };You can, of course, put export on the definition:
export template <class T> void A<T>::f(T){}but in order for the template to be used from other translation units (the whole point of export) the declaration in the other translation unit must also be declared export.
There is also the issue of whether or not we should permit this usage:
export struct A { template <class T> void f(T); };My initial reaction is to retain this prohibition as all current uses of "export" are preceding the "template" keyword.
If we eliminate the requirement that "export" precede "template" there is a similar issue regarding this case, which is currently prohibited:
template <class T> struct B { export void f(); };My preference is still to permit only "export template".
Notes from the 4/02 meeting:
This is resolved by the proposed changes for issue 323.
[Voted into WP at the October, 2006 meeting.]
Taken literally, 14 [temp] paragraph 2 does not permit operator functions to be templates:
In a function template declaration, the declarator-id shall be a template-name (i.e., not a template-id).
and, in
Issue 301 considered and rejected the idea of changing the definition of template-name to include operator-function-ids and conversion-function-ids. Either that decision should be reconsidered or the various references in the text to template-name should be examined to determine if they should also mention the non-identifier possibilities for function template names.
Proposed resolution (April, 2006):
This issue is resolved by the resolution of issue 301.
[Voted into WP at April 2003 meeting.]
John Spicer: Where can default values for the template parameters of template template parameters be specified and where so they apply?
For normal template parameters, defaults can be specified only in class template declarations and definitions, and they accumulate across multiple declarations in the same way that function default arguments do.
I think that defaults for parameters of template template parameters should be handled differently, though. I see no reason why such a default should extend beyond the template declaration with which it is associated. In other words, such defaults are a property of a specific template declaration and are not part of the interface of the template.
template <class T = float> struct B {}; template <template <class _T = float> class T> struct A { inline void f(); inline void g(); }; template <template <class _T> class T> void A<T>::f() { T<> t; // Okay? (proposed answer - no) } template <template <class _T = char> class T> // Okay? (proposed answer - yes) void A<T>::g() { T<> t; // T<char> or T<float>? (proposed answer - T<char>) } int main() { A<B> ab; ab.f(); }
I don't think this is clear in the standard.
Gabriel Dos Reis: On the other hand I fail to see the reasons why we should introduce yet another special rule to handle that situation differently. I think we should try to keep rules as uniform as possible. For default values, it has been the case that one should look for any declaration specifying default values. Breaking that rules doesn't buy us anything, at least as far as I can see. My feeling is that [allowing different defaults in different declarations] is very confusing.
Mike Miller: I'm with John on this one. Although we don't have the concept of "prototype scope" for template parameter lists, the analogy with function parameters would suggest that the two declarations of T (in the template class definition and the template member function definition) are separate declarations and completely unrelated. While it's true that you accumulate default arguments on top-level declarations in the same scope, it seems to me a far leap to say that we ought also to accumulate default arguments in nested declarations. I would expect those to be treated as being in different scopes and thus not to share default argument information.
When you look up the name T in the definition of A<T>::f(), the declaration you find has no default argument for the parameter of T, so T<> should not be allowed.
Proposed Resolution (revised October 2002):
In 14.1 [temp.param], add the following as a new paragraph at the end of this section:
A template-parameter of a template template-parameter is permitted to have a default template-argument. When such default arguments are specified, they apply to the template template-parameter in the scope of the template template-parameter. [Example:template <class T = float> struct B {}; template <template <class TT = float> class T> struct A { inline void f(); inline void g(); }; template <template <class TT> class T> void A<T>::f() { T<> t; // error - TT has no default template argument } template <template <class TT = char> class T>void A<T>::g() { T<> t; // OK - T<char> }-- end example]
[Voted into WP at April, 2007 meeting.]
According to 14.1 [temp.param] paragraph 3, the following fragment is ill-formed:
template <class T> class X{ friend void T::foo(); };
In the friend declaration, the T:: part is a nested-name-specifier (8 [dcl.decl] paragraph 4), and T must be a class-name or a namespace-name (5.1.1 [expr.prim.general] paragraph 7). However, according to 14.1 [temp.param] paragraph 3, it is only a type-name. The fragment should be well-formed, and instantiations of the template allowed as long as the actual template argument is a class which provides a function member foo. As a result of this defect, any usage of template parameters in nested names is ill-formed, e.g., in the example of 14.6 [temp.res] paragraph 2.
Notes from 04/00 meeting:
The discussion at the meeting revealed a self-contradiction in the current IS in the description of nested-name-specifiers. According to the grammar in 5.1.1 [expr.prim.general] paragraph 7, the components of a nested-name-specifier must be either class-names or namespace-names, i.e., the constraint is syntactic rather than semantic. On the other hand, 3.4.3 [basic.lookup.qual] paragraph 1 describes a semantic constraint: only object, function, and enumerator names are ignored in the lookup for the component, and the program is ill-formed if the lookup finds anything other than a class-name or namespace-name. It was generally agreed that the syntactic constraint should be eliminated, i.e., that the grammar ought to be changed not to use class-or-namespace-name.
A related point is the explicit prohibition of use of template parameters in elaborated-type-specifiers in 7.1.6.3 [dcl.type.elab] paragraph 2. This rule was the result of an explicit Committee decision and should not be unintentionally voided by the resolution of this issue.
Proposed resolution (04/01):
Change 5.1.1 [expr.prim.general] paragraph 7 and A.4 [gram.expr] from
to
This resolution depends on the resolutions for issues 245 (to change the name lookup rules in elaborated-type-specifiers to include all type-names) and 283 (to categorize template type-parameters as type-names).
Notes from 10/01 meeting:
There was some sentiment for going with simply identifier in front of the "::", and stronger sentiment for going with something with a more descriptive name if possible. See also issue 180.
Notes from April 2003 meeting:
This was partly resolved by the changes for issue 125. However, we also need to add a semantic check in 3.4.3 [basic.lookup.qual] to allow T::foo and we need to reword the first sentence of 3.4.3 [basic.lookup.qual].
Proposed resolution (October, 2004):
Change 3.4.3 [basic.lookup.qual] paragraph 1 as follows:
The name of a class or namespace member can be referred to after the :: scope resolution operator (5.1.1 [expr.prim.general]) applied to a nested-name-specifier that nominates its class or namespace. During the lookup for a name preceding the :: scope resolution operator, object, function, and enumerator names are ignored. If the name found is not a class-name (clause 9 [class]) or namespace-name (7.3.1 [namespace.def]) does not designate a class or namespace, the program is ill-formed. [...]
Notes from the April, 2005 meeting:
The 10/2004 resolution does not take into account the fact that template type parameters do not designate class types in the context of the template definition. Further drafting is required.
Proposed resolution (April, 2006):
Change 3.4.3 [basic.lookup.qual] paragraph 1 as follows:
The name of a class or namespace member can be referred to after the :: scope resolution operator (5.1.1 [expr.prim.general]) applied to a nested-name-specifier that nominates its class or namespace. During the lookup for a name preceding the :: scope resolution operator, object, function, and enumerator names are ignored. If the name found is not a class-name (clause 9 [class]) or namespace-name (7.3.1 [namespace.def]) does not designate a namespace or a class or dependent type, the program is ill-formed. [...]
[Voted into WP at April 2003 meeting.]
The prohibition of default template arguments for function templates is a misbegotten remnant of the time where freestanding functions were treated as second class citizens and required all template arguments to be deduced from the function arguments rather than specified.
The restriction seriously cramps programming style by unnecessarily making freestanding functions different from member functions, thus making it harder to write STL-style code.
Suggested resolution:
Replace
A default template-argument shall not be specified in a function template declaration or a function template definition, nor in the template-parameter-list of the definition of a member of a class template.
by
A default template-argument shall not be specified in the template-parameter-list of the definition of a member of a class template.
The actual rules are as stated for arguments to class templates.
Notes from 10/00 meeting:
The core language working group was amenable to this change. Questions arose, however, over the interaction between default template arguments and template argument deduction: should it be allowed or forbidden to specify a default argument for a deduced parameter? If it is allowed, what is the meaning: should one or the other have priority, or is it an error if the default and deduced arguments are different?
Notes from the 10/01 meeting:
It was decided that default arguments should be allowed on friend declarations only when the declaration is a definition. It was also noted that it is not necessary to insist that if there is a default argument for a given parameter all following parameters have default arguments, because (unlike in the class case) arguments can be deduced if they are not specified.
Note that there is an interaction with issue 115.
Proposed resolution (revised October 2002):
In 14.1 [temp.param] paragraph 9, replace
A default template-argument may be specified in a class template declaration or a class template definition. A default template-argument shall not be specified in a function template declaration or a function template definition, nor in the template-parameter-list of the definition of a member of a class template.
with
A default template-argument may be specified in a template declaration. A default template-argument shall not be specified in the template-parameter-lists of the definition of a member of a class template that appears outside of the member's class.
In 14.1 [temp.param] paragraph 9, replace
A default template-argument shall not be specified in a friend template declaration.
with
A default template-argument shall not be specified in a friend class template declaration. If a friend function template declaration specifies a default template-argument, that declaration shall be a definition and shall be the only declaration of the function template in the translation unit.
In 14.1 [temp.param] paragraph 11, replace
If a template-parameter has a default template-argument, all subsequent template-parameters shall have a default template-argument supplied.
with
If a template-parameter of a class template has a default template-argument, all subsequent template-parameters shall have a default template-argument supplied. [Note: This is not a requirement for function templates because template arguments might be deduced (14.8.2 [temp.deduct]).]
In 14.8 [temp.fct.spec] paragraph 1, replace
Template arguments can either be explicitly specified when naming the function template specialization or be deduced (14.8.2 [temp.deduct]) from the context, e.g. from the function arguments in a call to the function template specialization.
with
Template arguments can be explicitly specified when naming the function template specialization, deduced from the context (14.8.2 [temp.deduct]), e.g., deduced from the function arguments in a call to the function template specialization), or obtained from default template arguments.
In 14.8.1 [temp.arg.explicit] paragraph 2, replace
Trailing template arguments that can be deduced (14.8.2 [temp.deduct]) may be omitted from the list of explicit template-arguments.
with
Trailing template arguments that can be deduced (14.8.2 [temp.deduct]) or obtained from default template-arguments may be omitted from the list of explicit template-arguments.
In 14.8.2 [temp.deduct] paragraph 1, replace
The values can be either explicitly specified or, in some cases, deduced from the use.
with
The values can be explicitly specified or, in some cases, be deduced from the use or obtained from default template-arguments.
In 14.8.2 [temp.deduct] paragraph 4, replace
The resulting substituted and adjusted function type is used as the type of the function template for template argument deduction. When all template arguments have been deduced, all uses of template parameters in nondeduced contexts are replaced with the corresponding deduced argument values. If the substitution results in an invalid type, as described above, type deduction fails.
with
The resulting substituted and adjusted function type is used as the type of the function template for template argument deduction. If a template argument has not been deduced, its default template argument, if any, is used. [Example:
template <class T, class U = double> void f(T t = 0, U u = 0); void g() { f(1, 'c'); // f<int,char>(1,'c') f(1) // f<int,double>(1,0) f(); // error: T cannot be deduced f<int>(); // f<int,double>(0,0) f<int,char>(); // f<int,char>(0,0) }---end example]
When all template arguments have been deduced or obtained from default template arguments, all uses of template parameters in nondeduced contexts are replaced with the corresponding deduced or default argument values. If the substitution results in an invalid type, as described above, type deduction fails.
[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 April 2003 meeting.]
Consider the following example:
template<class T> struct X { virtual void f(); }; template<class T> struct Y { void g(X<T> *p) { p->template X<T>::f(); } };
This is an error because X is not a member template; 14.2 [temp.names] paragraph 5 says:
If a name prefixed by the keyword template is not the name of a member template, the program is ill-formed.
In a way this makes perfect sense: X is found to be a template using ordinary lookup even though p has a dependent type. However, I think this makes the use of the template prefix even harder to teach.
Was this intentionally outlawed?
Proposed Resolution (4/02):
Elide the first use of the word "member" in 14.2 [temp.names] paragraph 5 so that its first sentence reads:
If a name prefixed by the keyword template is not the name of a member template, the program is ill-formed.
[Voted into WP at the October, 2006 meeting.]
The grammar for a template-name is:
That's not right; consider:
template <class T> T operator+(const T&, const T&); template <> S operator+<S>(const S&, const S&);
This is ill-formed according to the standard, since operator+ is not a template-name.
Suggested resolution:
I think the right rule is
John Spicer adds that there's some question about whether conversion functions should be included, as they cannot have template argument lists.
Notes from 4/02 meeting:
If the change is made as a syntax change, we'll need a semantic restriction to avoid operator+<int> as a class. Clark Nelson will work on a compromise proposal -- not the minimal change to the syntax proposed, not the maximal change either.
Clark Nelson (April 2003):
The proposed solution (adding operator-function-id as an alternative for template-name) would have a large impact on the language described by the grammar. Specifically, for example, operator+<int> would become a syntactically valid class-name.
On the other hand, a change with (I believe) exactly the desired effect on the language accepted, would be to modify operator-function-id itself:
(Steve Adamczyk: this change was already made by issue 38 and is in TC1.)
Then there is the first sentence of 14.2 [temp.names] paragraph 3:
After name lookup (3.4 [basic.lookup]) finds that a name is a template-name, if this name is followed by a <, the < is always taken as the beginning of a template-argument-list and never as a name followed by the less-than operator.
This description seems to be adequate for names of class templates. As far as I can tell, the only ambiguity it resolves is from something that starts with new X <, in the scope of a class template X. But as far as I can tell is already inadequate for names of function templates, and is even worse for operator function templates.
Probably < should always be interpreted as introducing a template-argument-list if any member of the overload set is a function template. After all, function pointers are very rarely compared for ordering, and it's not clear what other rule might be workable.
I'm inclined to propose the simplest rule possible for operator-function-ids: if one is followed by <, then what follows is interpreted as a template-argument-list, unconditionally. Of course, if no template for that operator has been declared, then there's an error.
Also, note that if the operator in question is < or <<, it is possible to run into a problem similar to the famous >> nested template argument list closing delimiter problem. However, since in this case (at least) one of the < characters has a radically different interpretation than the other, and for other reasons as well, this is unlikely to be nearly as much of a practical problem as the >> problem.
Notes from April 2003 meeting:
We felt that the operator functions should not be special-cased. They should be treated like any other name.
September 2003:
Clark Nelson has provided the changes in N1490=03-0073.
Notes from October 2003 meeting:
We reviewed Clark Nelson's N1490. Clark will revise it and introduce a new syntax term for an identifier or the name of an operator function.
Notes from the April, 2005 meeting:
The CWG suggested a new approach to resolving this issue: the existing term template-id will be renamed to class-template-id, the term template-id will be defined to include operator functions with template arguments, and any current uses of template-id (such as in the definition of elaborated-type-specifier) where an operator function is not appropriate will be changed to refer to class-template-id.
Proposed resolution (April, 2006):
As specified in document J16/05-0156 = WG21 N1896, except that:
In change 9 (3.4.5 [basic.lookup.classref]), omit the change from “entire postfix-expression” to “nested-name-specifier.”
In change a (3.4.3.1 [class.qual] paragraph 1, third bullet), omit the change from “entire postfix-expression” to “qualified-id.”
In change b (3.4.3.2 [namespace.qual] paragraph 1), omit the change from “entire postfix-expression” to “qualified-id.”
(Some of these omitted changes are addressed by issue 562.)
(This resolution also resolves issue 534.)
[Voted into WP at the October, 2006 meeting.]
For the same reasons that issue 382 proposes for relaxation of the requirements on typename, it would make sense to allow the ::template disambiguator outside of templates.
See also issues 11, 30, 96, and 109.
Proposed resolution (October, 2005):
Change 14.2 [temp.names] paragraph 5 as indicated:
If a name prefixed by the keyword template is not the name of a template, the program is ill-formed. [Note: the keyword template may not be applied to non-template members of class templates. —end note] Furthermore, names of member templates shall not be prefixed by the keyword template if the postfix-expression or qualified-id does not appear in the scope of a template. [Note: just as is the case with the typename prefix, the template prefix is allowed in cases where it is not strictly necessary; i.e., when the nested-name-specifier or the expression on the left of the -> or ., or the nested-name-specifier is not dependent on a template-parameter, or the use does not appear in the scope of a template. —end note]
[Moved to DR at 4/01 meeting.]
Is it permitted to jump from a handler of a function-try-block into the body of the function?
15 [except] paragraph 2 would appear to disallow such a jump:
A goto, break, return, or continue statement can be used to transfer control out of a try block or handler, but not into one.
However, 15.3 [except.handle] paragraph 14 mentions only constructors and destructors for the prohibition:
If the handlers of a function-try-block contain a jump into the body of a constructor or destructor, the program is ill-formed.
Is this paragraph simply reemphasizing the more general restriction, or does it assume that such a jump would be permitted for functions other than constructors or destructors? If the former interpretation is correct, it is confusing and should be either eliminated or turned into a note. If the latter interpretation is accurate, 15 [except] paragraph 2 must be revised.
(See also issue 98.)
Proposed resolution (04/01):
Delete 15.3 [except.handle] paragraph 14.
[Voted into WP at the October, 2006 meeting.]
I'm not really sure what the standard says about this. Personally, I'd like for it to be ill-formed, but I can't find any words that I can interpret to say so.
template<class T> class X { protected: typedef T Type; }; template<class T> class Y { }; template<class T, template<class> class T1, template<class> class T2> class Z: public T2<typename T1<T>::Type>, public T1<T> { }; Z<int, X, Y> z;
John Spicer: I don't think the standard really addresses this case. There is wording about access checking of things used as template arguments, but that doesn't address accessing members of the template argument type (or template) from within the template.
This example is similar, but does not use template template arguments.
class X { private: struct Type {}; }; template <class T> struct A { typename T::Type t; }; A<X> ax;
This gets an error from most compilers, though the standard is probably mute on this as well. An error makes sense -- if there is no error, there is a hole in the access checking. (The special rule about no access checks on template parameters is not a hole, because the access is checked on the type passed in as an argument. But when you look up something in the scope of a template parameter type, you need to check the access to the member found.)
The logic in the template template parameter case should be similar: anytime you look up something in a template-dependent class, the member's access must be checked, because it could be different for different template instances.
Proposed Resolution (October 2002):
Change the last sentence of 14.3 [temp.arg] paragraph 3 from:
For a template-argument of class type, the template definition has no special access rights to the inaccessible members of the template argument type.to:
For a template-argument that is a class type or a class template, the template definition has no special access rights to the members of the template-argument. [Example:template <template <class TT> class T> class A { typename T<int>::S s; }; template <class U> class B { private: struct S { /* ... */ }; }; A<B> b; // ill-formed, A has no access to B::S-- end example]
Daniel Frey posts on comp.std.c++ in July 2003: I just read DR 372 and I think that the problem presented is not really discussed/solved properly. Consider this example:
class A { protected: typedef int N; }; template< typename T > class B {}; template< typename U > class C : public U, public B< typename U::N > {}; C< A > x;
The question is: If C is derived from A as above, is it allowed to access A::N before the classes opening '{'?
The main problem is that you need to access U's protected parts in C's base-clause. This pattern is common when using policies, Andrei's Loki library was bitten by it as he tried to make some parts of the policies 'protected' but some compilers rejected the code. They were right to reject it, I think it's 11.3 [class.friend]/2 that applies here and prevents the code above to be legal, although it addresses a different and reasonable example. To me, it seems wrong to reject the code as it is perfectly reasonable to write such stuff. The questions are:
Steve Adamczyk: In other words, the point of the issue is over what range access derived from base class specifiers is granted, and whether any part of that range is the base specifier list itself, either the parts afterwards or the whole base specifier list. (Clark Nelson confirms this is what he was asking with the original question.) Personally, I find it somewhat disturbing that access might arrive incrementally; I'd prefer that the access happen all at once, at the opening brace of the class.
Notes from October 2003 meeting:
We decided it makes sense to delay the access checking for the base class specifiers until the opening brace of the class is seen. In other words, the base specifiers will be checked using the full access available for the class, and the order of the base classes is not significant in that determination. The implementors present all said they already had code to handle accumulation of delayed access checks, because it is already needed in other contexts.
Proposed resolution (October, 2004):
Change the last sentence of 14.3 [temp.arg] paragraph 3 as indicated:
For a template-argument of that is a class type or a class template, the template definition has no special access rights to the inaccessible members of the template argument type template-argument. [Example:template <template <class TT> class T> class A { typename T<int>::S s; }; template <class U> class B { private: struct S { /* ... */ }; }; A<B> b; // ill-formed, A has no access to B::S—end example]
Insert the following as a new paragraph after the final paragraph of 11 [class.access]:
The access of names in a class base-specifier-list are checked at the end of the list after all base classes are known. [Example:
class A { protected: typedef int N; }; template<typename T> class B {}; template<typename U> class C : public B<typename U::N>, public U {}; C<A> x; // OK: A is a base class so A::N in B<A::N> is accessible
—end example]
Notes from the April, 2005 meeting:
The 10/2004 resolution is not sufficient to implement the CWG's intent to allow these examples: clause 11 [class.access] paragraph 1 grants protected access only to “members and friends” of derived classes, not to their base-specifiers. The resolution needs to be extended to say either that access in base-specifiers is determined as if they were members of the class being defined or that access is granted to the class as an entity, including its base-specifiers. See also issue 500, which touches on the same issue and should be resolved in the same way.
Proposed resolution (October, 2005):
Change the second bullet of 11 [class.access] paragraph 1 as indicated:
protected; that is, its name can be used only by members and friends of the class in which it is declared, and by members and friends of classes derived from this class by classes derived from that class, and by their friends (see 11.4 [class.protected]).
Change 11 [class.access] paragraph 2 as indicated:
A member of a class can also access all the names declared in the class of which it is a member to which the class has access.
Change 11 [class.access] paragraph 6 as indicated:
All access controls in clause 11 [class.access] affect the ability to access a class member name from a particular scope. The access control for names used in the definition of a class member that appears outside of the member's class definition is done as if the entire member definition appeared in the scope of the member's class. For purposes of access control, the base-specifiers of a class and the definitions of class members that appear outside of the class definition are considered to be within the scope of that class. In particular...
Change the example and commentary in 11 [class.access] paragraphs 6-7 as indicated:
[Example:
class A { typedef int I; // private member I f(); friend I g(I); static I x; protected: struct B { }; }; A::I A::f () { return 0; } A::I g(A::I p = A::x); A::I g(A::I p) { return 0; } A::I A::x = 0; struct D: A::B, A { };Here, all the uses of A::I are well-formed because A::f and A::x are members of class A and g is a friend of class A. This implies, for example, that access checking on the first use of A::I must be deferred until it is determined that this use of A::I is as the return type of a member of class A. Similarly, the use of A::B as a base-specifier is well-formed because D is derived from A, so access checking of base-specifiers must be deferred until the entire base-specifier-list has been seen. —end example]
In 11.3 [class.friend] paragraph 2, replace the following text:
Declaring a class to be a friend implies that the names of private and protected members from the class granting friendship can be accessed in declarations of members of the befriended class. [Note: this means that access to private and protected names is also granted to member functions of the friend class (as if the functions were each friends) and to the static data member definitions of the friend class. This also means that private and protected type names from the class granting friendship can be used in the base-clause of a nested class of the friend class. However, the declarations of members of classes nested within the friend class cannot access the names of private and protected members from the class granting friendship. Also, because the base-clause of the friend class is not part of its member declarations, the base-clause of the friend class cannot access the names of the private and protected members from the class granting friendship. For example,
class A { class B { }; friend class X; }; class X: A::B { // ill-formed: A::B cannot be accessed // in the base-clause for X A::B mx; // OK: A::B used to declare member of X class Y: A::B { // OK: A::B used to declare member of X A::B my; // ill-formed: A::B cannot be accessed // to declare members of nested class of X }; };—end note]
with:
Declaring a class to be a friend implies that the names of private and protected members from the class granting friendship can be accessed in the base-specifiers and member declarations of the befriended class. [Example:
class A { class B { }; friend class X; }; struct X: A::B { // OK: A::B accessible to friend A::B mx; // OK: A::B accessible to member of friend class Y { A::B my; // OK: A::B accessible to nested member of friend }; };—end example]
Change the last sentence of 14.3 [temp.arg] paragraph 3 as indicated:
For a template-argument of that is a class type or a class template, the template definition has no special access rights to the inaccessible members of the template argument type. template-argument. [Example:
template <template <class TT> class T> class A { typename T<int>::S s; }; template <class U> class B { private: struct S { /* ... */ }; }; A<B> b; // ill-formed, A has no access to B::S
—end example]
Change 9.7 [class.nest] paragraph 4 as indicated:
Like a member function, a friend function (11.3 [class.friend]) defined within a nested class is in the lexical scope of that class; it obeys the same rules for name binding as a static member function of that class (9.4 [class.static]), but it and has no special access rights to members of an enclosing class.
(Note: this resolution also resolves issues 494 and 500.)
[Moved to DR at 4/01 meeting.]
Section 14.3.1 [temp.arg.type] paragraph 2 says
A local type, a type with no linkage, an unnamed type or a type compounded from any of these types shall not be used as a template-argument for a template type-parameter.
It probably wasn't intended that classes with unnamed members should be included in this list, but they are arguably compounded from unnamed types.
Proposed resolution (04/01):
In 14.3.1 [temp.arg.type] paragraph 2, change
A local type, a type with no linkage, an unnamed type or a type compounded from any of these types shall not be used as a template-argument for a template type-parameter.
to
The following types shall not be used as a template-argument for a template type-parameter:
- a type whose name has no linkage
- an unnamed class or enumeration type that has no name for linkage purposes (7.1.3 [dcl.typedef])
- a cv-qualified version of one of the types in this list
- a type created by application of declarator operators to one of the types in this list
- a function type that uses one of the types in this list
[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:
a constant expression that evaluates to a null pointer value (4.10 [conv.ptr]); or
a constant expression that evaluates to a null member pointer value (4.11 [conv.mem]); or
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 April, 2007 meeting as part of paper N2258.]
One of the requirements for two template-ids to refer to the same class or function (14.4 [temp.type] paragraph 1) is that
If we have some template of the form
template <unsigned char c> struct A;
does this imply that A<'\001'> and A<257> (for an eight-bit char) refer to different specializations?
Jens Maurer: Looks like it should say something like, “their corresponding converted non-type template arguments of integral or enumeration type have identical values.”
Proposed resolution (April, 2007):
The change to 14.4 [temp.type] paragraph 1 shown in document J16/07-0118 = WG21 N2258, in which the syntactic non-terminal template-argument is changed to the English term “template argument” is sufficient to remove the confusion about whether the value before or after conversion is used in matching template-ids.
[Voted into the WP at the September, 2008 meeting.]
In order for two template-ids to refer to the same function, 14.4 [temp.type] paragraph 1, bullet 1 requires that
their template-names refer to the same template
This makes it impossible for two template-ids referring to operator function templates to be equivalent, because only simple-template-ids have a template-name, and a template-id referring to an operator function template is not a simple-template-id (14.2 [temp.names] paragraph 1).
Suggested resolution:
Change 14.4 [temp.type] paragraph 1, bullet 1 to read,
their template-names or operator-function-ids refer to the same template
Proposed resolution (June, 2008):
Change 14.4 [temp.type] paragraph 1, first bullet, as follows:
their template-names or operator-function-ids refer to the same template, and
[Voted into WP at April, 2007 meeting.]
The wholesale replacement of the phrase “template function” by the resolution of issue 105 seems to have overlooked the similar phrase “template conversion function.” This phrase appears a number of times in 13.3.3.1.2 [over.ics.user] paragraph 3, 14.5.2 [temp.mem] paragraphs 5-8, and 14.8.2 [temp.deduct] paragraph 1. It should be systematically replaced in similar fashion to the resolution of issue 105.
Proposed resolution (October, 2006):
Change 13.3.3.1.2 [over.ics.user] paragraph 3 as follows:
If the user-defined conversion is specified by a template conversion function specialization of a conversion function template, the second standard conversion sequence must have exact match rank.
Change 14.5.2 [temp.mem] paragraph 5 as follows:
A specialization of a template conversion function conversion function template is referenced in the same way as a non-template conversion function that converts to the same type.
Change 14.5.2 [temp.mem] paragraph 6 as follows:
A specialization of a template conversion function conversion function template is not found by name lookup. Instead, any template conversion functions conversion function templates visible in the context of the use are considered.
Change 14.5.2 [temp.mem] paragraph 7 as follows:
A using-declaration using-declaration in a derived class cannot refer to a specialization of a template conversion function conversion function template in a base class.
Change 14.5.2 [temp.mem] paragraph 8 as follows:
Overload resolution (13.3.3.2 [over.ics.rank]) and partial ordering (14.5.6.2 [temp.func.order]) are used to select the best conversion function among multiple template conversion functions specializations of conversion function templates and/or non-template conversion functions.
Change 14.8.2.3 [temp.deduct.conv] paragraph 1 as follows:
Template argument deduction is done by comparing the return type of the template conversion function conversion function template (call it P) with the type that is required as the result of the conversion (call it A) as described in 14.8.2.5 [temp.deduct.type].
[Voted into WP at October 2003 meeting.]
14.5.4 [temp.friend] paragraph 5 says:
When a function is defined in a friend function declaration in a class template, the function is defined at each instantiation of the class template. The function is defined even if it is never used. The same restrictions on multiple declarations and definitions which apply to non-template function declarations and definitions also apply to these implicit definitions. [Note: if the function definition is ill-formed for a given specialization of the enclosing class template, the program is ill-formed even if the function is never used. ]
This means that the following program is invalid, even without the call of f(ai):
template <class T> struct A { friend void f(A a) { g(a); } }; int main() { A<int> ai; // f(ai); // Error if f(ai) is actually called }
The EDG front end issues an error on this case even if f(ai) is never called. Of the compilers I tried (g++, Sun, Microsoft, Borland) we are the only ones to issue such an error.
This issue came up because there is a library that either deliberately or accidentally makes use of friend functions that are not valid for certain instantiations.
The wording in the standard is the result of a deliberate decision made long ago, but given the fact that most implementations do otherwise it raises the issue of whether we did the right thing.
Upon further investigation, the current rule was adopted as the resolution to issue 6.47 in my series of template issue papers. At the time the issue was discussed (7/96) most compilers did evaluate such friends. So it seems that a number of compilers have changed their behavior since then.
Based on current practice, I think the standard should be changed to evaluate such friends only when used.
Proposed resolution (October 2002):
Change section 14.5.4 [temp.friend] paragraph 5 from:
When a function is defined in a friend function declaration in a class template, the function is defined at each instantiation of the class template. The function is defined even if it is never used. The same restrictions on multiple declarations and definitions which apply to non-template function declarations and definitions also apply to these implicit definitions. [Note: if the function definition is ill-formed for a given specialization of the enclosing class template, the program is ill-formed even if the function is never used. ]to:
When a function is defined in a friend function declaration in a class template, the function is instantiated when the function is used. The same restrictions on multiple declarations and definitions that apply to non-template function declarations and definitions also apply to these implicit definitions.Note the change from "which" to "that" in the last sentence.
[Voted into WP at October 2004 meeting.]
14.5.4 [temp.friend] paragraph 2 was overlooked when the changes for issue 166 were made.
The friend declaration of f<>(int) is now valid.
A friend function declaration that is not a template declaration and in which the name of the friend is an unqualified template-id shall refer to a specialization of a function template declared in the nearest enclosing namespace scope. [Example:namespace N { template <class T> void f(T); void g(int); namespace M { template <class T> void h(T); template <class T> void i(T); struct A { friend void f<>(int); // ill-formed - N::f friend void h<>(int); // OK - M::h friend void g(int); // OK - new decl of M::g template <class T> void i(T); friend void i<>(int); // ill-formed - A::i }; } }--end example]
Proposed Resolution (October 2003):
Remove 14.5.4 [temp.friend] paragraph 2:
A friend function declaration that is not a template declaration and in which the name of the friend is an unqualified template-id shall refer to a specialization of a function template declared in the nearest enclosing namespace scope. [Example:namespace N { template <class T> void f(T); void g(int); namespace M { template <class T> void h(T); template <class T> void i(T); struct A { friend void f<>(int); // ill-formed - N::f friend void h<>(int); // OK - M::h friend void g(int); // OK - new decl of M::g template <class T> void i(T); friend void i<>(int); // ill-formed - A::i }; } }--end example]
[Moved to DR at 4/02 meeting.]
The example in 14.5.5 [temp.class.spec] paragraph 6 is incorrect. It reads,
template<class T> struct A { class C { template<class T2> struct B { }; }; }; // partial specialization of A<T>::C::B<T2> template<class T> template<class T2> struct A<T>::C::B<T2*> { }; A<short>::C::B<int*> absip; // uses partial specialization
Because C is a class rather than a struct, the use of the name B is inaccessible.
Proposed Resolution (10/01):
Change class C to struct C in the example in 14.5.5 [temp.class.spec] paragraph 6. The example becomes
template<class T> struct A { struct C { template<class T2> struct B { }; }; }; // partial specialization of A<T>::C::B<T2> template<class T> template<class T2> struct A<T>::C::B<T2*> { }; A<short>::C::B<int*> absip; // uses partial specialization
[Voted into WP at the October, 2006 meeting.]
According to 14.5.5 [temp.class.spec] paragraph 1,
If a template is partially specialized then that partial specialization shall be declared before the first use of that partial specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required.
This leaves the impression that an explicit instantiation of the primary template may precede the declaration of an applicable partial specialization. Is the following example well-formed?
template<typename T> class X{ public: void foo(){}; }; template class X<void *>; template<typename T> class X<T*>{ public: void baz(); }; void bar() { X<void *> x; x.foo(); }
Proposed resolution (October, 2005):
Replace the last sentence of 14.5.5 [temp.class.spec] paragraph 1:
If a template is partially specialized then that partial specialization shall be declared before the first use of that partial specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required.
with:
A partial specialization shall be declared before the first use of a class template specialization that would make use of the partial specialization as the result of an implicit or explicit instantiation in every translation unit in which such a use occurs; no diagnostic is required.
[Voted into WP at October 2003 meeting.]
In 14.5.6.2 [temp.func.order], partial ordering is explained in terms of template argument deduction. However, the exact procedure for doing so is not specified. A number of details are missing, they are explained as sub-issues below.
template<class T> void g(T); // #1 template<class T> void g(T&); // #2Here, #2 is at least as specialized as #1: With a synthetic type U, #2 becomes g(U&); argument deduction against #1 succeeds with T=U&. However, #1 is not at least as specialized as #2: Deducing g(U) against g(T&) fails. Therefore, the second template is more specialized than the first, and the call g(x) is not ambiguous.
template<class S> void g(S); // #1 template<class T> void g(T const &); // #3Here, #3 is clearly at least as specialized as #1. To determine whether #1 is at least as specialized as #3, a unique type U is synthesized, and deduction of g<U>(U) is performed against #3. Following the rules in 14.8.2.1 [temp.deduct.call], deduction succeeds with T=U. Since the template argument is U, and the deduced template parameter is also U, we have an exact match between the template parameters. Even though the conversion from U to U const & is an exact match, it is not clear whether the added qualification should be taken into account, as it is in other places.
Issue 200 covers a related issue, illustrated by the following example:
template <class T> T f(int); template <class T, class U> T f(U); void g() { f<int>(1); }
Even though one template is "obviously" more specialized than the other, deduction fails in both directions because neither function parameter list allows template parameter T to be deduced.
(See also issue 250.)
Nathan Sidwell:
14.5.6.2 [temp.func.order] describes the partial ordering of function templates. Paragraph 5 states,
A template is more specialized than another if, and only if, it is at least as specialized as the other template and that template is not at least as specialized as the first.To paraphrase, given two templates A & B, if A's template parameters can be deduced by B, but B's cannot be deduced by A, then A is more specialized than B. Deduction is done as if for a function call. In particular, except for conversion operators, the return type is not involved in deduction. This leads to the following templates and use being unordered. (This example is culled from G++ bug report 4672 http://gcc.gnu.org/cgi-bin/gnatsweb.pl?cmd=view&pr=4672)
template <typename T, class U> T checked_cast(U from); //#1 template <typename T, class U> T checked_cast(U * from); //#2 class C {}; void foo (int *arg) { checked_cast <C const *> (arg); }In the call,
#1 can be deduced with T = 'C const *' and U = 'int *'
#2 can be deduced with T = 'C const *' and U = 'int'
It looks like #2 is more specialized that #1, but 14.5.6.2 [temp.func.order] does not make it so, as neither template can deduce 'T' from the other template's function parameters.
Possible Resolutions:
There are several possible solutions, however through experimentation I have discounted two of them.
Option 1:
When deducing function ordering, if the return type of one of the templates uses a template parameter, then return types should be used for deduction. This, unfortunately, makes existing well formed programs ill formed. For example
template <class T> class X {}; template <class T> X<T> Foo (T *); // #1 template <class T> int Foo (T const *); // #2 void Baz (int *p1, int const *p2) { int j = Foo (p2); //#3 }Here, neither #1 nor #2 can deduce the other, as the return types fail to match. Considering only the function parameters gives #2 more specialized than #1, and hence makes the call #3 well formed.
Option 2:
As option 1, but only consider the return type when deducing the template whose return type involves template parameters. This has the same flaw as option 1, and that example is similarly ill formed, as #1's return type 'X<T,0>' fails to match 'int' so #1 cannot deduce #2. In the converse direction, return types are not considered, but the function parameters fail to deduce.
Option 3:
It is observed that the original example is only callable with a template-id-expr to supply a value for the first, undeducible, parameter. If that parameter were deducible it would also appear within at least one of the function parameters. We can alter paragraph 4 of [temp.func.order] to indicate that it is not necessary to deduce the parameters which are provided explicitly, when the call has the form of a template-id-expr. This is a safe extension as it only serves to make ill formed programs well formed. It is also in line with the concept that deduction for function specialization order should proceed in a similar manner to function calling, in that explicitly provided parameter values are taken into consideration.
Suggested resolution:
Insert after the first sentence of paragraph 4 in 14.5.6.2 [temp.func.order]
Should any template parameters remain undeduced, and the function call be of the form of a template-id-expr, those template parameters provided in the template-id-expr may be arbitrarily synthesized prior to determining whether the deduced arguments generate a valid function type.
See also issue 200.
(April 2002) John Spicer and John Wiegley have written a paper on this. See 02-0051/N1393.
Proposed resolution (October 2002):
Change 14.5.6.2 [temp.func.order] paragraph 2 to read:
Partial ordering selects which of two function templates is more specialized than the other by transforming each template in turn (see next paragraph) and performing template argument deduction using the function parameter types, or in the case of a conversion function the return type. The deduction process determines whether one of the templates is more specialized than the other. If so, the more specialized template is the one chosen by the partial ordering process.
Change 14.5.6.2 [temp.func.order] paragraph 3 to read:
To produce the transformed template, for each type, non-type, or template template parameter synthesize a unique type, value, or class template respectively and substitute it for each occurrence of that parameter in the function type of the template.
Change 14.5.6.2 [temp.func.order] paragraph 4 to read (note: the section reference should refer to the section added to 14.8.2 [temp.deduct] below):
Using the transformed function template's function parameter list, or in the case of a conversion function its transformed return type, perform type deduction against the function parameter list (or return type) of the other function. The mechanism for performing these deductions is given in 14.8.2.x.
Remove the text of 14.5.6.2 [temp.func.order] paragraph 5 but retain the example. The removed text is:
A template is more specialized than another if, and only if, it is at least as specialized as the other template and that template is not at least as specialized as the first.
Insert the following section before 14.8.2.5 (Note that this would either be a new 14.8.2.4, or would be given a number like 14.8.2.3a. If neither of these is possible from a troff point of view, this could be made 14.8.2.5. )
Deducing template arguments when determining the partial ordering of function templates (temp.deduct.partial)
Template argument deduction is done by comparing certain types associated with the two function templates being compared.
Two sets of types are used to determine the partial ordering. For each of the templates involved there is the original function type and the transformed function type. [Note: The creation of the transformed type is described in 14.5.6.2 [temp.func.order].] The deduction process uses the transformed type as the argument template and the original type of the other template as the parameter template. This process is done twice for each type involved in the partial ordering comparison: once using the transformed template-1 as the argument template and template-2 as the parameter template and again using the transformed template-2 as the argument template and template-1 as the parameter template.
The types used to determine the ordering depend on the context in which the partial ordering is
- In the context of a function call, the function parameter types are used.
- In the context of a call to a conversion operator, the return types of the conversion function templates are used.
- In other contexts (14.5.6.2 [temp.func.order]), the function template's function type is used.
Each type from the parameter template and the corresponding type from the argument template are used as the types of P and A.
Before the partial ordering is done, certain transformations are performed on the types used for partial ordering:
- If P is a reference type, P is replaced by the type referred to.
- If A is a reference type, A is replaced by the type referred to.
If both P and A were reference types (before being replaced with the type referred to above), determine which of the two types (if any) is more cv-qualified than the other; otherwise the types are considered to be equally cv-qualified for partial ordering purposes. The result of this determination will be used below.
Remove any top-level cv-qualifiers:
- If P is a cv-qualified type, P is replaced by the cv-unqualified version of P.
- If A is a cv-qualified type, A is replaced by the cv-unqualified version of A.
Using the resulting types P and A the deduction is then done as described in (14.8.2.5 [temp.deduct.type]). If deduction succeeds for a given type, the type from the argument template is considered to be at least as specialized as the type from the parameter template.
If, for a given type, deduction succeeds in both directions (i.e., the types are identical after the transformations above) if the type from the argument template is more cv-qualified than the type from the parameter template (as described above) that type is considered to be more specialized than the other. If neither type is more cv-qualified than the other then neither type is more specialized than the other.
If for each type being considered a given template is at least as specialized for all types and more specialized for some set of types and the other template is not more specialized for any types or is not at least as specialized for any types, then the given template is more specialized than the other template. Otherwise, neither template is more specialized than the other.
In most cases, all template parameters must have values in order for deduction to succeed, but for partial ordering purposes a template parameter may remain without a value provided it is not used in the types being used for partial ordering. [Note: A template parameter used in a non-deduced context is considered used.]
[Example:
template <class T> T f(int); // #1 template <class T, class U> T f(U); // #2 void g() { f<int>(1); // Calls #1 }--end example]
[Moved to DR at 4/02 meeting.]
Mike Miller: A question about typename came up in the discussion of issue 68 that is somewhat relevant to the idea of omitting typename in contexts where it is clear that a type is required: consider something like
template <class T> class X { friend class T::nested; };Is typename required here? If so, where would it go? (The grammar doesn't seem to allow it anywhere in an elaborated-type-specifier that has a class-key.)
Bill Gibbons: The class applies to the last identifier in the qualified name, since all the previous names must be classes or namespaces. Since the name is specified to be a class it does not need typename. [However,] it looks like 14.6 [temp.res] paragraph 3 requires typename and the following paragraphs do not exempt this case. This is not what we agreed on.
Proposed resolution (04/01):
In 14.6 [temp.res] paragraph 5, change
The keyword typename is not permitted in a base-specifier or in a mem-initializer; in these contexts a qualified-name that depends on a template-parameter (14.6.2 [temp.dep]) is implicitly assumed to be a type name.
to
A qualified name used as the name in a mem-initializer-id, a base-specifier, or an elaborated-type-specifier (in the class-key and enum forms) 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.]
(The expected resolution for issue 254 will remove the typename forms from the grammar for elaborated-type-specifier. If that resolution is adopted, the parenthetical phrase "(in the class-key and enum forms)" in the preceding wording should be removed because those will be the only forms of elaborated-type-specifier.)
This has been consolidated with the edits for some other issues. See N1376=02-0034.
[Voted into WP at April 2003 meeting.]
The following example from 14.6 [temp.res] paragraph 4:
struct A { struct X { }; int X; }; template<class T> void f(T t) { typename T::X x; // ill-formed: finds the data member X // not the member type X }
is not ill-formed. The intent of the example is obvious, but some mention should be made that it is only ill-formed when T=A. For other T's, it could be well formed.
Proposed resolution (October 2002):
In 14.6 [temp.res] paragraph 4, replace the example with:
struct A { struct X { }; int X; } ; struct B { struct X { }; } ; template<class T> void f(T t) { typename T::X x; } void foo() { A a; B b; f(b); // OK -- T::X refers to B::X. f(a); // error: T::X refers to the data member A::X not // the struct A::X. }
[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.6.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.6.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:
A When 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]) is intended to refer to a type, it shall be prefixed by the keyword typename to 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 2004 meeting.]
Paragraph 6 of 14.6 [temp.res] is obsolete as result of issue 224, and needs to be revised.
Within the definition of a class template or within the definition of a member of a class template, the keyword typename is not required when referring to the unqualified name of a previously declared member of the class template that declares a type. The keyword typename shall always be specified when the member is referred to using a qual- ified name, even if the qualifier is simply the class template name. [Example:template<class T> struct A { typedef int B; A::B b; // ill-formed: typename required before A::B void f(A<T>::B); // ill-formed: typename required before A<T>::B typename A::B g(); // OK };]
Proposed Resolution:
Change 14.6 [temp.res] paragraph 6 as follows
Within the definition of a class template or within the definition of
a member of a class template, the keyword typename is not
required when referring to the unqualified name of a previously
declared member of the class template that declares a type.
The keyword typename shall always be specified when the
member is referred to using a qualified name, even if the qualifier is
simply the class template name. [Example:
template<class T> struct A {
typedef int B;
B b; // ok, no typename required
A::B b; // ill-formed: typename required before A::B
void f(A<T>::B); // ill-formed: typename required before A<T>::B
typename A::B g(); // OK
};
The keyword typename is required whether the qualified name is A or
A<T> because A or A<T> are synonyms within a class template with
the parameter list <T>. ]
[Voted into WP at April, 2006 meeting.]
Part of the resolution for issue 224 was the addition of the phrase “but does not refer to a member of the current instantiation” to 14.6 [temp.res] paragraph 3. When the resolution of issue 382 was added to the current working draft, however, that phrase was inadvertently removed. Equivalent phrasing should be restored.
Proposed resolution (April, 2006):
Replace the first sentence of 14.6 [temp.res] paragraph 3 with the following text:
When a qualified-id is intended to refer to a type that is not a member of the current instantiation (14.6.2.1 [temp.dep.type]) and its nested-name-specifier depends on a template-parameter (14.6.2 [temp.dep]), it shall be prefixed by the keyword typename, forming a typename-specifier.
[Voted into the WP at the June, 2008 meeting.]
14.6 [temp.res] paragraphs 2 and 4 read,
A name used in a template declaration or definition and that is dependent on a template-parameter is assumed not to name a type unless the applicable name lookup finds a type name or the name is qualified by the keyword typename.
If a specialization of a template is instantiated for a set of template-arguments such that the qualified-id prefixed by typename does not denote a type, the specialization is ill-formed.
It is not clear whether this is intended to, or is sufficient to, render a specialization ill-formed if a dependent qualified-id that is not prefixed by typename actually does denote a type. For example,
int i; template <class T> void f() { T::x * i; // declaration or multiplication!? } struct Foo { typedef int x; }; struct Bar { static int const x = 5; }; int main() { f<Bar>(); // multiplication f<Foo>(); // declaration! }
I think that the specialization for Foo should be ill-formed.
Proposed resolution (February, 2008):
Add the following after 14.6 [temp.res] paragraph 5:
If, for a given set of template arguments, a specialization of a template is instantiated that refers to a qualified-id that denotes a type, and the nested-name-specifier of the qualified-id depends on a template parameter, the qualified-id shall either be prefixed by typename or shall be used in a context in which it implicitly names a type as described above. [Example:
template <class T> void f(int i) { T::x * i; // T::x must not be a type } struct Foo { typedef int x; }; struct Bar { static int const x = 5; }; int main() { f<Bar>(1); // OK f<Foo>(1); // error: Foo::x is a type }—end example]
[Voted into WP at the October, 2006 meeting.]
Implementations vary in their treatment of the following code:
struct A {
int foo_;
};
template <typename T> struct B: public A { };
template <typename T> struct C: B<T> {
int foo() {
return A::foo_; // #1
}
};
int f(C<int>* p) {
return p->foo();
}
According to one analysis, because the expression A::foo_ on line #1 is non-dependent, it must be analyzed in the definition context. It that context, it violates the restrictions of 9.2 [class.mem] paragraph 10 on how the name of a nonstatic data member of a class can be used and thus should be treated as an error.
On the other hand, the description of the transformation of an id-expression into a class member access expression (9.3.1 [class.mfct.non-static] paragraph 3) does not have any special treatment of templates; when C<int>::foo() is instantiated, the reference to A::foo_ turns out to be to a base class member and is thus transformed into (*this).A::foo_ and is thus not an error.
Proposed resolution (October, 2005):
Change 9.3.1 [class.mfct.non-static] paragraph 3 as indicated:
When an id-expression (5.1.1 [expr.prim.general]) that is not part of a class member access syntax (5.2.5 [expr.ref]) and not used to form a pointer to member (5.3.1 [expr.unary.op]) is used in the body of a non-static member function of class X or used in the mem-initializer for a constructor of class X, if name lookup (3.4.1 [basic.lookup.unqual]) resolves the name in the id-expression to a non-static non-type member of class X or of a base class of X some class C, the id-expression is transformed into a class member access expression (5.2.5 [expr.ref]) using (*this) (9.3.2 [class.this]) as the postfix-expression to the left of the . operator. [Note: If C is not X or a base class of X, the class member access expression is ill-formed. —end note] The member name then refers to the member of the object for which the function is called. Similarly during name lookup...
[Voted into WP at the October, 2006 meeting.]
The description of dependent function calls in 14.6.2 [temp.dep] paragraph 1 applies only to identifiers in postfix-notation function calls and to operator notation calls for operator functions:
In an expression of the form:
postfix-expression ( expression-listopt )
where the postfix-expression is an identifier, the identifier denotes a dependent name if and only if any of the expressions in the expression-list is a type-dependent expression (14.6.2.2 [temp.dep.expr]). If an operand of an operator is a type-dependent expression, the operator also denotes a dependent name.
It would appear from the related passage in 14.6.4.2 [temp.dep.candidate] paragraph 1 that the description of postfix-notation function calls should apply to all unqualified-ids that are not template-ids, including operator-function-ids, not just to identifiers:
For a function call that depends on a template parameter, if the function name is an unqualified-id but not a template-id, the candidate functions are found...
Proposed resolution (October, 2005):
Change 14.6.2 [temp.dep] paragraph 1 as indicated:
...In an expression of the form:
postfix-expression ( expression-listopt )
where the postfix-expression is an identifier unqualified-id but not a template-id, the identifier unqualified-id denotes a dependent name if and only if any of the expressions in the expression-list is a type-dependent expression (14.6.2.2 [temp.dep.expr])...
Change 14.6.4.2 [temp.dep.candidate] paragraph 1 as indicated:
For a function call that depends on a template parameter, if the function name is an unqualified-id but not a template-id, or if the function is called using operator notation, the candidate functions are found using the usual lookup rules (3.4.1 [basic.lookup.unqual], 3.4.2 [basic.lookup.argdep]) except that...
(See also issue 561.)
[Moved to DR at 10/01 meeting.]
The definition of when a type is dependent, given in 14.6.2.1 [temp.dep.type], is essentially syntactic: if the reference is a qualified-id and one of the class-names in the nested-name-specifier is dependent, the type is dependent. This approach leads to surprising results:
template <class T> class X { typedef int I; I a; // non-dependent typename X<T>::I b; // dependent typename X::I c; // dependent (X is equivalent to X<T>) };
Suggested resolution:
The decision on whether a name is dependent or non-dependent should be based on lookup, not on the form of the name: if the name can be looked up in the definition context and cannot be anything else as the result of specialization, the name should be non-dependent.
See papers J16/00-0028 = WG21 N1251 and J16/00-0056 = WG21 N1279.
Proposed resolution (10/00):
Replace section 14.6.2.1 [temp.dep.type] with the following:
In the definition of a class template, a nested class of a class template, a member of a class template, or a member of a nested class of a class template, a name refers to the current instantiation if it is
- the injected-class-name (clause 9 [class]) of the class template or nested class,
- in the definition of a primary class template, the name of the class template followed by the template argument list of the primary template (as described below) enclosed in <>,
- in the definition of a nested class of a class template, the name of the nested class referenced as a member of the current instantiation, or
- in the definition of a partial specialization, the name of the class template followed by the template argument list of the partial specialization enclosed in <>.
The template argument list of a primary template is a template argument list in which the nth template argument has the value of the nth template parameter of the class template.
A template argument that is equivalent to a template parameter (i.e., has the same constant value or the same type as the template parameter) can be used in place of that template parameter in a reference to the current instantiation. In the case of a nontype template argument, the argument must have been given the value of the template parameter and not an expression involving the template parameter.
[Example:
template <class T> class A { A* p1; // A is the current instantiation A<T>* p2; // A<T> is the current instantiation A<T*> p3; // A<T*> is not the current instantiation ::A<T>* p4; // ::A<T> is the current instantiation class B { B* p1; // B is the current instantiation A<T>::B* p2; // A<T>::B is the current instantiation typename A<T*>::B* p3; // A<T*>::B is not the // current instantiation }; }; template <class T> class A<T*> { A<T*>* p1; // A<T*> is the current instantiation A<T>* p2; // A<T> is not the current instantiation }; template <class T1, class T2, int I> struct B { B<T1, T2, I>* b1; // refers to the current instantiation B<T2, T1, I>* b2; // not the current instantiation typedef T1 my_T1; static const int my_I = I; static const int my_I2 = I+0; static const int my_I3 = my_I; B<my_T1, T2, my_I>* b3; // refers to the current instantiation B<my_T1, T2, my_I2>* b4; // not the current instantiation B<my_T1, T2, my_I3>* b5; // refers to the current instantiation };—end example]
A name is a member of the current instantiation if it is
- An unqualified name that, when looked up, refers to a member of a class template. [Note: This can only occur when looking up a name in a scope enclosed by the definition of a class template.]
- A qualified-id in which the nested-name-specifier refers to the current instantiation.
[Example:
template <class T> class A { static const int i = 5; int n1[i]; // i refers to a member of the current instantiation int n2[A::i]; // A::i refers to a member of the current instantiation int n3[A<T>::i]; // A<T>::i refers to a member of the current instantiation int f(); }; template <class T> int A<T>::f() { return i; // i refers to a member of the current instantiation }—end example]
A name is a member of an unknown specialization if the name is a qualified-id in which the nested-name-specifier names a dependent type that is not the current instantiation.
A type is dependent if it is
- a template parameter,
- a member of an unknown specialization,
- a nested class that is a member of the current instantiation,
- a cv-qualified type where the cv-unqualified type is dependent,
- a compound type constructed from any dependent type,
- an array type constructed from any dependent type or whose size is specified by a constant expression that is value-dependent, or
- a template-id in which either the template name is a template parameter or any of the template arguments is a dependent type or an expression that is type-dependent or value-dependent.
[Note: Because typedefs to not introduce new types, but instead simply refer to other types, a name that refers to a typedef that is a member of the current instantiation is dependent only if the type referred to is dependent.]
In 14.6.2.2 [temp.dep.expr] paragraph 3, replace
- a nested-name-specifier that contains a class-name that names a dependent type.
with
- a nested-name-specifier or qualified-id that names a member of an unknown specialization.
In 14.6.2.2 [temp.dep.expr], add the following paragraph:
A class member access expression (5.2.5 [expr.ref]) is type-dependent if the type of the referenced member is dependent. [Note: In an expression of the form x.y or xp->y the type of the expression is usually the type of the member y of the class of x (or the class pointed to by xp). However, if x or xp refers to a dependent type that is not the current instantiation, the type of y is always dependent. If x or xp refers to a non-dependent type or refers to the current instantiation, the type of y is the type of the class member access expression.]
In 14.6 [temp.res] paragraph 3, replace
A qualified-name that refers to a type and that depends on a template-parameter (14.6.2 [temp.dep]) shall be prefixed by the keyword typename.
with
A qualified-id that refers to a type and that depends on a template-parameter (14.6.2 [temp.dep]) but does not refer to a member of the current instantiation shall be prefixed by the keyword typename.
Note: the wording for this paragraph was changed in TC1. The words shown here are the pre-TC1 words.
In 14.2 [temp.names] paragraph 4, replace
When the name of a member template specialization appears after . or -> in a postfix-expression, or after a nested-name-specifier in a qualified-id, and the postfix-expression or qualified-id explicitly depends on a template-parameter (14.6.2 [temp.dep]), the member template name must be prefixed by the keyword template. Otherwise the name is assumed to name a non-template.
with
When the name of a member template specialization appears after . or -> in a postfix-expression, or after a nested-name-specifier in a qualified-id, and the postfix-expression or qualified-id explicitly depends on a template-parameter (14.6.2 [temp.dep]) but does not refer to a member of the current instantiation (14.6.2.1 [temp.dep.type]), the member template name must be prefixed by the keyword template. Otherwise the name is assumed to name a non-template.
In 14.6.1 [temp.local] paragraph 2, remove the following text, which was added for issue 108. The updated definition of dependent name now addresses this case.
Within the scope of a class template, when the unqualified name of a nested class of the class template is referred to, it is equivalent to the name of the nested class qualified by the name of the enclosing class template. [Example:
template <class T> struct A { class B {}; // B is equivalent to A::B, which is equivalent to A<T>::B, // which is dependent. class C : B { }; };—end example]
[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.2 [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.2 [support.types]. —end note]
In 18.2 [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 March 2004 meeting.]
The example in 14.6.5 [temp.inject] paragraph 2 is incorrect:
template<typename T> class number { number(int); //... friend number gcd(number& x, number& y) { /* ... */ } //... }; void g() { number<double> a(3), b(4); //... a = gcd(a,b); // finds gcd because number<double> is an // associated class, making gcd visible // in its namespace (global scope) b = gcd(3,4); // ill-formed; gcd is not visible }
Regardless of the last statement ("b = gcd(3,4);"), the above code is ill-formed:
a) number's constructor is private;
b) the definition of (non-void) friend 'gcd' function does not contain a return statement.
Proposed resolution (April 2003):
Replace the example in 14.6.5 [temp.inject] paragraph 2
bytemplate<typename T> class number { number(int); //... friend number gcd(number& x, number& y) { /* ... */ } //... }; void g() { number<double> a(3), b(4); //... a = gcd(a,b); // finds gcd because number<double> is an // associated class, making gcd visible // in its namespace (global scope) b = gcd(3,4); // ill-formed; gcd is not visible }
template<typename T> class number { public: number(int); //... friend number gcd(number x, number y) { return 0; } private: //... }; void g() { number<double> a(3), b(4); //... a = gcd(a,b); // finds gcd because number<double> is an // associated class, making gcd visible // in its namespace (global scope) b = gcd(3,4); // ill-formed; gcd is not visible }
Drafting note: Added "return" to the friend function, removed references in gcd arguments, added access specifiers.
[Moved to DR at 4/02 meeting.]
According to 14.7 [temp.spec] paragraph 5,
No program shall explicitly instantiate any template more than once, both explicitly instantiate and explicitly specialize a template, or specialize a template more than once for a given set of template-arguments.
This rule has an impact on library issue 120. Library authors would like to have the freedom to specialize (or not) various library functions without having to document their choices, while users need the flexibility to explicitly instantiate library functions in certain translation units.
If this rule could be slightly weakened, it would reduce the need for constraining either the library author or the programmer. For instance, the rule might be recast to say that if a specialization is followed by an explicit instantiation in the same translation unit, the explicit instantiation is ignored. A specialization and an explicit instantiation of the same template in two different translation units would still be an error, no diagnostic required.
Proposed resolution (04/01):
Replace the first sentence of 14.7 [temp.spec] paragraph 5,
No program shall explicitly instantiate any template more than once, both explicitly instantiate and explicitly specialize a template, or specialize a template more than once for a given set of template-arguments.
by
For a given template and a given set of template-arguments,
- an explicit instantiation shall appear at most once in a program,
- an explicit specialization shall be defined at most once according to 3.2 [basic.def.odr] in a program, and
- both an explicit instantiation and a declaration of an explicit specialization shall not appear in a program unless the explicit instantiation follows a declaration of the explicit specialization.
Replace 14.7.2 [temp.explicit] paragraph 4,
The definition of a non-exported function template, a non-exported member function template, or a non-exported member function or static data member of a class template shall be present in every translation unit in which it is explicitly instantiated.
by
For a given set of template parameters, if an explicit instantiation of a template appears after a declaration of an explicit specialization for that template, the explicit instantiation has no effect. Otherwise, the definition of a non-exported function template, a non-exported member function template, or a non-exported member function or static data member of a class template shall be present in every translation unit in which it is explicitly instantiated.
[Moved to DR at October 2002 meeting.]
A template is implicitly instantiated because of a "pointer conversion" on an argument. This was intended to include related-class conversions, but it also inadvertently includes conversions to void*, null pointer conversions, cv-qualification conversions and the identity conversion.
It is not clear whether a reinterpret_cast of a pointer should cause implicit instantiation.
Proposed resolution (10/01): Replace 14.7.1 [temp.inst] paragraph 4, up to the example, with the following:
A class template specialization is implicitly instantiated if the class type is used in a context that requires a completely-defined object type or if the completeness of the class type might affect the semantics of the program. [Note: in particular, if the semantics of an expression depend on the member or base class lists of a class template specialization, the class template specialization is implicitly generated. For instance, deleting a pointer to class type depends on whether or not the class declares a destructor, and conversion between pointer to class types depends on the inheritance relationship between the two classes involved. ]
This version differs from the previous version is its use of the word "might" in the first sentence.
(See also issue 212.)
[Voted into WP at April, 2006 meeting.]
The example in 14.7.1 [temp.inst] paragraph 4 has a typographical error: the third parameter of function g should be D<double>* ppp, but it is missing the *:
template <class T> class B { /* ... */ }; template <class T> class D : public B<T> { /* ... */ }; void f(void*); void f(B<int >*); void g(D<int>* p, D<char>* pp, D<double> ppp) { f(p); // instantiation of D<int> required: call f(B<int>*) B<char>* q = pp; // instantiation of D<char> required: // convert D<char>* to B<char>* delete ppp; // instantiation of D<double> required }
Proposed resolution (October, 2005):
As suggested.
[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 specialization implies the instantiation of all also explicitly instantiates each of its members not (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 the WP at the April, 2007 meeting as part of paper J16/07-0095 = WG21 N2235.]
The Standard does not definitively say when the inline specifier may be used in an explicit instantiation. For example, the following would seem to be innocuous, as the function being instantiated is already inline:
template <typename T> struct S { void f() { } }; template inline void S<int>::f();
However, presumably one would want to prohibit something like:
template <typename T> void f(T) { } template inline void f(int);
7.1.2 [dcl.fct.spec] paragraph 4 (after application of the resolution of issue 317) comes close to covering the obvious problematic cases:
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, it shall be declared inline in all translation units in which it appears; no diagnostic is required.
This would seem to prohibit the latter case, but apparently would not handle an exported template that was instantiated as inline (because the definition might not appear in the same translation unit as the inline instantiation). It would be better to make a clear statement regarding the use of inline in explicit instantiations.
Notes from the April, 2006 meeting:
The CWG favored completely disallowing the inline keyword in explicit instantiation directives.
[Moved to DR at 4/01 meeting.]
Some compilers reject the following:
struct A { template <int I> void f(); template <> void f<0>(); };on the basis of 14.7.3 [temp.expl.spec] paragraph 2:
An explicit specialization shall be declared in the namespace of which the template is a member, or, for member templates, in the namespace of which the enclosing class or enclosing class template is a member. An explicit specialization of a member function, member class or static data member of a class template shall be declared in the namespace of which the class template is a member. ...claiming that the specialization above is not "in the namespace of which the enclosing class ... is a member". Elsewhere, declarations are sometimes required to be "at" or "in" "namespace scope", which is not what it says here. Paragraph 17 says:
A member or a member template may be nested within many enclosing class templates. If the declaration of an explicit specialization for such a member appears in namespace scope, the member declaration shall be preceded by a template<> for each enclosing class template that is explicitly specialized.The qualification "if the declaration ... appears in namespace scope", implies that it might appear elsewhere. The only other place I can think of for a member specialization is in class scope.
Was it the intent of the committee to forbid the construction above? (Note that A itself is not a template.) If so, why?
Proposed resolution (04/01): In-class specializations of member templates are not allowed. In 14.7.3 [temp.expl.spec] paragraph 17, replace
If the declaration of an explicit specialization for such a member appears in namespace scope...with
In an explicit specialization for such a member...
Notes from 04/00 meeting:
This issue was kept in "review" status for two major reasons:
Notes from 10/00 meeting:
The core working group felt that the value of additional clarity here outweighs the potential disadvantages that were noted at the preceding meeting.
[Moved to DR at 4/02 meeting.]
Consider this example:
namespace N { template <class T> void f(T){} template <class T> void g(T){} template <> void f(int); template <> void f(char); template <> void g(char); } using namespace N; namespace M { template <> void N::f(char){} // prohibited by standard template <class T> void g(T){} template <> void g(char){} // specialization of M::g or ambiguous? template void f(long); // instantiation of N::f? } template <class T> void g(T){} template <> void N::f(char){} // okay template <> void f(int){} // is this a valid specialization of N::f? template void g(int); // instantiation of ::g(int) or ambiguous?
The question here is whether unqualified names made visible by a using-directive can be used as the declarator in an explicit instantiation or explicit specialization.
Note that this question is already answered for qualified names in 8.3 [dcl.meaning] paragraph 1. In a qualified name such as N::f, f must be a member of class or namespace N, not a name made visible in N by a using-directive (or a using-declaration, for that matter).
The standard does not, as far as I can tell, specify the behavior of these cases one way or another.
My opinion is that names from using-directives should not be considered when looking up the name in an unqualified declarator in an explicit specialization or explicit instantiation. In such cases, it is reasonable to insist that the programmer know exactly which template is being specialized or instantiated, and that a qualified name must be used if the template is a member of a namespace.
As the example illustrates, allowing names from using-directives to be used would also have the affect of making ambiguous otherwise valid instantiation and specialization directives.
Furthermore, permitting names from using-directives would require an additional rule to prohibit the explicit instantiation of an entity in one namespace from being done in another (non-enclosing) namespace (as in the instantiation of f in namespace M in the example).
Mike Miller: I believe the explicit specialization case is already covered by 7.3.1.2 [namespace.memdef] paragraph 2, which requires using a qualified name to define a namespace member outside its namespace.
John Spicer: 7.3.1.2 [namespace.memdef] deals with namespace members. An explicit specialization directive deals with something that is a specialization of a namespace member. I don't think the rules in 7.3.1.2 [namespace.memdef] could be taken to apply to specializations unless the standard said so explicitly.
Proposed resolution (suggested 04/01, proposed 10/01):
(The first change below will need to be revised in accordance with the resolution of issue 284 to add a cross-reference to the text dealing with class names.)
Add in 14.7.2 [temp.explicit] paragraph 2 before the example:
An explicit instantiation shall appear in an enclosing namespace of its template. If the name declared in the explicit instantiation is an unqualified name, the explicit instantiation shall appear in the namespace where its template is declared. [Note: Regarding qualified names in declarators, see 8.3 [dcl.meaning].]
Change the first sentence of 7.3.1.2 [namespace.memdef] paragraph 1 from
Members of a namespace can be defined within that namespace.
to
Members (including explicit specializations of templates (14.7.3 [temp.expl.spec])) of a namespace can be defined within that namespace.
Change the first sentence of 7.3.1.2 [namespace.memdef] paragraph 2 from
Members of a named namespace can also be defined...
to
Members (including explicit specializations of templates (14.7.3 [temp.expl.spec])) of a named namespace can also be defined...
Change the last sentence of 14.7.3 [temp.expl.spec] paragraph 2 from
If the declaration is not a definition, the specialization may be defined later in the namespace in which the explicit specialization was declared, or in a namespace that encloses the one in which the explicit specialization was declared.
to
If the declaration is not a definition, the specialization may be defined later (7.3.1.2 [namespace.memdef]).
[Voted into WP at April 2003 meeting.]
The examples corrected by issue 24 are still wrong in one case.
In item #4 (a correction to the example in paragraph 18), the proposed resolution is:
template<class T1> class A { template<class T2> class B { template<class T3> void mf1(T3); void mf2(); }; }; template<> template<class X> class A<int>::B { }; template<> template<> template<class T> void A<int>::B<double>::mf1(T t) { } template<class Y> template<> void A<Y>::B<double>::mf2() { } // ill-formed; B<double> is specialized but // its enclosing class template A is not
The explicit specialization of member A<int>::B<double>::mf1 is ill-formed. The class template A<int>::B is explicitly specialized and contains no members, so any implicit specialization (such as A<int>::B<double>) would also contain no members.
Proposed Resolution (4/02):
Fix the example in 14.7.3 [temp.expl.spec] paragraph 18 to read:
template<class T1> class A { template<class T2> class B { template<class T3> void mf1(T3); void mf2(); }; }; template<> template<class X> class A<int>::B { template<class T> void mf1(T); }; template<> template<> template<class T> void A<int>::B<double>::mf1(T t) { } template<class Y> template<> void A<Y>::B<double>::mf2() { } // ill-formed; B<double> is specialized but // its enclosing class template A is not
[Voted into WP at April 2003 meeting.]
In 14.8.2 [temp.deduct], attempting to create an array of abstract class type should be included in the list of things that cause type deduction to fail.
Proposed Resolution (4/02):
In 14.8.2 [temp.deduct] paragraph 2 amend the bullet item:
Attempting to create an array with an element type that is void, a function type, or a reference type, or attempting to create an array with a size that is zero or negative.
To the following:
Attempting to create an array with an element type that is void, a function type, or a reference type, or an abstract class type, or attempting to create an array with a size that is zero or negative.
[Voted into WP at October 2003 meeting.]
I understand the rules in 14.8.2 [temp.deduct] paragraph 2 are meant to be an exhaustive list of what can cause type deduction to fail.
Consider:
template<typename U,U u> struct wrap_t; template<typename U> static yes check( wrap_t<U,U(0)>* ); struct X { X(int); }; int main() { check<X>(0); }
I can see 2 reasons this might cause type deduction to fail:
Neither case is mentioned in 14.8.2 [temp.deduct] paragraph 2, nor do I see a DR mentioning these.
Proposed resolution (October 2002):
Add after the fourth-to-last bullet of 14.8.2 [temp.deduct] paragraph 2:
- Attempting to give an invalid type to a nontype template parameter. [Example:
template <class T, T> struct S {}; template <class T> int f(S<T, T()>*); struct X {}; int i0 = f<X>(0);]
[Voted into WP at March 2004 meeting.]
The following example (simplified from a posting to comp.lang.c++.moderated) is accepted by some compilers (e.g., EDG), but not by other (e.g., g++).
struct S { static int const I = 42; }; template<int N> struct X {}; template<typename T> void f(X<T::I>*) {} template<typename T> void f(X<T::J>*) {} int main() { f<S>(0); }
The wording in the standard that normally would cover this (third sub-bullet in 14.8.2 [temp.deduct] paragraph 2) says:
Attempting to use a type in the qualifier portion of a qualified name that names a type when that type does not contain the specified member, or if the specified member is not a type where a type is required.(emphasis mine). If the phrase "that names a type" applies to "a qualified name," then the example is invalid. If it applies to "the qualifier portion," then it is valid (because the second candidate is simply discarded).
I suspect we want this example to work. Either way, I believe the sub-bullet deserves clarification.
Notes from April 2003 meeting:
We agreed that the example should be valid. The phrase "that names a type" applies to "the qualifier portion."
Proposed resolution (October 2003):
In 14.8.2 [temp.deduct], paragraph 2, bullet 3, sub-bullet 3, replace
Attempting to use a type in the qualifier portion of a qualified name that names a type when that type does not contain the specified member, or if the specified member is not a type where a type is required.
With
Attempting to use a type in a nested-name-specifier of a qualified-id when that type does not contain the specified member, or
- the specified member is not a type where a type is required, or
- the specified member is not a template where a template is required, or
- the specified member is not a nontype where a nontype is required.
[Example:
Replace the example that follows the above text with
template <int I> struct X { }; template <template <class T> class> struct Z {}; template <class T> void f(typename T::Y*){} template <class T> void g(X<T::N>*){} template <class T> void h(Z<T::template TT>*){} struct A {}; struct B { int Y; }; struct C { typedef int N; }; struct D { typedef int TT; }; int main() { // Deduction fails in each of these cases: f<A>(0); // A does not contain a member Y f<B>(0); // The Y member of B is not a type g<C>(0); // The N member of C is not a nontype h<D>(0); // The TT member of D is not a template }]
[Voted into WP at April, 2006 meeting.]
According to 14.8.2 [temp.deduct] paragraph 2,
If a substitution in a template parameter or in the function type of the function template results in an invalid type, type deduction fails.
That would seem to apply to cases like the following:
template <class T> T f(T&){} void f(const int*){} int main() { int a[5]; f(a); }
Here, the return type of f is deduced as int[5], which is invalid according to 8.3.5 [dcl.fct] paragraph 6. The outcome of this example, then, should presumably be that type deduction fails and overload resolution selects the non-template function. However, the list of reasons in 14.8.2 [temp.deduct] for which type deduction can fail does not include function and array types as a function return type. Those cases should be added to the list.
Proposed resolution (October, 2005):
Change the last sub-bullet of 14.8.2 [temp.deduct] paragraph 2 as indicated:
Attempting to create a function type in which a parameter has a type of void, or in which the return type is a function type or array type.
[Voted into the WP at the June, 2008 meeting as paper N2657.]
It is not clear how to handle the following example:
struct S {
template <typename T> S(const T&);
};
void f(const S&);
void f(int);
void g() {
enum E { e };
f(e); // ill-formed?
}
Three possibilities suggest themselves:
Fail during overload resolution. In order to perform overload resolution for the call to f, the declaration of the required specialization of the S constructor must be instantiated. This instantiation uses a local type and is thus ill-formed (14.3.1 [temp.arg.type] paragraph 2), rendering the example as a whole ill-formed, as well.
Treat this as a type-deduction failure. Although it is not listed currently among the causes of type-deduction failure in 14.8.2 [temp.deduct] paragraph 2, it could plausibly be argued that instantiating a function declaration with a local type as a template type-parameter falls under the rubric of “If a substitution in a template parameter or in the function type of the function template results in an invalid type” and thus should be a type-deduction failure. The result would be that the example is well-formed because f(const S&) would be removed from the list of viable functions.
Fail only if the function selected by overload resolution requires instantiation with a local type. This approach would require that the diagnostic resulting from the instantiation of the function type during overload resolution be suppressed and either regenerated or regurgitated once overload resolution is complete. (The example would be well-formed under this approach because f(int) would be selected as the best match.)
(See also issue 489.)
Notes from the April, 2005 meeting:
The question in the original example was whether there should be an error, even though the uninstantiable template was not needed for calling the best-matching function. The broader issue is whether a user would prefer to get an error or to call a “worse” non-template function in such cases. For example:
template<typename T> void f(T); void f(int); void g() { enum E { e }; f(e); // call f(int) or get an error? }
It was observed that the type deduction rules are intended to model, albeit selectively, the other rules of the language. This would argue in favor of the second approach, a type-deduction failure, and the consensus of the group was that the incremental benefit of other approaches was not enough to outweigh the additional complexity of specification and implementation.
Proposed resolution (October, 2005):
Add a new sub-bullet following bullet 3, sub-bullet 7 ("Attempting to give an invalid type to a non-type template parameter") of 14.8.2 [temp.deduct] paragraph 2:
Attempting to use a local or unnamed type as the value of a template type parameter.
Additional note (December, 2005):
The Evolution Working Group is currently considering an extension that would effectively give linkage to some (but perhaps not all) types that currently have no linkage. If the proposed resolution above is adopted and then later a change along the lines that the EWG is considering were also adopted, the result would be a silent change in the result of overload resolution, because the newly-acceptable specializations would become part of the overload set. It is not clear whether that possibility is sufficient reason to delay adoption of this resolution or not.
Notes from the April, 2007 meeting:
The Evolution Working Group is now actively pursuing an extension that would allow local and/or nameless types to be used as template arguments, so this resolution will be held in abeyance until the outcome of that proposal is known.
Notes from the June, 2008 meeting:
Paper N2657, adopted at the Sophia Antipolis (June, 2008) meeting, removed the restriction against local and unnamed types as template parameters. The example is now well-formed.
[Voted into WP at March 2004 meeting.]
The current definition of the C++ language speaks about nondeduced contexts only in terms of deducing template arguments from a type 14.8.2.5 [temp.deduct.type] paragraph 4. Those cases, however, don't seem to be the only ones when template argument deduction is not possible. The example below illustrates that:
namespace A { enum ae { }; template<class R, class A> int foo(ae, R(*)(A)) { return 1; } } template<typename T> void tfoo(T) { } template<typename T> int tfoo(T) { return 1; } /*int tfoo(int) { return 1; }*/ int main() { A::ae a; foo(a, &tfoo); }
Here argument-dependent name lookup finds the function template 'A::foo' as a candidate function. None of the function template's function parameter types constitutes a nondeduced context as per 14.8.2.5 [temp.deduct.type] paragraph 4. And yet, quite clearly, argument deduction is not possible in this context. Furthermore it is not clear what a conforming implementation shall do when the definition of the non-template function '::tfoo' is uncommented.
Suggested resolution:
Add the following as a new paragraph immediately before paragraph 3 of 14.8.2.1 [temp.deduct.call]:
After the above transformations, in the event of P being a function type, a pointer to function type, or a pointer to member function type and the corresponding A designating a set of overloaded (member) functions with at least one of the (member) functions introduced by the use of a (member) function template name (13.4 [over.over]) or by the use of a conversion function template (14.8.2.3 [temp.deduct.conv]), the whole function call expression is considered to be a nondeduced context. [Example:
namespace A { enum ae { }; template<class R, class A> int foo(ae, R(*)(A)) { return 1; } } template<typename T> void tfoo(T) { } template<typename T> int tfoo(T) { return 1; } int tfoo(int) { return 1; } int main() { A::ae a; foo(a, &tfoo); // ill-formed, the call is a nondeduced context using A::foo; foo<void,int>(a, &tfoo); // well-formed, the address of the spe- // cialization 'void tfoo<int>(int)' is // the second argument of the call }
Notes from October 2002 meeting:
There was agreement that deduction should fail but it's still possible to get a result -- it's just not a "nondeduced context" in the sense of the standard.
The presence of a template in the overload set should not automatically disqualify the overload set.
Proposed Resolution (April 2003, revised October 2003):
In 14.8.2.5 [temp.deduct.type] paragraph 4 replace:
The nondeduced contexts are:with:
- The nested-name-specifier of a type that was specified using a qualified-id.
- A type that is a template-id in which one or more of the template-arguments is an expression that references a template-parameter.
The nondeduced contexts are:
- The nested-name-specifier of a type that was specified using a qualified-id.
- A non-type template argument or an array bound that is an expression that references a template-parameter.
- A template parameter used in the parameter type of a function parameter that has a default argument that is being used in the call for which argument deduction is being done.
- A function parameter for which argument deduction cannot be done because the associated function argument is a function, or a set of overloaded functions (13.4 [over.over]), and one or more of the following apply:
- more than one function matches the function parameter type (resulting in an ambiguous deduction), or
- no function matches the function parameter type, or
- the set of functions supplied as an argument contains one or more function templates.
In 14.8.2.1 [temp.deduct.call], add after paragraph 3:
When P is a function type, pointer to function type, or pointer to member function type:
- If the argument is an overload set containing one or more function templates, the parameter is treated as a nondeduced context.
- If the argument is an overload set (not containing function templates), trial argument deduction is attempted using each of the members of the set. If deduction succeeds for only one of the overload set members, that member is used as the argument value for deduction. If deduction succeeds for more than one member of the overload set the parameter is treated as a nondeduced context.
[Example:
// Only one function of an overload set matches the call so the function // parameter is a deduced context. template <class T> int f(T (*p)(T)); int g(int); int g(char); int i = f(g); // calls f(int (*)(int))--end example][Example:
// Ambiguous deduction causes the second function parameter to be a // nondeduced context. template <class T> int f(T, T (*p)(T)); int g(int); char g(char); int i = f(1, g); // calls f(int, int (*)(int))--end example][Example:
// The overload set contains a template, causing the second function // parameter to be a nondeduced context. template <class T> int f(T, T (*p)(T)); char g(char); template <class T> T g(T); int i = f(1, g); // calls f(int, int (*)(int))--end example]
In 14.8.2.5 [temp.deduct.type] paragraph 14, replace:
If, in the declaration of a function template with a non-type template-parameter, the non-type template-parameter is used in an expression in the function parameter-list, the corresponding template-argument must always be explicitly specified or deduced elsewhere because type deduction would otherwise always fail for such a template-argument.With:
If, in the declaration of a function template with a non-type template parameter, the non-type template parameter is used in an expression in the function parameter list, the expression is a nondeduced context.
Replace the example with:
[Example:template<int i> class A { /* ... */ }; template<int i> void g(A<i+1>); template<int i> void f(A<i>, A<i+1>); void k() { A<1> a1; A<2> a2; g(a1); //error: deduction fails for expression i+1 g<0>(a1); //OK f(a1, a2); // OK }--end example]
In 14.8.2.5 [temp.deduct.type] paragraph 16, replace:
A template-argument can be deduced from a pointer to function or pointer to member function argument if the set of overloaded functions does not contain function templates and at most one of a set of overloaded functions provides a unique match.
With:
A template-argument can be deduced from a function, pointer to function, or pointer to member function type.
[Voted into WP at the October, 2006 meeting.]
Consider the following example:
char* cmdline3_[1] = {}; template<class charT> void func(const charT* const argv[]) {} int main() { func(cmdline3_); }
In terms of the process described in 14.8.2.1 [temp.deduct.call], P is const charT* const * and A is char*[1]. According to the first bullet in paragraph 2, the type used in deduction is not A but “the pointer type produced by the array-to-pointer standard conversion.”
According to paragraph 4,
In general, the deduction process attempts to find template argument values that will make the deduced A identical to A (after the type A is transformed as described above). However, there are three cases that allow a difference:
In this example, the deduced A is not identical to the transformed A, because the deduced A has additional cv-qualification, so the three exceptions must be examined to see if they apply. The only one that might apply is the second bullet of paragraph 4:
- A can be another pointer or pointer to member type that can be converted to the deduced A via a qualification conversion (4.4 [conv.qual]).
However, A is not a pointer type but an array type; this provision does not apply and deduction fails.
It has been argued that the phrase “after the type A is transformed as described above” should be understood to apply to the A in the three bullets of paragraph 4. If that is the intent, the wording should be changed to make that explicit.
Proposed resolution (October, 2005):
Add the indicated words to 14.8.2.1 [temp.deduct.call] paragraph 4:
In general, the deduction process attempts to find template argument values that will make the deduced A identical to A (after the type A is transformed as described above). However, there are three cases that allow a difference:
If the original P is a reference type, the deduced A (i.e., the type referred to by the reference) can be more cv-qualified than the transformed A.
The transformed A can be another pointer or pointer to member type that can be converted to the deduced A via a qualification conversion (4.4 [conv.qual]).
If P is a class, and P has the form template-id, then the transformed A can be a derived class of the deduced A. Likewise, if P is a pointer to a class of the form template-id, the transformed A can be a pointer to a derived class pointed to by the deduced A.
[Voted into the WP at the September, 2008 meeting as part of paper N2757.]
There are a couple of minor problems with the rvalue reference wording in the WP. The non-normative note in 14.8.2.1 [temp.deduct.call] paragraph 3 says,
[Note: The effect of this rule for lvalue arguments and rvalue reference parameters is that deduction in such cases will fail unless the function parameter is of the form cv T&& (14.8.2.5 [temp.deduct.type]). —end note]
It turns out that this isn't correct. For example:
template <class T> void g(basic_string<T> && ); ... basic_string<char> s; g(s); // Note says that it should fail, we want it to call // g<char>(basic_string<char>&&)
Additionally, consider this case:
template <class T> void f(const T&&); ... int i; f(i);
If we deduce T as int& in this case then f(i) calls f<int&>(int&), which seems counterintuitive. We prefer that f<int>(const int&&) be called. Therefore, we would like the wording clarified that the A& deduction rule in 14.8.2.1 [temp.deduct.call] paragraph 3 applies only to the form T&& and not to cv T&& as the note currently implies.
These are minor tweaks to the rvalue reference wording and a fallout from issue 540. In particular, the major applications of move semantics and perfect forwarding are not impacted with respect to the original intentions of the rvalue reference work by these suggestions.
Suggested resolution:
Change 14.8.2.1 [temp.deduct.call] paragraph 3 as follows:
If P is an rvalue reference type of the form T&&, where T is a template parameter, and the argument is an lvalue, the type A& is used in place of A for type deduction T is deduced as A&. [Example:
template <typename T> int f(T&&); int i; int j = f(i); // calls f<int&>(i) template <typename T> int g(const T&&); int k; int n = g(k); // calls g<int>(k)—end example][Note: The effect of this rule for lvalue arguments and rvalue reference parameters is that deduction in such cases will fail unless the function parameter is of the form cv T&& (14.8.2.5 [temp.deduct.type]). —end note]
Proposed resolution (August, 2008):
Change 14.8.2.1 [temp.deduct.call] paragraph 3 as follows:
If P is an rvalue reference type of the form T&&, where T is a template parameter, and the argument is an lvalue, the type A& is used in place of A for type deduction. [Example:
template <typename T> int f(T&&); int i; int j = f(i); // calls f<int&>(i) template <typename T> int g(const T&&); int k; int n = g(k); // calls g<int>(k)—end example][Note: The effect of this rule for lvalue arguments and rvalue reference parameters is that deduction in such cases will fail unless the function parameter is of the form cv T&& (14.8.2.5 [temp.deduct.type]). —end note]
[Voted into WP at April 2003 meeting.]
Consider:
struct S { template <class T> operator T& (); }; int main () { S s; int i = static_cast<int&> (s); }14.8.2.3 [temp.deduct.conv] says that we strip the reference from int&, but doesn't say anything about T&. As a result, P (T&) and A (int) have incompatible forms and deduction fails.
Proposed Resolution (4/02):
Change the last chunk of 14.8.2.3 [temp.deduct.conv] paragraph 2 from
If A is a cv-qualified type, the top level cv-qualifiers of A's type are ignored for type deduction. If A is a reference type, the type referred to by A is used for type deduction.to
If A is a cv-qualified type, the top level cv-qualifiers of A's type are ignored for type deduction. If A is a reference type, the type referred to by A is used for type deduction. If P is a reference type, the type referred to by P is used for type deduction.
[Voted into WP at October 2003 meeting.]
We ran into an issue concerning qualification conversions when doing template argument deduction for conversion functions.
The question is: What is the type of T in the conversion functions called by this example? Is T "int" or "const int"?
If T is "int", the conversion function in class A works and the one in class B fails (because the return expression cannot be converted to the return type of the function). If T is "const int", A fails and B works.
Because the qualification conversion is performed on the result of the conversion function, I see no benefit in deducing T as const int.
In addition, I think the code in class A is more likely to occur than the code in class B. If the author of the class was planning on returning a pointer to a const entity, I would expect the function to have been written with a const in the return type.
Consequently, I believe the correct result should be that T is int.
struct A { template <class T> operator T***() { int*** p = 0; return p; } }; struct B { template <class T> operator T***() { const int*** p = 0; return p; } }; int main() { A a; const int * const * const * p1 = a; B b; const int * const * const * p2 = b; }
We have just implemented this feature, and pending clarification by the committee, we deduce T as int. It appears that g++ and the Sun compiler deduce T as const int.
One way or the other, I think the standard should be clarified to specify how cases like this should be handled.
Notes from October 2002 meeting:
There was consensus on having the deduced type be "int" in the above.
Proposed resolution (April 2003):
Add to the end of 14.8.2.3 [temp.deduct.conv] (as a new paragraph following paragraph 3):
When the deduction process requires a qualification conversion for a pointer or pointer to member type as described above, the following process is used to determine the deduced template argument values:
If A is a type cv1,0 pointer to ... cv 1,n-1 pointer to cv1,n T1
and P is a type cv2,0 pointer to ... cv2,n-1 pointer to cv2,n T2
The cv-unqualified T1 and T2 are used as the types of A and P respectively for type deduction.
[Example:
struct A { template <class T> operator T***(); }; A a; const int * const * const * p1 = a; // T is deduced as int, not const int-- end example]
[Moved to DR at 4/01 meeting.]
Paragraph 4 lists contexts in which template formals are not deduced. Were template formals in an expression in the array bound of an array type specification intentionally left out of this list? Or was the intent that such formals always be explicitly specified? Otherwise I believe the following should be valid:
template <int I> class IntArr {}; template <int I, int J> void concat( int (&d)[I+J], const IntArr<I>& a, const IntArr<J>& b ) {} int testing() { IntArr<2> a; IntArr<3> b; int d[5]; concat( d, a, b ); }Can anybody shed some light on this?
From John Spicer:
Expressions involving nontype template parameters are nondeduced contexts, even though they are omitted from the list in 14.8.2.5 [temp.deduct.type] paragraph 4. See 14.8.2.5 [temp.deduct.type] paragraphs 12-14:
...
Proposed resolution (04/01): In 14.8.2.5 [temp.deduct.type] paragraph 4, add a third bullet:
[Moved to DR at October 2002 meeting.]
Paragraph 9 of 14.8.2.5 [temp.deduct.type] enumerates the forms that the types P and A need to have in order for template argument deduction to succeed.
For P denoting a pointer to function the paragraph lists the following forms as allowing for template argument deduction:
type(*)(T) T(*)() T(*)(T)
On the other hand, no provision has been made to accommodate similar cases for references to functions, which in light of the wording of 14.8.2.5 [temp.deduct.type] paragraph 11 means that the program below is ill-formed (some of the C++ compilers do not reject it however):
template<typename Arg, typename Result, typename T> Result foo_r(Result(& rf)(Arg), T x) { return rf(Arg(x)); } template<typename Arg, typename Result, typename T> Result foo_p(Result(* pf)(Arg), T x) { return pf(Arg(x)); } #include <iostream> int show_arg(char c) { std::cout << c << ' '; if(std::cout) return 0; return -1; } int main() { // The deduction int (& rf1)(int(&)(char), double) = foo_r; // shall fail here // While here int (& rf2)(int(*)(char), double) = foo_p; // it shall succeed return rf2(show_arg, 2); }
Proposed resolution (10/01, same as suggested resolution):
In the list of allowable forms for the types P and A in paragraph 9 of 14.8.2.5 [temp.deduct.type] replace
type(*)(T) T(*)() T(*)(T)
by
type(T) T() T(T)
[Voted into WP at the October, 2006 meeting.]
14.8.2.5 [temp.deduct.type] paragraph 5 reads:
The non-deduced contexts are:
The nested-name-specifier of a type that was specified using a qualified-id.
A non-type template argument or an array bound that is an expression that references a template parameter.
A template parameter used in the parameter type of a function parameter that has a default argument that is being used in the call for which argument deduction is being done.
A function parameter for which argument deduction cannot be done because the associated function argument is a function, or a set of overloaded functions (13.4 [over.over]), and one or more of the following apply:
more than one function matches the function parameter type (resulting in an ambiguous deduction), or
no function matches the function parameter type, or
the set of functions supplied as an argument contains one or more function templates.
An array bound that is an expression that references a template-parameter.
There are two problems with this list:
The last bullet is redundant with the second bullet. This appears to have been the result of applying the resolutions of issues 70 and 352 independently instead of in coordination.
The second bullet appears to be contradicted by the statement in paragraph 8 saying that an argument can be deduced if P and A have the forms type[i] and template-name<i>.
The intent of the wording in bullet 2 appears to have been that deduction cannot be done if the template parameter is a sub-expression of the template argument or array bound expression and that it can be done if it is the complete expression, but the current wording does not say that very clearly. (Similar wording also appears in 14.6.2.1 [temp.dep.type] paragraph 3 and 14.8.2.5 [temp.deduct.type] paragraph 14.)
Proposed resolution (October, 2005):
Change 14.8.2.5 [temp.deduct.type] paragraph 5 as indicated:
The non-deduced contexts are:
The nested-name-specifier of a type that was specified using a qualified-id.
A non-type template argument or an array bound that is an expression that in either of which a subexpression references a template parameter.
A template parameter used in the parameter type of a function parameter that has a default argument that is being used in the call for which argument deduction is being done.
A function parameter for which argument deduction cannot be done because the associated function argument is a function, or a set of overloaded functions (13.4 [over.over]), and one or more of the following apply:
more than one function matches the function parameter type (resulting in an ambiguous deduction), or
no function matches the function parameter type, or
the set of functions supplied as an argument contains one or more function templates.
An array bound that is an expression that references a template-parameter.
Change 14.8.2.5 [temp.deduct.type] paragraph 14 as indicated:
If, in the declaration of a function template with a non-type template parameter, the non-type template parameter is used in an expression a subexpression in the function parameter list, the expression is a non-deduced context as specified above...
Change 14.6.2.1 [temp.dep.type] paragraph 3 as indicated:
A template argument that is equivalent to a template parameter (i.e., has the same constant value or the same type as the template parameter) can be used in place of that template parameter in a reference to the current instantiation. In the case of a non-type template argument, the argument must have been given the value of the template parameter and not an expression involving that contains the template parameter as a subexpression...
[Voted into WP at the October, 2006 meeting.]
Mike Miller: In fact, now that I've looked more closely, that appears not to be the case. (At least, it's not the error I get when I compile his example.) Here's a minimal extract (without the inflammatory using-directive :-) that illustrates what I think is going on:
template <typename _Iterator> struct iterator_traits { typedef typename _Iterator::difference_type difference_type; }; template <typename _InputIterator> inline typename iterator_traits<_InputIterator>::difference_type distance(_InputIterator, _InputIterator); double distance(const int&, const int&); void f() { int i = 0; int j = 0; double d = distance(i, j); }
What happens is that iterator_traits<int> is instantiated as part of type deduction for the function template distance, and the instantiation fails. (Note that it can't be instantiation of distance<int>, as I had originally posited, because in this case only a declaration, not a definition, of that template is in scope.)
John Spicer: Yes, I believe that is what is going on.
Mike Miller: I seem to recall that there was some discussion of questions related to this during the core meetings in Oxford. I think Steve Adamczyk said something to the effect that it's infeasible to suppress all instantiation errors during template type deduction and simply call any such errors a deduction failure. (I could be misremembering, and I could be misapplying that comment to this situation.)
John Spicer: Regardless of other conditions in which this may apply, I don't think it would be reasonable for compilers to have to do "speculative instantiations" during template argument deduction. One class instantiation could kick off a series of other instantiations, etc.
Mike Miller: I don't see anything in the Standard that tells me whether it's legitimate or not to report an error in this case. I hope John or another template expert can enlighten me on that.
John Spicer: My opinion is that, because this case is not among those enumerated that cause deduction failure (rather than being ill-formed) that reporting an error is the right thing to do.
Mike Miller: I am still interested, though, in the question of why 14.8.3 [temp.over] says that viable function template specializations are instantiated, even if they are not selected by overload resolution.
John Spicer: I believe the wording in 14.8.3 [temp.over] is incorrect. I researched this and found that a change was made during the clause 14 restructuring that was incorporated in March of 1996. The prior wording was "the deduced template arguments are used to generate a single template function". This was changed to "deduced template arguments are used to instantiate a single function template specialization". I believe this resulted from what was basically a global replace of "generate" with "instantiate" and of "template function" with "function template specialization". In this case, the substitution changed the meaning. This paragraph needs reworking.
Proposed resolution (April, 2006):
Change 14.8.3 [temp.over] paragraph 1 as indicated:
...For each function template, if the argument deduction and checking succeeds, the template-arguments (deduced and/or explicit) are used to instantiate synthesize the declaration of a single function template specialization which is added to the candidate functions set to be used in overload resolution. If, for a given function template, argument deduction fails, no such function is added to the set of candidate functions for that template. The complete set of candidate functions includes all the function templates instantiated in this way synthesized declarations and all of the non-template overloaded functions of the same name. The function template specializations synthesized declarations are treated like any other functions in the remainder of overload resolution, except as explicitly noted in 13.3.3 [over.match.best].
[Moved to DR at 4/01 meeting.]
Paragraph 7 of 15.1 [except.throw] discusses which exception is thrown by a throw-expression with no operand.
May an expression which has been "finished" (paragraph 7) by an inner catch block be rethrown by an outer catch block?
catch(...) // Catch the original exception { try{ throw; } // rethrow it at an inner level // (in reality this is probably // inside a function) catch (...) { } // Here, an exception (the original object) // is "finished" according to 15.1p7 wording // 15.1p7 says that only an unfinished exception // may be rethrown. throw; // Can we throw it again anyway? It is // certainly still alive (15.1p4). }
I believe this is ok, since the paragraph says that the exception is finished when the "corresponding" catch clause exits. However since we have two clauses, and only one exception, it would seem that the one exception gets "finished" twice.
Proposed resolution (04/01):
In 15.1 [except.throw] paragraph 4, change
When the last handler being executed for the exception exits by any means other than throw; ...to
When the last remaining active handler for the exception exits by any means other than throw; ...
In 15.1 [except.throw] paragraph 6, change
A throw-expression with no operand rethrows the exception being handled.to
A throw-expression with no operand rethrows the currently handled exception (15.3 [except.handle]).
Delete 15.1 [except.throw] paragraph 7.
Add the following before 15.1 [except.throw] paragraph 6:
An exception is considered caught when a handler for that exception becomes active (15.3 [except.handle]). [Note: an exception can have active handlers and still be considered uncaught if it is rethrown.]
Change 15.3 [except.handle] paragraph 8 from
An exception is considered handled upon entry to a handler. [Note: the stack will have been unwound at that point.]to
A handler is considered active when initialization is complete for the formal parameter (if any) of the catch clause. [Note: the stack will have been unwound at that point.] Also, an implicit handler is considered active when std::terminate() or std::unexpected() is entered due to a throw. A handler is no longer considered active when the catch clause exits or when std::unexpected() exits after being entered due to a throw.
The exception with the most recently activated handler that is still active is called the currently handled exception.
In 15.3 [except.handle] paragraph 16, change "exception being handled" to "currently handled exception."
[Voted into WP at March 2004 meeting.]
15.1 [except.throw] paragraph 3 says that the type of a throw expression shall not be a pointer or reference to an incomplete type. But an expression never has reference type.
Proposed Resolution (October 2003):
Change the penultimate sentence of 15.1 [except.throw] paragraph 3 as follows:
The type of the throw-expression shall not be an incomplete type, or a pointer or reference to an incomplete type other than (possibly cv-qualified) void, other than void*, const void*, volatile void*, or const volatile void*.
[Voted into WP at April, 2006 meeting.]
I have noticed a couple of confusing and overlapping passages dealing with copy elision. The first is 15.1 [except.throw] paragraph 5:
If the use of the temporary object can be eliminated without changing the meaning of the program except for the execution of constructors and destructors associated with the use of the temporary object (12.2 [class.temporary]), then the exception in the handler can be initialized directly with the argument of the throw expression.
The other is 15.3 [except.handle] paragraph 17:
If the use of a temporary object can be eliminated without changing the meaning of the program except for execution of constructors and destructors associated with the use of the temporary object, then the optional name can be bound directly to the temporary object specified in a throw-expression causing the handler to be executed.
I think these two passages are intended to describe the same optimization. However, as is often the case where something is described twice, there are significant differences. One is just different terminology — is “the exception in the handler” the same as “the object declared in the exception-declaration or, if the exception-declaration does not specify a name, a temporary object of that type” (15.3 [except.handle] paragraph 16)?
More significant, there is a difference in which kinds of throw-expressions are eligible for the optimization. In 15.1 [except.throw] paragraph 5, it appears that any object is a candidate, while in 15.3 [except.handle] paragraph 17 the thrown object must be a temporary (“the temporary object specified in a throw-expression”). For example, it's not clear looking at these two passages whether the copy of a local automatic can be elided. I.e., by analogy with the return value optimization described in 12.8 [class.copy] paragraph 15:
X x; return x; // copy may be elided X x; throw x; // unclear whether copy may be elided
Which brings up another point: 12.8 [class.copy] paragraph 15 purports to be an exhaustive list in which copy elision is permitted even if the constructor and/or destructor have side effects; however, these two passages describe another case that is not mentioned in 12.8 [class.copy] paragraph 15.
A final point of confusion: in the unoptimized abstract machine, there are actually two copies in throwing and handling an exception: the copy from the object being thrown to the exception object, and the copy from the exception object to the object or temporary in the exception-declaration. 15.1 [except.throw] paragraph 5 speaks only of eliminating the exception object, copying the thrown object directly into the exception-declaration object, while 15.3 [except.handle] paragraph 17 refers to directly binding the exception-declaration object to the thrown object (if it's a temporary). Shouldn't these be separated, with a throw of an automatic object or temporary being like the return value optimization and the initialization of the object/temporary in the exception-declaration being a separate optimizable step (which could, presumably, be combined to effectively alias the exception-declaration onto the thrown object)?
(See paper J16/04-0165 = WG21 N1725.)
Proposed resolution (April, 2005):
Add two items to the bulleted list in 12.8 [class.copy] paragraph 15 as follows:
This elision of copy operations is permitted in the following circumstances (which may be combined to eliminate multiple copies):
in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object with the same cv-unqualified type as the function return type, the copy operation can be omitted by constructing the automatic object directly into the function's return value
in a throw-expression, when the operand is the name of a non-volatile automatic object, the copy operation from the operand to the exception object (15.1 [except.throw]) can be omitted by constructing the automatic object directly into the exception object
when a temporary class object that has not been bound to a reference (12.2 [class.temporary]) would be copied to a class object with the same cv-unqualified type, the copy operation can be omitted by constructing the temporary object directly into the target of the omitted copy
when the exception-declaration of an exception handler (clause 15 [except]) declares an object of the same type (except for cv-qualification) as the exception object (15.1 [except.throw]), the copy operation can be omitted by treating the exception-declaration as an alias for the exception object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by the exception-declaration
Change 15.1 [except.throw] paragraph 5 as follows:
If the use of the temporary object can be eliminated without changing the meaning of the program except for the execution of constructors and destructors associated with the use of the temporary object (12.2 [class.temporary]), then the exception in the handler can be initialized directly with the argument of the throw expression. When the thrown object is a class object, and the copy constructor used to initialize the temporary copy is not and the destructor shall be accessible, the program is ill-formed (even when the temporary object could otherwise be eliminated) even if the copy operation is elided (12.8 [class.copy]). Similarly, if the destructor for that object is not accessible, the program is ill-formed (even when the temporary object could otherwise be eliminated).
Change 15.3 [except.handle] paragraph 17 as follows:
If the use of a temporary object can be eliminated without changing the meaning of the program except for execution of constructors and destructors associated with the use of the temporary object, then the optional name can be bound directly to the temporary object specified in a throw-expression causing the handler to be executed. The copy constructor and destructor associated with the object shall be accessible even when the temporary object is eliminated if the copy operation is elided (12.8 [class.copy]).
[Voted into the WP at the September, 2008 meeting (resolution in paper N2757).]
According to 15.2 [except.ctor] paragraph 2,
An object that is partially constructed or partially destroyed will have destructors executed for all of its fully constructed subobjects, that is, for subobjects for which the principal constructor (12.6.2 [class.base.init]) has completed execution and the destructor has not yet begun execution. Similarly, if the non-delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object's destructor will be invoked. Should a constructor for an element of an automatic array throw an exception, only the constructed elements of that array will be destroyed.
The requirement for destruction of array elements explicitly applies only to automatic arrays, and one might conclude from the context that only automatic class objects are in view as well, although that is not explicitly stated. What about local static arrays and class objects? Are they intended also to be subject to the requirement that fully-constructed subobjects are to be destroyed?
Proposed resolution (October, 2006):
Change 15.2 [except.ctor] paragraph 2 as follows:
An object that is partially constructed or partially destroyed will have destructors executed for all of its fully constructed subobjects, that is, for subobjects for which the principal constructor (12.6.2 [class.base.init]) has completed execution and the destructor has not yet begun execution. Similarly, if the non-delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object's destructor will be invoked. Should a constructor for an element of an automatic array throw an exception, only the constructed elements of that array will be destroyed. If the object or array was allocated in a new-expression, the matching deallocation function (3.7.4.2 [basic.stc.dynamic.deallocation], 5.3.4 [expr.new], 12.5 [class.free]), if any, is called to free the storage occupied by the object.
[Moved to DR at 4/01 meeting.]
In 15.4 [except.spec] paragraph 2:
An exception-specification shall appear only on a function declarator in a function, pointer, reference or pointer to member declaration or definition.Does that mean in the top-level function declarator, or one at any level? Can one, for example, specify an exception specification on a pointer-to-function parameter of a function?
void f(int (*pf)(float) throw(A))Suggested answer: no. The exception specifications are valid only on the top-level function declarators.
However, if exception specifications are made part of a function's type as has been tentatively agreed, they would have to be allowed on any function declaration.
There is already an example of an exception specification for a parameter in the example in 15.4 [except.spec] paragraph 1.
Proposed resolution (04/01): Change text in 15.4 [except.spec] paragraph 1 from:
An exception-specification shall appear only on a function declarator in a function, pointer, reference or pointer to member declaration or definition.to:
An exception-specification shall appear only on a function declarator for a function type, pointer to function type, reference to function type, or pointer to member function type that is the top-level type of a declaration or definition, or on such a type appearing as a parameter or return type in a function declarator.
(See also issues 25, 92, and 133.)
[Voted into WP at October 2004 meeting.]
In clause 16 [cpp], paragraph 1, the control-line non-terminal symbol is defined in terms of the identifier-list non-terminal, which is never defined within the standard document.
The same definition is repeated in clause A.14 [gram.cpp].
I suggest that the following definition is added to clause 16 [cpp], paragraph 1, after the one for replacement-list:
This should be repeated again in clause A.14 [gram.cpp], again after the one for replacement-list. It might also be desirable to include a third repetition in clause 16.3 [cpp.replace], paragraph 9.
Proposed Resolution (Clark Nelson, Dec 2003):
In clause 16 [cpp], paragraph 1, immediately before the definition of replacement-list, add:
If the correct TROFF macros are used, the definition will appear automatically in appendix A. It doesn't need to be repeated in 16.3p9.
With respect to the question of having the preprocessor description be synchronized with C99, this would fall into the category of a justified difference. (Other justified differences include those for Boolean expressions, alternative tokens, and terminology differences.)
[Voted into WP at the October, 2006 meeting.]
The motivation for this issue is a desire to write portable programs which will work with any conforming implementation.
The C++ Standard (16.2 [cpp.include]) provides two forms of #include directives, with the <...> form being described (16.2 [cpp.include] paragraph 2) as "for a header", and the "..." form (16.2 [cpp.include] paragraph 3) as for "the source file" identified between the delimiters. When the standard uses the term "header", it often appears to be limiting the term to apply to the Standard Library headers only. Users of the standard almost always use the term "header" more broadly, to cover all #included source files, but particularly those containing interface declarations.
Headers, including source files, can be categorized according to their origin and usage:
Existing practice varies widely, but it is fairly easy to find users advocating:
Do any of the practices A, B, or C result in programs which can be rejected by a conforming implementation?
The first defect is that readers of the standard have not been able to reach consensus on the answers to the above question.
A second possible defect is that if A, B, or C can be rejected by a conforming implementation, then the standard should be changed because would mean there is a wide variance between the standard and existing practice.
Matt Austern: I really only see two positions:
I agree that the standard should clarify which of those two is the case (I imagine it'll hinge on finding one crucual sentence that either says "implementation defined" or "unspecified"), but from the standpoint of portability I don't see much difference between the two. I claim that, with either of those two interpretations, using #include <foo> is already nonportable.
(Of course, I claim that almost anything having to do with headers, including the #include "foo" form, is also nonportable. In practice there's wide variation in how compilers handle paths, especially relative paths.)
Beman Dawes: The whole issue can be resolved by replacing "header" with "header or source file" in 16.2 [cpp.include] paragraph 2. That will bring the standard into alignment with existing practice by both users and implementations. The "header and/or source file" wording is used at least three other places in the standard where otherwise there might be some confusion.
John Skaller: In light of Andrew Koenig's comments, this doesn't appear to be the case, since the mapping of include names to file names is implementation defined, and therefore source file inclusion cannot be made portable within the ISO C/C++ standards (since that provision obviously cannot be removed).
A possible idea is to create a binding standard, outside the C/C++ ISO Standards, which specifies not only the path lookup mechanism but also the translation from include names to file names. Clearly that is OS dependent, encoding dependent, etc, but there is no reason not to have a binding standard for Unix, Windows, etc, and specify these bindings in such a way that copying directories from one OS to the other can result in programs working on both OS's.
Andy Koenig: An easier solution might be to specify a (presumably unbounded, or bounded only by implementation capacity) collection of header-file names that every implementation must make it possible for programs to access somehow, without specifying exactly how.
Notes from October 2002 meeting:
This was discussed at some length. While there was widespread agreement that such inclusion is inherently implementation-dependent, we agreed to try to add wording that would make it clear that implementations are permitted (but not required) to allow inclusion of files using the <...> form of #include.
Proposed resolution (April, 2005):
Change 16.2 [cpp.include] paragraph 7 from:
[Example: The most common uses of #include preprocessing directives are as in the following:#include <stdio.h> #include "myprog.h"—end example]
to:
[Note: Although an implementation may provide a mechanism for making arbitrary source files available to the < > search, in general programmers should use the < > form for headers provided with the implementation, and the " " form for sources outside the control of the implementation. For instance:#include <stdio.h> #include <unistd.h> #include "usefullib.h" #include "myprog.h"—end note]
Notes from October, 2005 meeting:
Some doubt was expressed as to whether the benefit of this non-normative clarification outweighs the overall goal of synchronizing clause 16 with the corresponding text in the C99 Standard. As a result, this issue is being left in “review” status to allow further discussion.
Additional notes (October, 2006):
WG14 takes no position on this change.
[Moved to DR at 10/01 meeting.]
The main defect is in the library, where the binder template can easily lead to reference-to-reference situations.
Proposed resolution (04/01):
If a typedef TD names a type "reference to cv1 S," an attempt to create the type "reference to cv2 TD" creates the type "reference to cv12" S," where cv12 is the union of the cv-qualifiers cv1 and cv2. Redundant qualifiers are ignored. [Example:
int i; typedef int& RI; RI& r = i; // r has the type int& const RI& r = i; // r has the type const int&—end example]
If a template-argument for a template-parameter T names a type "reference to cv1 S," an attempt to create the type "reference to cv2 T" creates the type "reference to cv12 S," where cv12 is the union of the cv-qualifiers cv1 and cv2. Redundant cv-qualifiers are ignored. [Example:
template <class T> class X { f(const T&); /* ... */ }; X<int&> x; // X<int&>::f has the parameter type const int&—end example]
Attempting to create a reference to a reference type or a reference to void.
(See also paper J16/00-0022 = WG21 N1245.)
[Voted into WP at July, 2009 meeting.]
According to _N3225_.7.6.4 [dcl.attr.final] paragraph 2, overriding a virtual function with the [[final]] attribute renders a program ill-formed, but no diagnostic is required. This is easily diagnosable and a diagnostic should be required in this case.
Notes from the March, 2009 meeting:
This specification was a deliberate decision on the part of the EWG; the general rule was that it should be possible to ignore attributes without changing the meaning of a program. However, the consensus of the CWG was that violation of the [[final]] attribute should require a diagnostic.
Proposed resolution (March, 2009):
Change _N3225_.7.6.4 [dcl.attr.final] paragraph 2 as follows:
If a virtual member function f in some class B is marked final and in a class D derived from B a function D::f overrides B::f, the program is ill-formed; no diagnostic required. [Footnote: If an implementation does not emit a diagnostic it should execute the program as if final were not present. —end footnote]
[Voted into WP at March, 2010 meeting.]
According to _N3225_.7.6.4 [dcl.attr.final] paragraph 1, the [[final]] attribute applied to a class is just a shorthand notation for marking each of the class's virtual functions as [[final]]. This is different from the similar usage in other languages, where it means that the class so marked cannot be used as a base class. This discrepancy is confusing, and the definition used by the other languages is more useful.
Notes from the March, 2009 meeting:
The intent of the [[final]] attribute is as an aid in optimization, to avoid virtual function calls when the final overrider is known. It is possible to use the [[final]] attribute to prevent derivation by marking the destructor as [[final]]; in fact, as most polymorphic classes will, as a matter of good programming practice, have a virtual destructor, marking the class as [[final]] will have the effect of preventing derivation.
Nonetheless, the general consensus of the CWG was to change the meaning of class [[final]] to parallel the usage in other languages.
Proposed resolution (October, 2009):
Change _N3225_.7.6.4 [dcl.attr.final] paragraph 1 and add a new paragraph, as follows:
The attribute-token final specifies derivation semantics for a class and overriding semantics for a virtual function. It shall appear at most once in each attribute-list and no attribute-argument-clause shall be present. The attribute applies to class definitions and to virtual member functions being declared in a class definition. If the attribute is specified for a class definition, it is equivalent to being specified for each virtual member function of that class, including inherited member functions.
If some class B is marked final and a class D is derived from B the program is ill-formed.
Change the example in _N3225_.7.6.4 [dcl.attr.final] paragraph 3 as follows:
struct B1 { virtual void f [[ final ]] (); }; struct D1 : B1 { void f(); // ill-formed }; struct [[ final ]] B2 { }; struct D2 : B2 { // ill-formed };
[Voted into WP at March, 2010 meeting as document N3055.]
According to 1.3 [intro.defs], “dynamic type,”
The dynamic type of an rvalue expression is its static type.
This is not true of an rvalue reference, which can be bound to an object of a class type derived from the reference's static type.
Proposed resolution (June, 2008):
Change 1.3 [intro.defs], “dynamic type,” as follows:
the type of the most derived object (1.8 [intro.object]) to which the lvalue denoted by an lvalue or an rvalue-reference (clause 5 [expr]) expression refers. [Example: if a pointer (8.3.1 [dcl.ptr]) p whose static type is “pointer to class B” is pointing to an object of class D, derived from B (clause 10 [class.derived]), the dynamic type of the expression *p is “D.” References (8.3.2 [dcl.ref]) are treated similarly. —end example] The dynamic type of an rvalue expression that is not an rvalue reference is its static type.
Notes from the June, 2008 meeting:
Because expressions have an rvalue reference type only fleetingly, immediately becoming either lvalues or rvalues and no longer references, the CWG expressed a desire for a different approach that would somehow describe an rvalue that resulted from an rvalue reference instead of using the concept of an expression that is an rvalue reference, as above. This approach could also be used in the resolution of issue 664.
Additional note (August, 2008):
This issue, along with issue 664, indicates that rvalue references have more in common with lvalues than with other rvalues: they denote particular objects, thus allowing object identity and polymorphic behavior. That suggests that these issues may be just the tip of the iceberg: restrictions on out-of-lifetime access to objects, the aliasing rules, and many other specifications are written to apply only to lvalues, on the assumption that only lvalues refer to specific objects. That assumption is no longer valid with rvalue references.
This suggests that it might be better to classify all rvalue references, not just named rvalue references, as lvalues instead of rvalues, and then just change the reference binding, overload resolution, and template argument deduction rules to cater to the specific kind of lvalues that are associated with rvalue references.
Additional note, May, 2009:
Another place in the Standard where the assumption is made that only lvalues can have dynamic types that differ from their static types is 5.2.8 [expr.typeid] paragraph 2.
(See also issues 846 and 863.)
Additional note, September, 2009:
Yet another complication is the statement in 3.10 [basic.lval] paragraph 9 stating that “non-class rvalues always have cv-unqualified types.” If an rvalue reference is an rvalue, then the following example is well-formed:
void f(int&&); // reference to non-const
void g() {
const int i = 0;
f(static_cast<const int&&>(i));
}
The static_cast binds an rvalue reference to the const object i, but the fact that it's an rvalue means that the cv-qualification is lost, effectively allowing the parameter of f, a reference to non-const, to bind directly to the const object.
Proposed resolution (February, 2010):
See paper N3030.
[Voted into WP at October, 2009 meeting.]
The execution requirements on a conforming implementation are described twice in the Standard, once in 1.9 [intro.execution] paragraphs 5-6 and again in paragraph 11. These descriptions differ in at least a couple of important ways:
The most significant discrepancy has to do with the way output is described. In paragraph 11, the least requirements are described in terms of data written at program termination, clearly allowing arbitrary buffering, whereas in paragraph 6, the observable behavior is described in terms of calls to I/O functions. For example, there are compilers which transform a call to printf with a single argument into a call to fputs. That's valid under paragraph 11, but not under paragraph 6.
Also, in paragraph 6, volatile accesses and I/O operations are included in a single sequence, suggesting that they are equally constrained by sequencing requirements, whereas in paragraph 11, they are clearly not.
There are also editorial discrepancies that should be cleaned up.
Proposed resolution (September, 2009):
The resolution of issue 785 also resolves this issue.
[Voted into WP at October, 2009 meeting.]
In the presence of threads, it is no longer appropriate to characterize the abstract machine as having an “execution sequence.”
Proposed resolution (September, 2009):
Change 1.9 [intro.execution] paragraph 3 as follows:
...An instance of the abstract machine can thus have more than one possible execution sequence for a given program and a given input.
Change 1.9 [intro.execution] paragraph 5 as follows:
A conforming implementation executing a well-formed program shall produce the same observable behavior as one of the possible execution sequences executions of the corresponding instance of the abstract machine with the same program and the same input. However, if any such execution sequence contains an undefined operation, this International Standard places no requirement on the implementation executing that program with that input (not even with regard to operations preceding the first undefined operation).
Delete 1.9 [intro.execution] paragraph 6, including the footnote:
The observable behavior of the abstract machine is its sequence of reads and writes to volatile data and calls to library I/O functions. [Footnote: An implementation can offer additional library I/O functions as an extension. Implementations that do so should treat calls to those functions as “observable behavior” as well. —end footnote]
Change 1.9 [intro.execution] paragraph 9 as follows:
The least requirements on a conforming implementation are:
Access to volatile objects are evaluated strictly according to the rules of the abstract machine.
At program termination, all data written into files shall be identical to one of the possible results that execution of the program according to the abstract semantics would have produced.
The input and output dynamics of interactive devices shall take place in such a fashion that prompting messages actually appear prior to a program waiting prompting output is actually delivered before a program waits for input. What constitutes an interactive device is implementation-defined.
These collectively are referred to as the observable behavior of the program. [Note: more stringent correspondences between abstract and actual semantics may be defined by each implementation. —end note]
(Note; this resolution also resolves issue 612.)
[Voted into WP at October, 2009 meeting.]
In general, the description of the memory model is very careful to specify when the objects under discussion are atomic or non-atomic. However, there are a few cases where it could be clearer.
Proposed resolution (March, 2009):
Modify 1.10 [intro.multithread] paragraph 5 as follows:
All modifications to a particular atomic object M occur in some particular total order, called the modification order of M. If A and B are modifications of an atomic object M and A happens before (as defined below) B, then A shall precede B in the modification order of M, which is defined below. [Note: This states that the modification orders must respect happens before. —end note] [Note: There is a separate order for each scalar atomic object. There is no requirement that these can be combined into a single total order for all objects. In general this will be impossible since different threads may observe modifications to different variables in inconsistent orders. —end note]
Modify 1.10 [intro.multithread] paragraph 7 as follows:
Certain library calls synchronize with other library calls performed by another thread. In particular, an atomic operation A that performs a release operation on an atomic object M synchronizes with an atomic operation B that performs an acquire operation on M and reads a value written by any side effect in the release sequence headed by A...
Modify 1.10 [intro.multithread] paragraph 12 as follows:
A visible side effect A on an a scalar object or bit-field M with respect to a value computation B of M satisfies the conditions:
A happens before B, and
there is no other side effect X to M such that A happens before X and X happens before B.
The value of a non-atomic scalar object or bit-field M, as determined by evaluation B, shall be the value stored by the visible side effect A. [Note: If there is ambiguity about which side effect to a non-atomic object or bit-field is visible, then there is a data race, and the behavior is undefined. —end note] ...
[Voted into WP at March, 2010 meeting.]
1.10 [intro.multithread] paragraph 12 says,
A visible side effect A on an object M with respect to a value computation B of M satisfies the conditions:
A happens before B, and
there is no other side effect X to M such that A happens before X and X happens before B.
The value of a non-atomic scalar object M, as determined by evaluation B, shall be the value stored by the visible side effect A. [Note: If there is ambiguity about which side effect to a non-atomic object is visible, then there is a data race, and the behavior is undefined. —end note]
The note here suggests that, except in the case of a data race, visible side effects to value computation can always be determined. But unsequenced and indeterminately sequenced side effects on the same object create ambiguities with respect to a later value computation as well. So the wording needs to be revisited, see the following examples.
int main(){ int i = 0; i = // unsequenced side effect A i++; // unsequenced side effect B return i; // value computation C }
According to the definition in the draft, both A and B are visible side effects to C. However, there is no data race, because (paragraph 14) a race involves at least two threads. So the note in paragraph 12 is logically false.
The model introduces the special case of indeterminately sequenced side effects, that leave open what execution order is taken in a concrete situation. If the execution paths access the same data, unpredictable results are possible, just as it is the case with data races. Whereas data races constitute undefined behavior, indeterminatedly sequenced side effects on the same object do not. As a consequence of this disparity, indeterminately sequenced execution occasionally needs exceptional treatment.
int i = 0; int f(){ return i = 1; // side effect A } int g(){ return i = 2; // side effect B } int h(int, int){ return i; // value computation C } int main(){ return h(f(),g()); // function call D returns 1 or 2? }
Here, either A or B is the visible side effect on the value computation C, but you cannot tell which (cf. 1.9 [intro.execution] paragraph 16). Although an ambiguity is present, it is neither because of a data race, nor is the behavior undefined, in total contradiction to the note.
Proposed resolution (October, 2009):
Change 1.10 [intro.multithread] paragraph 12 as follows:
...The value of a non-atomic scalar object or bit-field M, as determined by evaluation B, shall be the value stored by the visible side effect A. [Note: If there is ambiguity about which side effect to a non-atomic object or bit-field is visible, then there is a data race, and the behavior is either unspecified or undefined. —end note]...
[Voted into WP at October, 2009 meeting.]
The term “thread” is introduced but not defined in 1.10 [intro.multithread] paragraph 1. A definition is needed.
Proposed resolution (September, 2009):
Chamge 1.10 [intro.multithread] paragraph 1 as follows:
A thread of execution (a.k.a. thread) is a single flow of control within a program, including the initial invocation of a specific top-level function, and recursively including every function invocation subsequently executed by the thread. [Note: When one thread creates another, the initial call to the top-level function of the new thread is executed by the new thread, not by the creating thread. —end note] Every thread in a program can potentially access every object and function in the program. [Footnote: An object with automatic or thread storage duration (3.7 [basic.stc]) is associated with one specific thread, and can be accessed by a different thread only indirectly through a pointer or reference (3.9.2 [basic.compound]). —end footnote] Under a hosted implementation, a C++ program can have more than one thread of execution (a.k.a. thread) thread running concurrently...
[Voted into WP at March, 2010 meeting.]
There are several instances of undefined behavior in lexical processing:
2.2 [lex.phases] paragraph 1, phase 2: a universal-character-name resulting from a line splice.
2.2 [lex.phases] paragraph 1, phase 2: a file ending without a new-line character or with a new-line character that is spliced away.
2.2 [lex.phases] paragraph 1, phase 4: a universal-character-name resulting from macro token concatenation.
2.9 [lex.header] paragraph 2: ', \, /*, //, or " appearing in a header-name.
These would be more appropriately handled as conditionally-supported behavior, requiring implementations either to document their handling of these constructs or to issue a diagnostic.
Additional note, March, 2009:
The undefined behavior referred to above regarding universal-character-names is the result of the considerations described in the C99 Rationale, section 5.2.1, in the part entitled “UCN models.” Three different models for support of UCNs are described, each involving different conversions between UCNs and wide characters and/or at different times during program translation. Implementations, as well as the specification in a language standard, can employ any of the three, but it must be impossible for a well-defined program to determine which model was actually employed by implementation. The implication of this “equivalence principle” is that any construct that would give different results under the different models must be classified as undefined behavior. For example, an apparent UCN resulting from a line-splice would be recognized as a UCN by an implementation in which all wide characters were translated immediately into UCNs, as described in C++ phase 1, but would not be recognized as a UCN by another implementation in which all UCNs were translated immediately into wide characters (a possibility mentioned parenthetically in C++ phase 1).
There are additional implications for this “equivalence principle” beyond the ones identified in the UK CD comments. See also issue 578; presumably a string like the one in that issue should also be described as having undefined behavior. Also, because C++'s model introduces backslash characters as part of UCNs for any character outside the basic source character set, any header-name that contains such a character (e.g., #include "@.h") will have undefined behavior in C++. This is also the reason that UCNs are translated into wide characters inside raw strings: two of the three models articulated in the C99 Rationale translate to or from UCNs in phase 1, before raw strings are recognized as tokens in phase 3, so raw strings cannot treat UCNs differently from the way they are treated in other contexts. See also issue 789 for similar points regarding trigraphs.
Notes from the October, 2009 meeting:
The CWG decided that the non-UCN aspects of this issue should be resolved, while the overall questions regarding trigraphs, UCNs, and raw strings will be investigated separately.
Proposed resolution (February, 2010):
Change 2.2 [lex.phases] paragraph 1 phase 2 as follows:
...If a A source file that is not empty and that does not end in a new-line character, or that ends in a new-line character immediately preceded by a backslash character before any such splicing takes place, the behavior is undefined shall be processed as if an additional new-line character were appended to the file.
Change 2.9 [lex.header] paragraph 2 as follows:
If The appearance of either of the characters ' or \, or of either of the character sequences /* or // appears in a q-char-sequence or a an h-char-sequence is conditionally-supported with implementation-defined semantics, or as is the appearance of the character " appears in a an h-char-sequence, the behavior is undefined. [Footnote: Thus, a sequences of characters that resembles an escape sequences cause undefined behavior might result in an error, be interpreted as the character corresponding to the escape sequence, or have a completely different meaning, depending on the implementation. —end footnote]
[Voted into WP at October, 2009 meeting.]
WG14 accepted DR 279 regarding the rule known colloquially as the L'x'=='x' rule. This change was made to C99 in TC2. The Austin Group subsequently opened DR 321 against TC2, observing that the change made in TC2 would invalidate existing conforming C code that relied on the L'x'=='x' rule.
DR 321 is now closed and will be included in the CD3 to C99. This change defines a new standard macro, which WG14 drafted as follows:
__STDC_MB_MIGHT_NEQ_WC__: The integer constant 1, intended to indicate that there might be some character x in the basic character set, such that 'x' need not be equal to L'x'.
WG14 requests that WG21 adopt this revision and this macro in C++0x.
Proposed resolution (July, 2009):
Add the following to 16.8 [cpp.predefined] paragraph 2:
- __STDC_MB_MIGHT_NEQ_WC__
- The integer constant 1, intended to indicate that, in the encoding for wchar_t, a member of the basic character set need not have a code value equal to its value when used as the lone character in an ordinary character literal.
[Voted into WP at March, 2010 meeting.]
According to 2.3 [lex.charset] paragraph 3,
The values of the members of the execution character sets are implementation-defined, and any additional members are locale-specific.
This makes it sound as if the locale determines only whether an extended character (one not in the basic execution character set) exists, not its value (which is just implementation-defined, not locale-specific). The description should be clarified to indicate that the value of a given character can vary between locales, as well.
Proposed resolution (February, 2010):
Change 2.3 [lex.charset] paragraph 3 as follows:
...The execution character set and the execution wide-character set are implementation-defined supersets of the basic execution character set and the basic execution wide-character set, respectively. The values of the members of the execution character sets and the sets of additional members are implementation-defined, and any additional members are locale-specific.
[Voted into WP at March, 2010 meeting as document N3077.]
Trigraphs are a complicated solution to an old problem, that cause more problems than they solve in the modern environment. Unexpected trigraphs in string literals and occasionally in comments can be very confusing for the non-expert. They should be deprecated.
Notes from the March, 2009 meeting:
IBM, at least, uses trigraphs in its header files in conditional compilation directives to select character-set dependent content in a character-set independent fashion and would thus be negatively affected by the removal of trigraphs. One possibility that was discussed was to avoid expanding trigraphs inside character string literals, which is the context that causes most surprise and confusion, but still to support them in the rest of the program text. Specifying that approach, however, would be challenging because trigraphs are replaced in phase 1, before character strings are recognized in phase 3. See also the similar discussion of universal-character-names in issue 787.
The consensus of the CWG was that trigraphs should be deprecated.
Proposed resolution (September, 2009):
See paper PL22.16/09-0168 = WG21 N2978.
Notes from the October, 2009 meeting:
The CWG is interested in exploring other alternatives that address the particular problem of trigraphs in raw strings but that do not require the grammar changes of the approach in N2978. One possibility might be to recognize raw strings in some way in translation phase 1.
Notes from the March, 2010 meeting:
The CWG decided not to deprecate trigraphs, acknowledging that there are communities in which they are viewed as necessary. Instead, it was decided to address what was considered to be the most pressing issue regarding trigraphs, that is, recognizing trigraph sequences inside raw string literals.
[Voted into WP at October, 2009 meeting.]
2.10 [lex.ppnumber] paragraph 2 says,
A preprocessing number does not have a type or a value; it acquires both after a successful conversion (as part of translation phase 7, 2.2 [lex.phases]) to an integral literal token or a floating literal token.
However, preprocessing directives are executed in phase 4, and the evaluation of constant-expressions in #if directives requires that preprocessing numbers have values.
Proposed resolution (July, 2009):
Change 2.10 [lex.ppnumber] paragraph 2 as follows:
A preprocessing number does not have a type or a value; it acquires both after a successful conversion (as part of translation phase 7 (2.2 [lex.phases])) to an integral literal token or a floating literal token.
[Voted into WP at October, 2009 meeting.]
According to 2.14.3 [lex.ccon] paragraph 2,
A character literal that begins with the letter L, such as L'x', is a wide-character literal. A wide-character literal has type wchar_t. The value of a wide-character literal containing a single c-char has value equal to the numerical value of the encoding of the c-char in the execution wide-character set.
A c-char that is a universal character name might, when translated to the execution character set, result in a multi-character sequence that is larger than can be represented in a wchar_t. There is wording that prevents this in char16_t literals, but not for wchar_t literals. This seems undesirable.
Proposed resolution (July, 2009):
Change 2.14.3 [lex.ccon] paragraph 2 as follows:
...The value of a wide-character literal containing a single c-char has value equal to the numerical value of the encoding of the c-char in the execution wide-character set, unless the c-char has no representation in the execution wide-character set, in which case the value is implementation-defined. [Note: The type wchar_t is able to represent all members of the execution wide-character set, see 3.9.1 [basic.fundamental]. —end note]. The value of a wide-character literal containing multiple c-chars is implementation-defined.
Change 2.14.3 [lex.ccon] paragraph 5 as follows:
A universal-character-name is translated to the encoding, in the appropriate execution character set, of the character named...
[Voted into WP at October, 2009 meeting.]
The description of concatenation of string literals in 2.14.5 [lex.string] paragraph 11 does not mention raw strings explicitly, so it is not clear whether, and if so, how, they combine with non-raw strings.
Notes from the March, 2009 meeting:
A raw string should be considered equivalent to the corresponding non-raw string in string literal concatenation.
Proposed resolution (September, 2009):
In 2.14.5 [lex.string], replace the definition of string-literal with:
Change 2.14.5 [lex.string] paragraph 5 as follows:
A After translation phase 6, a string literal that does not begin with u8, u, U, or L an encoding-prefix is an ordinary string literal, and is initialized with the given characters.
Change 2.14.5 [lex.string] paragraph 12 as follows:
In translation phase 6 (2.2 [lex.phases]), adjacent string literals are concatenated. If both string literals have the same prefix encoding-prefix, the resulting concatenated string literal has that prefix encoding-prefix. If one string literal has no prefix encoding-prefix, it is treated as a string literal of the same prefix encoding-prefix as the other operand. If a UTF-8 string literal token is adjacent to a wide string literal token, the program is ill-formed. Any other concatenations are conditionally supported with implementation-defined behavior. [Note: This concatenation is an interpretation, not a conversion. Because the interpretation happens in translation phase 6 (after each character from each literal has been translated into a value from the appropriate character set), a string literal's initial rawness has no effect on the interpretation or well-formedness of the concatenation. —end note] [Example:...
(Note: this resolution also resolves issue 834.)
[Voted into WP at October, 2009 meeting.]
According to 2.14.5 [lex.string] paragraph 4,
A string literal that does not begin with u8, u, U, or L is an ordinary string literal, and is initialized with the given characters.
This is not as clear as it could be that a string like u8R"[xxx]" is not an ordinary string literal, because the string's prefix is not one of those listed (i.e., it's not obvious that possible substrings of the prefix are in view). This would be clearer if it simply said,
A string literal with no prefix or a prefix of R is an ordinary string literal.
Proposed resolution (September, 2009):
This issue is resolved by the resolution of issue 790.
[Voted into WP at March, 2010 meeting as document N3077.]
The specification of raw string literals interacts poorly with the specification of preprocessing tokens. The grammar in 2.5 [lex.pptoken] has a production reading
This is echoed in the max-munch rule in paragraph 3:
If the input stream has been parsed into preprocessing tokens up to a given character, the next preprocessing token is the longest sequence of characters that could constitute a preprocessing token, even if that would cause further lexical analysis to fail.
This raises questions about the handling of raw string literals. Consider, for instance,
#define R "x" const char* s = R"y";
The character sequence R"y" does not satisfy the syntactic requirements for a raw string. Should it be diagnosed as an ill-formed attempt at a raw string, or should it be well-formed, interpreting R as a preprocessor token that is a macro name and thus initializing s with a pointer to the string "xy"?
For another example, consider:
#define R "]" const char* x = R"foo[";
Presumably this means that the entire rest of the file must be scanned for the characters ]foo" and, if they are not found, macro-expand R and initialize x with a pointer to the string "]foo[". Is this the intended result?
Finally, does the requirement in 2.14.5 [lex.string] that
A d-char-sequence shall consist of at most 16 characters.
mean that
#define R "x" const char* y = R"12345678901234567[y]12345678901234567";
is ill-formed, or a valid initialization of y with a pointer to the string "x12345678901234567[y]12345678901234567"?
Additional note, June, 2009:
The translation of characters that are not in the basic source character set into universal-character-names in translation phase 1 raises an additional problem: each such character will occupy at least six of the 16 r-chars that are permitted. Thus, for example, R"@@@[]@@@" is ill-formed because @@@ becomes \u0040\u0040\u0040, which is 18 characters.
One possibility for addressing this might be to disallow the \ character completely as an d-char, which would have the effect of restricting r-chars to the basic source character set.
Proposed resolution (October, 2009):
Change the grammar in 2.14.5 [lex.string] as follows:
Change 2.14.5 [lex.string] paragraph 2 as follows:
A string literal that has an R in the prefix is a raw string literal. The d-char-sequence serves as a delimiter. The terminating d-char-sequence of a raw-string is the same sequence of characters as the initial d-char-sequence. A d-char-sequence shall consist of at most 16 characters. If the input stream contains a sequence of characters that could be the prefix and initial double quote of a raw string literal, such as R", those characters are considered to begin a raw string literal even if that literal is not well-formed. [Example:
#define R "x" const char* s = R"y"; // ill-formed raw string, not "x" "y"
—end example]
[Voted into WP at March, 2010 meeting.]
Since members of the basic source character set can be written inside a string using a universal character name, it is not clear whether a UCN that represents ']' or one of the characters in the terminating d-char-sequence should be interpreted as that character or as an attempt to “escape” that character and prevent its interpretation as part of the terminating sequence of a raw character string.
Notes from the July, 2009 meeting:
The CWG supported a resolution in which the d-char-sequence of a raw string literal is considered to be outside the literal and thus, by 2.3 [lex.charset] paragraph 2, could not contain a UCN designating a member of the basic source character set.
Proposed resolution (October, 2009):
Change 2.3 [lex.charset] paragraph 2 as follows:
Additionally, if the hexadecimal value for a universal-character-name outside the c-char-sequence, s-char-sequence, or r-char-sequence of a character or string literal corresponds to a control character (in either of the ranges 0x00-0x1F or 0x7F-0x9F, both inclusive) or to a character in the basic source character set, the program is ill-formed.
[Voted into WP at March, 2010 meeting.]
2.14.8 [lex.ext] paragraph 5 says,
If L is a user-defined-string-literal, let str be the literal without its ud-suffix and let len be the number of characters (or code points) in str (i.e., its length excluding the terminating null character).
The length of a null-terminated string is defined in 17.5.2.1.4.1 [byte.strings] as the number of bytes preceding the terminator, but a single code point in a UTF-8 string can require more than one byte, so this sentence is inconsistent and needs to be revised to make clear which definition is in view.
Proposed resolution (October, 2009):
Change 2.14.8 [lex.ext] paragraph 5 as follows:
If L is a user-defined-string-literal, let str be the literal without its ud-suffix and let len be the number of characters (or code points) code units in str (i.e., its length excluding the terminating null character)...
[Voted into WP at March, 2010 meeting as document N2993.]
There are a number of specifications in the Standard that should also apply to references. For example:
3 [basic] paragraphs 3-4 indicate that a reference cannot have a name because it is not an entity. (See also issue 485.)
3.4.1 [basic.lookup.unqual] paragraph 13 covers unqualified lookup in the initializer of a variable member of a namespace but not that of a reference member of a namespace. It would be very strange if the lookup in these two cases were different.
3.5 [basic.link] paragraph 8 prohibits use of a type without linkage as the type of a variable with linkage, but not as the type of a reference with linkage. (References with linkage are explicitly mentioned earlier in the section.)
3.7.1 [basic.stc.static] paragraph 3 permits local static variables but not local static references.
A number of other examples could be cited. A thorough review is needed to make sure that references are completely specified.
Notes from the September, 2008 meeting:
The CWG expressed interest in an approach that would define “variable” to include both objects and references and to use that for both this issue and issue 570.
Proposed resolution (October, 2009):
See paper PL22.16/09-0183 = WG21 N2993. This resolution also resolves issue 570.
[Voted into WP at October, 2009 meeting.]
When user-defined literals were added, a new form of operator function was created. Presumably many of the existing specifications that deal with operator-function-ids (the definition of name, for instance, in paragraph 4 of 3 [basic]) should also apply to literal-operator-ids.
Proposed resolution (June, 2009):
Change 3 [basic] paragraph 4 as follows:
A name is a use of an identifier (2.11 [lex.name]), operator-function-id (13.5 [over.oper]), literal-operator-id (13.5.8 [over.literal]), conversion-function-id (12.3.2 [class.conv.fct]), or template-id (14.2 [temp.names]) that denotes an entity or label (6.6.4 [stmt.goto], 6.1 [stmt.label]).
Change 5.1.1 [expr.prim.general] paragraph 3 as follows:
The operator :: followed by an identifier, a qualified-id, or an operator-function-id, or a literal-operator-id is a primary-expression. Its type is specified by the declaration of the identifier, qualified-id, or operator-function-id, or literal-operator-id. The result is the entity denoted by the identifier, qualified-id, or operator-function-id, or literal-operator-id. The result is an lvalue if the entity is a function or variable. The identifier, qualified-id, or operator-function-id, or literal-operator-id shall have global namespace scope or be visible in global scope because of a using-directive (7.3.4 [namespace.udir])...
Add the following production to the grammar for qualified-id in 5.1.1 [expr.prim.general] paragraph 7:
Add the following production to the grammar for template-id in 14.2 [temp.names] paragraph 1:
Change 14.2 [temp.names] paragraph 3 as follows:
After name lookup (3.4 [basic.lookup]) finds that a name is a template-name, or that an operator-function-id or a literal-operator-id refers to a set of overloaded functions any member of which is a function template...
Change 14.4 [temp.type] paragraph 1 bullet 1 as follows:
their template-names, or operator-function-ids, or literal-operator-ids refer to the same template, and
[Voted into WP at March, 2010 meeting.]
At least in the new wording for 5.1.2 [expr.prim.lambda] paragraph 10 as found in paper N2927, this is explicitly assumed to be an entity. It should be investigated whether this should be added to the list of entities found in 3 [basic] paragraph 3.
Proposed resolution (October, 2009):
Change 3 [basic] paragraph 3 as follows:
An entity is a value, object, variable, reference, function, enumerator, type, class member, template, template specialization, namespace, or parameter pack, or this.
Change 3.2 [basic.def.odr] paragraph 2 as follows:
...is immediately applied. this is used if it appears as a potentially-evaluated expression (including as the result of the implicit transformation in the body of a non-static member function (9.3.1 [class.mfct.non-static])). A virtual member function...
Delete 5.1.2 [expr.prim.lambda] paragraph 7:
For the purpose of describing the behavior of lambda-expressions below, this is considered to be “used” if replacing this by an invented variable v with automatic storage duration and the same type as this would result in v being used (3.2 [basic.def.odr]).
[Voted into WP at March, 2010 meeting as document N2993.]
3.2 [basic.def.odr] paragraph 1 says,
No translation unit shall contain more than one definition of any variable, function, class type, enumeration type or template.
This says nothing about references. Is it permitted to define a reference more than once in a single translation unit? (The list in paragraph 5 of things that can have definitions in multiple translation units does not include references.)
Notes from the September, 2008 meeting:
The CWG expressed interest in an approach that would define “variable” to include both objects and references and to use that for both this issue and issue 633.
Proposed resolution (October, 2009):
This issue is resolved by the resolution of issue 633.
[Voted into WP at October, 2009 meeting.]
Sections 3.3.3 [basic.scope.block] to 3.3.7 [basic.scope.class] define and summarize different kinds of scopes in a C++ program. However it is missing a description for the scope of template parameters. I believe a section is needed there — even though some information may be found in clause 14.
Proposed resolution (September, 2009):
Insert the following as a new paragraph following 3.3.2 [basic.scope.pdecl] paragraph 8:
The point of declaration of a template parameter is immediately after its complete template-parameter. [Example:typedef unsigned char T; template<class T = T // Lookup finds the typedef name of unsigned char. , T //Lookup finds the template parameter. N = 0> struct A {};—end example]
Delete 14.1 [temp.param] paragraph 14:
A template-parameter shall not be used in its own default argument.[Drafting note: This change conflicts with the resolution for issue 187 but is in accord with widespread implementation practice.]
Insert the following as a new section following 3.3.8 [basic.scope.enum]:
Template Parameter Scope [basic.scope.temp]
The declarative region of the name of a template parameter of a template template-parameter is the smallest template-parameter-list in which the name was introduced.
The declarative region of the name of a template parameter of a template is the smallest template-declaration in which the name was introduced. Only template parameter names belong to this declarative region; any other kind of name introduced by the declaration of a template-declaration is instead introduced into the same declarative region where it would be introduced as a result of a non-template declaration of the same name. [Example:
namespace N { template<class T> struct A{}; // line 2 template<class U> void f(U){} // line 3 struct B { template<class V>friend int g(struct C*); // line 5 }; }The declarative regions of T, U and V are the template-declarations on lines 2, 3 and 5, respectively. But the names A, f, g and C all belong to the same declarative region—namely, the namespace-body of N. (g is still considered to belong to this declarative region in spite of its being hidden during qualified and unqualified name lookup.) —end example]
The potential scope of a template parameter name begins at its point of declaration (3.3.2 [basic.scope.pdecl]) and ends at the end of its declarative region. [Note: this implies that a template-parameter can be used in the declaration of subsequent template-parameters and their default arguments but cannot be used in preceding template-parameters or their default arguments. For example,
template<class T, T* p, class U = T> class X { /* ... */ }; template<class T> void f(T* p = new T);This also implies that a template-parameter can be used in the specification of base classes. For example,
template<class T> class X : public Array<T> { /* ... */ }; template<class T> class Y : public T { /* ... */ };The use of a template parameter as a base class implies that a class used as a template argument must be defined and not just declared when the class template is instantiated. —end note]
The declarative region of the name of a template parameter is nested within the immediately-enclosing declarative region. [Note: as a result, a template-parameter hides any entity with the same name in an enclosing scope (3.3.10 [basic.scope.hiding]). [Example:
typedef int N; template<N X, typename N, template<N Y> class T> struct A;Here, X is a non-type template parameter of type int and Y is a non-type template parameter of the same type as the second template parameter of A. —end example] —end note]
[Note: because the name of a template parameter cannot be redeclared within its potential scope (14.6.1 [temp.local]), a template parameter's scope is often its potential scope. However, it is still possible for a template parameter name to be hidden; see 14.6.1 [temp.local]. —end note]
Delete 14.1 [temp.param] paragraph 13, including the example:
The scope of a template-parameter extends...
Delete 14.6.1 [temp.local] paragraph 6, including the note and example:
The scope of a template-parameter extends...
[Voted into WP at March, 2010 meeting.]
The Standard uses the terms “block scope” and “local scope” interchangeably, but the former is never formally defined. Would it be better to use only one term consistently? “Block scope” seems to be more frequently used.
Notes from the October, 2007 meeting:
The CWG expressed a preference for the term “local scope.”
Notes from the September, 2008 meeting:
Reevaluating the relative prevalence of the two terms (including the fact that new uses of “block scope” are being introduced, e.g., in both the lambda and thread-local wording) led to CWG reversing its previous preference for “local scope.” The resolution will need to add a definition of “block scope” and should change the title of 3.3.3 [basic.scope.block].
Proposed resolution (October, 2009):
Change 3.3.2 [basic.scope.pdecl] paragraph 2 as follows:
[Note: a nonlocal name from an outer scope remains visible up to the point of declaration of the local name that hides it. [Example:
const int i = 2; { int i[i]; }declares a local block-scope array of two integers. —end example] —end note]
Change the section heading of 3.3.3 [basic.scope.block] from “Local scope” to “Block scope.”
Change 3.3.3 [basic.scope.block] paragraph 1 as follows:
A name declared in a block (6.3 [stmt.block]) is local to that block; it has block scope. Its potential scope begins at its point of declaration (3.3.2 [basic.scope.pdecl]) and ends at the end of its declarative region block. A variable declared at block scope is a local variable.
Change 3.3.3 [basic.scope.block] paragraph 3 as follows:
The name in a catch exception-declaration declared in an exception-declaration is local to the handler handler and shall not be redeclared in the outermost block of the handler handler.
Change 3.3.10 [basic.scope.hiding] paragraph 3 as follows:
In a member function definition, the declaration of a local name at block scope hides the declaration of a member of the class with the same name...
Change 3.5 [basic.link] paragraph 8 as follows:
...Moreover, except as noted, a name declared in a local at block scope (3.3.3 [basic.scope.block]) has no linkage...
Change 3.6.3 [basic.start.term] paragraph 1 as follows:
...For an object of array or class type, all subobjects of that object are destroyed before any local block-scope object with static storage duration initialized during the construction of the subobjects is destroyed.
Change 3.6.3 [basic.start.term] paragraph 2 as follows:
If a function contains a local block-scope object of static or thread storage duration that has been destroyed and the function is called during the destruction of an object with static or thread storage duration, the program has undefined behavior if the flow of control passes through the definition of the previously destroyed local block-scope object. Likewise, the behavior is undefined if the function-local block-scope object is used indirectly (i.e., through a pointer) after its destruction.
Change 3.6.3 [basic.start.term] paragraph 3 as follows:
If the completion of the initialization of a non-local non-block-scope object with static storage duration is sequenced before a call to std::atexit (see <cstdlib>, 18.5 [support.start.term]), the call to the function passed to std::atexit is sequenced before the call to the destructor for the object. If a call to std::atexit is sequenced before the completion of the initialization of a non-local non-block-scope object with static storage duration, the call to the destructor...
[Editorial note: the occurrences of “non-local” in this change are removed by the proposed resolution for issue 946.]
Change 6.3 [stmt.block] paragraph 1 as follows:
...A compound statement defines a local block scope (3.3 [basic.scope])...
Change 6.4 [stmt.select] paragraph 1 as follows:
...The substatement in a selection-statement (each substatement, in the else form of the if statement) implicitly defines a local block scope (3.3 [basic.scope])...
Change 6.4 [stmt.select] paragraph 5 as follows:
If a condition can be syntactically resolved as either an expression or the declaration of a local block-scope name, it is interpreted as a declaration.
Change 6.5 [stmt.iter] paragraph 2 as follows:
The substatement in an iteration-statement implicitly defines a local block scope (3.3 [basic.scope]) which is entered and exited each time through the loop.
Change 6.7 [stmt.dcl] paragraph 3 as follows:
...A program that jumps84 from a point where a local variable with automatic storage duration...
Change 6.7 [stmt.dcl] paragraph 4 as follows:
The zero-initialization (8.5 [dcl.init]) of all local block-scope objects with static storage duration (3.7.1 [basic.stc.static]) or thread storage duration (3.7.2 [basic.stc.thread]) is performed before any other initialization takes place. Constant initialization (3.6.2 [basic.start.init]) of a local block-scope entity with static storage duration, if applicable, is performed before its block is first entered. An implementation is permitted to perform early initialization of other local block-scope objects...
Change 6.7 [stmt.dcl] paragraph 5 as follows:
The destructor for a local block-scope object with static or thread storage duration will be executed if and only if the variable was constructed. [Note: 3.6.3 [basic.start.term] describes the order in which local block-scope objects with static and thread storage duration are destroyed. —end note]
Change 8.4 [dcl.fct.def] paragraph 7 as follows:
In the function-body, a function-local predefined variable denotes a local block-scope object of static storage duration that is implicitly defined (see 3.3.3 [basic.scope.block]).
Change the example in 9.1 [class.name] paragraph 2 as follows:
... void g() { struct s; // hide global struct s // with a local block-scope declaration ...
Change the example in 9.1 [class.name] paragraph 3 as follows:
... void g(int s) { struct s* p = new struct s; // global s p->a = s; // local parameter s }
[Voted into WP at March, 2010 meeting.]
When 3.4.1 [basic.lookup.unqual] paragraph 10 says,
In a friend declaration naming a member function, a name used in the function declarator and not part of a template-argument in a template-id is first looked up in the scope of the member function's class. If it is not found, or if the name is part of a template-argument in a template-id, the look up is as described for unqualified names in the definition of the class granting friendship.
what does “in the scope of the member function's class” mean? Does it mean that only members of the class and its base classes are considered? Or does it mean that the same lookup is to be performed as if the name appeared in the member function's class? Implementations vary in this regard. For example:
struct s1; namespace ns { struct s1; } struct s2 { void f(s1 &); }; namespace ns { struct s3 { friend void s2::f(s1 &); }; }
Microsoft Visual C++ and Comeau C++ resolve s1 in the friend declaration to ns::s1 and issue an error, while g++ resolves it to ::s1 and accepts the code.
Notes from the April, 2005 meeting:
The phrase “looked up in the scope of [a] class” occurs frequently throughout the Standard and always refers to the member name lookup described in 10.2 [class.member.lookup]. This is the first interpretation mentioned above (“only members of the class and its base classes”), resolving s1 to ns::s1. A cross-reference to 10.2 [class.member.lookup] will be added to 3.4.1 [basic.lookup.unqual] paragraph 10 to make this clearer.
In discussing this question, the CWG noticed another problem: the text quoted above applies to all template-arguments appearing in the function declarator. The intention of this rule, however, is that only template-arguments in the declarator-id should ignore the member function's class scope; template-arguments used elsewhere in the function declarator should be treated like other names. For example:
template<typename T> struct S;
struct A {
typedef int T;
void foo(S<T>);
};
template <typename T> struct B {
friend void A::foo(S<T>); // i.e., S<A::T>
};
Proposed resolution (February, 2010):
Change 3.4.1 [basic.lookup.unqual] paragraph 10 as follows:
In a friend declaration naming a member function, a name used in the function declarator and not part of a template-argument in a template-id the declarator-id is first looked up in the scope of the member function's class (10.2 [class.member.lookup]). If it is not found, or if the name is part of a template-argument in a template-id the declarator-id, the look up is as described for unqualified names in the definition of the class granting friendship. [Example:
struct A { typedef int AT; void f1(AT); void f2(float); template<typename T> void f3(); }; struct B { typedef char AT; typedef float BT; friend void A::f1(AT); // parameter type is A::AT friend void A::f2(BT); // parameter type is B::BT friend void A::f3<AT>(); // template argument is B::AT };—end example]
[Voted into the WP at the March, 2009 meeting.]
The resolution of issue 33 added the following wording in 3.4.2 [basic.lookup.argdep]:
In addition, if the argument is the name or address of a set of overloaded functions and/or function templates, its associated classes and namespaces are the union of those associated with each of the members of the set: the namespace in which the function or function template is defined and the classes and namespaces associated with its (non-dependent) parameter types and return type.
This wording is self-contradictory: although it claims that the treatment of overload sets is intended to be “the union of those associated with each of the members of the set,” it says that the namespace of which each function or function template is a member is to be considered an associated namespace. That is different from the case of a non-overloaded function argument; in that case, because only the type of the argument is considered, the namespace of which the function is a member is not an associated namespace. This should be rectified so that overloaded and unoverloaded functions really are treated the same.
Proposed resolution (June, 2008):
Change 3.4.2 [basic.lookup.argdep] paragraph 2 as follows:
...In addition, if the argument is the name or address of a set of overloaded functions and/or function templates, its associated classes and namespaces are the union of those associated with each of the members of the set: the namespace in which the function or function template is defined and, i.e., the classes and namespaces associated with its (non-dependent) parameter types and return type.
[Voted into WP at October, 2009 meeting.]
During the discussion of issue 704, some people expressed a desire to reconsider whether parentheses around the name of the function in a function call should suppress argument-dependent lookup, on the basis that this is overly subtle and not obvious. Others pointed out that this technique is used (both intentionally and inadvertently) in existing code and changing the behavior could cause problems.
It was also observed that the normative text that specifies this behavior is itself subtle, relying an a very precise interpretation of the preposition used in 3.4.2 [basic.lookup.argdep] paragraph 1:
When an unqualified name is used as the postfix-expression in a function call...
This is taken to mean that something like (f)(x) is not subject to argument-dependent lookup because the name f is used in but not as the postfix-expression. This could be confusing, especially in light of the use of the term postfix-expression to refer to the name inside the parentheses, not to the parenthesized expression, in 13.3.1.1 [over.match.call] paragraph 1. If the decision is to preserve this effect of a parenthesized name in a function call, the wording should probably be revised to specify it more explicitly.
Notes from the September, 2008 meeting:
The CWG agreed that the suppression of argument-dependent lookup by parentheses surrounding the postfix-expression is widely known and used in the C++ community and must be preserved. The wording should be changed to make this effect clearer.
Proposed resolution (September, 2008):
Change 3.4.2 [basic.lookup.argdep] paragraph 1 as follows:
When an unqualified name is used as the postfix-expression in a function call (5.2.2 [expr.call] ) is an unqualified-id, other namespaces not considered during the usual unqualified lookup (3.4.1 [basic.lookup.unqual]) may be searched...
Proposed resolution (September, 2009):
Change 3.4.2 [basic.lookup.argdep] paragraph 1 as follows:
When an unqualified name is used as the postfix-expression in a function call (5.2.2 [expr.call]) is an unqualified-id, 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.3 [class.friend]) not otherwise visible may be found. These modifications to the search depend on the types of the arguments (and for template template arguments, the namespace of the template argument). [Example:
namespace N { struct S { }; void f(S); } void g() { N::S s; f(s); // calls N::f (f)(s); // error: N::f not considered; parentheses prevent argument-dependent lookup }
—end example]
[Voted into WP at March, 2010 meeting.]
The recent addition to support inherited constructors changed 3.4.3.1 [class.qual] paragraph 2 to say that
if the name specified after the nested-name-specifier is the same as the identifier or the simple-template-id's template-name in the last component of the nested-name-specifier,
the qualified-id is considered to name a constructor. This causes problems for a common naming scheme used in some class libraries:
struct A { typedef int type; }; struct B { typedef A type; }; B::type::type t;
This change causes this to name the A constructor instead of the A::type typedef.
Proposed resolution (February, 2010):
Change 3.4.3.1 [class.qual] paragraph 2 as follows:
In a lookup in which the constructor is an acceptable lookup result and the nested-name-specifier nominates a class C:
if the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C (Clause 9 [class]), or
in a using-declaration (7.3.3 [namespace.udecl]) that is a member-declaration, if the name specified after the nested-name-specifier is the same as the identifier or the simple-template-id's template-name in the last component of the nested-name-specifier,
the name is instead considered to name the constructor of class C...
[Voted into WP at March, 2010 meeting as part of document N3079.]
The algorithm for namespace-qualified lookup is given in 3.4.3.2 [namespace.qual] paragraph 2:
Given X::m (where X is a user-declared namespace), or given ::m (where X is the global namespace), let S be the set of all declarations of m in X and in the transitive closure of all namespaces nominated by using-directives in X and its used namespaces, except that using-directives that nominate non-inline namespaces (7.3.1 [namespace.def]) are ignored in any namespace, including X, directly containing one or more declarations of m.
Consider the following example:
namespace A { inline namespace B { namespace C { int i; } using namespace C; } int i; } int j = A::i; // ambiguous
The transitive closure includes B because it is inline, and it includes C because there is no declaration of i in B. As a result, A::i finds both the i declared in A and the one declared in C, and the lookup is ambiguous.
This result is apparently unintended.
Proposed resolution (November, 2009):
Change 7.3.1 [namespace.def] paragraph 9 as follows:
These properties are transitive: if a namespace N contains an inline namespace M, which in turn contains an inline namespace O, then the members of O can be used as though they were members of M or N. The transitive closure of all inline namespaces in N is the inline namespace set of N. The set of namespaces consisting of the innermost non-inline namespace enclosing an inline namespace O, together with any intervening inline namespaces, is the enclosing namespace set of O.
Insert a new paragraph before 3.4.3.2 [namespace.qual] paragraph 2 and change the existing paragraph 2 as follows:
For a namespace X and name m, the namespace-qualified lookup set S(X,m) is defined as follows: Let S'(X,m) be the set of all declarations of m in X and the inline namespace set of X (7.3.1 [namespace.def]). If S'(X,m) is not empty, S(X,m) is S'(X,m); otherwise, S(X,m) is the union of S(Ni,m) for all non-inline namespaces Ni nominated by using-directives in X and its inline namespace set.
Given X::m (where X is a user-declared namespace), or given ::m (where X is the global namespace), let S be the set of all declarations of m in X and in the transitive closure of all namespaces nominated by using-directives in X and its used namespaces, except that using-directives that nominate non-inline namespaces (7.3.1 [namespace.def]) are ignored in any namespace, including X, directly containing one or more declarations of m. No namespace is searched more than once in the lookup of a name. If if S(X,m) is the empty set, the program is ill-formed. Otherwise, if S(X,m) has exactly one member, or if the context of the reference is a using-declaration (7.3.3 [namespace.udecl]), S(X,m) is the required set of declarations of m. Otherwise if the use of m is not one that allows a unique declaration to be chosen from S(X,m), the program is ill-formed. [Example:...
[Voted into WP at October, 2009 meeting.]
The resolution of issue 389 makes code like
static struct { int i; int j; } X;
ill-formed. This breaks a lot of code for no apparent reason, since the name X is not known outside the translation unit in which it appears; there is therefore no danger of collision and no need to mangle its name.
There has also been recent discussion on the email reflectors as to whether the restrictions preventing use of types without linkage as template arguments is needed or not, with the suggestion that a mechanism like that used to give members of the unnamed namespace unique names could be used for unnamed and local types. See also issue 488, which would become moot if types without linkage could be used as template parameters.
Notes from the October, 2005 meeting:
The Evolution Working Group is discussing changes that would address this issue. CWG will defer consideration until the outcome of the EWG discussions is clear.
Notes from the April, 2006 meeting:
The CWG agreed that the restriction in 3.5 [basic.link] paragraph 8 on use of a type without linkage should apply only to variables and functions with external linkage, not to variables and functions with internal linkage (i.e., the example should be accepted). This is a separate issue from the question before the EWG and should be resolved independently.
Additional note (April, 2006):
Even the restriction of the rule to functions and objects with external linkage may not be exactly what we want. Consider an example like:
namespace { struct { int i; } s; }
The variable s has external linkage but can't be named outside its translation unit, so there's again no reason to prohibit use of a type without linkage in its declaration.
Notes from the June, 2008 meeting:
Paper N2657, adopted at the June, 2008 meeting, allows local and unnamed types to be used as template parameters. That resolution is narrowly focused, however, and does not address this issue.
Proposed resolution (June, 2009):
Change 3.5 [basic.link] paragraph 8 as follows:
...A type without linkage shall not be used as the type of a variable or function with external linkage, unless
the variable or function has extern "C" C language linkage (7.5 [dcl.link]), or
the variable or function is declared within an unnamed namespace (7.3.1 [namespace.def]), or
the variable or function is not used (3.2 [basic.def.odr]) or is defined in the same translation unit.
[Drafting note: the context shown for the preceding resolution assumes that the resolution for issue 757 has been applied.]
[Voted into the WP at the March, 2009 meeting.]
According to 3.5 [basic.link] paragraph 3,
A name having namespace scope (3.3.6 [basic.scope.namespace]) has internal linkage if it is the name of
an object, reference, function or function template that is explicitly declared static or,
an object or reference that is explicitly declared const and neither explicitly declared extern nor previously declared to have external linkage;
It is not possible to declare a reference to be const.
Proposed resolution (March, 2008):
Change 3.5 [basic.link] paragraph 3 as indicated (note addition of punctuation in the first bullet):
A name having namespace scope (3.3.6 [basic.scope.namespace]) has internal linkage if it is the name of
an object, reference, function, or function template that is explicitly declared static; or,
an object or reference that is explicitly declared const and neither explicitly declared extern nor previously declared to have external linkage; or
a data member of an anonymous union.
[Voted into WP at July, 2009 meeting.]
Paper N2657, adopted at the June, 2008 meeting, removed the prohibition of local and unnamed types as template arguments. As part of the change, 3.5 [basic.link] paragraph 8 was modified to read,
A type without linkage shall not be used as the type of a variable or function with linkage, unless
the variable or function has extern "C" linkage (7.5 [dcl.link]), or
the type without linkage was named using a dependent type (14.6.2.1 [temp.dep.type]).
Because a type without linkage can only be named as a dependent type, there are still some potentially useful things that cannot be done:
template <class T> struct A { friend void g(A, T); // this can't be defined later void h(T); // this cannot be explicitly specialized }; template <class T> void f(T) { A<T> at; g(at, (T)0); } enum { e }; void g(A<decltype(e)>, decltype(e)){} // not allowed int main() { f(e); }
These deficiencies could be addressed by allowing types without linkage to be used as the type of a variable or function, but with the requirement that any such entity that is used must also be defined in the same translation unit. This would allow issuing a compile-time, instead of a link-time, diagnostic if the definition were not provided, for example. It also seems to be easier to implement than the current rules.
Proposed resolution (March, 2009):
Change 3.5 [basic.link] paragraph 8 as follows:
...A type without linkage shall not be used as the type of a variable or function with linkage, unless
the variable or function has extern "C" linkage (7.5 [dcl.link]), or
the type without linkage was named using a dependent type (14.6.2.1 [temp.dep.type]) the variable or function is not used (3.2 [basic.def.odr]) or is defined in the same translation unit.
[Note: in other words, a type without linkage contains a class or enumeration that cannot be named outside its translation unit. An entity with external linkage declared using such a type could not correspond to any other entity in another translation unit of the program and thus is not permitted must be defined in the translation unit if it is used. Also note that classes with linkage may contain members whose types do not have linkage, and that typedef names are ignored in the determination of whether a type has linkage. —end note] [Example:
void f() { struct A { int x; }; // no linkage extern A a; // ill-formed typedef A B; extern B b; // ill-formed }
—end example]
[Example:
template <class T> struct A { // in A<X>, the following is allowed because the type with no linkage // X is named using template parameter T. friend void f(A, T){} }; template <class T> void g(T t) { A<T> at; f(at, t); } int main() { class X {} x; g(x); }
template <typename T> struct B { void g(T){} void h(T); friend void i(B, T){} }; void f() { struct A { int x; }; // no linkage A a = {1}; B<A> ba; // declares B<A>::g(A) and B<A>::h(A) ba.g(a); // OK ba.h(a); // error: B<A>::h(A) not defined in the translation unit i(ba, a); // OK }
—end example]
[Drafting note: issue 527 also changes part of the same text.]
[Voted into WP at March, 2010 meeting.]
The recent changes to allow use of unnamed types as template arguments require some rethinking of how unnamed types are treated in general. At least, a class-scope unnamed type should have the same linkage as its containing class. For example:
// File "hdr.h" struct S { static enum { No, Yes } locked; }; template<class T> void f(T); // File "impl1.c" #include "hdr.h" template void f(decltype(S::locked)); // File "impl2.c" #include "hdr.h" template void f(decltype(S::locked));
The two explicit instantiation directives should refer to the same specialization.
Proposed resolution (February, 2010):
Change 3.5 [basic.link] paragraph 8 as follows:
Names not covered by these rules have no linkage. Moreover, except as noted, a name declared in a local scope (3.3.3 [basic.scope.block]) has no linkage. A type is said to have linkage if and only if:
it is a class or enumeration type that is named (or has a name for linkage purposes (7.1.3 [dcl.typedef])) and the name has linkage; or
it is an unnamed class or enumeration member of a class with linkage; or
...
[Voted into WP at October, 2009 meeting.]
3.6.1 [basic.start.main] paragraph 4 discusses the effects of calling std::exit but says nothing about std::quick_exit.
Proposed resolution (July, 2009):
Change 3.6.1 [basic.start.main] paragraph 4 as follows:
It should be stated in 3.6.1 [basic.start.main] that it a program that defines main as deleted is ill-formed.
Proposed resolution (July, 2009):
Change 3.6.1 [basic.start.main] paragraph 3 as follows:
...A program that declares main to be inline, static, or constexpr, or that defines main as deleted, is ill-formed...
[Voted into WP at October, 2009 meeting.]
According to 3.6.3 [basic.start.term] paragraph 1,
Destructors (12.4 [class.dtor]) for initialized objects with static storage duration are called as a result of returning from main and as a result of calling std::exit (18.5 [support.start.term]).
It is unclear, in the presence of delegating constructors, exactly what an “initialized object” is. 3.8 [basic.life] paragraph 1 says that the lifetime of an object does not begin until it is completely initialized, i.e., when its principal constructor finishes execution. 15.2 [except.ctor] paragraph 2 says that an exception during the construction of class object only invokes destructors for fully-constructed base and member sub-objects (those for which the principal constructor has completed). On the other hand, the destructor for a complete class object is called if its non-delegating constructor has completed, even if the principal constructor has not yet finished. Which of these models is appropriate for the behavior of std::exit?
Notes from the March, 2009 meeting:
The CWG agreed that the destructor for a complete object should be called by std::exit if its non-delegating constructor has finished, just as for an exception.
Notes from the July, 2009 meeting:
The CWG decided that the direction adopted at the March, 2009 meeting was incorrect. Instead, the model should be the way completely-constructed base and member subobjects are handled: their destructors are called when an exception is thrown but not when std::exit is called.
Proposed resolution (July, 2009):
Change 3.6.3 [basic.start.term] paragraph 1 as follows:
Destructors (12.4 [class.dtor]) for initialized objects (that is, objects whose lifetime (3.8 [basic.life]) has begun) with static storage duration are called as a result of returning from main and as a result of calling std::exit (18.5 [support.start.term]). Destructors for initialized objects with thread storage duration...
[Voted into WP at March, 2010 meeting.]
18.5 [support.start.term] paragraph 7 says that the order of destruction of objects with static storage duration and calls to functions registered by calling std::atexit is given in 3.6.3 [basic.start.term]. Paragraph 1 of 3.6.3 [basic.start.term] says,
If the completion of the constructor or dynamic initialization of an object with static storage duration is sequenced before that of another, the completion of the destructor of the second is sequenced before the initiation of the destructor of the first.
This wording covers both local and namespace-scope objects, so it fixes the relative ordering of local object destructors with respect to those of namespace scope. Paragraph 3 says,
If the completion of the initialization of a non-local object with static storage duration is sequenced before a call to std::atexit (see <cstdlib>, 18.5 [support.start.term]), the call to the function passed to std::atexit is sequenced before the call to the destructor for the object. If a call to std::atexit is sequenced before the completion of the initialization of a non-local object with static storage duration, the call to the destructor for the object is sequenced before the call to the function passed to std::atexit.
This fixes the relative ordering of destructors for namespace scope objects with respect to calls of atexit functions. However, the relative ordering of local destructors and atexit functions is left unspecified.
In the 2003 Standard, this was clear: 18.3 paragraph 8 said,
A local static object obj3 is destroyed at the same time it would be if a function calling the obj3 destructor were registered with atexit at the completion of the obj3 constructor.
Proposed resolution (October, 2009):
Change 3.6.3 [basic.start.term] paragraph 3 as follows:
If the completion of the initialization of a non-local an object with static storage duration is sequenced before a call to std::atexit (see <cstdlib>, 18.5 [support.start.term]), the call to the function passed to std::atexit is sequenced before the call to the destructor for the object. If a call to std::atexit is sequenced before the completion of the initialization of a non-local an object with static storage duration, the call to the destructor for the object is sequenced before the call to the function passed to std::atexit...
[Voted into WP at October, 2009 meeting.]
The bullets in 3.7.4.3 [basic.stc.dynamic.safety] paragraph 2 do not appear to cover the following example:
int& i = *new int(5); // do something with i delete &i;
Should &i be a safely-derived pointer value?
Proposed resolution (September, 2009):
Change 3.7.4.3 [basic.stc.dynamic.safety] paragraph 2, bullet 2, as follows:
[Voted into WP at March, 2010 meeting.]
According to 20.7.4 [util.dynamic.safety] paragraph 16, when std::get_pointer_safety() returns std::pointer_safety::relaxed,
pointers that are not safely derived will be treated the same as pointers that are safely derived for the duration of the program.
However, 3.7.4.3 [basic.stc.dynamic.safety] paragraph 4 says unconditionally that
If a pointer value that is not a safely-derived pointer value is dereferenced or deallocated, and the referenced complete object is of dynamic storage duration and has not previously been declared reachable (20.7.4 [util.dynamic.safety]), the behavior is undefined.
This is a contradiction: the library clause attempts to constrain undefined behavior, which by definition is unconstrained.
Proposed resolution (July, 2009):
Change 3.7.4.3 [basic.stc.dynamic.safety] paragraph 4 as follows to define the terms “strict pointer safety” and “relaxed pointer safety,” which could then be used by the library clauses to achieve the desired effect:
An implementation may have relaxed pointer safety, in which case the validity of a pointer value does not depend on whether it is a safely-derived pointer value or not. Alternatively, an implementation may have strict pointer safety, in which case if If a pointer value that is not a safely-derived pointer value is dereferenced or deallocated, and the referenced complete object is of dynamic storage duration and has not previously been declared reachable (20.7.4 [util.dynamic.safety]), the behavior is undefined. [Note: this is true even if the unsafely-derived pointer value might compare equal to some safely-derived pointer value. —end note] It is implementation-defined whether an implementation has relaxed or strict pointer safety.
[Voted into WP at March, 2010 meeting.]
Read literally, 3.8 [basic.life] paragraphs 1 and 5 would make any access to non-static members of a class from the class's destructor undefined behavior. This is clearly not the intent.
Proposed resolution (October, 2009):
Change 3.8 [basic.life] paragraphs 5-6 as follows:
...any pointer that refers to the storage location where the object will be or was located may be used but only in limited ways. Such For an object under construction or destruction, see 12.7 [class.cdtor]. Otherwise, such a pointer refers to allocated storage...
...any lvalue which refers to the original object may be used but only in limited ways. Such For an object under construction or destruction, see 12.7 [class.cdtor]. Otherwise, such an lvalue refers to allocated storage...
[Voted into WP at October, 2009 meeting.]
The std::memcpy library function is singled out for special treatment in 3.9 [basic.types] paragraph 3:
For any trivially copyable type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base-class subobject, if the value of obj1 is copied into obj2, using the std::memcpy library function, obj2 shall subsequently hold the same value as obj1.
This specification should not be restricted to std::memcpy but should apply to any bytewise copying, including std::memmove (as is done in the footnote in the preceding paragraph, for example).
Proposed resolution (July, 2009):
Change 3.9 [basic.types] paragraph 3 as follows:
For any trivially copyable type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base-class subobject, if the value of underlying bytes (1.7 [intro.memory]) making up obj1 is are copied into obj2, using the std::memcpy library function [Footnote: By using, for example, the library functions (17.6.1.2 [headers]) std::memcpy or std::memmove. —end footnote], obj2 shall subsequently hold the same value as obj1. [Example:...
[Voted into WP at March, 2010 meeting as document N3055.]
The status of rvalue references to functions is not clear in the current wording. For example, 3.10 [basic.lval] paragraph 2 says,
An lvalue refers to an object or function. Some rvalue expressions—those of (possibly cv-qualified) class or array type—also refer to objects. [Footnote: Expressions such as invocations of constructors and of functions that return a class type refer to objects, and the implementation can invoke a member function upon such objects, but the expressions are not lvalues. —end footnote]
This would tend to indicate that there are no rvalues of function type. However, 5 [expr] paragraph 6 says,
If an expression initially has the type “rvalue reference to T” (8.3.2 [dcl.ref], 8.5.3 [dcl.init.ref]), the type is adjusted to “T” prior to any further analysis, and the expression designates the object or function denoted by the rvalue reference. If the expression is the result of calling a function, whether implicitly or explicitly, it is an rvalue; otherwise, it is an lvalue.
This explicitly indicates that rvalue references to functions are possible and that, in some cases, they yield function-typed rvalues. Furthermore, _N2914_.20.2.4 [concept.operator] paragraph 20 describes the concept Callable as:
auto concept Callable<typename F, typename... Args> { typename result_type; result_type operator()(F&, Args...); result_type operator()(F&&, Args...); }
It would be strange if Callable were satisfied for a function object type but not for a function type.
However, assuming that rvalue references to functions are
intended to be supported, it is not clear how an rvalue of function
type is supposed to behave. For instance,
For an ordinary function call, the postfix expression shall be either an lvalue that refers to a function (in which case the function-to-pointer standard conversion (4.3 [conv.func]) is suppressed on the postfix expression), or it shall have pointer to function type.
From this, it appears that an rvalue of function type cannot be used in a function call. It can't be converted to a pointer to function, either, as 4.3 [conv.func] paragraph 1 says,
An lvalue of function type T can be converted to an rvalue of type “pointer to T.” The result is a pointer to the function.
(See also issues 664 and especially 690. The approach described in the latter issue, viewing rvalue references as essentially lvalues rather than as essentially rvalues, could resolve the specification problems described above by eliminating the concept of an rvalue of function type.)
Proposed resolution (February, 2010):
See paper N3030.
[Voted into WP at October, 2009 meeting.]
The deprecated conversion from string literal to pointer to (non-const) character in 4.2 [conv.array] paragraph 2 has been extended to apply to char16_t and char32_t types, but not to UTF8 and raw string literals. Is this disparity intentional? Should it be extended to all new string types, reverted to just the original character types, or revoked altogether?
Additional places in the Standard that may need to change include 15.1 [except.throw] paragraph 3 and 13.3.3.2 [over.ics.rank] paragraph 3.
Additional discussion (August, 2008):
The removal of this conversion for current string literals would affect overload resolution for existing programs. For example,
struct S { S(const char*); }; int f(char *); int f(X); int i = f("hello");
If the conversion were removed, the result would be a quiet change in behavior. Another alternative to consider would be a required diagnostic (without making the program ill-formed).
Notes from the September, 2008 meeting:
The CWG agreed that the deprecated conversion should continue to apply to the literals to which it applied in C++ 2003. Consensus was not reached regarding whether it should apply only to those literals or to all the new literals as well, although it was agreed that the current situation in which it applies to some, but not all, of the new literals is unacceptable.
Notes from the July, 2009 meeting:
The CWG reached consensus that the deprecated conversion should be removed altogether.
Proposed resolution (September, 2009):
Remove 4.2 [conv.array] paragraph 2:
A string literal (2.14.5 [lex.string]) with no prefix, with a u prefix, with a U prefix, or with an L prefix can be converted to an rvalue of type “pointer to char”, “pointer to char16_t”, “pointer to char32_t”, or “pointer to wchar_t”, respectively. In any case, the result is a pointer to the first element of the array. This conversion is considered only when there is an explicit appropriate pointer target type, and not when there is a general need to convert from an lvalue to an rvalue. [Note: this conversion is deprecated. See Annex D [depr]. —end note] For the purpose of ranking in overload resolution (13.3.3.1.1 [over.ics.scs]), this conversion is considered an array-to-pointer conversion followed by a qualification conversion (4.4 [conv.qual]). [Example: "abc" is converted to “pointer to const char” as an array-to-pointer conversion, and then to “pointer to char” as a qualification conversion. —end example]
Delete the indicated text from the third sub-bullet of the first bullet of paragraph 3 of 13.3.3.2 [over.ics.rank]:
S1 and S2 differ only in their qualification conversion and yield similar types T1 and T2 (4.4 [conv.qual]), respectively, and the cv-qualification signature of type T1 is a proper subset of the cv-qualification signature of type T2, and S1 is not the deprecated string literal array-to-pointer conversion (4.2). [Example: ...
Delete the note from 15.1 [except.throw] paragraph 3 as follows:
A throw-expression initializes a temporary object, called the exception object, the type of which is determined by removing any top-level cv-qualifiers from the static type of the operand of throw and adjusting the type from “array of T” or “function returning T” to “pointer to T” or “pointer to function returning T”, respectively. [Note: the temporary object created for a throw-expression that is a string literal is never of type char*, char16_t*, char32_t*, or wchar_t*; that is, the special conversions for string literals from the types “array of const char”, “array of const char16_t”, “array of const char32_t”, and “array of const wchar_t” to the types “pointer to char”, “pointer to char16_t”, “pointer to char32_t”, and “pointer to wchar_t”, respectively (4.2 [conv.array]), are never applied to a throw-expression. —end note] The temporary is an lvalue...
Change the discussion of 2.14.5 [lex.string] in C.1.1 [diff.lex] as follows:
Change: String literals made const
The type of a string literal is changed... “array of const wchar_t.”char* p = "abc"; // valid in C, invalid in C++
...
Difficulty of converting: Simple syntactic transformation, because string literals can be converted to char*; (4.2 [conv.array]). The most common cases are handled by a new but deprecated standard conversion Syntactic transformation. The fix is to add a cast:
char* p = "abc"; // valid in C, deprecated in C++ char* q = expr ? "abc" : "de"; // valid in C, invalid in C++ void f(char*) { char* p = (char*)"abc"; // cast added f(p); f((char*)"def"); // cast added }
Delete _N3000_.D.4 [depr.string]:
D.4 Implicit conversion from const strings [depr.string]
The implicit conversion from const to non-const qualification for string literals (4.2 [conv.array]) is deprecated.
[Voted into WP at July, 2009 meeting.]
According to 4.5 [conv.prom] paragraph 2,
An rvalue of an unscoped enumeration type (7.2 [dcl.enum]) can be converted to an rvalue of the first of the following types that can represent all the values of the enumeration (i.e. the values in the range bmin to bmax as described in 7.2 [dcl.enum]): int, unsigned int, long int, unsigned long int, long long int, or unsigned long long int.
This wording may have surprising behavior in this case:
enum E: long { e }; void f(int); void f(long); void g() { f(e); // Which f is called? }
Intuitively, as the programmer has explicitly expressed preference for long as the underlying type, he/she might expect f(long) to be called. However, if long and int happen to have the same size, then e is promoted to int (as it is the first type in the list that can represent all values of E) and f(int) is called instead.
According to 7.2 [dcl.enum] the underlying type of an enumeration is always well-defined for both the fixed and the non-fixed cases, so it makes sense simply to promote to the underlying type unless such a type would itself require promotion.
Suggested resolution:
In 4.5 [conv.prom] paragraph 2, replace all the text from “An rvalue of an unscoped enumeration type” through the end of the paragraph with the following:
An rvalue of an unscoped enumeration type (7.2 [dcl.enum]) is converted to an rvalue of its underlying type if it is different from char16_t, char32_t, wchar_t, or has integer conversion rank greater than or equal to int. Otherwise, it is converted to an rvalue of the first of the following types that can represent all the values of its underlying type: int, unsigned int, long int, unsigned long int, long long int, or unsigned long long int.
(Note that this wording no longer needs to mention extended integer types as special cases.)
Proposed resolution (August, 2008):
Move the following text from 4.5 [conv.prom] paragraph 2 into a separate paragraph, making the indicated changes, and add the following new paragraph after it:
An rvalue of an unscoped enumeration type whose underlying type is not fixed (7.2 [dcl.enum]) can be converted to an rvalue of the first of the following types that can represent all the values of the enumeration (i.e. the values in the range bmin to bmax as described in 7.2 [dcl.enum]): int, unsigned int, long int, unsigned long int, long long int, or unsigned long long int. If none of the types in that list can represent all the values of the enumeration, an rvalue of an unscoped enumeration type can be converted to an rvalue of the extended integer type with lowest integer conversion rank (4.13 [conv.rank]) greater than the rank of long long in which all the values of the enumeration can be represented. If there are two such extended types, the signed one is chosen.
An rvalue of an unscoped enumeration type whose underlying type is fixed (7.2 [dcl.enum]) can be converted to an rvalue of its underlying type. Moreover, if integral promotion can be applied to its underlying type, an rvalue of an unscoped enumeration type whose underlying type is fixed can also be converted to an rvalue of the promoted underlying type.
[Voted into WP at July, 2009 meeting.]
The current wording of 4.9 [conv.fpint] paragraph 2 does not specify what should happen when converting an integer value that is outside the representable range of the target floating point type. The C99 Standard covers this case explicitly in 6.3.1.4 paragraph 2:
When a value of integer type is converted to a real floating type, if the value being converted can be represented exactly in the new type, it is unchanged. If the value being converted is in the range of values that can be represented but cannot be represented exactly, the result is either the nearest higher or nearest lower representable value, chosen in an implementation-defined manner. If the value being converted is outside the range of values that can be represented, the behavior is undefined.
While the current C++ specification requires defined behavior in all cases, the C specification allows for use of NaNs and traps, if those are needed for efficiency.
Notes from the September, 2008 meeting:
The CWG agreed that the C approach should be adopted.
Proposed resolution (March, 2009):
Change 4.9 [conv.fpint] paragraph 2 as indicated:
An rvalue of an integer type or of an unscoped enumeration type can be converted to an rvalue of a floating point type. The result is exact if possible. Otherwise If the value being converted is in the range of values that can be represented but cannot be represented exactly, it is an implementation-defined choice of either the next lower or higher representable value. [Note: loss of precision occurs if the integral value cannot be represented exactly as a value of the floating type. —end note] If the value being converted is outside the range of values that can be represented, the behavior is undefined. If the source type is bool, the value false is converted to zero and the value true is converted to one.
Lisa Lippincott mentioned this case to me:
A[0] = 0; A[A[0]] = 1;
This seems to use the old value of A[0] other than to calculate the new value, which is said to be undefined, but it also seems reasonable, since the old value is used in order to select the object to modify, so there's no ordering ambiguity.
Steve Adamczyk: the ordering rule referred to is in 5 [expr] paragraph 4.
Notes from the March 2004 meeting:
Clark Nelson mentions that the C committee may have done something on this.
Note (July, 2009):
This issue was resolved by the adoption of the “sequenced before” wording.
[Voted into WP at October, 2009 meeting.]
Evaluating an expression like 1/0 is intended to produce undefined behavior during the execution of a program but be ill-formed at compile time. The wording for this is in 5 [expr] paragraph 4:
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 appears where an integral constant expression is required (5.19 [expr.const]), in which case the program is ill-formed.
The formulation “appears where an integral constant expression is required” is intended as an acceptable Standardese circumlocution for “evaluated at compile time,” a concept that is not directly defined by the Standard. It is not clear that this formulation adequately covers constexpr functions.
Notes from the September, 2008 meeting:
The CWG felt that the concept of “compile-time evaluation” needs to be defined for use in discussing constexpr functions. There is a tension between wanting to diagnose errors at compile time versus not diagnosing errors that will not actually occur at runtime. In this context, a constexpr function might never be invoked, either in a constant expression context or at runtime, although the requirement that the expression in a constexpr function be a potential constant expression could be interpreted as triggering the provisions of 5 [expr] paragraph 4.
There are also contexts in which it is not known in advance whether an expression must be constant or not, notably in the initializer of a const integer variable, where the nature of the initializer determines whether the variable can be used in constant expressions or not. In such a case, it is not clear whether an erroneous expression should be considered ill-formed or simply non-constant (and thus subject to runtime undefined behavior, if it is ever evaluated). The consensus of the CWG was that an expression like 1/0 should simply be considered non-constant; any diagnostic would result from the use of the expression in a context requiring a constant expression.
Proposed resolution (July, 2009):
This issue is resolved by the resolution of issue 699.
[Voted into WP at October, 2009 meeting.]
A number of the operators described in clause 5 [expr] take operands of enumeration type, relying on the “usual arithmetic conversions” (5 [expr] paragraph 10) to convert them to an appropriate integral type. The assumption behind this pattern is invalid when one or more of the operands has a scoped enumeration type.
Each operator that accepts operands of enumeration type should be evaluated as to whether the operation makes sense for scoped enumerations (for example, it is probably a good idea to allow comparison of operands having the same scoped enumeration type and conditional expressions in which the second and third operands have the same scoped enumeration type) and, if so, create a special case. The usual arithmetic conversions should not be invoked for scoped enumeration types.
(See also issue 880.)
Proposed resolution (July, 2009):
Change 5 [expr] paragraph 10 as follows:
...This pattern is called the usual arithmetic conversions, which are defined as follows:
If either operand is of scoped enumeration type (7.2 [dcl.enum]), no conversions are performed, and if the other operand does not have the same type, the expression is ill-formed.
If either operand is of type long double...
Change 5.2.1 [expr.sub] paragraph 1 as follows:
...One of the expressions shall have the type “pointer to T” and the other shall have unscoped enumeration or integral type...
Change 5.3 [expr.unary] paragraphs 7-8 and 10 as follows:
The operand of the unary + operator shall have arithmetic, unscoped enumeration, or pointer type...
The operand of the unary - operator shall have arithmetic or unscoped enumeration type...
The operand of ~ shall have integral or unscoped enumeration type...
Change 5.3.4 [expr.new] paragraph 6 as follows:
...The expression in a noptr-new-declarator shall be of integral type, unscoped enumeration type, or a class type for which a single non-explicit conversion function to integral or unscoped enumeration type exists (12.3 [class.conv]). If the expression...
Change 5.6 [expr.mul] paragraph 2 as follows:
The operands of * and / shall have arithmetic or unscoped enumeration type; the operands of % shall have integral or unscoped enumeration type....
Change 5.7 [expr.add] paragraph 1-2 as follows:
...For addition, either both operands shall have arithmetic or unscoped enumeration type, or one operand shall be a pointer to a completely-defined effective object type and the other shall have integral or unscoped enumeration type.
For subtraction, one of the following shall hold:
both operands have arithmetic or unscoped enumeration type; or
both operands are pointers to cv-qualified or cv-unqualified versions of the same completely-defined effective object type; or
the left operand is a pointer to a completely-defined effective object type and the right operand has integral or unscoped enumeration type.
Change 5.8 [expr.shift] paragraph 1 as follows:
...The operands shall be of integral or unscoped enumeration type...
Change 5.9 [expr.rel] paragraph 4 as follows:
If both operands (after conversions) are of arithmetic or enumeration type, each of the operators shall yield true if the specified relationship is true and false if it is false.
Change 5.11 [expr.bit.and] paragraph 1 as follows:
...The operator applies only to integral or unscoped enumeration operands.
Change 5.12 [expr.xor] paragraph 1 as follows:
...The operator applies only to integral or unscoped enumeration operands.
Change 5.13 [expr.or] paragraph 1 as follows:
...The operator applies only to integral or unscoped enumeration operands.
[Voted into WP at March, 2010 meeting as part of document N3055.]
The adoption of paper N2844 made it ill-formed to attempt to bind an rvalue reference to an lvalue, but the example in 5 [expr] paragraph 6 was overlooked in making this change:
struct A { }; A&& operator+(A, A); A&& f(); A a; A&& ar = a;
The last line should be changed to use something like static_cast<A&&>(a).
(See also issue 847.)
Proposed resolution (July, 2009):
Change the example in 5 [expr] paragraph 6 as follows:
[Example:
struct A { }; A&& operator+(A, A); A&& f(); A a; A&& ar = static_cast<A&&>(a);The expressions f() and a + a are rvalues of type A. The expression ar is an lvalue of type A. —end example]
[Voted into WP at March, 2010 meeting as document N3049.]
The grammar for nested-name-specifier in 5.1.1 [expr.prim.general] paragraph 7 does not allow decltype to be used in a qualified-id. This could be useful for cases like:
auto vec = get_vec(); decltype(vec)::value_type v = vec.first();(See also issue 950.)
Proposed resolution (September, 2009):
See paper PL22.16/09-0181 = WG21 N2991.
Proposed resolution (February, 2010):
See paper PL22.16/10-0021 = WG21 N3031.
[Voted into WP at March, 2010 meeting.]
this is a keyword and thus not subject to ordinary name lookup. That makes the interpretation of examples like the following somewhat unclear:
struct outer { void f() { struct inner { int a[sizeof(*this)]; // #1 }; } };
According to 5.1.1 [expr.prim.general] paragraph 3,
The keyword this shall be used only inside a non-static class member function body (9.3 [class.mfct]) or in a brace-or-equal-initializer for a non-static data member.
Should the use of this at #1 be interepreted as a well-formed reference to outer::f()'s this or as an ill-formed attempt to refer to a this for outer::inner?
One possible interpretation is that the intent is as if this were an ordinary identifier appearing as a parameter in each non-static member function. (This view applies to the initializers of non-static data members as well if they are considered to be rewritten as mem-initializers in the constructor body.) Under this interpretation, the prohibition against using this in other contexts simply falls out of the fact that name lookup would fail to find this anywhere else, so the reference in the example is well-formed. (Implementations vary in their treatment of this example, so clearer wording is needed, whichever way the interpretation goes.)
Proposed resolution (February, 2010):
Change 5.1.1 [expr.prim.general] paragraph 2 as follows:
...The keyword this shall be used only inside the body of a non-static class member function body (9.3 [class.mfct]) of the nearest enclosing class or in a brace-or-equal-initializer for a non-static data member (9.2 [class.mem]). The type of the expression is a pointer to the class of the function or non-static data member, possibly with cv-qualifiers on the class type. The expression is an rvalue. [Example:
class Outer { int a[sizeof(*this)]; // error: not inside a member function unsigned int sz = sizeof(*this); // OK, in brace-or-equal-initializer void f() { int b[sizeof(*this)]; // OK struct Inner { int c[sizeof(*this)]; // error: not inside a member function of Inner }; } };
—end example]
[Voted into WP at October, 2009 meeting.]
The resolution of issue 613, as reflected in the sixth bullet of 5.1.1 [expr.prim.general] paragraph 10, allows an id-expression designating a non-static data member to be used
- if... it is the sole constituent of an unevaluated operand, except for optional enclosing parentheses.
The requirement that the id-expression be the “sole constituent” of the unevaluated operand seems unnecessarily strict, forbidding such plausible use cases as
struct S { int ar[42]; }; int i = sizeof(S::ar[0]);
or the use of the member as a function argument in template metaprogramming. The more general version of the restriction seems not to be very difficult to implement and may actually represent a simplification in some implementations.
Proposed resolution (July, 2009):
Change 5.1.1 [expr.prim.general] paragraph 10 as follows:
...
if that id-expression denotes a non-static data member and it is the sole constituent of appears in an unevaluated operand, except for optional enclosing parentheses. [Example:
struct S { int m; }; int i = sizeof(S::m); // OK int j = sizeof(S::m + 42); // error: reference to non-static member in subexpression OK
—end example]
[Voted into the WP at the July, 2009 meeting as part of N2927.]
There is not a single example of a lambda-expression in their specification. The Standard would be clearer if a few judiciously-chosen examples were added.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
Consider an example like:
void f(vector<double> vec) { double x, y, z; fancy_algorithm(vec, [&]() { /* use x, y, and z in various ways */ }); }
5.1.2 [expr.prim.lambda] paragraph 8 requires that the closure class for this lambda will have three reference members, and paragraph 12 requires that it be derived from std::reference_closure, implying two additional pointer members. Although 8.3.2 [dcl.ref] paragraph 4 allows a reference to be implemented without allocation of storage, current ABIs require that references be implemented as pointers. The practical effect of these requirements is that the closure object for this lambda expression will contain five pointers. If not for these requirements, however, it would be possible to implement the closure object as a single pointer to the stack frame, generating data accesses in the function-call operator as offsets relative to the frame pointer. The current specification is too tightly constrained.
Lawrence Crowl:
The original intent was that the reference members could be omitted from the closure object by an implementation. The problem we had was that we want the call to f in
extern f(std::reference_closure<void()>); extern f(std::function<void()>); f([&](){});
to unambiguously bind to the reference_closure; using reference_closure can be an order of magnitude faster than using function.
(See also issue 751.)
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927. (See also document PL22.16/09-0035 = WG21 N2845, which partially addressed this issue by the removal of std::reference_clossure.)
[Voted into the WP at the July, 2009 meeting as part of N2927.]
During the discussion of issue 750, it was suggested that an implementation might be permitted to omit fields in the closure object of a lambda expression if the implementation does not need them to address the corresponding automatic variables. If permitted, this implementation choice might be visible to the program via inheritance. Consider:
void f() { int const N = 10; typedef decltype([&N](){}) F; struct X: F { void n() { float z[N]; } // Error? }; }
If it is implementation-defined or unspecified whether the reference member F::N will exist, then it is unknown whether the the reference to N in X::n() will be an error (because lookup finds F::N, which is private) or well-formed (because there is no F::N, so the reference is to the local automatic variable).
If implementations can omit fields, the implementation dependency might be addressed by either treating the lookup “as if” the fields existed, even if they are not present in the object layout, or by defining the names of the fields in the closure class to be unique identifiers, similar to the names of unnamed namespaces (7.3.1.1 [namespace.unnamed]).
Another suggestion was made that derivation from a closure class should be prohibited, at least for now. However, it was pointed out that inheritance is frequently used to give stateless function objects some state, suggesting a use case along the lines of:
template<class T> struct SomeState: T { // ... }; template<class F, typename T< void algo(T functor, ...) { SomeState<T< state(functor); ... } ... algo([](int a){ return 2*a; }) ...
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
How does name binding work in nested lambda-expressions? For example,
void f1() { float v; []() { return [v]() { return v; } } } void f2() { float v; [v]() { return [v]() { return v; } } }
According to 5.1.2 [expr.prim.lambda] paragraph 3,
A name in the lambda-capture shall be in scope in the context of the lambda expression, and shall be this or shall refer to a local variable or reference with automatic storage duration.
One possible interpretation is that the lambda expression in f1 is ill-formed because v is used in the compound-statement of the outer lambda expression but does not appear in its effective capture set. However, the appearance of v in the inner lambda-capture is not a “use” in the sense of 3.2 [basic.def.odr] paragraph 2, because a lambda-capture is not an expression, and it's not clear whether the reference in the inner lambda expression's return expression should be considered a use of the automatic variable or of the member of the inner lambda expression's closure object.
Similarly, the lambda expression in f2 could be deemed to be ill-formed because the reference to v in the inner lambda expression's lambda-capture would refer to the field of the outer lambda-expression's closure object, not to a local automatic variable; however, it's not clear whether the inner lambda expression should be evaluated in situ or as part of the generated operator() member of the outer lambda expression's closure object.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
The current specification does not adequately describe what happens when an array name is part of the effective capture set of a lambda expression. 5.1.2 [expr.prim.lambda] paragraph 13 says that the array member of the closure object is direct-initialized by the local array; however, 8.5 [dcl.init] paragraph 16 says that such an initialization is ill-formed. There are several possibilities for handling this problem:
This results in an array member of the closure object, which is initialized by copying each element, along the lines of 12.8 [class.copy] paragraph 8.
This results in a pointer member of the closure object, initialized to point to the first element of the array (i.e., the array lvalue decays to a pointer rvalue).
This is ill-formed.
This results in a reference-to-array member of the closure object, initialized to refer to the array, regardless of whether & was used or not.
This is ill-formed unless the capture is “by reference.”
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
Is a lambda expression permitted in a default argument expression for a block-scope function declaration? For example,
void g() { void f(std::reference_closure<void()> rc = []() {}); f(); }
This was not discussed in either the Evolution Working Group nor in the Core Working Group, and it is possible that some of the same implementation difficulties that led to prohibiting use of automatic variables in such default argument expressions (8.3.6 [dcl.fct.default] paragraph 7) might also apply to closure objects, even though they are not automatic variables.
(See also issue 772.)Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
Consider the following example:
void f() { int const N = 10; [=]() mutable { N = 30; } // Okay: this->N has type int, not int const. N = 20; // Error. }
That is, the N that is a member of the closure object is not const, even though the captured variable is const. This seems strange, as capturing is basically a means of capturing the local environment in a way that avoids lifetime issues. More seriously, the change of type means that the results of decltype, overload resolution, and template argument deduction applied to a captured variable inside a lambda expression can be different from those in the scope containing the lambda expression, which could be a subtle source of bugs.
On the other hand, the copying involved in capturing has uses beyond avoiding lifetime issues (taking snapshots of values, avoiding data races, etc.), and the value of a cv-qualified object is not cv-qualified.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
The specification of closure objects is missing a couple of important points regarding their destruction. First, although 5.1.2 [expr.prim.lambda] paragraph 11 mentions other implicitly-declared special member functions, it is silent on the destructor, leading to questions about whether the closure class has one or not.
Second, nothing is said about the timing of the destruction of a closure object: is it normally destroyed at the end of the full-expression to which the lambda expression belongs, and is its lifetime extended if the closure object is bound to a reference? These questions would be addressed if paragraph 2 defined the closure object as a temporary instead of just as an rvalue. (It should be noted that 5.2.3 [expr.type.conv] also does not define the conceptually-similar T() as a temporary.)
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927. (The question regarding the failure of 5.2.3 [expr.type.conv] failing to categorize T() as a temporary was split off into a separate issue; see issue 943.)
[Voted into the WP at the July, 2009 meeting as part of N2927.]
According to 5.1.2 [expr.prim.lambda] paragraph 10, the following lambda expressions are ill-formed because the return types of the generated operator() functions are an array type and a function type, respectively:
void f() { []{ return ""; }; []{ return f; }; }
It would seem reasonable to expect the array-to-pointer and function-to-pointer decay to apply to these return values and hence to the inferred return type of operator().
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
The current wording of 5.1.2 [expr.prim.lambda] is not clear as to how name lookup is to be performed for names appearing in the compound-statement of a lambda expression. Consider, for example:
int fac(int n) { return [=]{ return n <= 1 ? 1 : n*operator()(n-1); }(); }
There is no operator() in scope in the context of the lambda expression. Consequently, according to bullet 5 of paragraph 10, the reference to operator() is not transformed to the class member access syntax but appears untransformed in the closure object's function call operator, where presumably it is interpreted as a recursive call to itself.
A similar question (although it does not involve name lookup per se) arises with respect to use of this in the compound-statement of a lambda expression that does not appear in the body of a non-static member function; for example,
void f() { float v; [v]() { return v+this->v; } }
this cannot refer to anything except the closure object, so are the two references to v equivalent?
The crux of this question is whether the lookups for names in the compound-statement are done in the context of the lambda expression or from the call operator of the closure object. The note at the end of paragraph 10 bullet 5 would tend to support the latter interpretation:
[Note: Reference to captured variables or references within the compound-statement refer to the data members of F. —end note]
Another possible interpretation of the current wording is that there are two distinct compound-statements in view: the compound-statement that is part of the lambda-expression and the body of the closure object's function call operator that is “obtained from” the former. If this is the intended interpretation, one way of addressing the issues regarding the operator() example above would be to state that it is an error if the lookup of a name in the compound-statement fails, making the example ill-formed.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
A lambda expression appearing in local scope presumably creates a local class (in the sense of 9.8 [class.local]) as the type of the closure object, because that class is “considered to be defined at the point where the lambda expression occurs” (5.1.2 [expr.prim.lambda] paragraph 7), and in the absence of any indication to the contrary that class must satisfy the restrictions of 9.8 [class.local] on local classes. One such restriction is that all its member functions must be defined within the class definition, making them inline. However, nothing is said about whether the function call operator for a non-local closure class is inline, and even for the local case it would be better if the specification were explicit.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
5.1.2 [expr.prim.lambda] paragraph 5 says,
The compound-statement of a lambda expression shall use (3.2 [basic.def.odr]) an automatic variable or reference from the context where the lambda expression appears only if the name of the variable or reference is a member of the effective capture set...
The reference to 3.2 [basic.def.odr] makes clear that the technical meaning of “use” is in view here, and that the names of variables can appear without being captured if they are constants used as values or if they are unevaluated operands.
There appears to be a disconnect with the preceding paragraph, however, in the description of which variables are implicitly captured by a capture-default:
for each name v that appears in the lambda expression and denotes a local variable or reference with automatic storage duration in the context where the lambda expression appears and that does not appear in the capture-list or as a parameter name in the lambda-parameter-declaration-list...
It would be more consistent if only variables that were required by paragraph 5 to be captured were implicitly captured, i.e., if “that appears in the lambda expression” were replaced by “that is used (3.2 [basic.def.odr]) in the compound-statement of the lambda expression.” For example,
struct A { A(); A(const A&); ~A(); }; void f() { A a; int i = [=]() { return sizeof a; }(); }
Here, a will be captured (and copied), even though it is not “used” in the lambda expression.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
According to 5.1.2 [expr.prim.lambda] paragraph 7, the appearance of a lambda expression results in the definition of a class “considered to be defined at the point where the lambda expression occurs.” It is not clear whether that means that a lambda expression cannot appear at any point where it is not permitted to define a class type. For example, 8.3.5 [dcl.fct] paragraph 10 says, “Types shall not be defined in return or parameter types.” Does that mean that a function declaration like
void f(int a[sizeof ([]{ return 0; })]);
is ill-formed, because the parameter type defines the closure class for the lambda expression? (Issue 686 lists many contexts in which type definitions are prohibited. Each of these should be examined to see whether a lambda expression should be allowed or prohibited there.)
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
The grammar in 5.1.2 [expr.prim.lambda] for lambda-parameter specifies that a declarator must be present, i.e., that all lambda-parameters must be named. This also has the effect of prohibiting a lambda like [](void){}. It is not clear that there is a good reason for these restrictions; programmers could reasonably expect that lambda-parameters were like ordinary function parameters in these regards.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
The grammar in 5.1.2 [expr.prim.lambda] for lambda-parameter-declaration does not allow for an ellipsis. Is this a desirable restriction?
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
5.1.2 [expr.prim.lambda] paragraph 13 says simply,
The closure object is initialized by direct-initializing each member N of F with the local variable or reference named N; the member t is initialized with this.
The mechanism for this initialization is not specified. In particular, does the closure class have a default constructor that performs this initialization?
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
According to 5.1.2 [expr.prim.lambda] paragraph 11, the closure class “has a public move constructor that performs a member-wise move.” Although the terms “move constructor” and “member-wise move” are not currently defined (see issue 680), this presumably means that a lambda like [&i]{} results in a closure class similar to:
class F { int& i; public: F(&& other): i(std::move(other.i)) { } // etc. };
This constructor is ill-formed because it attempts to initialize an lvalue reference to non-const int with the rvalue returned by std::move.
It is not clear whether this should be handled by:
Not generating the move constructor.
Generating the declaration of the move constructor but only defining it (and giving the corresponding error) if the move constructor would be used, similar to the handling of other implicitly-defined special member functions.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
Assuming that it is permitted to use a lambda as a default argument in a block-scope function declaration (see issue 754), it is presumably ill-formed for such a lambda expression to refer to a local automatic variable (8.3.6 [dcl.fct.default] paragraph 7). What does this mean for capture-defaults? For example,
void f() { int i = 1; void f(int = ([i]() { return i; })()); // Definitely an error void g(int = ([i]() { return 0; })()); // Probably an error void h(int = ([=]() { return i; })()); // Definitely an error void o(int = ([=]() { return 0; })()); // Okay? void p(int = ([]() { return sizeof i; })()); // Presumably okay }
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
The current wording does not state under what conditions, if ever, a closure class is a POD. It should either be explicitly unspecified or definitively stated that a closure class is never a POD, to allow implementations freedom to determine the contents of closure classes.
Notes from the March, 2009 meeting:
A closure class is neither standard-layout nor trivial.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
According to 5.1.2 [expr.prim.lambda] paragraph 8, the “object type” of a captured function is the type to which the reference refers. That's clearly wrong when the captured reference is a reference to a function, because the resulting data member of the closure class will have a function type:
void f() { } void g() { void (&fr)() = f; [fr]{}; // Oops... }
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
5.1.2 [expr.prim.lambda] paragraph 8, bullet 2, says of members of a closure class,
if the element is of the form & N, the data member has the name N and type “reference to object type of N”
Is an implementation free to use an rvalue reference as the type of this member, as only a “reference” is specified? (See issue 771; the move constructor would be well-formed if the reference member were an rvalue reference.)
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
Functions and function objects behave differently with respect to argument-dependent lookup. In particular, the associated namespaces of a function's parameters and return types, but not the namespace in which the function is declared, are associated namespaces of the function; the exact opposite is true of a function object. The Committee should consider rectifying that disparity; however, in the absence of such action, an explicit decision should be made as to whether lambdas are more function-like or object-like with respect to argument-dependent lookup. For example:
namespace M { struct S { }; } namespace N { void func(M::S); struct { void operator()(M::S); } fn_obj; const auto& lambda = [](M::S){}; } void g() { f(N::func); // assoc NS == M, not N f(N::fn_obj); // assoc NS == N, not M f(N::lambda); // assoc NS == ?? }
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
5.1.2 [expr.prim.lambda] paragraph 13 ties the effective lifetime of a closure object with members captured by reference to the innermost block scope in which the lambda appears, rather than to the lifetime of the objects to which the references are bound. This seems too restrictive.
Notes from the March, 2009 meeting:
Making the suggested change would be problematic for an implementation in which the “reference members” were actually implemented using offsets from a captured stack pointer and in which nested blocks were pushed onto the stack (to optimize space for large local objects, for example).
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into WP at March, 2010 meeting as document N3052.]
A lambda with an empty capture list has identical semantics to a regular function type. By requiring this mapping we get an efficient lambda type with a known API that is also compatible with existing operating system and C library functions.
Notes from the July, 2009 meeting:
This functionality is part of the “unified function syntax” proposal and will be considered in that context.
[Voted into WP at March, 2010 meeting.]
The following is not allowed by the current syntax of lambda-capture but would be useful:
template <typename ...Args> void f(Args... args) { auto l = [&, args...] { return g(args...); }; }
Proposed resolution (October, 2009):
Change the grammar in 5.1.2 [expr.prim.lambda] paragraph 1 as follows:
Add a new paragraph at the end of 5.1.2 [expr.prim.lambda]:
A capture followed by an ellipsis is a pack expansion (14.5.3 [temp.variadic]). [Example:
template<typename ...Args> void f(Args... args) { auto l = [&, args...] { return g(args...); }; l(); }
—end example]
Add a new bullet to the list in 14.5.3 [temp.variadic] paragraph 4:
[Editorial note: the editor may wish to consider sorting the bullets in this list in order of section reference.]
[Voted into WP at March, 2010 meeting.]
The specification of the members of a closure type does not rule out the possibility that its operator() might be virtual. It would be better to make it clear that it cannot.
Proposed resolution (October, 2009):
Change 5.1.2 [expr.prim.lambda] paragraph 5 as follows:
... followed by mutable. It is not neither virtual nor declared volatile. Default arguments...
[Voted into WP at March, 2010 meeting as document N3055.]
A cast to an rvalue reference type produces an rvalue, and rvalues must have complete types (3.10 [basic.lval] paragraph 9). However, none of the sections dealing with cast operators in 5.2 [expr.post] require that the referred-to type must be complete in an rvalue reference cast.
(Note that the approach described for issue 690, in which an rvalue reference type would be essentially an lvalue instead of an rvalue, would address this issue as well, since lvalues can have incomplete types.)Proposed resolution (February, 2010):
See paper N3030.
[Voted into WP at March, 2010 meeting.]
The current wording of 5.2.2 [expr.call] paragraph 7 is:
After these conversions, if the argument does not have arithmetic, enumeration, pointer, pointer to member, or effective class type, the program is ill-formed.
It's not clear whether this is intended to exclude anything other than void, but the effect is to disallow passing nullptr to ellipsis. That seems unnecessary.
Notes from the October, 2009 meeting:
The CWG agreed that this should be supported and the effect should be like passing (void*)nullptr.
Proposed resolution (February, 2010):
Change 5.2.2 [expr.call] paragraph 7 as follows:
When there is no parameter for a given argument, the argument is passed in such a way that the receiving function can obtain the value of the argument by invoking va_arg (18.10 [support.runtime]). [Note: This paragraph does not apply to arguments passed to a function parameter pack. Function parameter packs are expanded during template instantiation (14.5.3 [temp.variadic]), thus each such argument has a corresponding parameter when a function template specialization is actually called. —end note] The lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), and function-to-pointer (4.3 [conv.func]) standard conversions are performed on the argument expression. An argument that has (possibly cv-qualified) type std::nullptr_t is converted to type void* (4.10 [conv.ptr]). After these conversions...
[Voted into WP at October, 2009 meeting.]
There are several places in the Standard that were overlooked when reference qualification of member functions was added. For example, 5.2.5 [expr.ref] paragraph 4, bullet 3, sub-bullet 2 says,
...if E1.E2 refers to a non-static member function, and the type of E2 is “function of parameter-type-list cv returning T”, then...
This wording incorrectly excludes member functions declared with a ref-qualifier.
Another place that should consider reference qualification is 5.5 [expr.mptr.oper]; it should not be possible to invoke an &-qualified member function with an rvalue object expression.
A third place is 7.3.3 [namespace.udecl] paragraph 15, which does not mention reference qualification in connection with the hiding/overriding of member functions brought in from a base class via a using-declaration.
Proposed resolution (September, 2009):
Change 5.2.5 [expr.ref] paragraph 4, bullet 3, sub-bullet 2 as follows:
...
...
Otherwise, if E1.E2 refers to a non-static member function and the type of E2 is “function of parameter-type-list cv ref-qualifieropt returning T”, then E1.E2 is an rvalue. The expression designates a non-static member function...
Change 5.5 [expr.mptr.oper] paragraph 6 as follows:
...—end example] In a .* expression whose object expression is an rvalue, if the second operand is a pointer to member function with ref-qualifier &, the program is ill-formed. In a ->* expression, or in a .* expression whose object expression is an lvalue, if the second operand is a pointer to member function with ref-qualifier &&, the program is ill-formed. The result of a .* expression is an lvalue only if its first operand is an lvalue and...
Change 7.3.3 [namespace.udecl] paragraph 15 as follows:
When a using-declaration brings names from a base class into a derived class scope, member functions and member function templates in the derived class override and/or hide member functions and member function templates with the same name, parameter-type-list (8.3.5 [dcl.fct]), and cv-qualification, and ref-qualifier (if any) in a base class (rather than conflicting). [Note:...
[Voted into the WP at the March, 2009 meeting.]
At least one implementation accepts the following example as well-formed (returning a null pointer at runtime), although others reject it at compile time:
struct A { virtual ~A(); }; struct B: private A { } b; A* pa = dynamic_cast<A*>(&b);
Presumably the intent of 5.2.7 [expr.dynamic.cast] paragraph 5 is that all up-casts (converting from derived to base) are to be handled at compile time, regardless of whether the class involved is polymorphic or not:
If T is “pointer to cv1 B” and v has type “pointer to cv2 D” such that B is a base class of D, the result is a pointer to the unique B subobject of the D object pointed to by v. Similarly, if T is “reference to cv1 B” and v has type cv2 D such that B is a base class of D, the result is the unique B subobject of the D object referred to by v... In both the pointer and reference cases, cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2, and B shall be an accessible unambiguous base class of D.
One explanation for the implementation that accepts the example at compile time is that the final sentence is interpreted as part of the condition for the applicability of this paragraph, so that this case falls through into the description of runtime checking that follows. This (mis-)interpretation is buttressed by the example in paragraph 9, which reads in significant part:
class A { virtual void f(); };
class B { virtual void g(); };
class D : public virtual A, private B {};
void g() {
D d;
B* bp;
bp = dynamic_cast<B*>(&d); // fails
}
The “fails” comment is identical to the commentary on the lines in the example where the run-time check fails. If the interpretation that paragraph 5 is supposed to apply to all up-casts, presumably this comment should change to “ill-formed,” or the line should be removed from the example altogether.
It should be noted that this interpretation (that the example is ill-formed and the runtime check applies only to down-casts and cross-casts) rejects some programs that could plausibly be accepted and actually work at runtime. For example,
struct B { virtual ~B(); }; struct D: private virtual B { }; void test(D* pd) { B* pb = dynamic_cast<B*>(pd); // #1 } struct D2: virtual B, virtual D {}; void demo() { D2 d2; B* pb = dynamic_cast<B*>(&d2); // #2 test(&d2); // #3 }
According to the interpretation that paragraph 5 applies, line #1 is ill-formed. However, converting from D2 to B (line #2) is well-formed; if the alternate interpretation were applied, the conversion in line #1 could succeed when applied to d2 (line #3).
One final note: the wording in 5.2.7 [expr.dynamic.cast] paragraph 8 is incorrect:
The run-time check logically executes as follows:
If, in the most derived object pointed (referred) to by v, v points (refers) to a public base class subobject of a T object, and if only one object of type T is derived from the subobject pointed (referred) to by v the result is a pointer (an lvalue referring) to that T object.
Otherwise, if v points (refers) to a public base class subobject of the most derived object, and the type of the most derived object has a base class, of type T, that is unambiguous and public, the result is a pointer (an lvalue referring) to the T subobject of the most derived object.
Otherwise, the run-time check fails.
All uses of T in this paragraph treat it as if it were a class type; in fact, T is the type to which the expression is being cast and thus is either a pointer type or a reference type, not a class type.
Proposed resolution (June, 2008):
Change 5.2.7 [expr.dynamic.cast] paragraph 5 as follows:
...In both the pointer and reference cases, cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2, and B shall be an accessible unambiguous base class of D the program is ill-formed if cv2 is greater cv-qualification than cv1 or if B is an inaccessible or ambiguous base class of D.
Change the comment in the example in 5.2.7 [expr.dynamic.cast] paragraph 9 as follows:
bp = dynamic_cast<B*>(&d); // fails ill-formed (not a run-time check)
Change 5.2.7 [expr.dynamic.cast] paragraph 8 as follows:
The If C is the class type to which T points or refers, the run-time check logically executes as follows:
If, in the most derived object pointed (referred) to by v, v points (refers) to a public base class subobject of a T C object, and if only one object of type T C is derived from the subobject pointed (referred) to by v the result is a pointer (an lvalue referring) to that T C object.
Otherwise, if v points (refers) to a public base class subobject of the most derived object, and the type of the most derived object has a base class, of type T C, that is unambiguous and public, the result is a pointer (an lvalue referring) to the T C subobject of the most derived object.
Otherwise, the run-time check fails.
[Voted into WP at October, 2009 meeting.]
The current wording of 5.2.9 [expr.static.cast] paragraph 9 does not permit conversion of a value of a scoped enumeration type to a floating point type. This was presumably an oversight during the specification of scoped enumerations and should be rectified.
Proposed resolution (July, 2009):
Change 5.2.9 [expr.static.cast] paragraph 9 as follows:
A value of a scoped enumeration type (7.2 [dcl.enum]) can be explicitly converted to an integral type. The value is unchanged if the original value can be represented by the specified type. Otherwise, the resulting value is unspecified. A value of a scoped enumeration type can also be explicitly converted to a floating point type; the result is the same as that of converting from the original value to the floating point type.
[Voted into the WP at the March, 2009 meeting.]
For years I've noticed that people will write code like this to get the address of an object's bytes:
void foo(long* p) { char* q = reinterpret_cast<char*>(p); // #1 // do something with the bytes of *p by using q }
When in fact the only portable way to do it according to the standard is:
void foo(long* p) { char* q = static_cast<char*>(static_cast<void*>(p)); // #2 // do something with the bytes of *p by using q }
I thought reinterpret_cast existed so that vendors could provide some weird platform-specific things. However, recently Peter Dimov pointed out to me that if we substitute a class type for long above, reinterpret_cast is required to work as expected by 9.2 [class.mem] paragraph 18:
A pointer to a standard-layout struct object, suitably converted using a reinterpret_cast, points to its initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa.
So there isn't a whole lot of flexibility to do something different and useful on non-class types. Are there any implementations for which #1 actually fails? If not, I think it would be a good idea to nail reinterpret_cast down so that the standard says it does what people (correctly) think it does in practice.
Proposed resolution (March, 2008):
Change 5.2.10 [expr.reinterpret.cast] paragraph 7 as indicated:
A pointer to an object can be explicitly converted to a pointer to an object of different type. When an rvalue v of type “pointer to T1” is converted to the type “pointer to cv T2,” the result is static_cast<cv T2*>(static_cast<cv void*>(v)) if both T1 and T2 are standard-layout types (3.9 [basic.types]) and the alignment requirements of T2 are no stricter than those of T1. Except that cConverting an rvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value, t. The result of any other such a pointer conversion is unspecified.
[Voted into WP at March, 2010 meeting.]
Consider the following example:
static const char test1 = 'x'; static const char test2 = 'x'; bool f() { return &test1 != &test2; }
Is f() allowed to return false? Can a smart optimizer alias these two variables, taking advantage of the fact that they are const, initialized to the same value, and thus can never be different in a well-defined program?
The C++ Standard doesn't explicitly specify address allocation of objects except as members of arrays and classes, so the answer would appear to be that such an implementation would be conforming.
This situation appears to have been the inadvertent result of the resolution of issue 73. Prior to that change, 5.10 [expr.eq] said,
Two pointers of the same type compare equal if and only if they... both point to the same object...
That resolution introduced the current wording,
Two pointers of the same type compare equal if and only if... both represent the same address.
Notes from the March, 2009 meeting:
The CWG agreed that this aliasing should not be permitted in a conforming implementation.
Proposed resolution (November, 2009):
Add the following as a new paragraph after 1.8 [intro.object] paragraph 5:
Unless an object is a bit-field or a base class subobject of zero size, the address of that object is the address of the first byte it occupies. Two distinct objects that are neither bit-fields nor base class subobjects of zero size shall have distinct addresses [Footnote: Under the “as-if” rule an implementation is allowed to store two objects at the same machine address or not store an object at all if the program cannot observe the difference (1.9 [intro.execution]). —end footnote]. [Example:
static const char test1 = 'x'; static const char test2 = 'x'; const bool b = &test1 != &test2; // always true
—end example]
Change 5.3.1 [expr.unary.op] paragraph 3 as follows:
The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue or a qualified-id. In the first case, if the type of the expression is “T,” the type of the result is “pointer to T.” In particular, the address of an object of type “cv T” is “pointer to cv T,” with the same cv-qualifiers. For a qualified-id, if the member is a static member of type “T”, the type of the result is plain “pointer to T.” If the member is a non-static member of class C of type T, the type of the result is “pointer to member of class C of type T.” If the operand is a qualified-id naming a non-static member m of some class C with type T, the result has type “pointer to member of class C of type T” and is an rvalue designating C::m. Otherwise, if the type of the expression is T, the result has type “pointer to T” and is an rvalue that is the address of the designated object (1.7 [intro.memory]) or a pointer to the designated function. [Note: In particular, the address of an object of type “cv T” is “pointer to cv T,” with the same cv-qualification. —end note] [Example:...
[Voted into WP at March, 2010 meeting.]
The note in 5.2.10 [expr.reinterpret.cast] paragraph 2 says,
Subject to the restrictions in this section, an expression may be cast to its own type using a reinterpret_cast operator.
However, there is nothing in the normative text that permits this conversion, and paragraph 1 forbids any conversion not explicitly permitted.
(See also issue 944.)
Proposed resolution (October, 2009):
Change 5.2.10 [expr.reinterpret.cast] paragraph 2 as follows:
The reinterpret_cast operator shall not cast away constness (5.2.11 [expr.const.cast]). [Note: Subject to the restrictions in this section, an expression may be cast to its own type using a reinterpret_cast operator. —end note] An expression of integral, enumeration, pointer, or pointer-to-member type can be explictly converted to its own type; such a cast yields the value of its operand.
Change 5.2.10 [expr.reinterpret.cast] paragraph 10 as follows:
An rvalue of type “pointer to member of X of type T1” can be explicitly converted to an rvalue of a different type “pointer to member of Y of type T2” if T1 and T2 are both function types or both object types...
[Voted into WP at October, 2009 meeting.]
Both const_cast (5.2.11 [expr.const.cast] paragraph 1) and reinterpret_cast (5.2.10 [expr.reinterpret.cast] paragraph 1) say,
If T is an lvalue reference type, the result is an lvalue; otherwise, the result is an rvalue and the lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), and function-to-pointer (4.3 [conv.func]) standard conversions are performed on the expression v.
This introduces a contradiction in the text. According to 5.2.11 [expr.const.cast] paragraph 4,
The result of a reference const_cast refers to the original object.
However, the lvalue-to-rvalue conversion applied to the operand when the target is an rvalue reference type creates a temporary if the operand has class type (4.1 [conv.lval] paragraph 2), meaning that the result will not refer to the original object but to the temporary.
A similar problem exists for reinterpret_cast: according to 5.2.10 [expr.reinterpret.cast] paragraph 11,
a reference cast reinterpret_cast<T&>(x) has the same effect as the conversion *reinterpret_cast<T*>(&x) with the built-in & and * operators (and similarly for reinterpret_cast<T&&>(x)). The result refers to the same object as the source lvalue, but with a different type.
Here the issue is that the unary & operator used in the description requires an lvalue, but the lvalue-to-rvalue conversion is applied to the operand when the target is an rvalue reference type.
It would seem that the lvalue-to-rvalue conversion should not be applied when the target of the cast is an rvalue reference type.
Proposed resolution (July, 2009):
Change 5.2.10 [expr.reinterpret.cast] paragraph 1 as follows:
The result of the expression reinterpret_cast<T>(v) is the result of converting the expression v to type T. If T is an lvalue reference type, the result is an lvalue; if T is an rvalue reference type, the result is an rvalue; otherwise, the result is an rvalue and the lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), and function-to-pointer (4.3 [conv.func]) standard conversions are performed on the the expression v. Conversions that can be performed explicitly using reinterpret_cast are listed below. No other conversion can be performed explicitly using reinterpret_cast.
Change 5.2.11 [expr.const.cast] paragraph 1 as follows:
The result of the expression const_cast<T>(v) is of type T. If T is an lvalue reference type, the result is an lvalue; if T is an rvalue reference type, the result is an rvalue; otherwise, the result is an rvalue and the lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), and function-to-pointer (4.3 [conv.func]) standard conversions are performed on the expression v. Conversions that can be performed explicitly using const_cast are listed below. No other conversion shall be performed explicitly using const_cast.
[Voted into WP at October, 2009 meeting.]
The rules in 5.2.11 [expr.const.cast] paragraphs 8 and following, defining “casting away constness,” do not cover a cast to an rvalue reference type.
Proposed resolution (September, 2009):
Change 5.2.11 [expr.const.cast] paragraph 9 as follows:
Casting from an lvalue of type T1 to an lvalue of type T2 using a an lvalue reference cast, or casting from an expression of type T1 to an rvalue of type T2 using an rvalue reference cast, casts away constness if a cast from an rvalue of type “pointer to T1” to the type “pointer to T2” casts away constness.
[Voted into WP at March, 2010 meeting.]
5.2.11 [expr.const.cast] paragraph 4 says,
...Similarly, for two effective object types T1 and T2, an expression of type T1 can be explicitly converted to an rvalue of type T2 using the cast const_cast<T2&&> if a pointer to T1 can be explicitly converted to the type “pointer to T2” using a const_cast. The result of a reference const_cast refers to the original object.
However, in some rvalue-reference const_casts there is no “original object,” e.g.,
const_cast<int&&>(2)
Notes from the July, 2009 meeting:
The coresponding cast to an lvalue reference to const is ill-formed: in such cases, the operand must be an lvalue. The consensus of the CWG was that a cast to an rvalue reference should only accept an rvalue that is an rvalue reference (i.e., an object).
Proposed resolution (February, 2010):
Change 5.2.11 [expr.const.cast] paragraph 4 as follows:
An lvalue of type T1 can be explicitly converted to an lvalue of type T2 using the cast const_cast<T2&> (where T1 and T2 are object types) if a pointer to T1 can be explicitly converted to the type “pointer to T2” using a const_cast. Similarly, for two object types T1 and T2, an expression lvalue of type T1 or, if T1 is a class type, an expression of type T1, can be explicitly converted to an rvalue of type T2 using the cast const_cast<T2&&> if a pointer to T1 can be explicitly converted to the type “pointer to T2” using a const_cast. The result of a reference const_cast refers to the original object.
[Voted into WP at March, 2010 meeting.]
The resolution of issue 39 changed the diagnosis of ambiguity because of multiple subobjects from being a lookup error to being diagnosed where the result of the lookup is used. The formation of a pointer to member is one such context but was overlooked in the changes. 5.3.1 [expr.unary.op] paragraph 3 should have language similar to 5.2.5 [expr.ref] paragraph 5 and should be mentioned in the note in 10.2 [class.member.lookup] paragraph 13.
Proposed resolution (October, 2009):
Change 5.3.1 [expr.unary.op] paragraph 3 as follows:
...For a qualified-id, if the member is a static member of type “T”, the type of the result is plain “pointer to T.” If the member is a non-static member of class C of type T, the type of the result is “pointer to member of class C of type T.,” and the program is ill-formed if C is an ambiguous base (10.2 [class.member.lookup]) of the class designated by the nested-name-specifier of the qualified-id....
Change 10.2 [class.member.lookup] paragraph 13 as follows:
[Note: Even if the result of name lookup is unambiguous, use of a name found in multiple subobjects might still be ambiguous (4.11 [conv.mem], 5.2.5 [expr.ref], 5.3.1 [expr.unary.op], 11.2 [class.access.base]). —end note] [Example:...
[Voted into WP at October, 2009 meeting.]
There is no reason for the prohibition of using sizeof on “an enumeration type before all its enumerators have been declared” (5.3.3 [expr.sizeof] paragraph 1) if the underlying type of the enumeration is fixed.
Proposed resolution (July, 2009):
Change 5.3.3 [expr.sizeof] paragraph 1 as follows:
...The sizeof operator shall not be applied to an expression that has function or incomplete type, or to an enumeration type whose underlying type is not fixed before all its enumerators have been declared, or to the parenthesized name of such types, or to an lvalue that designates a bit-field...
[Voted into WP at October, 2009 meeting.]
Consider the following code, which uses double-checked locking (DCL):
Widget* Widget::Instance() { if (pInstance == 0) { // 1: first check lock<mutex> hold(mutW); // 2: acquire lock if (pInstance == 0) { // 3: second check pInstance = new Widget(); // 4: create and assign } } // 5: release lock }
We want this to be fully correct when pInstance is an atomic pointer to Widget. To get that result, we have to disallow any assignment to pInstance until after the new object is fully constructed. In other words, we want this to be an invalid transformation of line 4:
pInstance = operator new(sizeof(Widget)); new (pInstance) Widget;
I don't think it would be surprising if this were disallowed. For example, if the constructor were to throw an exception, I think many people would expect the variable not to be modified. I think the question is whether it's sufficiently clearly disallowed.
This could be clarified by stating (somewhere appropriate — probably either in 5.3.4 [expr.new] paragraph 16 or paragraph 22) that the initialization of the allocated object is sequenced before the value computation of the new-expression. Then by 5.17 [expr.ass] paragraph 1 (“In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.”), the initialization would have to be sequenced before the assignment.
This is probably not a problem for atomic<Widget*> because its operator= is a function, and function calls provide the necessary guarantees. But for the plain pointer assignment case, there's still a question about whether the sequencing of side effects is constrained as tightly as it should be. In fact, you don't even have to throw an exception from the constructor for there to be a question.
struct X { static X* p; X(); }; X* X::p = new X;
When the constructor for X is invoked by this new-expression, would it be valid for X::p to be non-null? If the answer is supposed to be “no,” then I think the Standard should express that intent more clearly.
Proposed resolution (March, 2008):
Change 5.3.4 [expr.new] paragraph 22 as indicated:
Whether Initialization of the allocated object is sequenced before the value computation of the new-expression. It is unspecified whether the allocation function is called before evaluating the constructor arguments or after evaluating the constructor arguments but before entering the constructor is unspecified. It is also unspecified whether the arguments to a constructor are evaluated if the allocation function returns the null pointer or exits using an exception.
[Drafting note: The editor may wish to move paragraph 22 up to immediately follow paragraph 16 or 17. The paragraphs numbered 18-21 deal with the case where deallocation is done because initialization terminates with an exception, whereas paragraph 22 applies more to the initialization itself, described in paragraph 16.]
Notes from the September, 2008 meeting:
The proposed wording does not (but should) allow the call to the allocation function to occur in the middle of evaluating arguments for the constructor call.
Proposed resolution (July, 2009):
Change 5.3.4 [expr.new] paragraph 21 as follows:
Whether the allocation function is called before evaluating the constructor arguments or after evaluating the constructor arguments but before entering the constructor is unspecified. The invocation of the allocation function is indeterminately sequenced with respect to the evaluations of expressions in the new-initializer. Initialization of the allocated object is sequenced before the value computation of the new-expression. It is also unspecified whether the arguments to a constructor expressions in the new-initializer are evaluated if the allocation function returns the null pointer or exits using an exception.
[Drafting note: the editor may wish to consider moving this paragraph to follow paragraph 15 or 16. Paragraphs 17-19 deal with the case where deallocation is done because initialization terminates with an exception, whereas this paragraph applies more to the initialization itself (described in paragraph 15).]
[Voted into WP at October, 2009 meeting.]
The type of an allocated object wih the type specifier auto is determined by the rules of copy initialization, but the initialization applied will be direct initialization. This would affect classes which declare their copy constructor explicit, for instance. For consistency, use the same form of initiailization for the deduction as the new expression.
Proposed resolution (July, 2009):
Change 5.3.4 [expr.new] paragraph 2 as follows:
If the auto type-specifier appears in the type-specifier-seq of a new-type-id or type-id of a new-expression, the new-expression shall contain a new-initializer of the form
( assignment-expression )
The allocated type is deduced from the new-initializer as follows: Let (e) be e be the assignment-expression in the new-initializer and T be the new-type-id or type-id of the new-expression, then the allocated type is the type deduced for the variable x in the invented declaration (7.1.6.4 [dcl.spec.auto]):
T x = e x(e);
[Example:...
[Voted into WP at July, 2009 meeting as part of N2932.]
Throwing std::length_error (5.3.4 [expr.new] paragraph 7) for an attempt to allocate a too-large array brings in too much of the Standard library. A simpler exception, like std::bad_alloc, should be thrown instead.
Notes from the March, 2009 meeting:
The CWG was in favor of throwing an exception derived from std::bad_alloc. This would be upwardly compatible; it would be harmless for programs that currently catch std::bad_alloc, but would allow programs to treat the calculation overflow case separately if they wish.
[Voted into WP at July, 2009 meeting.]
The requirements for the operand of the delete operators are given in 5.3.5 [expr.delete] paragraph 2:
In either alternative, the value of the operand of delete may be a null pointer value. If it is not a null pointer value, in the 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 subobject (1.8 [intro.object]) representing a base class of such an object (clause 10 [class.derived]). If not, the behavior is undefined. In the second alternative (delete array), the value of the operand of delete shall be the pointer value which resulted from a previous array new-expression. If not, the behavior is undefined.
There are no restrictions on the type of a null pointer, only on a pointer that is not null. That seems wrong.
Proposed resolution (June, 2008):
Change 5.3.5 [expr.delete] paragraph 1 as follows:
...The operand shall have a pointer to object type, or a class type having a single non-explicit conversion function (12.3.2 [class.conv.fct]) to a pointer to object type...
Proposed resolution (September, 2008):
Change 5.3.5 [expr.delete] paragraph 1 as follows:
...The operand shall have a pointer to object type, or a class type having a single non-explicit conversion function (12.3.2) to a pointer to object type. [Footnote: This implies that an object cannot be deleted using a pointer of type void* because void is not an object type. —end footnote] ...
Delete the footnote at the end of 5.3.5 [expr.delete] paragraph 3:
...if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined. [Footnote: This implies that an object cannot be deleted using a pointer of type void* because there are no objects of type void. —end footnote]
[Voted into WP at October, 2009 meeting.]
5.3.6 [expr.alignof] paragraph 1 currently says regarding alignof,
The operand shall be a type-id representing a complete effective object type or a reference to a complete effective object type.
This prohibits taking the alignment of an array type with an unknown bound. There doesn't appear to be any reason for this restriction.
Proposed resolution (July, 2009):
Change 5.3.6 [expr.alignof] paragraph 1 as follows:
The operand shall be a type-id representing a complete effective object type or an array thereof or a reference to a complete effective object type.
[Voted into WP at October, 2009 meeting.]
According to 5.8 [expr.shift] paragraph 2,
The value of E1 << E2 is E1 (interpreted as a bit pattern) left-shifted E2 bit positions; vacated bits are zero-filled. If E1 has an unsigned type, the value of the result is E1 multiplied by the quantity 2 raised to the power E2, reduced modulo ULLONG_MAX+1 if E1 has type unsigned long long int, ULONG_MAX+1 if E1 has type unsigned long int, UINT_MAX+1 otherwise.
This specification does not allow for extended types with rank greater than long long; in particular, it says that the value of a shifted unsigned extended type is truncated as if it were the same width as an unsigned int.
It's unclear that the second sentence has any normative value; it might be better to relegate it to a note or omit it than to correct it.
Proposed resolution (July, 2009):
Change 5.8 [expr.shift] paragraphs 2-3 as follows:
The value of E1 << E2 is E1 (interpreted as a bit pattern) left-shifted E2 bit positions; vacated bits are zero-filled. If E1 has an unsigned type, the value of the result is E1 multiplied by the quantity 2 raised to the power E2 × 2E2, reduced modulo ULLONG_MAX+1 if E1 has type unsigned long long int, ULONG_MAX+1 if E1 has type unsigned long int, UINT_MAX+1 otherwise. [Note: the constants ULLONG_MAX, ULONG_MAX, and UINT_MAX are defined in the header <climits>. —end note] one more than the maximum value representable in the result type. Otherwise, if E1 has a signed type and nonnegative value, and E1 × 2E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.
The value of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1 divided by the quantity 2 raised to the power E2 / 2E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined.
[Voted into WP at March, 2010 meeting.]
The current wording of the draft does not indicate what is supposed to happen when an rvalue of type std::nullptr_t is compared with an integral null pointer constant. (This could occur, for example, in template code like
template<typename T> void f(T t) { if (t == 0) // ... }
in a call like f(nullptr) -- presumably the body of the template was written before nullptr became available and thus used an integral null pointer constant.) Because an integral null pointer constant can be converted to std::nullptr_t (4.10 [conv.ptr] paragraph 1), one might expect that 0 would be converted to std::nullptr_t and the two operands would compare equal, but 5.9 [expr.rel] paragraph 2 does not handle this case at all, leaving it as undefined behavior.
The current situation is more well-defined (but perhaps not better) with respect to the conditional operator. 5.16 [expr.cond] paragraphs 3-6 make it ill-formed to have std::nullptr_t and 0 as the second and third operands. Again, it's not too hard to imagine a legacy function template like
template<typename T> void f(T t, bool b) { T t = b ? t : 0; }
which would be ill-formed under the current wording of 5.16 [expr.cond].
Either 5.9 [expr.rel] and 5.10 [expr.eq] should be changed to make this combination of operands ill-formed, or those two sections should be changed to give the comparison defined semantics and 5.16 [expr.cond] should be changed to make those operands well-formed.
Proposed resolution (October, 2009):
Change 5.9 [expr.rel] paragraph 2 as follows:
The usual arithmetic conversions are performed on operands of arithmetic or enumeration type. Pointer conversions (4.10 [conv.ptr]) and qualification conversions (4.4 [conv.qual]) are performed on pointer operands (or on a pointer operand and a null pointer constant, or on two null pointer constants, at least one of which is non-integral) to bring them to their composite pointer type. If one operand is a null pointer constant, the composite pointer type is std::nullptr_t if the other operand is also a null pointer constant or, if the other operand is a pointer, the type of the other operand. Otherwise...
Change 5.16 [expr.cond] paragraph 6 bullet 3 as follows:
[Voted into WP at October, 2009 meeting.]
Consider the following example:
template <typename T> const T* f(bool b) { static T t1 = T(); static const T t2 = T(); return &(b ? t1 : t2); // error? }
According to 5.16 [expr.cond], this function is well-formed if T is a class type and ill-formed otherwise. If the second and third operands of a conditional expression are lvalues of the same class type except for cv-qualification, the result of the conditional expression is an lvalue; if they are lvalues of the same non-class type except for cv-qualification, the result is an rvalue.
This difference seems gratuitous and should be removed.
Proposed resolution (June, 2009):
Change 5.16 [expr.cond] paragraph 3 as follows:
Otherwise, if the second and third operand have different types, and either has (possibly cv-qualified) class type, or if both are lvalues of the same type except for cv-qualification, an attempt is made to convert each of those operands to the type of the other. The process for determining whether an operand expression E1 of type T1 can be converted to match an operand expression E2 of type T2 is defined as follows:
If E2 is an lvalue: E1 can be converted to match E2 if E1 can be implicitly converted (Clause 4 [conv]) to the type “lvalue reference to T2”, subject to the constraint that in the conversion the reference must bind directly (8.5.3 [dcl.init.ref]) to E1.
If E2 is an rvalue, or if the conversion above cannot be done and at least one of the operands has (possibly cv-qualified) class type:
...
[Voted into the WP at the March, 2009 meeting.]
There appear to be two different specifications for when aliasing is permitted. One is in 3.10 [basic.lval] paragraph 15:
If a program attempts to access the stored value of an object through an lvalue of other than one of the following types the behavior is undefined
the dynamic type of the object,
a cv-qualified version of the dynamic type of the object,
a type similar (as defined in 4.4 [conv.qual]) to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union),
a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
a char or unsigned char type.
There is also a much more restrictive specification in 5.17 [expr.ass] paragraph 8:
If the value being stored in an object is accessed from another object that overlaps in any way the storage of the first object, then the overlap shall be exact and the two objects shall have the same type, otherwise the behavior is undefined.
This affects, for example, the definedness of operations on union members: when may a value be stored into one union member and accessed via another.
It should be noted that this conflict existed in C90 and is unchanged in C99 (see, for example, section 6.5 paragraph 7 and section 6.5.16.1 paragraph 3 of ISO/IEC 9899:1999, which directly parallel the sections cited above).
Notes from the October, 2006 meeting:
This issue is based on a misunderstanding of the intent of the wording in 5.17 [expr.ass] paragraph 8. Instead of being a general statement about aliasing, it's describing the situation in which the source of the value being assigned is storage that overlaps the storage of the target object. The proposed resolution should make that clearer rather than changing the specification.
Proposed resolution (June, 2008):
Add the following note at the end of 5.17 [expr.ass] paragraph 8:
If the value being stored in an object is accessed from another object that overlaps in any way the storage of the first object, then the overlap shall be exact and the two objects shall have the same type, otherwise the behavior is undefined. [Note: This restriction applies to the relationship between the left and right sides of the assignment operation; it is not a statement about how the target of the assignment may be aliased in general. See 3.10 [basic.lval]. —end note]
[Voted into WP at October, 2009 meeting.]
complex<double> z; z = { 1,2 }; // meaning z.operator=(1,2) z += { 1, 2 }; // meaning z.operator+=(1,2)
These comments make it look as if the assignment operator takes two arguments, which is obviously not the case. It would be better if the comments read something like
// meaning z.operator=(complex<double>(1,2))
or even
// meaning z.operator=({1,2}), resolves to // z.operator=(complex<double>(1,2)
Proposed resolution (July, 2009):
Change the example in 5.17 [expr.ass] paragraph 9 as follows:
[Example:
complex<double> z; z = { 1,2 }; // meaning z.operator=({1,2}) z += { 1, 2 }; // meaning z.operator+=({1,2}) int a, b; a = b = { 1 }; // meaning a=b=1; a = { 1 } = b; // syntax error—end example]
[Voted into the WP at the March, 2009 meeting.]
It was the intention of the constexpr proposal that implementations be required to evaluate floating-point expressions at compile time. This intention is not reflected in the actual wording of 5.19 [expr.const] paragraph 2, bullet 5:
This restriction has the effect of forbidding the use of floating-point expressions in integral constant expressions.
Proposed resolution (June, 2008):
Delete bullet 6 of 5.19 [expr.const] paragraph 2:
Notes from the June, 2008 meeting:
The CWG agreed with the intent of this issue, that floating-point calculations should be permitted in constant expressions, but acknowledged that this opens the possibility of differing results between compile time and run time. Such issues should be addressed non-normatively, e.g., via a “recommended practice” note like that of C99's 6.4.4.2 or in a technical report.
Proposed resolution (August, 2008):
Delete bullet 6 of 5.19 [expr.const] paragraph 2:
Add a new paragraph after 5.19 [expr.const] paragraph 3:
[Note: Although in some contexts constant expressions must be evaluated during program translation, others may be evaluated during program execution. Since this International Standard imposes no restrictions on the accuracy of floating-point operations, it is unspecified whether the evaluation of a floating-point expression during translation yields the same result as the evaluation of the same expression (or the same operations on the same values) during program execution. [Footnote: Nonetheless, implementations are encouraged to provide consistent results, irrespective of whether the evaluation was actually performed during translation or during program execution. —end footnote] [Example:
bool f() { char array[1 + int(1 + 0.2 - 0.1 - 0.1)]; // Must be evaluated during translation int size = 1 + int(1 + 0.2 - 0.1 - 0.1); // May be evaluated at runtime return sizeof(array) == size; }It is unspecified whether the value of f() will be true or false. —end example] —end note]
[Voted into WP at October, 2009 meeting.]
Bullet 12 of paragraph 2 of 5.19 [expr.const] says,
a class member access (5.2.5 [expr.ref]) unless its postfix-expression is of effective literal type or of pointer to effective literal type;
This wording needs to be clearer that the “effective literal type” provision applies only to the . form of member access and the “pointer to effective literal type” applies only to the -> form.
Proposed resolution (March, 2009):
Delete 5.19 [expr.const] paragraph 2 bullet 11:
[Voted into WP at October, 2009 meeting.]
5.19 [expr.const] paragraph 2 allows an lvalue-to-rvalue conversion in a constant expression if it is applied to “an lvalue of effective integral type that refers to a non-volatile const variable or static data member initialized with constant expressions.” However, this does not require, as it presumably should, that the initialization occur in the same translation unit and precede the constant expression, nor that the static data member be initialized within the member-specification of its class.
Proposed resolution (March, 2009):
Change
an lvalue of effective integral type that refers to a non-volatile const variable with a preceding initialization or to a non-volatile const static data member with an initialization in the class definition (9.4.2 [class.static.data]), in either case initialized with constant expressions, or
Additional note, June, 2009:
It has been suggested that the requirement that a static data member be initialized in the class definition is not actually needed but that static data members should be treated like other variable declarations -- a preceding definition with initialization should be sufficient. That is, given
extern const int i; const int i = 5; struct S { static const int j; }; const int S::j = 5; int a1[i]; int a2[S::j];
there doesn't appear to be a good rationale for making a1 well-formed and a2 ill-formed. Some major implementations accept the declaration of a2 without error.
Proposed resolution (July, 2009):
Change 5.19 [expr.const] paragraph 2, bullet 4, sub-bullet 1 as follows:
[Voted into WP at October, 2009 meeting.]
According to 5.19 [expr.const] paragraph 2, bullet 4, sub-bullet 1, a non-volatile const variable or static data member initialized with constant expressions can be used in an integral constant expression only if it is “of effective integral type.” Unscoped enumeration types should also be accepted in such contexts.
Proposed resolution (September, 2009):
Change 5.19 [expr.const] paragraph 2, bullet 4, sub-bullet 1 as indicated:
an lvalue-to-rvalue conversion (4.1 [conv.lval]) unless it is applied to
an lvalue of effective integral or enumeration type that refers to a non-volatile const variable or static data member initialized with constant expressions, or
...
[Voted into WP at March, 2010 meeting as part of document N3078.]
5.19 [expr.const] paragraph 2 prohibits the unary & operator and an array-to-pointer conversion on operands with automatic and thread storage duration, but operands with dynamic storage duration are apparently allowed. Both these operations should be allowed only on operands with static storage duration.
[Voted into the WP at the March, 2009 meeting.]
The grammar in 7 [dcl.dcl] paragraph 1 says that a declaration-seq is either declaration or declaration-seq declaration. Some declarations end with semicolons and others (e.g. function definitions and namespace declarations) don't. This means that users who put a semicolon after every declaration are technically writing ill-formed code. The trouble is that in this respect the standard is out of sync with reality. It's convenient to allow semicolons after every declaration, and there's no implementation difficulty in doing so. All existing compilers accept this, except in extra-pedantic mode. When all implementations disagree with the standard, it's time for the standard to change.
Suggested resolution:
In the grammar in 7 [dcl.dcl] paragraph 11, change the second line in the definition of declaration-seq to
Proposed resolution (October, 2006):
Add the indicated lines to the grammar definitions in 7 [dcl.dcl] paragraph 1:
declaration:
...
namespace-definition
empty-declaration
...
static_assert-declaration:
static_assert ( constant-expression , string-literal ) ;
empty-declaration:
;
Add the following as a new paragraph after 7 [dcl.dcl] paragraph 4:
An empty-declaration has no effect.
[Voted into WP at March, 2010 meeting.]
According to 7.1 [dcl.spec] paragraph 2,
The longest sequence of decl-specifiers that could possibly be a type name is taken as the decl-specifier-seq of a declaration.
However, there are many decl-specifiers that cannot appear in a type name that are, nonetheless, part of a declaration's decl-specifier-seq, such as typedef, friend, static, etc.
Proposed resolution (November, 2009):
Change 7.1 [dcl.spec] paragraph 2 as follows:
The longest sequence of decl-specifiers that could possibly be a type name is taken as the decl-specifier-seq of a declaration If a type-name is encountered while parsing a decl-specifier-seq, it is interpreted as part of the decl-specifier-seq if and only if there is no previous type-specifier other than a cv-qualifier in the decl-specifier-seq.. The sequence shall be self-consistent as described below. [Example:...
[Voted into WP at October, 2009 meeting.]
The current wording unintentionally restricts the use of the thread_local specifier in two contexts: block-scope extern variable declarations and static data members. These restrictions are in conflict with 7.1.1 [dcl.stc] paragraph 1.
Proposed resolution (July, 2009):
Change 7.1.1 [dcl.stc] paragraph 4 as follows:
The thread_local specifier shall be applied only to the names of objects or references of namespace scope and, to the names of objects or references of block scope that also specify extern or static, and to the names of static data members. It specifies that the named object or reference has thread storage duration (3.7.2 [basic.stc.thread]).
[Voted into WP at October, 2009 meeting.]
The register keyword serves very little function, offering no more than a hint that a note says is typically ignored. It should be deprecated in this version of the standard, freeing the reserved name up for use in a future standard, much like auto has been re-used this time around for being similarly useless.
Notes from the March, 2009 meeting:
The consensus of the CWG was in favor of deprecating register.
Proposed resolution (September, 2009):
Change 7.1.1 [dcl.stc] paragraph 3 as follows:
A register specifier is a hint to the implementation that the object so declared will be heavily used. [Note: the hint can be ignored and in most implementations it will be ignored if the address of the object is taken. This use is deprecated (see [depr.register]). —end note]
Add a new section following _N3000_.D.4 [depr.string]:
register keyword [depr.register]
The use of the register keyword as a storage-class-specifier is deprecated (see 7.1.1 [dcl.stc]).
[Voted into WP at March, 2010 meeting.]
According to 7.1.1 [dcl.stc] paragraph 4,
The thread_local specifier shall be applied only to the names of objects or references of namespace scope and to the names of objects or references of block scope that also specify static.
Why require two keywords, where one on its own becomes ill-formed? thread_local should imply static in this case, and the combination of keywords should be banned rather than required. This would also eliminate the one of two exceptions documented in paragraph 1.
Notes from the July, 2009 meeting:
The consensus of the CWG was that thread_local should imply static, as suggested, but that the combination should still be allowed (it is needed, for example, for thread-local static data members).
Proposed resolution (October, 2009):
Change 7.1.1 [dcl.stc] paragraph 4 as follows:
The thread_local specifier indicates that the named entity has thread storage duration (3.7.2 [basic.stc.thread]). It shall be applied only to the names of objects or references of namespace scope, to the names of objects or references of or block scope that also specify extern or static, and to the names of static data members. It specifies that the named object or reference has thread storage duration (3.7.2 [basic.stc.thread]). When thread_local is applied to a variable of block scope the storage-class-specifier static is implied if it does not appear explicitly.
[Voted into WP at October, 2009 meeting.]
7.1.1 [dcl.stc] paragraph 1 refers to “global anonymous unions.” This reference should include anonymous unions declared in a named namespace, not just in global scope (cf 9.5 [class.union] paragraph 3).
Proposed resolution (September, 2009):
Change 7.1.1 [dcl.stc] paragraph 1 as follows:
If a storage-class-specifier appears in a decl-specifier-seq, there can be no typedef specifier in the same decl-specifier-seq and the init-declarator-list of the declaration shall not be empty (except for global an anonymous unions declared in a named namespace or in the global namespace, which shall be declared static (9.5))...
[Voted into WP at March, 2010 meeting.]
7.1.2 [dcl.fct.spec] paragraph 4 specifies that local static variables and string literals appearing in the body of an inline function with external linkage must be the same entities in every translation unit in the program. Nothing is said, however, about whether local types are likewise required to be the same.
Although a conforming program could always have determined this by use of typeid, recent changes to C++ (allowing local types as template type arguments, lambda expression closure classes) make this question more pressing.
Notes from the July, 2009 meeting:
The types are intended to be the same.
Proposed resolution (November, 2009):
Change 7.1.2 [dcl.fct.spec] paragraph 4 as follows:
...A static local variable in an extern inline function always refers to the same object. A string literal in the body of an extern inline function is the same object in different translation units. [Note: A string literal appearing in a default argument expression is not in the body of an inline function merely because the expression is used in a function call from that inline function. —end note] A type defined within the body of an extern inline function is the same type in every translation unit.
[Voted into the WP at the March, 2009 meeting.]
7.1.3 [dcl.typedef] paragraph 1 says,
The typedef specifier shall not be used in a function-definition (8.4 [dcl.fct.def])...
Does this mean that the following is ill-formed?
void f() { typedef int INT; }
Proposed resolution (March, 2008):
Change 7.1.3 [dcl.typedef] paragraph 1 as follows:
...The typedef specifier shall not be used in a function-definition (8.4 [dcl.fct.def]), and it shall not be combined in a decl-specifier-seq with any other kind of specifier except a type-specifier, and it shall not be used in the declaration of a function parameter nor in the decl-specifier-seq of a function-definition (8.4 [dcl.fct.def])...
Proposed resolution (September, 2008):
Change 7.1.3 [dcl.typedef] paragraph 1 as follows:
...The typedef specifier shall not be used in a function-definition (8.4 [dcl.fct.def]), and it shall not be combined in a decl-specifier-seq with any other kind of specifier except a type-specifier, and it shall be used neither in the decl-specifier-seq of a parameter-declaration (8.3.5 [dcl.fct]) nor in the decl-specifier-seq of a function-definition (8.4 [dcl.fct.def]).
[Voted into WP at October, 2009 meeting.]
According to 7.1.5 [dcl.constexpr] paragraph 1,
The constexpr specifier shall be applied only to the definition of an object, function, or function template, or to the declaration of a static data member of a literal type (3.9 [basic.types]).
As a result, a constexpr member function cannot be simply declared in the class member-specification and defined later; it must be defined in its initial declaration.
This restriction was not part of the initial proposal but was added during the CWG review. However, the original intent is still visible in some of the wording in 7.1.5 [dcl.constexpr]. For example, paragraph 2 refers to applying the constexpr specifier to the “declaration” and not the “definition” of a function or constructor. Although that is formally correct, as definitions are also declarations, it could be confusing. Also, the example in paragraph 6 reads,
class debug_flag { public: explicit debug_flag(bool); constexpr bool is_on(); // error: debug_flag not literal type ...
when the proximate error is that is_on is only declared, not defined. There are also many occurrences of the constexpr specifier in the library clauses where the member function is only declared, not defined.
It's not clear how much simplification is gained by this restriction. There are reasons for defining ordinary inline functions outside the class member-specification (reducing the size and complexity of the class definition, separating interface from implementation, making the editing task easier if program evolution results in an inline function being made non-inline, etc.) that would presumably apply to constexpr member functions as well. It seems feasible to allow separate declaration and definition of a constexpr function; it would simply not be permitted to use it in a constant expression before the definition is seen (although it could presumably still be used in non-constant expressions in that region, like an ordinary inline function).
If the prohibition were relaxed to allow separate declaration and definition of constexpr member functions, some questions would need to be answered, such as whether the constexpr specifier must appear on both declaration and definition (the inline specifier need not). If it can be omitted in one or the other, there's a usability issue regarding the fact that constexpr implies const; the const qualifier would need to be specified explicitly in the declaration in which constexpr was omitted.
If the current restriction is kept, the library clauses should state in an introduction that a non-defining declaration of a constexpr member function should be considered “for exposition only” and not literal code.
Notes from the September, 2008 meeting:
In addition to the original issues described above, the question has arisen whether recursive constexpr functions are or should be permitted. Although they were originally desired by the proposers of the feature, they were prohibited out of an abundance of caution. However, the wording that specified the prohibition was changed during the review process, inadvertently permitting them.
The CWG felt that there are sufficient use cases for recursion that it should not be forbidden (although a new minimum for recursion depth should be added to Annex B [implimits]). If mutual recursion is to be allowed, forward declaration of constexpr functions must also be permitted (answering the original question in this issue). A call to an undefined constexpr function in the body of a constexpr function should be diagnosed when the outer constexpr function is invoked in a context requiring a constant expression; in all other contexts, a call to an undefined constexpr function should be treated as a normal runtime function call, just as if it had been invoked with non-constant arguments.
Proposed resolution (July, 2009):
Change 5 [expr] paragraph 4 as follows:
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 appears where an integral constant expression is required (5.19 [expr.const]), in which case the program is ill-formed. [Note: most existing implementations of C++ ignore integer overflows. Treatment of division by zero, forming a remainder using a zero divisor, and all floating point exceptions vary among machines, and is usually adjustable by a library function. —end note]
Add the indicated text to 5.19 [expr.const] paragraph 2:
...
an invocation of a function other than a constexpr function or a constexpr constructor [Note: overload resolution (13.3 [over.match]) is applied as usual —end note];
a direct or indirect invocation of an undefined constexpr function or an undefined constexpr constructor outside the definition of a constexpr function or a constexpr constructor;
a result that is not mathematically defined or not in the range of representable values for its type;
...
Change 7.1.5 [dcl.constexpr] paragraph 1 as follows:
The constexpr specifier shall be applied only to the definition of an object, the declaration of a function, or function template, or to the declaration of a static data member of an effective literal type (3.9 [basic.types]). If any declaration of a function or function template has the constexpr specifier, then all its declarations shall contain the constexpr specifier. [Note: An explicit specialization can differ from the template declaration with respect to the constexpr specifier. —end note] [Note: function parameters cannot be declared constexpr. —end note] [Example:
constexpr int square(int x); //OK, declaration constexpr int square(int x) { // OK return x * x; } constexpr int bufsz = 1024; // OK, definition constexpr struct pixel { // error: pixel is a type int x; int y; constexpr pixel(int); // OK, declaration }; constexpr pixel::pixel(int a) : x(square(a)), y(square(a)) { } //OK, definition constexpr pixel small(2); // error: square not defined, so small(2) // not constant (5.19 [expr.const]), so constexpr not satisfied constexpr int square(int x) { // OK, definition return x * x; } constexpr pixel large(4); // OK, square defined int next(constexpr int x) { // error, not for parameters return x + 1; } extern constexpr int memsz; // error: not a definition—end example]
Add a new section following 17.6.5.5 [member.functions]:
Implementations shall provide definitions for any non-defining declarations of constexpr functions and constructors within the associated header files.
Add the following bullet to the list in B [implimits] paragraph 2:
Nested external specifications [1 024].
Recursive constexpr function invocations [512].
...
(This resolution also resolves issue 695.)
[Voted into WP at March, 2010 meeting as document N3078.]
It would be useful if constexpr functions and constructors could take
arguments via reference-to-const parameters.
[Voted into WP at March, 2010 meeting.]
The normative text in 7.1.6.1 [dcl.type.cv] paragraph 2 reads,
An object declared in namespace scope with a const-qualified type has internal linkage unless it is explicitly declared extern or unless it was previously declared to have external linkage. A variable of non-volatile const-qualified integral or enumeration type initialized by an integral constant expression can be used in integral constant expressions (5.19 [expr.const]).
These two sentences parallel the specifications of 7.1.1 [dcl.stc] paragraph 7 and 5.19 [expr.const]. However, the passages are not identical, leading to questions about whether the meanings are the same.
Proposed resolution (October, 2009):
Change 7.1.6.1 [dcl.type.cv] paragraph 2 as follows:
An object declared in namespace scope with a const-qualified type has internal linkage unless it is explicitly declared extern or unless it was previously declared to have external linkage. A variable of non-volatile const-qualified integral or enumeration type initialized by an integral constant expression can be used in integral constant expressions (5.19 [expr.const]). [Note: Declaring a variable const can affect its linkage (7.1.1 [dcl.stc]) and its usability in constant expressions (5.19 [expr.const]). As as described in 8.5 [dcl.init], the definition of an object or subobject of const-qualified type must specify an initializer or be subject to default-initialization. —end note]
[Voted into WP at March, 2010 meeting as document N3049.]
In the current specification, a decltype resulting in a class type is not a class-name, meaning that it cannot be used as a base-specifier. There doesn't seem to be any reason not to allow that, and it would be consistent with the proposed outcome of issue 743.
Proposed resolution (February, 2010):
See paper PL22.16/10-0021 = WG21 N3031.
[Voted into WP at March, 2010 meeting.]
References to references are ill-formed, but special provision is made in cases where this occurs via typedefs or template type parameters. A similar provision is probably needed for types resulting from decltype:
int x, *p = &x; decltype(*p) &y = *p; // reference to reference is ill-formed
Proposed resolution (October, 2009):
Delete 7.1.3 [dcl.typedef] paragraph 9:
If a typedef TD names a type that is a reference to a type T, an attempt to create the type “lvalue reference to cv TD” creates the type “lvalue reference to T,” while an attempt to create the type “rvalue reference to cv TD” creates the type TD. [Example: ... —end example]
Delete 14.3.1 [temp.arg.type] paragraph 4:
If a template-argument for a template-parameter T names a type that is a reference to a type A, an attempt to create the type “lvalue reference to cv T” creates the type “lvalue reference to A,” while an attempt to create the type “rvalue reference to cv T” creates the type T [Example: ... —end example]
Add the following as a new paragraph at the end of 8.3.2 [dcl.ref]:
If a typedef (7.1.3 [dcl.typedef]), a type template-parameter (14.3.1 [temp.arg.type]), or a decltype-specifier (7.1.6.2 [dcl.type.simple]) denotes a type TR that is a reference to a type T, an attempt to create the type “lvalue reference to cv TR” creates the type “lvalue reference to T,” while an attempt to create the type “rvalue reference to cv TR” creates the type TR. [Example:
int i; typedef int& LRI; typedef int&& RRI; LRI& r1 = i; // r1 has the type int& const LRI& r2 = i; // r2 has the type int& const LRI&& r3 = i; // r3 has the type int& RRI& r4 = i; // r4 has the type int& RRI&& r5 = i; // r5 has the type int&& decltype(r2)& r6 = i; // r6 has the type int& decltype(r2)&& r7 = i; // r7 has the type int&
—end example]
[Voted into WP at March, 2010 meeting.]
There is a lack of symmetry in the specification of attributes that apply to class and enum types. For example:
class X [[attr]]; // #1 typedef class Y [[attr]] YT; // #2
According to 7.1.6.3 [dcl.type.elab] paragraph 1, #1 associates the attr attribute with class X for all subsequent references. On the other hand, 8.3 [dcl.meaning] paragraph 5 says that #2 associates the attr attribute with the type but not with class Y.
Existing implementations (Microsoft, GNU, Sun) with attributes place an attribute that is intended to be associated with a class type between the class-key and the class name, and it would be preferable to adopt such an approach instead of the contextual approach in the current formulation.
Proposed resolution (October, 2009):
Change 3.3.2 [basic.scope.pdecl] paragraph 6 bullet 1 as follows:
for a declaration of the form
the identifier is declared...
Change 3.4.4 [basic.lookup.elab] paragraph 2 as follows:
...unless the elaborated-type-specifier appears in a declaration with the following form:
class-key attribute-specifieropt identifier attribute-specifieropt ;
the identifier is looked up... if the elaborated-type-specifier appears in a declaration with the form:
class-key attribute-specifieropt identifier attribute-specifieropt ;
the elaborated-type-specifier is a declaration...
In 7.1.6.3 [dcl.type.elab], change the grammar and paragraph 1 as follows:
elaborated-type-specifier:
class-key attribute-specifieropt ::opr nested-name-specifieropt identifier
...An attribute-specifier shall not appear in an elaborated-type-specifier unless the latter is the sole constituent of a declaration. If an elaborated-type-specifier is the sole constituent of a declaration, the declaration is ill-formed unless it is an explicit specialization (14.7.3 [temp.expl.spec]), an explicit instantiation (14.7.2 [temp.explicit]) or it has one of the following forms:
class-key attribute-specifieropt identifier attribute-specifieropt ;
...
Change the grammar in 7.2 [dcl.enum] paragraph 1 as follows:
Change the grammar in 9 [class] paragraph 1 as follows:
[Voted into WP at March, 2010 meeting.]
The auto specifier can be used only in certain contexts, as specified in 7.1.6.4 [dcl.spec.auto] paragraphs 2-3:
Otherwise (auto appearing with no type specifiers other than cv-qualifiers), the auto type-specifier signifies that the type of an object being declared shall be deduced from its initializer. The name of the object being declared shall not appear in the initializer expression.
This use of auto is allowed when declaring objects in a block (6.3 [stmt.block]), in namespace scope (3.3.6 [basic.scope.namespace]), and in a for-init-statement (6.5.3 [stmt.for]). The decl-specifier-seq shall be followed by one or more init-declarators, each of which shall have a non-empty initializer of either of the following forms:
= assignment-expression
( assignment-expression )
It was intended that auto could be used only at the top level of a declaration, but it is not clear whether this wording is sufficient to forbid usage like the following:
template <class T> struct A {}; template <class T> void f(A<T> x) {} void g() { f(A<short>()); A<auto> x = A<short>(); }
Notes from the February, 2008 meeting:
It was agreed that the example should be ill-formed.
Proposed resolution (October, 2009):
Change 7.1.6.4 [dcl.spec.auto] paragraph 3 as follows:
...The auto shall appear as one of the decl-specifiers in the decl-specifier-seq and the decl-specifier-seq shall be followed by one or more init-declarators, each of which shall have a non-empty initializer.
[Voted into WP at July, 2009 meeting.]
One effect of the initializer-list proposal is that now we allow
auto x = { 1, 2, 3 }; // decltype(x) is std::initializer_list<int>
but not
auto ar[3] = { 1, 2, 3 }; // ill-formed
This seems unfortunate, as the code for the first could also support the second. Incidentally, I also failed to update the text in 7.1.6.4 [dcl.spec.auto] paragraph 3 which forbids the use of auto with braced-init-lists, so technically the first form above is currently ill-formed but has defined semantics.
Bjarne Stroustrup:
Is this the thin edge of a wedge? How about
vector<auto> v = { 1, 2, 3 };
and
template<class T> void f(vector<T>& v); f({1, 2, 3 });
(See also issue 625.)
Proposed resolution (March, 2009):
Change 7.1.6.4 [dcl.spec.auto] paragraph 3 as follows:
...The decl-specifier-seq shall be followed by one or more init-declarators, each of which shall have a non-empty initializer. of either of the following forms:= assignment-expression
( assignment-expression )
[Drafting note: This change does not address the original issue of the inability to use auto with an array initializer, only the secondary issue of permitted the braced-init-list. The CWG explicitly decided not to support the array case.]
[Voted into WP at July, 2009 meeting.]
In listing the acceptable contexts in which the auto specifier may appear, 7.1.6.4 [dcl.spec.auto]) paragraph 4 mentions “the type-specifier-seq in a new-type-id” but not the type-id in the parenthesized form; that is, new auto (42) is well-formed but new (auto) (42) is not. This seems an unnecessary restriction, as well as contradicting 5.3.4 [expr.new] paragraph 2:
If the auto type-specifier appears in the type-specifier-seq of a new-type-id or type-id of a new-expression...
Proposed resolution (March, 2009):
Change 7.1.6.4 [dcl.spec.auto] paragraph 4 as follows:
The auto type-specifier can also be used in declaring an object in the condition of a selection statement (6.4 [stmt.select]) or an iteration statement (6.5 [stmt.iter]), in the type-specifier-seq in a the new-type-id or type-id of a new-expression (5.3.4 [expr.new]), in a for-range-declaration...
[Voted into WP at March, 2010 meeting.]
7.1.6.4 [dcl.spec.auto] paragraph 6 says,
Once the type of a declarator-id has been determined according to 8.3 [dcl.meaning], the type of the declared variable using the declarator-id is determined from the type of its initializer using the rules for template argument deduction. Let T be the type that has been determined for a variable identifier d. Obtain P from T by replacing the occurrences of auto with either a new invented type template parameter U or, if the initializer is a braced-init-list (8.5.4 [dcl.init.list]), with std::initializer_list<U>. The type deduced for the variable d is then the deduced type determined using the rules of template argument deduction from a function call (14.8.2.1 [temp.deduct.call]), where P is a function template parameter type and the initializer for d is the corresponding argument.
The reference to “the deduced type” is unclear; it could be taken as referring either to the template parameter or to the function parameter type. 14.8.2.1 [temp.deduct.call] uses the term “deduced A,” and that usage should be repeated here.
Proposed resolution (October, 2009):
Change 7.1.6.4 [dcl.spec.auto] paragraph 6 as follows:
...The type deduced for the variable d is then the deduced type A determined using the rules of template argument deduction...
[Voted into the WP at the March, 2009 meeting.]
According to 7.2 [dcl.enum] paragraph 6, the underlying type of an enumeration with an empty enumeration-list is determined as if the enumeration-list contained a single enumerator with value 0. Paragraph 7, which specifies the values of an enumeration and the minimum size of bit-field needed represent those values needs a similar provision for empty enumeration-lists.
Proposed resolution (March, 2008):
Add the indicated sentence to the end of 7.2 [dcl.enum] paragraph 5:
...It is possible to define an enumeration that has values not defined by any of its enumerators. If the enumerator-list is empty, the values of the enumeration are as if the enumeration had a single enumerator with value 0.
[Voted into WP at October, 2009 meeting.]
The type of an enumerator that has no initializing value in an enumeration whose underlying type is not fixed is given by the third bullet of 7.2 [dcl.enum] paragraph 5:
the type of the initializing value is the same as the type of the initializing value of the preceding enumerator unless the incremented value is not representable in that type, in which case the type is an unspecified integral type sufficient to contain the incremented value.
This does not address the case in which there is no such type, meaning that it is apparently undefined behavior. Other cases in which an enumeration value is unrepresentable are made ill-formed (see the preceding paragraph for an enumeration with a fixed underlying type and the following paragraph for the case in which the minimum and maximum values cannot be represented by a single type). It would be better if this case were ill-formed as well, instead of causing undefined behavior.
Proposed resolution (July, 2009):
Change 7.2 [dcl.enum] paragraph 5, bullet 3 as follows:
[Voted into WP at March, 2010 meeting.]
It is not clear from the specification in 7.3.1 [namespace.def] paragraph 8 how a declaration in an inline namespace should be handled if the name is the same as one in the containing namespace or in an parallel inline namespace. For example:
namespace Q { inline namespace V1 { int i; int j; } inline namespace V2 { int j; } int i; } int Q::i = 1; // Q::i or Q::V1::i? int Q::j = 2; // Q::V1::j or Q::V2::j?
Proposed resolution (July, 2009):
This issue is resolved by the resolution of issue 861.
[Voted into WP at March, 2010 meeting as part of document N3079.]
According to 7.3.1 [namespace.def] paragraph 8,
Members of an inline namespace can be used in most respects as though they were members of the enclosing namespace... Furthermore, each member of the inline namespace can subsequently be explicitly instantiated (14.7.2 [temp.explicit]) or explicitly specialized (14.7.3 [temp.expl.spec]) as though it were a member of the enclosing namespace.
However, that assertion is contradicted for class template specializations by 9 [class] paragraph 11:
If a class-head contains a nested-name-specifier, the class-specifier shall refer to a class that was previously declared directly in the class or namespace to which the nested-name-specifier refers...
It is also contradicted for function template specializations by 3.4.3.2 [namespace.qual] paragraph 6:
In a declaration for a namespace member in which the declarator-id is a qualified-id, given that the qualified-id for the namespace member has the formnested-name-specifier unqualified-id
the unqualified-id shall name a member of the namespace designated by the nested-name-specifier.
Proposed resolution (November, 2009):
Change 9 [class] paragraph 11 as follows:
If a class-head contains a nested-name-specifier, the class-specifier shall refer to a class that was previously declared directly in the class or namespace to which the nested-name-specifier refers, or in an element of the inline namespace set (7.3.1 [namespace.def]) of that namespace (i.e., neither not merely inherited nor or introduced by a using-declaration), and the class-specifier shall appear in a namespace enclosing the previous declaration.
Change 3.4.3.2 [namespace.qual] paragraph 6 as follows:
In a declaration for a namespace member in which the declarator-id is a qualified-id, given that the qualified-id for the namespace member has the form
nested-name-specifier unqualified-id
the unqualified-id shall name a member of the namespace designated by the nested-name-specifier, or of an element of the inline namespace (7.3.1 [namespace.def]) of that namespace. [Example:...
(Note: this resolution depends on the resolution for issue 861.)
[Voted into WP at October, 2009 meeting.]
According to 7.3.1 [namespace.def] paragraph 8,
Specifically, the inline namespace and its enclosing namespace are considered to be associated namespaces (3.4.2 [basic.lookup.argdep]) of one another, and a using-directive (7.3.4 [namespace.udir]) that names the inline namespace is implicitly inserted into the enclosing namespace.
There are two problems with this sentence. First, the concept of namespaces being associated with each other is undefined; 3.4.2 [basic.lookup.argdep] describes how namespaces are associated with types, not with other namespaces. Second, unlike unnamed namespaces, the location of the implicit using-directive is not specified.
Proposed resolution (July, 2009):
Change 7.3.1 [namespace.def] paragraph 8 as follows:
...Specifically, the inline namespace and its enclosing namespace are considered to be associated namespaces (3.4.2 [basic.lookup.argdep]) of one another both added to the set of associated namespaces used in argument-dependent lookup (3.4.2 [basic.lookup.argdep]) whenever one of them is, and a using-directive (7.3.4 [namespace.udir]) that names the inline namespace is implicitly inserted into the enclosing namespace as for an unnamed namespace (7.3.1.1 [namespace.unnamed]). Furthermore...
[Voted into WP at October, 2009 meeting.]
In 7.3.1 [namespace.def] paragraph 1, an unnamed-namespace-definition is defined as
However, there is no provision for the inline keyword in the expansion of unnamed namespaces in 7.3.1.1 [namespace.unnamed] paragraph 1. Strictly interpreted, that would mean that the inline qualifier is ignored for unnamed namespaces.
Proposed resolution (September, 2009):
Change 7.3.1.1 [namespace.unnamed] paragraph 1 as follows:
An unnamed-namespace-definition behaves as if it were replaced by
inlineopt namespace unique { /* empty body */ } using namespace unique ; namespace unique { namespace-body }
where inline appears if and only if it appears in the unnamed-namespace-definition, all occurrences of unique in a translation unit are replaced by the same identifier, and this identifier differs from all other identifiers in the entire program.87 [Example:...
[Voted into WP at March, 2010 meeting.]
According to 7.3.4 [namespace.udir] paragraph 4,
The using-directive is transitive: if a scope contains a using-directive that nominates a second namespace that itself contains using-directives, the effect is as if the using-directives from the second namespace also appeared in the first.
This is true only for unqualified lookup; the algorithm in 3.4.3.2 [namespace.qual] paragraph 2 gives different results (the transitive closure terminates when a declaration of the name being looked up is found).
Proposed resolution (October, 2009):
Change 7.3.4 [namespace.udir] paragraph 4 as follows:
The For unqualified lookup (3.4.1 [basic.lookup.unqual]), the using-directive is transitive: if a scope contains a using-directive that nominates a second namespace that itself contains using-directives, the effect is as if the using-directives from the second namespace also appeared in the first. [Note: For qualified lookup, see 3.4.3.2 [namespace.qual]. —end note] [Example:...[Voted into the WP at the March, 2009 meeting.]
The wording of 7.5 [dcl.link] paragraph 5 is suspect:
If two declarations of the same function or object specify different linkage-specifications (that is, the linkage-specifications of these declarations specify different string-literals), the program is ill-formed if the declarations appear in the same translation unit, and the one definition rule (3.2) applies if the declarations appear in different translation units.
But what if only one of the declarations has a linkage-specification, while the other is left with the default C++ linkage? Shouldn't this restriction be phrased in terms of the functions’ or objects’ language linkage rather than linkage-specifications?
(Additional note [wmm]: Is the ODR the proper vehicle for enforcing this requirement? This is dealing with declarations, not necessarily definitions. Shouldn't this say “ill-formed, no diagnostic required” instead of some vague reference to the ODR?)
Proposed resolution (June, 2008):
Change 7.5 [dcl.link] paragraph 5 as follows:
If two declarations of the same function or object declare functions with the same name and parameter-type-list (8.3.5 [dcl.fct]) to be members of the same namespace or declare objects with the same name to be members of the same namespace specify different linkage-specifications (that is, the linkage-specifications of these declarations specify different string-literals) and the declarations give the names different language linkages, the program is ill-formed if the declarations appear in the same translation unit, and the one definition rule (3.2 [basic.def.odr]) applies; no diagnostic is required if the declarations appear in different translation units.
[Voted into WP at March, 2010 meeting as paper N3050.]
A function with an exception-specification of throw() must be given a catch(...) clause to enforce its contract, i.e., to call std::unexpected() if it exits with an exception. It would be useful to have an attribute indicating that the function really does throw nothing and thus that the catch(...) clause need not be generated.
(See also issue 830.)
Proposed resolution (September, 2009):
See paper PL22.16/09-0162 = WG21 N2972.
[Voted into WP at March, 2010 meeting as document N3067.]
There are a number of problems with the treatment of attributes in the current draft. One issue is the failure to permit attributes to appear at various points in the grammar at which one might plausibly expect them:
In a new-type-id (5.3.4 [expr.new])
Preceding the type-specifier-seq in a condition (6.4 [stmt.select])
In a for-init-statement that is an expression-statement (6.5 [stmt.iter])
Preceding the type-specifier-seq in a for-range-declaration (6.5 [stmt.iter])
In a reference ptr-operator (8 [dcl.decl])
Preceding the type-specifier-seq in a type-id (8.1 [dcl.name])
Preceding the decl-specifier-seq in a parameter-declaration (8.3.5 [dcl.fct])
In a function-definition (8.4 [dcl.fct.def]) at any of the three locations where they might be expected:
preceding the decl-specifier-seq
following the parameter list (paragraph 2 repeats the syntax from 8.3.5 [dcl.fct] with the conspicuous omission of the attribute-specifier)
preceding the compound-statement of the function-body (this would introduce an ambiguity with the attribute-specifier following the parameter list that would need to be addressed)
Preceding the decl-specifier-seq of a member-declaration (9.2 [class.mem])
Preceding the compound-statement of a try-block or handler (15 [except])
Preceding the type-specifier-seq of an exception-declaration (15 [except])
Another group of problems is the failure to specify to what a given attribute-specifier appertains:
In a condition (6.4 [stmt.select])
In a for-range-declaration (6.5.4 [stmt.ranged])
In a parameter-declaration (8.3.5 [dcl.fct])
In a conversion-type-id (12.3.2 [class.conv.fct])
There is also a problem in the specification of the interpretation of an initial attribute-specifier. 8.3 [dcl.meaning] paragraph 5 says,
In a declaration attribute-specifieropt T attribute-specifieropt D where D is an unadorned identifier the type of this identifier is “T”. The first optional attribute-specifier appertains to the entity being declared.
This wording only covers the case where the declarator is a simple identifier. It leaves unspecified the meaning of the initial attribute-specifier with more complex declarators for pointers, references, functions, and arrays.
Finally, something needs to be said about the case where attribute-specifiers occur in both the initial position and following the declarator-id: is this permitted, and if so, under what constraints?
(See also issue 968.)
Proposed resolution (February, 2010):
See paper PL22.16/10-0023 = WG21 N3033.
[Voted into WP at March, 2010 meeting.]
The terms “appertain” and “apply” are used in different ways in different subsections of 7.6 [dcl.attr]. A thorough editorial sweep of the entire section is needed to regularize their usage.
Proposed resolution (October, 2009):
Change 7.6.1 [dcl.attr.grammar] paragraph 4 as follows:
...If an attribute-specifier that appertains to some entity or statement contains an attribute that does not is not allowed to apply to that entity or statement, the program is ill-formed...
Change 7.6.2 [dcl.align] paragraph 1 as follows:
...The attribute can may be applied to a variable...
Change 7.6.3 [dcl.attr.noreturn] paragraph 1 as follows:
...The attribute applies may be applied to the declarator-id in a function declaration...
Change _N3225_.7.6.4 [dcl.attr.final] paragraph 1 as follows:
...The attribute applies may be applied to class definitions...
Change _N3225_.7.6.5 [dcl.attr.override] paragraph 1 as follows:
...The attribute applies may be applied to virtual member functions...
Change _N3225_.7.6.5 [dcl.attr.override] paragraph 3 as follows:
...The attribute applies may be applied to class members...
Change _N3225_.7.6.5 [dcl.attr.override] paragraph 5 as follows:
...The attribute applies may be applied to a class definition.
Change 7.6.4 [dcl.attr.depend] paragraph 1 as follows:
...The attribute applies may be applied to the declarator-id of a parameter-declaration... The attribute may also applies be applied to the declarator-id of a function declaration...
[Voted into WP at July, 2009 meeting as N2933.]
Parameter packs should be expanded inside attributes. For example, it would be useful to specify the alignment of each element in a pack expansion using a parallel pack expansion.
[Voted into WP at March, 2010 meeting.]
7.6.1 [dcl.attr.grammar] paragraph 3 specifies that keywords can be used as attribute-tokens. However, the alternative tokens in 2.6 [lex.digraph], such as bitor and compl, are not keywords. The text should be changed to make the alternative tokens acceptable as attribute-tokens as well.
Proposed resolution, October, 2009:
Change 7.6.1 [dcl.attr.grammar] paragraph 3 as follows:
...A If a keyword (2.12 [lex.key]) or an alternative token (2.6 [lex.digraph]) that satisfies the syntactic requirements of an identifier (2.11 [lex.name]) is contained in an attribute-token, it is considered an identifier...
[Voted into WP at March, 2010 meeting as document N3063.]
The [[ ... ]] notation for attributes was thought to be completely unambiguous. However, it turns out that two [ characters can be adjacent and not be an attribute-introducer: the first could be the beginning of an array bound or subscript operator and the second could be the beginning of a lambda-introducer. This needs to be explored and addressed.
(See also issue 951.)
Proposed resolution (November, 2009):
Add the following paragraph at the end of 8.2 [dcl.ambig.res]:
Two consecutive left square bracket tokens shall appear only when introducing an attribute-specifier. [Note: If two consecutive left square brackets appear where an attribute-specifier is not allowed, the program is ill-formed even if the brackets match an alternative grammar production. —end note] [Example:
int p[10]; void f() { int x = 42; int(p[[x]{return x;}()]); // Error: malformed attribute on a nested // declarator-id and not a function-style cast of // an element of p. new int[[]{return x;}()]; // Error even though attributes are not allowed } // in this context.
—end example]
[Voted into WP at March, 2010 meeting.]
According to 7.6.2 [dcl.align] paragraph 1, an alignment attribute can be specified only for a variable or a class data member. The corresponding Microsoft and GNU attributes can be also specified for a class type, and this usage seems to be widespread. It should be permitted with the standard attribute and there seems no good reason not to do so for enumeration types, as well.
Notes from the October, 2009 meeting:
Although there was initial concern for how to integrate the suggested change into the type system, it was observed that current practice is to have the attribute affect only the layout, not the type.
Proposed resolution (February, 2010):
Change 7.6.2 [dcl.align] paragraphs 1-2 as follows:
...The attribute can be applied to a variable that is neither a function parameter nor declared with the register storage class specifier and to a class data member that is not a bit-field. The attribute can also be applied to the declaration of a class or enumeration type.
When the alignment attribute is of the form align(assignment-expression):
...
if the constant expression evaluates to a fundamental alignment, the alignment requirement of the declared object entity shall be the specified fundamental alignment
if the constant expression evaluates to an extended alignment and the implementation supports that alignment in the context of the declaration, the alignment of the declared object entity shall be that alignment
...
Change 7.6.2 [dcl.align] paragraphs 4-6 as follows:
When multiple alignment attributes are specified for an object entity, the alignment requirement shall be set to the strictest specified alignment.
The combined effect of all alignment attributes in a declaration shall not specify an alignment that is less strict than the alignment that would otherwise be required for the object entity being declared.
If the defining declaration of an object entity has an alignment attribute, any non-defining declaration of that object entity shall either specify equivalent alignment or have no alignment attribute. Conversely, if any declaration of an entity has an alignment attribute, every defining declaration of that entity shall specify an equivalent alignment. No diagnostic is required if declarations of an object entity have different alignment attributes in different translation units.
Insert the following as a new paragraph after 7.6.2 [dcl.align] paragraph 6:
[Example:
// Translation unit #1: struct S { int x; } s, p = &s; // Translation unit #2: struct [[align(16)]] S; // error: definition of S lacks alignment; no extern S* p; // diagnostic required
—end example]
Delete 7.6.2 [dcl.align] paragraph 8:
[Note: the alignment of a union type can be strengthened by applying the alignment attribute to any non-static data member of the union. —end note]
[Voted into WP at March, 2010 meeting.]
The current wording for the carries_dependency attribute does not limit it to value-returning functions (when applied to the declarator-id, indicating that the return value is affected), nor does it prohibit use in the declaration of a typedef or function pointer. Arguably these meaningless declarations should be prohibited.
Proposed resolution (October, 2009):
Change 7.6.4 [dcl.attr.depend] paragraph 1 as follows:
...The attribute applies to the declarator-id of a parameter-declaration in a function declaration or lambda, in which case it specifies that the initialization of the parameter carries a dependency to (1.10 [intro.multithread]) each lvalue-to-rvalue conversion (4.1 [conv.lval]) of that object. The attribute also applies to the declarator-id of a function declaration, in which case it specifies that the return value, if any, carries a dependency to the evaluation of the function call expression.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
It is currently unspecified whether a declaration like
f() -> struct S { };
should be parsed as a declaration of f whose return type is a class definition (which will be ill-formed according to 7.1.6 [dcl.type] paragraph 3) or as a definition of f whose return type is an elaborated-type-specifier.
Proposed resolution (June, 2009):
See document PL22.16/09-0117 = WG21 N2927.
In function, pointer, and pointer-to-member declarators, the attribute-specifier appertains to the type being declared, but the syntax has the attribute-specifieropt appearing before the full type is seen — i.e., before the cv-qualifier-seqopt and, for the function case, before the ref-qualifieropt. GNU attributes appear after these elements (and, for the function case, after the exception-specificationopt as well). It would be better, both logically and for consistency with existing practice, to move the attribute-specifieropt accordingly.
[Voted into WP at March, 2010 meeting as document N3064.]
This case is nonstandard by 8.3 [dcl.meaning] paragraph 1 (there is a requirement that the specialization first be declared within the namespace before being defined outside of the namespace), but probably should be allowed:
namespace NS1 { template<class T> class CDoor { public: int mtd() { return 1; } }; } template<> int NS1::CDoor<char>::mtd() { return 0; }
Notes from October 2002 meeting:
There was agreement that we wanted to allow this.
Proposed resolution (February, 2010):
Change 8.3 [dcl.meaning] as follows:
...A declarator-id shall not be qualified except for the definition of a member function (9.3 [class.mfct]) or static data member (9.4 [class.static]) outside of its class, the definition or explicit instantiation of a function or variable member of a namespace outside of its namespace, or the definition of a previously declared an explicit specialization outside of its namespace, or the declaration of a friend function that is a member of another class or namespace (11.3 [class.friend]). When the declarator-id is qualified, the declaration shall refer to a previously declared member of the class or namespace to which the qualifier refers (or of an inline namespace within that scope (7.3.1 [namespace.def])) or to a specialization thereof, and the member shall not have been introduced by a using-declaration in the scope of the class or namespace nominated by the nested-name-specifier of the declarator-id. [Note:...
Change 14.7.3 [temp.expl.spec] paragraphs 2-4 as follows:
An explicit specialization shall appear in namespace scope. An explicit specialization whose declarator-id is not qualified shall be declared in the nearest enclosing namespace of the template, or, if the namespace is inline (7.3.1 [namespace.def]), any namespace from its enclosing namespace set. Such a declaration may also be a definition. If the declaration is not a definition, the specialization may be defined later (7.3.1.2 [namespace.memdef]).
A declaration of a function template or class template being explicitly specialized shall be in scope at the point of precede the declaration of an the explicit specialization. [Note: a declaration, but not a definition of the template is required. —end note] The definition of a class or class template shall be in scope at the point of precede the declaration of an explicit specialization for a member template of the class or class template. [Example: ... —end example]
A member function, a member class or a static data member of a class template may be explicitly specialized for a class specialization that is implicitly instantiated; in this case, the definition of the class template shall be in scope at the point of declaration of preced the explicit specialization for the member of the class template. If such an explicit specialization for the member of a class template names an implicitly-declared special member function (Clause 12 [special]), the program is ill-formed.
[Voted into WP at March, 2010 meeting as part of document N3079.]
According to 8.3 [dcl.meaning] paragraph 1,
When the declarator-id is qualified, the declaration shall refer to a previously declared member of the class or namespace to which the qualifier refers (or of an inline namespace within that scope (7.3.1 [namespace.def])), and the member shall not have been introduced by a using-declaration in the scope of the class or namespace nominated by the nested-name-specifier of the declarator-id.
This would appear to make the following example ill-formed, even though it would be well-formed if the using-declaration were omitted:
namespace A { inline namespace B { template <class T> void foo() { } } using B::foo; } template void A::foo<int>();
This seems strange.
Proposed resolution (July, 2009):
Change 8.3 [dcl.meaning] paragraph 1 as follows:
...When the declarator-id is qualified, the declaration shall refer to a previously declared member of the class or namespace to which the qualifier refers (or, in the case of a namespace, of an element of the inline namespace within that scope set of that namespace (7.3.1 [namespace.def])), and; the member shall not merely have been introduced by a using-declaration in the scope of the class or namespace nominated by the nested-name-specifier of the declarator-id. [Note:...
(Note: this resolution depends on the resolution of issue 861.)
[Voted into WP at March, 2010 meeting.]
Paragraph 7 of 8.3.4 [dcl.array] says,
If E is an n-dimensional array of rank i × j × ... × k, then E appearing in an expression is converted to a pointer to an (n - 1)-dimensional array with rank j × ... × k.
This formulation does not allow for the existence of expressions in which the array-to-pointer conversion does not occur (as specified in clause 5 [expr] paragraph 9). This paragraph should be no more than a note, if it appears at all, and the wording should be corrected.
Proposed resolution (November, 2009):
Change paragraphs 6-8 of 8.3.4 [dcl.array] into a note and make the indicated changes:
[Note: Except where it has been declared for a class (13.5.5 [over.sub]), the subscript operator [] is interpreted in such a way that E1[E2] is identical to *((E1)+(E2)). Because of the conversion rules that apply to +, if E1 is an array and E2 an integer, then E1[E2] refers to the E2-th member of E1. Therefore, despite its asymmetric appearance, subscripting is a commutative operation.
A consistent rule is followed for multidimensional arrays. If E is an n-dimensional array of rank i × j × . . . × k, then E appearing in an expression that is subject to the array-to-pointer conversion (4.2 [conv.array]) is converted to a pointer to an (n-1)-dimensional array with rank j × . . . × k. If the * operator, either explicitly or implicitly as a result of subscripting, is applied to this pointer, the result is the pointed-to (n-1)-dimensional array, which itself is immediately converted into a pointer.
[Example: consider
int x[3][5];
Here x is a 3 × 5 array of integers. When x appears in an expression, it is converted to a pointer to (the first of three) five-membered arrays of integers. In the expression x[i] which is equivalent to *(x+i), x is first converted to a pointer as described; then x+i is converted to the type of x, which involves multiplying i by the length of the object to which the pointer points, namely five integer objects. The results are added and indirection applied to yield an array (of five integers), which in turn is converted to a pointer to the first of the integers. If there is another subscript the same argument applies again; this time the result is an integer. —end example] —end note]
[Voted into WP at October, 2009 meeting.]
4.4 [conv.qual] paragraph 3 consists of a note reading,
[Note: Function types (including those used in pointer to member function types) are never cv-qualified (8.3.5 [dcl.fct]). —end note]
However, 8.3.5 [dcl.fct] paragraph 7 says,
A cv-qualifier-seq shall only be part of the function type...
This sounds like a contradiction, although formally it is not: a “function type with a cv-qualifier-seq” is not a “cv-qualified function type.” It would be helpful to make this distinction clearer.
Proposed resolution (March, 2009):
Change 8.3.5 [dcl.fct] paragraph 7 as follows:
A cv-qualifier-seq shall only be part of the function type for a non-static member function, the function type to which a pointer to member refers, or the top-level function type of a function typedef declaration. [Note: A function type that has a cv-qualifier-seq is not a cv-qualified type; there are no cv-qualified function types. —end note] The effect of a cv-qualifier-seq in a function declarator...
Change 3.9.3 [basic.type.qualifier] paragraph 3 as follows:
...See 8.3.5 [dcl.fct] and 9.3.2 [class.this] regarding cv-qualified function types that have cv-qualifiers.
[Voted into WP at March, 2010 meeting as part of document N3079.]
8.3.5 [dcl.fct] paragraph 13 requires that a parameter pack, if present, must appear at the end of the parameter list. This restriction is not necessary when template argument deduction is not needed and is inconsistent with the way pack expansions are handled. It should be removed.
(See also issue 692.)
[Voted into WP at March, 2010 meeting.]
According to 3.3.4 [basic.scope.proto] paragraph 1,
In a function declaration, or in any function declarator except the declarator of a function definition (8.4 [dcl.fct.def]), names of parameters (if supplied) have function prototype scope, which terminates at the end of the nearest enclosing function declarator.
Happily, this permits the use of parameter names with decltype in a late-specified return type, because the return type is part of the function's declarator. However, the note in 8.3.5 [dcl.fct] paragraph 11 is now inaccurate and should be updated:
[Note: ...If a parameter name is present in a function declaration that is not a definition, it cannot be used outside of the parameter-declaration-clause since it goes out of scope at the end of the function declarator (3.3 [basic.scope]). —end note]
Proposed resolution (February, 2010):
Change the note in 8.3.5 [dcl.fct] paragraph 10 as follows:
...[Note: in particular, parameter names are also optional in function definitions and names used for a parameter in different declarations and the definition of a function need not be the same. If a parameter name is present in a function declaration that is not a definition, it cannot be used outside of the parameter-declaration-clause since it goes out of scope at the end of the function declarator (3.3 [basic.scope]) its function declarator because that is the extent of its potential scope (3.3.4 [basic.scope.proto]). —end note]
[Voted into WP at March, 2010 meeting.]
8.3.6 [dcl.fct.default] paragraph 4 says,
In a given function declaration, all parameters subsequent to a parameter with a default argument shall have default arguments supplied in this or previous declarations.
It is not clear whether this applies to parameter packs or not. For example, is the following well-formed?
template <typename... T> void f(int i = 0, T ...args) { }
Note for comparison the corresponding wording in 14.1 [temp.param] paragraph 11 regarding template parameter packs:
If a template-parameter of a class template has a default template-argument, each subsequent template-parameter shall either have a default template-argument supplied or be a template parameter pack.
Proposed resolution (October, 2009):
Change 8.3.6 [dcl.fct.default] paragraph 4:
...In a given function declaration, all each parameters subsequent to a parameter with a default argument shall have a default arguments supplied in this or a previous declarations or shall be a function parameter pack. A default argument...
[Voted into the WP at the July, 2009 meeting as part of N2927.]
The grammar in 8.4 [dcl.fct.def] paragraph 2 incorrectly excludes late-specified return types and should be corrected.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into WP at March, 2010 meeting.]
According to 8.4 [dcl.fct.def] paragraph 10,
A deleted definition of a function shall be the first declaration of the function.
The Standard is not currently clear about what the “first declaration” of an explicit specialization of a function template is. For example,
template<typename T> void f() { }
template<> void f<int>() = delete; // First declaration?
Proposed resolution (October, 2009):
A deleted definition of a function shall be the first declaration of the function or, for an explicit specialization of a function template, the first declaration of that specialization.
(This resolution also resolves issue 915.)
Notes from the October, 2009 meeting:
It was observed that this specification is complicated by the fact that the “first declaration” of a function might be in a block-extern declaration.
[Voted into WP at March, 2010 meeting.]
The only restriction placed on the use of “=default” in 8.4 [dcl.fct.def] paragraph 9 is that a defaulted function must be a special member function. However, there are many variations of declarations of special member functions, and it's not clear which of those should be able to be defaulted. Among the possibilities:
default arguments
by-value parameter for a copy assignment operator
exception specifications
arbitrary return values for copy assignment operators
a const reference parameter when the implicit function would have a non-const
Presumably, you should only be able to default a function if it is declared compatibly with the implicit declaration that would have been generated.
Proposed resolution (October, 2009):
Change 8.4 [dcl.fct.def] paragraph 9 as follows:
A function definition of the form:
decl-specifier-seqopt attribute-specifieropt declarator = default ;
is called an explicitly-defaulted definition. Only special member functions may be explicitly defaulted, and the implementation shall define them as if they had implicit definitions (12.1 [class.ctor], 12.4 [class.dtor], 12.8 [class.copy]). A function that is explicitly defaulted shall
be a special member function,
have the same declared function type (except for possibly-differing ref-qualifiers and except that in the case of a copy constructor or copy assignment operator, the parameter type may be “reference to non-const T,” where T is the name of the member function's class) as if it had been implicitly declared,
not have default arguments, and
not have an exception-specification.
[Note: This implies that parameter types, return type, and cv-qualifiers must match the hypothetical implicit declaration. —end note] An explicitly-defaulted function may be declared constexpr only if it would have been implicitly declared as constexpr. If it is explicitly defaulted on its first declaration,
it shall be public,
it shall not be explicit,
it shall not be virtual,
it is implicitly considered to have the same exception-specification as if it had been implicitly declared (15.4 [except.spec]), and
in the case of a copy constructor or copy assignment operator, it shall have the same parameter type as if it had been implicitly declared.
[Note: Such a special member function may be trivial, and thus its accessibility and explicitness should match the hypothetical implicit definition; see below. —end note] [Example:
struct S { S(int a = 0) = default; // ill-formed: default argument void operator=(const S&) = default; // ill-formed: non-matching return type ~S() throw() = default; // ill-formed: exception-specification private: S(S&); // OK: private copy constructor }; S::S(S&) = default; // OK: defines copy constructor
—end example] Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall provide implicit definitions for them (12.1 [class.ctor], 12.4 [class.dtor], 12.8 [class.copy]), which might mean defining them as deleted.A special member function that would be implicitly defined as deleted may be explicitly defaulted only on its first declaration, in which case it is defined as deleted. A special member function is user-provided if it is user-declared and not explicitly defaulted on its first declaration. A user-provided explicitly-defaulted function is defined at the point where it is explicitly defaulted. [Note:...
[Editorial note: this change incorporates the overlapping portion of the resolution of issue 667.]
Change 12.1 [class.ctor] paragraph 6 as follows:
This resolution also resolves issue 905. See also issue 667.
[Voted into WP at October, 2009 meeting.]
According to 8.4 [dcl.fct.def] paragraph 10, a deleted definition of a function must be its first declaration. It is not clear whether this requirement can be satisfied for the global allocation and deallocation functions. According to 3.7.4 [basic.stc.dynamic] paragraph 2, they are “implicitly declared in global scope in each translation unit of a program.” However, that does not specify where in the translation unit the declaration is considered to take place. This needs to be clarified.
Proposed resolution (July, 2009):
Change 8.4 [dcl.fct.def] paragraph 10 as follows:
...A deleted definition of a function shall be the first declaration of the function. An implicitly declared allocation or deallocation function (3.7.4 [basic.stc.dynamic]) shall not be defined as deleted. [Example:...
[Voted into WP at March, 2010 meeting.]
It is not clear whether the following definition of an explicit specialization of a member function template is permitted or not:
template <typenanme T> struct S { template <typename U> void f(); }; template <> template <typename U> void S<int>::f() = delete;
Is the explicit specialization the “first declaration” of the member function template?
(See also issue 845.)
Notes from the July, 2009 meeting:
The intent is that this usage should be supported.
Proposed resolution (October, 2009):
This issue is resolved by the resolution of issue 845.
[Voted into WP at October, 2009 meeting.]
8.4 [dcl.fct.def] paragraph 9 says,
A special member function that would be implicitly defined as deleted shall not be explicitly defaulted.
It would be more regular (and thus useful in generic programming) if such a member function were itself simply defined as deleted rather than being made ill-formed.
Proposed resolution (July, 2009):
Change 8.4 [dcl.fct.def] paragraph 9 as follows:
Only special member functions may be explicitly defaulted, and the implementation shall define them as if they had implicit definitions (12.1 [class.ctor], 12.4 [class.dtor], 12.8 [class.copy]). A special member function that would be implicitly defined as deleted shall not be explicitly defaulted. A special member function that would be implicitly defined as deleted may be explicitly defaulted only on its first declaration, in which case it is defined as deleted. A special member function is user-provided if...
Change 12.1 [class.ctor] paragraph 6 as follows:
A non-user-provided default constructor for a class is implicitly defined when it is used (3.2 [basic.def.odr]) to create an object of its class type (1.8 [intro.object]). If the implicitly-defined default constructor is explicitly defaulted but the corresponding implicit declaration would have been deleted, the program is ill-formed. The implicitly-defined or explicitly-defaulted default constructor...
Change 12.4 [class.dtor] paragraph 4 as follows:
A program is ill-formed if the class for which a destructor is implicitly defined or explicitly defaulted has: if the implicitly-defined destructor is explicitly defaulted, but the corresponding implicit declaration would have been deleted.
a non-static data member of class type (or array thereof) with an inaccessible destructor, or
a base class with an inaccessible destructor.
Change 12.8 [class.copy] paragraph 7 as follows:
...[Note: the copy constructor is implicitly defined even if the implementation elided its use (12.2 [class.temporary]). —end note] A program is ill-formed if the implicitly-defined copy constructor is explicitly defaulted, but the corresponding implicit declaration would have been deleted.
Change 12.8 [class.copy] paragraph 12 as follows:
A non-user-provided copy assignment operator is implicitly defined when an object of its class type is assigned a value of its class type or a value of a class type derived from its class type. A program is ill-formed if the implicitly-defined copy assignment operator is explicitly defaulted, but the corresponding implicit declaration would have been deleted.
According to 8.5 [dcl.init] paragraph 5,
To zero-initialize an object of type T means:
...
if T is a reference type, no initialization is performed.
However, a reference is not an object, so this makes no sense.
Proposed resolution (March, 2010):
This issue is resolved by the resolution of issue 633 in document N2993.
[Voted into WP at March, 2010 meeting.]
8.5 [dcl.init] paragraph 11 says,
If no initializer is specified for an object, the object is default-initialized; if no initialization is performed, a non-static object has indeterminate value.
This is inaccurate, because objects with thread storage duration are zero-initialized (3.6.2 [basic.start.init] paragraph 2).
Proposed resolution (November, 2009):
Change 8.5 [dcl.init] paragraph 11 as follows:
If no initializer is specified for an object, the object is default-initialized; if no initialization is performed, a non-static an object with automatic or dynamic storage duration has indeterminate value. [Note: objects with static or thread storage duration are zero-initialized, see 3.6.2 [basic.start.init]. —end note].
[Voted into WP at March, 2010 meeting.]
The current wording of 8.5.1 [dcl.init.aggr] paragraph 1 does not consider brace-or-equal-initializers on members as affecting whether a class type is an aggregate or not. Because in-class member initializers are essentially syntactic sugar for mem-initializers, and the presence of a user-provided constructor disqualifies a class from being an aggregate, presumably the same should hold true of member initializers.
Proposed resolution (November, 2009):
Change 8.5.1 [dcl.init.aggr] paragraph 1 as follows:
An aggregate is an array or a class (Clause 9 [class]) with no user-provided constructors (12.1 [class.ctor]), no brace-or-equal-initializers for non-static data members (9.2 [class.mem]), no private or protected non-static data members (Clause 11 [class.access]), no base classes (Clause 10 [class.derived]), and no virtual functions (10.3 [class.virtual]).
[Voted into WP at October, 2009 meeting.]
The current specification of string initialization in 8.5.2 [dcl.init.string] leaves uninitialized all characters following the terminating '\0' of a character array with automatic storage duration. This is different from C99, in which string initialization is handled like aggregate initialization and all trailing characters are zeroed (6.7.8 paragraph 21).
(See also issue 694, in which we are considering following C99 in a somewhat similar case of zero-initializing trailing data.)
Proposed resolution (September, 2009):
Add a new paragraph following 8.5.2 [dcl.init.string] paragraph 2:
There shall not be more initializers than there are array elements. [Example:
char cv[4] = "asdf"; // error
is ill-formed since there is no space for the implied trailing '\0'. —end example]
If there are fewer initializers than there are array elements, then each element not explicitly initialized shall be zero-initialized (8.5 [dcl.init]).
[Voted into WP at October, 2009 meeting.]
8.5.2 [dcl.init.string] paragraph 1 says,
A char array (whether plain char, signed char, or unsigned char), char16_t array, char32_t array, or wchar_t array can be initialized by a string-literal (optionally enclosed in braces) with no prefix, with a u prefix, with a U prefix, or with an L prefix, respectively...
This formulation does not allow for raw and UTF-8 literals.
Proposed resolution (July, 2009):
Change 8.5.2 [dcl.init.string] paragraph 1 as follows:
A char array (whether plain char, signed char, or unsigned char), char16_t array, char32_t array, or wchar_t array can be initialized by a string-literal (optionally enclosed in braces) with no prefix, with a u prefix, with a U prefix, or with an L prefix narrow character literal, char16_t string literal, char32_t string literal, or wide string literal, respectively; successive, or by an appropriately-typed string literal enclosed in braces. Successive characters of the string-literal value of the string literal initialize the members elements of the array. [Example: ...
[Voted into WP at October, 2009 meeting.]
The resolutions of issues 391 and 450 say that the reference is “bound to” the class or array rvalue, but it does not say that the reference “binds directly” to the initializer, as it does for the cases that fall under the first bullet in 8.5.3 [dcl.init.ref] paragraph 5. However, this phrasing is important in determining the implicit conversion sequence for an argument passed to a parameter with reference type (13.3.3.1.4 [over.ics.ref]), where paragraph 2 says,
When a parameter of reference type is not bound directly to an argument expression, the conversion sequence is the one required to convert the argument expression to the underlying type of the reference according to 13.3.3.1 [over.best.ics]. Conceptually, this conversion sequence corresponds to copy-initializing a temporary of the underlying type with the argument expression.
The above-mentioned issue resolutions stated that no copy is to be made in such reference initializations, so the determination of the conversion sequence does not reflect the initialization semantics.
Simply using the “binds directly” terminology in the new wording may not be the right approach, however, as there are other places in the Standard that also give special treatment to directly-bound references. For example, the first bullet of 5.16 [expr.cond] paragraph 3 says,
If E2 is an lvalue: E1 can be converted to match E2 if E1 can be implicitly converted (clause 4 [conv]) to the type “reference to T2,” subject to the constraint that in the conversion the reference must bind directly (8.5.3 [dcl.init.ref]) to E1.
The effect of simply saying that a reference “binds directly” to a class rvalue can be seen in this example:
struct B { };
struct D: B { };
D f();
void g(bool x, const B& br) {
x ? f() : br; // result would be lvalue
}
It is not clear that treating this conditional expression as an lvalue is a desirable outcome, even if the result of f() were to “bind directly” to the const B& reference.
Proposed resolution (June, 2009):
Change 8.5.3 [dcl.init.ref] paragraph 5 as follows:
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
If the reference is an lvalue reference and the initializer expression
is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2,” or
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an lvalue of type “cv3 T3,” where “cv1 T1” is reference-compatible with “cv3 T3” (this conversion is selected by enumerating the applicable conversion functions (13.3.1.6 [over.match.ref]) and choosing the best one through overload resolution (13.3 [over.match])),
then the reference is bound directly to the initializer expression lvalue in the first case, and the reference is bound and to the lvalue result of the conversion in the second case. In these cases the reference is said to bind directly to the initializer expression. [Note: the usual lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), and function-to-pointer (4.3 [conv.func]) standard conversions are not needed, and therefore are suppressed, when such direct bindings to lvalues are done. —end note]
[Example: ... —end example]
Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference shall be an rvalue reference and the initializer expression shall be an rvalue. [Example: ... —end example]
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.
[Example: ... —end example]
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]).
Otherwise, a temporary of type “cv1 T1” is created and initialized from the initializer expression using the rules for a non-reference copy initialization (8.5 [dcl.init]). The reference is then bound to the temporary. If T1 is reference-related to T2, cv1 must be the same cv-qualification as, or greater cv-qualification than, cv2; otherwise, the program is ill-formed. [Example: ... —end example]
In all cases except the last (i.e., creating and initializing a temporary from the initializer expression), the reference is said to bind directly to the initializer expression.
Change 5.16 [expr.cond] paragraph 3 bullet 1 as follows:
If E2 is an lvalue: E1 can be converted to match E2 if E1 can be implicitly converted (Clause 4 [conv]) to the type “lvalue reference to T2”, subject to the constraint that in the conversion the reference must bind directly (8.5.3 [dcl.init.ref]) to E1 an lvalue.
[Voted into WP at October, 2009 meeting.]
Consider the following example:
struct A { }; struct B : public A { }; struct X { operator B(); }; X x; int main() { const A& r = x; return 0; }
It seems like the resolution of issue 391 doesn't actually cover this; X is not reference-compatible with A, so we go past the modified bullet (8.5.3 [dcl.init.ref] paragraph 5, bullet 2, sub-bullet 1), which reads:
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.
and hit
Otherwise, a temporary of type “cv1 T1” is created and initialized from the initializer expression using the rules for a non-reference copy initialization (8.5 [dcl.init]). The reference is then bound to the temporary.
which seems to require that we create an A temporary copied from the return value of X::operator B() rather than bind directly to the A subobject. I think that the resolution of issue 391 should cover this situation as well, and the EDG compiler seems to agree with me.
(See also issue 896.)
Proposed resolution (September, 2009):
Change 8.5.3 [dcl.init.ref] paragraph 5 as follows:
If the reference is an lvalue reference...
Otherwise, the reference shall be an lvalue reference to a non-volatile const type...
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. If T1 and T2 are class types and
the initializer expression is an rvalue, and “cv1 T1” is reference-compatible with “cv2 T2,” or
T1 is not reference-related to T2, and the initializer expression can be implicitly converted to an rvalue of type “cv3 T3,” where “cv1 T1” is reference-compatible with “cv3 T3” (this conversion is selected by enumerating the applicable conversion functions (13.3.1.6 [over.match.ref]) and choosing the best one through overload resolution (13.3 [over.match]) ),
then the reference is bound to the initializer expression rvalue in the first case, and to the object that is the result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object). [Example:
struct A { }; struct B : A { } b; extern B f(); const A& rca = f(); // Bound to the A subobject of the B rvalue. A&& rcb = f(); // Same as above struct X { operator B(); } x; const A& r = x; // Bound to the A subobject of the result of the conversion
—end example]
...
Editorial note: issue 589 makes edits to the top-level bullet preceding this one. The wording resulting from those edits should be changed for consistency with this wording so that the text there reads, “...in the first case and to the lvalue result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object).”
Change 13.3 [over.match] paragraph 2, last bullet as follows:
Change 13.3.1.6 [over.match.ref] paragraph 1 as follows:
Under the conditions specified in 8.5.3 [dcl.init.ref], a reference can be bound directly to an lvalue or class rvalue that is the result of applying a conversion function to an initializer expression. Overload resolution is used to select the conversion function to be invoked. Assuming that “cv1 T” is the underlying type of the reference being initialized, and “cv S” is the type of the initializer expression, with S a class type, the candidate functions are selected as follows:
The conversion functions of S and its base classes are considered, except that for copy-initialization, only the non-explicit conversion functions are considered. Those that are not hidden within S and yield type “lvalue reference to cv2 T2” (when 8.5.3 [dcl.init.ref] requires an lvalue result), or “cv2 T2” or “rvalue reference to cv2 T2 (when 8.5.3 [dcl.init.ref] requires an rvalue result), where “cv1 T” is reference-compatible (8.5.3 [dcl.init.ref]) with “cv2 T2”, are candidate functions.
(Note: this resolution also resolves issue 896.)
[Voted into WP at March, 2010 meeting as document N3055.]
According to 8.5.3 [dcl.init.ref] paragraph 5, a reference initialized with a reference-compatible rvalue of class type binds directly to the object. A reference-compatible non-class rvalue reference, however, is first copied to a temporary and the reference binds to that temporary, not to the target of the rvalue reference. This can cause problems when the result of a forwarding function is used in such a way that the address of the result is captured. For example:
struct ref { explicit ref(int&& i): p(&i) { } int* p; }; int&& forward(int&& i) { return i; } void f(int&& i) { ref r(forward(i)); // Here r.p is a dangling pointer, pointing to a defunct int temporary }
A formulation is needed so that rvalue references are treated like class and array rvalues.
Notes from the February, 2008 meeting:
You can't just treat scalar rvalues like class and array rvalues, because they might not have an associated object. However, if you have an rvalue reference, you know that there is an object, so probably the best way to address this issue is to specify somehow that binding a reference to an rvalue reference does not introduce a new temporary.
(See also issues 690 and 846.)
Proposed resolution (February, 2010):
See paper N3030.
[Voted into WP at October, 2009 meeting.]
Consider the following example:
struct A { } a; struct B { operator A&&() { return static_cast<A&&>(a); } }; A&& r = B();
One would expect that r would be bound to the object returned by B::operator A&&(), i.e., a. However, the logic in 8.5.3 [dcl.init.ref] paragraph 5 requires that the result of the conversion function be copied to a temporary and r bound to the temporary.
Probably the way to address this is to add another top-level bullet between the first and second that would essentially mimic the first bullet except dealing with rvalue references: direct binding to reference-compatible rvalues or to the reference-compatible result of a conversion function. (Note that this should only apply to class rvalues; the creation of a temporary for non-class rvalues is necessary to have an object for the reference to bind to.)
(See also issue 656.)
Proposed resolution (September, 2009):
This issue is resolved by the resolution of issue 656.
[Voted into WP at October, 2009 meeting.]
Both of the following initializations are ill-formed because of narrowing, although they were previously well-formed:
struct A { int i; } a = { 1.0 }; struct B { float f; } b = { 1.1 };
The first one doesn't seem like a big problem, as there probably isn't much code that has this kind of aggregate initialization. The second might be of more concern, because 1.1 is not representable in either float or double. Is the resulting loss of precision a kind of narrowing that we want to diagnose?
Notes from the September, 2008 meeting:
The CWG agreed that the second initialization should not be a narrowing error; furthermore, this exemption should apply not only to literals but to any floating-point constant expression. Instead of the current formulation, requiring exact bidirectional convertibility, the Standard should only require that the initializer value be within the representable range of the target type.
Proposed resolution (July, 2009):
Change 8.5.4 [dcl.init.list] paragraph 6 as follows:
A narrowing conversion is an implicit conversion
from a floating-point type to an integer type, or
from long double to double or float, or from double to float, except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted back to the original type is within the range of values that can be represented (even if it cannot be represented exactly), or
...
[Voted into WP at October, 2009 meeting.]
There are several problems with the wording of 8.5.4 [dcl.init.list] paragraph 4:
When an initializer list is implicitly converted to a std::initializer_list<E>, the object passed is constructed as if the implementation allocated an array of N elements of type E, where N is the number of elements in the initializer list. Each element of that array is initialized with the corresponding element of the initializer list converted to E, and the std::initializer_list<E> object is constructed to refer to that array. If a narrowing conversion is required to convert the element to E, the program is ill-formed.
First, an initializer list is not an expression, so it is not appropriate to refer to “implicitly convert[ing]” it, as is done in the first sentence.
Also, the conversion of the elements of the initializer list to the elements of the array is not specified to be either copy-initialization or direct-initialization. If this is intended to be viewed as an aggregate initialization, it would be copy-initialization, but that needs to be specified more clearly.
Finally, the initializer list can have nested initializer lists, so the references to converting the element also need to be cleaned up.
Proposed resolution (July, 2009):
Change 8.5.4 [dcl.init.list] paragraph 4 as follows:
When an initializer list is implicitly converted to a An object of type std::initializer_list<E> is constructed from an initializer list, the object passed is constructed as if the implementation allocated an array of N elements of type E, where N is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list converted to E, and the std::initializer_list<E> object is constructed to refer to that array. If a narrowing conversion is required to convert the element to E initialize any of the elements, the program is ill-formed. [Example:...
[Voted into WP at October, 2009 meeting.]
According to 8.5.4 [dcl.init.list] paragraph 3,
Otherwise, if T is a reference type, an rvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary.
This means, for an example like
int i; const int& r1{ i }; int&& r2{ i };
r1 is bound to a temporary containing the value of i, not to i itself, which seems surprising. Also, there's no prohibition here against binding the rvalue reference to an lvalue, as there is in 8.5.3 [dcl.init.ref] paragraph 5 bullet 2, so the initialization of r2 is well-formed, even though the corresponding non-list initialization int&& r3(i) is ill-formed.
There's also a question as to whether this bullet even applies to these examples. According to the decision tree in 8.5 [dcl.init] paragraph 16, initialization of a reference is dispatched to 8.5.3 [dcl.init.ref] in the first bullet, so these cases never make it to the third bullet sending the remaining braced-init-list cases to 8.5.4 [dcl.init.list]. If that's the correct interpretation, there's a problem with 8.5.3 [dcl.init.ref], since it doesn't deal with the braced-init-list cases, and the bullet in 8.5.4 [dcl.init.list] paragraph 3 dealing with references is dead code that's never used.
Proposed resolution (July, 2009):
Move the third bullet of the list in 8.5 [dcl.init] paragraph 16 to the top of the list:
If the initializer is a braced-init-list, the object is list-initialized (8.5.4 [dcl.init.list]).
If the destination type is a reference type, see 8.5.3 [dcl.init.ref].
...
Change 8.5.4 [dcl.init.list] paragraph 3, bullets 4 and 5, as follows:
Otherwise, if T is a reference to class type, or if T is any reference type and the initializer list has no elements, an rvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary. [Note:...
Otherwise (i.e., if T is not an aggregate, class type, or reference), if the initializer list has a single element...
[Voted into WP at March, 2010 meeting.]
The final set of declarations in the example following 8.5.4 [dcl.init.list] paragraph 3 bullet 3 is:
struct S2 { int m1; double m2,m3; }; S2 s21 = { 1, 2, 3.0 }; // OK S2 s22 { 1.0, 2, 3 }; // error: narrowing S2 s23 {}; // OK: default to 0,0,0
However, S2 is an aggregate. Aggregates are handled in bullet 1, while bullet 3 deals with classes with constructors. This part of the example should be moved to the first bullet.
Proposed resolution (October, 2009):
Move the S2 example from bullet 3 to bullet 1 in 8.5.4 [dcl.init.list] paragraph 3:
If T is an aggregate, aggregate initialization is performed (8.5.1 [dcl.init.aggr]).
[Example:
double ad[] = { 1, 2.0 }; // OK int ai[] = { 1, 2.0 }; // error: narrowing struct S2 { int m1; double m2,m3; }; S2 s21 = { 1, 2, 3.0 }; // OK S2 s22 { 1.0, 2, 3 }; // error: narrowing S2 s23 {}; // OK: default to 0,0,0
—end example]
Otherwise, if T is a specialization...
Otherwise, if T is a class type...
[Example:
... S s3 { }; // OK: invoke #2 struct S2 { int m1; double m2,m3; }; S2 s21 = { 1, 2, 3.0 }; // OK S2 s22 { 1.0, 2, 3 }; // error: narrowing S2 s23 {}; // OK: default to 0,0,0
—end example]
...
[Voted into WP at March, 2010 meeting as part of document N3079.]
It should always be possible to use the new brace syntax to value-initialize an object. However, the current rules make the following example ill-formed because of ambiguity:
struct S { S(); S(std::initializer_list<int>); S(std::initializer_list<double>); }; S s{}; // Ambiguous initializer-list constructor reference, // not value initialization.
Proposed resolution (February, 2010):
Change 8.5.4 [dcl.init.list] paragraph 3 as follows:
List-initialization of an object or reference of type T is defined as follows:
If the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
Otherwise, if the initializer list has no elements and T is an aggregate, the initializer list is used to initialize each of the members of T. [Example:
struct A { A(std::initializer_list<int>); // #1 }; struct B { A a; }; B b { }; // OK, uses #1 B b { 1 }; // error
—end example]
If Otherwise, if T is an aggregate...
...
[Example:
struct S { S(std::initializer_list<double>); // #1 S(std::initializer_list<int>); // #2 S(); // #3 // ... }; S s1 = { 1.0, 2.0, 3.0 }; // invoke #1 S s2 = { 1, 2, 3 }; // invoke #2 S s3 = { }; // invoke #3 (for value-initialization; see above)—end example]
[Voted into WP at March, 2010 meeting.]
It is presumably possible to declare a defaulted copy constructor to be explicit. Should that render a class not trivially copyable, even though the copy constructor is trivial? That is, does being “trivally copyable” mean that copy initialization, and not just direct initialization, is possible?
A related question is whether the specification of triviality should require that the copy constructor and copy assignment operator must be public. (With the advent of “=default” it is possible to make them non-public, which was not the case when these definitions were crafted.)
Proposed resolution (October, 2009):
This issues is resolved by the resolution of issue 906.
[Voted into the WP at the March, 2009 meeting.]
The current wording defining a “common initial sequence” in 9.2 [class.mem] paragraph 17 does not address the case in which one member is a bit-field and the corresponding member is not:
Two standard-layout structs share a common initial sequence if corresponding members have layout-compatible types (and, for bit-fields, the same widths) for a sequence of one or more initial members.
Presumably the intent was something like, “(and, if one of the pair is a bit-field, the other is also a bit-field of the same width).”
Proposed Resolution (September, 2008):
Change 9.2 [class.mem] paragraph 18 as follows:
... Two standard-layout structs share a common initial sequence if corresponding members have layout-compatible types (and, for bit-fields, the same widths) and either neither member is a bit-field or both are bit-fields with the same widths for a sequence of one or more initial members.
[Voted into WP at October, 2009 meeting.]
According to 9.2 [class.mem] paragraph 1,
The enumerators of an enumeration (7.2 [dcl.enum]) defined in the class are members of the class... A member shall not be declared twice in the member-specification, except that a nested class or member class template can be declared and then later defined.
The enumerators of a scoped enumeration are not members of the containing class; the wording should be revised to apply only to unscoped enumerations.
The second part of the cited wording from 9.2 [class.mem] prohibits constructs like:
class C { public: enum E: int; private: enum E: int { e0 }; };
which might be useful in making the enumeration type, but not its enumerators, accessible.
Notes from the July, 2009 meeting:
According to 11.1 [class.access.spec] paragraph 4, the access must be the same for all declarations of a class member. The suggested usage given above violates that requirement: the second declaration of E declares the enumeration itself, not just the enumerators, to be private. The CWG did not feel that the utility of the suggested feature warranted the complexity of an exception to the general rule.
Proposed resolution (July, 2009):
Change 9.2 [class.mem] paragraph 1 as follows:
...The enumerators of an unscoped enumeration (7.2 [dcl.enum]) defined in the class are members of the class... A member shall not be declared twice in the member-specification, except that a nested class or member class template can be declared and then later defined, and except that an enumeration can be first introduced with an opaque-enum-declaration and then later be redeclared with an enum-specifier.
Change the example in 11.1 [class.access.spec] paragraph 4 as follows:
When a member is redeclared within its class definition, the access specified at its redeclaration shall be the same as at its initial declaration. [Example:
struct S { class A; enum E : int; private: class A { }; // error: cannot change access enum E : int { e0 }; // error: cannot change access };
—end example]
[Voted into WP at July, 2009 meeting.]
The recent changes in the handling of initialization have not touched the requirement that the in-class initializer for a const static data member must be of the form = assignment-expression and not a braced-init-list. It would be more consistent and general to allow the braced form as well.
Proposed resolution (March, 2009):
Change 5.19 [expr.const] paragraph 3 as follows:
...as enumerator initializers (7.2 [dcl.enum]), as static member initializers (9.4.2 [class.static.data]), and as integral or enumeration non-type template arguments (14.4 [temp.type]).
Change 9.4.2 [class.static.data] paragraph 3 as follows:
If a static data member is of const effective literal type, its declaration in the class definition can specify a brace-or-equal-initializer with an in which every initializer-clause that is an assignment-expression is a integral constant expression. A static data member of effective literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer with an in which every initializer-clause that is an assignment-expression is a integral constant expression. [Note: In both these cases, the member may appear in integral constant expressions. —end note] The member shall still be defined in a namespace scope if it is used in the program and the namespace scope definition shall not contain an initializer.
[Drafting note: this change also corrects an editorial error resulting from overlapping changes that inadvertently retained the original restriction that only members of integral type could be initialized inside the class definition.]
[Voted into WP at July, 2009 meeting.]
Unions are no longer forbidden to have static data members; however, much of the wording of 9.5 [class.union] (and possibly other places in the Standard) is still written with that assumption and refers only to “data members” when clearly non-static data members are in view. From paragraph 1, for example:
In a union, at most one of the data members can be active at any time... The size of a union is sufficient to contain the largest of its data members...
Proposed resolution (March, 2009):
Change the footnote in 3.9.3 [basic.type.qualifier] paragraph 1 as follows:
The same representation and alignment requirements are meant to imply interchangeability as arguments to functions, return values from functions, and non-static data members of unions.
Change 3.10 [basic.lval] paragraph 15 bullet 6 as follows:
Change 5.9 [expr.rel] paragraph 2 bullet 5 as follows:
Change 7.6.2 [dcl.align] paragraph 8 as follows:
[Note: the alignment of a union type can be strengthened by applying the alignment attribute to any non-static data member of the union. —end note]
Change 8.5.1 [dcl.init.aggr] paragraph 15 as follows:
When a union is initialized with a brace-enclosed initializer, the braces shall only contain an initializer-clause for the first non-static data member of the union...
Change 9.5 [class.union] paragraph 1 as follows:
In a union, at most one of the non-static data members can be active at any time, that is, the value of at most one of the non-static data members can be stored in a union at any time. [Note: one special guarantee is made in order to simplify the use of unions: If a standard-layout union contains several standard-layout structs that share a common initial sequence (9.2 [class.mem]), and if an object of this standard-layout union type contains one of the standard-layout structs, it is permitted to inspect the common initial sequence of any of standard-layout struct members; see 9.2 [class.mem]. —end note] The size of a union is sufficient to contain the largest of its non-static data members. Each non-static data member is allocated as if it were the sole member of a struct. A union can have...
[Voted into WP at October, 2009 meeting.]
According to 10.3 [class.virtual] paragraph 2:
Then in any well-formed class, for each virtual function declared in that class or any of its direct or indirect base classes there is a unique final overrider that overrides that function and every other overrider of that function. The rules for member lookup (10.2 [class.member.lookup]) are used to determine the final overrider for a virtual function in the scope of a derived class but ignoring names introduced by using-declarations.
I think that description is wrong on at least a couple of counts. First, consider the following example:
struct A { virtual void f(); }; struct B: A { }; struct C: A { void f(); }; struct D: B, C { };
What is the “unique final overrider” of A::f() in D? According to 10.3 [class.virtual] paragraph 2, we determine that by looking up f in D using the lookup rules in 10.2 [class.member.lookup]. However, that lookup determines that f in D is ambiguous, so there is no “unique final overrider” of A::f() in D. Consequently, because “any well-formed class” must have such an overrider, D must be ill-formed.
Of course, we all know that D is not ill-formed. In fact, 10.3 [class.virtual] paragraph 10 contains an example that illustrates exactly this point:
struct A { virtual void f(); }; struct B1 : A { // note non-virtual derivation void f(); }; struct B2 : A { void f(); }; struct D : B1, B2 { // D has two separate A subobjects };In class D above there are two occurrences of class A and hence two occurrences of the virtual member function A::f. The final overrider of B1::A::f is B1::f and the final overrider of B2::A::f is B2::f.
It appears that the requirement for a “unique final overrider” in 10.3 [class.virtual] paragraph 2 needs to say something about sub-objects. Whatever that “something” is, you can't just say “look up the name in the derived class using 10.2 [class.member.lookup].”
There's another problem with using the 10.2 [class.member.lookup] lookup to specify the final overrider: name lookup just looks up the name, while the overriding relationship is based not only on the name but on a matching parameter-type-list and cv-qualification. To illustrate this point:
struct X { virtual void f(); }; struct Y: X { void f(int); }; struct Z: Y { };
What is the “unique final overrider” of X::f() in A? Again, 10.3 [class.virtual] paragraph 2 says you're supposed to look up f in Z to find it; however, what you find is Y::f(int), not X::f(), and that's clearly wrong.
Proposed Resolution (December, 2006):
Change 10.3 [class.virtual] paragraph 2 as follows:
Then in any well-formed class, for each virtual function declared in that class or any of its direct or indirect base classes there is a unique final overrider that overrides that function and every other overrider of that function. The rules for member lookup (10.2 [class.member.lookup]) are used to determine the final overrider for a virtual function in the scope of a derived class but ignoring names introduced by using-declaration s. A virtual member function vf of a class C is a final overrider unless the most derived class (1.8 [intro.object]) of which C is a base class (if any) declares or inherits another member function that overrides vf. In a derived class, if a virtual member function of a base class subobject has more than one final overrider, the program is ill-formed.
Proposed resolution (July, 2009):
Change 10.3 [class.virtual] paragraph 2 as follows:
...Then in any well-formed class, for each virtual function declared in that class or any of its direct or indirect base classes there is a unique final overrider that overrides that function and every other overrider of that function. The rules for member lookup (10.2 [class.member.lookup]) are used to determine the final overrider for a virtual function in the scope of a derived class but ignoring names introduced by using-declarations. A virtual member function C::vf of a class object S is a final overrider unless the most derived class (1.8 [intro.object]) of which S is a base class subobject (if any) declares or inherits another member function that overrides vf. In a derived class, if a virtual member function of a base class subobject has more than one final overrider, the program is ill-formed. [Example: ... —end example] [Example:
struct A { virtual void f(); }; struct B: A { }; struct C: A { void f(); }; struct D: B, C { }; // OK; A::f and C::f are the final overriders // for the B and C subobjects, respectively
—end example]
[Voted into WP at July, 2009 meeting as N2928.]
There should be a way to detect errors in overriding a virtual function.
Proposed resolution (July, 2009):
This issue is resolved by paper PL22.16/09-0118 = WG21 N2928.
[Voted into WP at March, 2010 meeting.]
10.3 [class.virtual] paragraph 5 requires that covariant return types be either both pointers or both references, but it does not specify that references must be both lvalue references or both rvalue references. Presumably this is an oversight.
Proposed resolution (February, 2010):
Change 10.3 [class.virtual] paragraph 5 bullet 1 as follows:
...If a function D::f overrides a function B::f, the return types of the functions are covariant if they satisfy the following criteria:
both are pointers to classes, both are lvalue references to classes, or both are rvalue references to classes106
...
[Voted into WP at March, 2010 meeting.]
According to 12.1 [class.ctor] paragraph 5,
An implicitly-declared default constructor for class X is defined as deleted if: ... any non-static data member of const-qualified type (or array thereof) does not have a user-provided default constructor, or...
It is not clear if this adequately covers the case in which some variant members are const-qualified but others are not. The intent of the restriction is to prevent creation of an object with uninitialized members that would require a const_cast to set their value later, but const-qualified members of an anonymous union in which other members are not const do not seem to present that problem.
Proposed resolution (October, 2009):
Change 12.1 [class.ctor] paragraph 5 bullet 3 of the second list and add a fourth bullet as follows:
...
any non-variant non-static data member of const-qualified type (or array thereof) does not have a user-provided default constructor, or
all variant members are of const-qualified type (or array thereof), or
...
Proposed resolution (November, 2009):
Change 12.1 [class.ctor] paragraph 5 bullet 3 of the second list and add two bullets as follows:
...
any non-variant non-static data member of const-qualified type (or array thereof) does not have a user-provided default constructor, or
X is a union and all its variant members are of const-qualified type (or array thereof),
X is a non-union class and all members of any anonymous union member are of const-qualified type (or array thereof), or
...
[Voted into WP at March, 2010 meeting.]
(From message 14555.)
The reasons for which an implicitly-declared default constructor is defined as deleted, given in 12.1 [class.ctor] paragraph 4, all deal with cases in which a member cannot be default-initialized. Presumably a brace-or-equal-initializer for such a member would eliminate the need to define the constructor as deleted, but this case is not addressed by the current wording.
Proposed resolution (October, 2009):
Change 12.1 [class.ctor] paragraph 5, the second list, as follows:
An implicitly-declared default constructor for class X is defined as deleted if:
X is a union-like class that has a variant member with a non-trivial default constructor,
any non-static data member with no brace-or-equal-initializer is of reference type,
any non-static data member of const-qualified type (or array thereof) with no brace-or-equal-initializer does not have a user-provided default constructor, or
any direct or virtual base class, or non-static data member with no brace-or-qual-initializer, or direct or virtual base class has class type M (or array thereof) and either M has no default constructor, or if constructor or overload resolution (13.3 [over.match]) as applied to M's default constructor, results in an ambiguity or in a function that is deleted or inaccessible from the implicitly-declared default constructor.
[Voted into the WP at the March, 2009 meeting.]
In describing the order of destruction of temporaries, 12.2 [class.temporary] paragraphs 4-5 say,
There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression...
The second context is when a reference is bound to a temporary... A temporary bound to the returned value in a function return statement (6.6.3 [stmt.return]) persists until the function exits.
The following example illustrates the issues here:
struct S { ~S(); }; S& f() { S s; // #1 return (S(), // #2 S()); // #3 }
If the return type of f() were simply S instead of S&, the two temporaries would be destroyed at the end of the full-expression in the return statement in reverse order of their construction, followed by the destruction of the variable s at block-exit, i.e., the order of destruction of the S objects would be #3, #2, #1.
Because the temporary #3 is bound to the returned value, however, its lifetime is extended beyond the end of the full-expression, so that S object #2 is destroyed before #3.
There are two problems here. First, it is not clear what “until the function exits” means. Does it mean that the temporary is destroyed as part of the normal block-exit destructions, as described in 6.6 [stmt.jump] paragraph 2:
On exit from a scope (however accomplished), destructors (12.4 [class.dtor]) are called for all constructed objects with automatic storage duration (3.7.3 [basic.stc.auto]) (named objects or temporaries) that are declared in that scope, in the reverse order of their declaration.
Or is the point of destruction for #3 after the destruction of the “constructed objects... that are declared [emphasis mine] in that scope” (because temporary #3 was not “declared”)? I.e., should #3 be destroyed before or after #1?
The other problem is that, according to the recollection of one of the participants responsible for this wording, the intent was not to extend the lifetime of #3 but simply to emphasize that its lifetime ended before the function returned, i.e., that the result of f() could not be used without causing undefined behavior. This is also consistent with the treatment of this example by many implementations; MSVC++, g++, and EDG all destroy #3 before #2.
Suggested resolution:
Change 12.2 [class.temporary] paragraph 5 as indicated:
A The lifetime of a temporary bound to the returned value in a function return statement (6.6.3 [stmt.return]) persists until the function exits is not extended; it is destroyed at the end of the full-expression in the return statement.
Proposed resolution (June, 2008):
Change 12.2 [class.temporary] paragraph 5 as follows (converting the running text into a bulleted list and making the indicated edits to the wording):
... The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except: as specified below.
A temporary bound to a reference member in a constructor's ctor-initializer (12.6.2 [class.base.init]) persists until the constructor exits.
A temporary bound to a reference parameter in a function call (5.2.2 [expr.call]) persists until the completion of the full expression containing the call.
A The lifetime of a temporary bound to the returned value in a function return statement (6.6.3 [stmt.return]) persists until the function exits is not extended; the temporary is destroyed at the end of the full-expression in the return statement.
The destruction of a temporary whose lifetime is not extended...
[Voted into the WP at the March, 2009 meeting.]
12.6 [class.init] paragraph 2 says,
When an array of class objects is initialized (either explicitly or implicitly), the constructor shall be called for each element of the array, following the subscript order;
That implies that, given
struct POD { int x; }; POD data[10] = {};
this should call the implicitly declared default ctor 10 times, leaving 10 uninitialized ints, rather than value initialize each member of data, resulting in 10 initialized ints (which is required by 8.5.1 [dcl.init.aggr] paragraph 7).
I suggest rephrasing along the lines:
When an array is initialized (either explicitly or implicitly), each element of the array shall be initialized in turn, following the subscript order;
This would allow for PODs and other classes with a dual nature under value/default initialization, and cover copy initialization for arrays too.
Proposed resolution (October, 2006):
Change 12.6 [class.init] paragraph 3 as follows:
When an array of class objects is initialized (either explicitly or implicitly) and the elements are initialized by constructor, the constructor shall be called for each element of the array, following the subscript order; see 8.3.4 [dcl.array].
[Voted into WP at October, 2009 meeting.]
Must a constructor for an abstract base class provide a mem-initializer for each virtual base class from which it is directly or indirectly derived? Since the initialization of virtual base classes is performed by the most-derived class, and since an abstract base class can never be the most-derived class, there would seem to be no reason to require constructors for abstract base classes to initialize virtual base classes.
It is not clear from the Standard whether there actually is such a requirement or not. The relevant text is found in 12.6.2 [class.base.init] paragraph 6:
All sub-objects representing virtual base classes are initialized by the constructor of the most derived class (1.8 [intro.object]). If the constructor of the most derived class does not specify a mem-initializer for a virtual base class V, then V's default constructor is called to initialize the virtual base class subobject. If V does not have an accessible default constructor, the initialization is ill-formed. A mem-initializer naming a virtual base class shall be ignored during execution of the constructor of any class that is not the most derived class.
This paragraph requires only that the most-derived class's constructor have a mem-initializer for virtual base classes. Should the silence be construed as permission for constructors of classes that are not the most-derived to omit such mem-initializers?
Christopher Lester, on comp.std.c++, March 19, 2004: If any of you reading this posting happen to be members of the above working group, I would like to encourage you to review the suggestion contained therein, as it seems to me that the final tenor of the submission is both (a) correct (the silence of the standard DOES mandate the omission) and (b) describes what most users would intuitively expect and desire from the C++ language as well.
The suggestion is to make it clearer that constructors for abstract base classes should not be required to provide initialisers for any virtual base classes they contain (as only the most-derived class has the job of initialising virtual base classes, and an abstract base class cannot possibly be a most-derived class).
For example:
struct A { A(const int i, const int j) {}; }; struct B1 : virtual public A { virtual void moo()=0; B1() {}; // (1) Look! not "B1() : A(5,6) {};" }; struct B2 : virtual public A { virtual void cow()=0; B2() {}; // (2) Look! not "B2() : A(7,8) {};" }; struct C : public B1, public B2 { C() : A(2,3) {}; void moo() {}; void cow() {}; }; int main() { C c; return 0; };
I believe that, by not expressly forbidding it, the standard does (and should!) allow the above code. However, as the standard doesn't expressly allow it either (have I missed something?) there appears to be room for misunderstanding. For example, g++ version 3.2.3 (and maybe other versions as well) rejects the above code with messages like:
In constructor `B1::B1()': no matching function for call to `A::A()' candidates are: A::A(const A&) A::A(int, int)
Fair enough, the standard is perhaps not clear enough. But it seems to be a shame that although this issue was first raised in 2000, we are still living with it today.
Note that we can work-around, and persuade g++ to compile the above by either (a) providing a default constructor A() for A, or (b) supplying default values for i and j in A(i,j), or (c) replace the construtors B1() and B2() with the forms shown in the two comments in the above example.
All three of these workarounds may at times be appropriate, but equally there are other times when all of these workarounds are particularly bad. (a) and (b) may be very bad if you are trying to enforce string contracts among objects, while (c) is just barmy (I mean why did I have to invent random numbers like 5, 6, 7 and 8 just to get the code to compile?).
So to to round up, then, my plea to the working group is: "at the very least, please make the standard clearer on this issue, but preferrably make the decision to expressly allow code that looks something like the above"
Proposed resolution (July, 2009):
Add the indicated text (moved from paragraph 11) to the end of 12.6.2 [class.base.init] paragraph 7:
...The initialization of each base and member constitutes a full-expression. Any expression in a mem-initializer is evaluated as part of the full-expression that performs the initialization. A mem-initializer where the mem-initializer-id names a virtual base class is ignored during execution of a constructor of any class that is not the most derived class.
Change 12.6.2 [class.base.init] paragraph 8 as follows:
If a given non-static data member or base class is not named by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer) and the entity is not a virtual base class of an abstract class (10.4 [class.abstract]), then
if the entity is a non-static data member that has a brace-or-equal-initializer, the entity is initialized as specified in 8.5 [dcl.init];
otherwise, if the entity is a variant member (9.5 [class.union]), no initialization is performed;
otherwise, the entity is default-initialized (8.5 [dcl.init]).
[Note: An abstract class (10.4 [class.abstract]) is never a most derived class, thus its constructors never initialize virtual base classes, therefore the corresponding mem-initializers may be omitted. —end note] After the call to a constructor for class X has completed...
Change 12.6.2 [class.base.init] paragraph 10 as follows:
Initialization shall proceed proceeds in the following order:
First, and only for the constructor of the most derived class as described below (1.8 [intro.object]), virtual base classes shall be are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where “left-to-right” is the order of appearance of the base class names in the derived class base-specifier-list.
Then, direct base classes shall be are initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).
Then, non-static data members shall be are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).
Finally, the compound-statement of the constructor body is executed.
[Note: the declaration order is mandated to ensure that base and member subobjects are destroyed in the reverse order of initialization. —end note]
Remove all normative text in 12.6.2 [class.base.init] paragraph 11, keeping the example:
All subobjects representing virtual base classes are initialized by the constructor of the most derived class (1.8 [intro.object]). If the constructor of the most derived class does not specify a mem-initializer for a virtual base class V, then V's default constructor is called to initialize the virtual base class subobject. If V does not have an accessible default constructor, the initialization is ill-formed. A mem-initializer naming a virtual base class shall be ignored during execution of the constructor of any class that is not the most derived class. [Example:...
[Voted into WP at October, 2009 meeting.]
12.6.2 [class.base.init] paragraph 5 forbids initializing multiple members of a union via mem-initializers:
If a ctor-initializer specifies more than one mem-initializer for the same member, for the same base class or for multiple members of the same union (including members of anonymous unions), the ctor-initializer is ill-formed.
However, there is no corresponding restriction against specifying brace-or-equal-initializers for multiple union members, nor for a non-overlapping pair of brace-or-equal-initializer and mem-initializer. This is presumably an oversight.
Proposed resolution (July, 2009):
Change 9.5 [class.union] paragraph 1 as follows:
...If a union contains a non-static data member of reference type the program is ill-formed. At most one non-static data member of a union shall have a brace-or-equal-initializer. [Note:...
Change 12.6.2 [class.base.init] paragraph 5 as follows:
...If a ctor-initializer specifies more than one mem-initializer for the same member, or for the same base class or for multiple members of the same union (including members of anonymous unions), the ctor-initializer is ill-formed.
Change 12.6.2 [class.base.init] paragraph 8 as follows:
...An attempt to initialize more than one non-static data member of a union renders the program ill-formed. After the call to a constructor for class X has completed...
[Voted into WP at March, 2010 meeting.]
Consider the following example:
struct A { A() { std::thread(&A::Func, this).detach(); } virtual void Func() { printf("In A"); } }; struct B : public A { virtual void Func() { printf("In B"); } }; struct C : public B { virtual void Func() { printf("In C"); } }; C c;
What is the program allowed to print? Should it be undefined behavior or merely unspecified which of the Func()s is called?
There is a related question about which variables C::Func() can depend on having been constructed. Unless we want to require the equivalent of at least memory_order_consume on the presumed virtual function table pointer, I think the answer is just the members of A.
If I instead just have
A a;
I think the only reasonable behavior is to print In A.
Finally, given
struct F { F() { std::thread(&F::Func, this).detach(); } virtual void Func() { print("In F"); } }; struct G : public F { }; G g;
I can see the behavior being undefined, but I think a lot of people would be confused if it did anything other than print In F.
Suggested resolution:
I think the intent here is that an object should not be used in another thread until any non-trivial constructor has been called. One possible way of saying that would be to add a new paragraph at the end of 12.7 [class.cdtor]:
A constructor for a class with virtual functions or virtual base classes modifies a memory location in the object that is accessed by any access to a virtual function or virtual base class or by a dynamic_cast. [Note: This implies that access to an object by another thread while it is being constructed often introduces a data race (see 1.10 [intro.multithread]). —end note]
Proposed resolution (October, 2009):
Add the following as a new paragraph at the end of 3.8 [basic.life]:
In this section, “before” and “after” refer to the “happens before” relation (1.10 [intro.multithread]). [Note: Therefore, undefined behavior results if an object that is being constructed in one thread is referenced from a different thread without adequate synchronization. —end note]
[Voted into WP at July, 2009 meeting.]
How does copy assignment for unions work? For example,
union U { int a; float b; }; void f() { union U u = { 5 }; union U v; v = u; // what happens here? }
9.5 [class.union] is silent on the issue, therefore it seems that 12.8 [class.copy] applies. There is no special case for unions, thus paragraph 13 (memberwise assignment of subobjects) seems to apply. That would seem to imply these actions in the compiler-generated copy assignment operator:
v.a = u.a; v.b = u.b;
And this is just wrong. For example, the lifetime of v.a ends once the second assignment reuses the memory of v.a.
We should probably prescribe “memcpy” copying for unions (both for the copy constructor and the assignment operator) unless the user provided his own special member function.
Proposed resolution (March, 2008):
Change 12.8 [class.copy] paragraph 8 as follows:
The implicitly-defined or explicitly-defaulted copy constructor for a non-union class X performs a memberwise copy of its subobjects...
Add a new paragraph after 12.8 [class.copy] paragraph 8:
The implicitly-defined or explicitly-defaulted copy constructor for a union X where all members have a trivial copy constructor copies the object representation (3.9 [basic.types]) of X. [Note: The behavior is undefined if X is not a trivial type. —end note]
Change 12.8 [class.copy] paragraph 13 as follows:
The implicitly-defined or explicitly-defaulted copy assignment operator for a non-union class X performs memberwise assignment of its subobjects...
Add a new paragraph after 12.8 [class.copy] paragraph 13:
The implicitly-defined or explicitly-defaulted copy assignment operator for a union X where all members have a trivial copy assignment operator copies the object representation (3.9 [basic.types]) of X. [Note: The behavior is undefined if X is not a trivial type. —end note]
Notes from the September, 2008 meeting:
The proposed wording needs to be updated to reflect the changes adopted in papers N2757 and N2762, resolving issue 683, which require “no non-trivial” special member functions instead of “a trivial” function. Also, the notes regarding undefined behavior are incorrect, because the member functions involved are defined as deleted when there are non-trivial members.
Proposed resolution (October, 2008):
Change 12.8 [class.copy] paragraph 8 as follows:
The implicitly-defined or explicitly-defaulted copy constructor for a non-union class X performs a memberwise copy of its subobjects...
Add a new paragraph following 12.8 [class.copy] paragraph 8:
The implicitly-defined or explicitly-defaulted copy constructor for a union X copies the object representation (3.9 [basic.types]) of X.
Change 12.8 [class.copy] paragraph 13 as follows:
Add a new paragraph following 12.8 [class.copy] paragraph 13:
The implicitly-defined or explicitly-defaulted copy assignment operator for a union X copies the object representation (3.9 [basic.types]) of X.
[Voted into WP at March, 2010 meeting as part of document N3079.]
Should the following class have a trivial copy assignment operator?
struct A { int& m; A(); A(const A&); };
12.8 [class.copy] paragraph 11 does not mention whether the presence of reference members (or cv-qualifiers, etc.) should affect triviality. Should it?
One reason why this matters is that implementations have to make the builtin type trait operator __has_trivial_default_ctor(T) work so that they can support the type trait template std::has_trivial_default_constructor.
Assuming the answer is “yes,” it looks like we probably need similar wording for trivial default and trivial copy ctors.
Notes from the February, 2008 meeting:
Deleted special member functions are also not trivial. Resolution of this issue should be coordinated with the concepts proposal.
Notes from the June, 2008 meeting:
It appears that this issue will be resolved by the concepts proposal directly. The issue is in “review” status to check if that is indeed the case in the final version of the proposal.
Additional notes (May, 2009):
Consider the following example:
struct Base { private: ~Base() = default; }; struct Derived: Base { };
The implicitly-declared destructor of Derived is defined as deleted because Base::~Base() is inaccessible, but it fulfills the requirements for being trivial. Presumably the Base destructor should be non-trivial, either by directly specifying that it is non-trivial or by specifying that it is user-provided. An alternative would be to make it ill-formed to attempt to declare a defaulted non-public special member function.
Any changes to the definition of triviality should be checked against 9 [class] paragraph 6 for any changes needed there to accommodate the new definitions.
Notes from the July, 2009 meeting:
The July, 2009 resolution of issue 906 addresses the example above (with an inaccessible defaulted destructor): a defaulted special member function can only have non-public access if the defaulted definition is outside the class, making it non-trivial. The example as written above would be ill-formed.
Proposed resolution (October, 2009):
Change 8.4 [dcl.fct.def] paragraph 9 as follows:
...Only special member functions may be explicitly defaulted. Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall define them as if they had provide implicit definitions for them (12.1 [class.ctor], 12.4 [class.dtor], 12.8 [class.copy]), which might mean defining them as deleted. A special member function that would be implicitly defined as deleted may be explicitly defaulted only on its first declaration, in which case it is defined as deleted. A special member function is user-provided if it is user-declared and not explicitly defaulted on its first declaration. A user-provided explicitly-defaulted function is defined at the point where it is explicitly defaulted. [Note:...
Change 12.1 [class.ctor] paragraphs 5-6 as follows:
A default constructor for a class X is a constructor of class X that can be called without an argument. If there is no user-declared constructor for class X, a constructor having no parameters is implicitly declared as defaulted (8.4 [dcl.fct.def]). An implicitly-declared default constructor is an inline public member of its class. A default constructor is trivial if it is not user-provided (8.4 [dcl.fct.def]) and if:
its class has no virtual functions (10.3 [class.virtual]) and no virtual base classes (10.1 [class.mi]), and
no non-static data member of its class has a brace-or-equal-initializer, and
all the direct base classes of its class have trivial default constructors, and
for all the non-static data members of its class that are of class type (or array thereof), each such class has a trivial default constructor.
An implicitly-declared defaulted default constructor for class X is defined as deleted if:
X is a union-like class that has a variant member with a non-trivial default constructor,
any non-static data member is of reference type,
any non-static data member of const-qualified type (or array thereof) does not have a user-provided default constructor, or
any non-static data member or direct or virtual base class has class type M (or array thereof) and M has no default constructor, or if overload resolution (13.3 [over.match]) as applied to M's default constructor, results in an ambiguity or a function that is deleted or inaccessible from the implicitly-declared default constructor.
A default constructor is trivial if it is neither user-provided nor deleted and if:
its class has no virtual functions (10.3 [class.virtual]) and no virtual base classes (10.1 [class.mi]), and
no non-static data member of its class has a brace-or-equal-initializer, and
all the direct base classes of its class have trivial default constructors, and
for all the non-static data members of its class that are of class type (or array thereof), each such class has a trivial default constructor.
Otherwise, the default constructor is non-trivial.
A non-user-provided default constructor for a class that is defaulted and not deleted is implicitly defined when it is used (3.2 [basic.def.odr]) to create an object of its class type (1.8 [intro.object]), or when it is explicitly defaulted after its first declaration. The implicitly-defined or explicitly-defaulted default constructor performs the set of initializations of the class that would be performed by a user-written default constructor for that class with no ctor-initializer (12.6.2 [class.base.init]) and an empty compound-statement. If that user-written default constructor would be ill-formed, the program is ill-formed. If that user-written default constructor would satisfy the requirements of a constexpr constructor (7.1.5 [dcl.constexpr]), the implicitly-defined default constructor is constexpr. Before the non-user-provided defaulted default constructor for a class is implicitly defined, all the non-user-provided default constructors for its base classes and its non-static data members shall have been implicitly defined. [Note: an implicitly-declared default constructor has an exception-specification (15.4 [except.spec]). An explicitly-defaulted definition has no implicit exception-specification. —end note]
Change 12.4 [class.dtor] paragraphs 3-4 as follows:
If a class has no user-declared destructor, a destructor is declared implicitly declared as defaulted (8.4 [dcl.fct.def]). An implicitly-declared destructor is an inline public member of its class. If the class is a union-like class that has a variant member with a non-trivial destructor, an implicitly-declared destructor is defined as deleted (8.4 [dcl.fct.def]). A destructor is trivial if it is not user-provided and if:
the destructor is not virtual,
all of the direct base classes of its class have trivial destructors, and
for all of the non-static data members of its class that are of class type (or array thereof), each such class has a trivial destructor.
An implicitly-declared defaulted destructor for a class X is defined as deleted if:
X is a union-like class that has a variant member with a non-trivial destructor,
any of the non-static data members has class type M (or array thereof) and M has an a deleted destructor or a destructor that is inaccessible from the implicitly-declared destructor, or
any direct or virtual base class has a deleted destructor or a destructor that is inaccessible from the implicitly-declared destructor.
A destructor is trivial if it is neither user-provided nor deleted and if:
the destructor is not virtual,
all of the direct base classes of its class have trivial destructors, and
for all of the non-static data members of its class that are of class type (or array thereof), each such class has a trivial destructor.
Otherwise, the destructor is non-trivial.
A non-user-provided destructor that is defaulted and not defined as deleted is implicitly defined when it is used to destroy an object of its class type (3.7 [basic.stc]), or when it is explicitly defaulted after its first declaration. A program is ill-formed if the class for which a destructor is implicitly defined or explicitly defaulted has:
a non-static data member of class type (or array thereof) with an inaccessible destructor, or
a base class with an inaccessible destructor.
Before the non-user-provided defaulted destructor for a class is implicitly defined, all the non-user-defined non-user-provided destructors for its base classes and its non-static data members shall have been implicitly defined. [Note: an implicitly-declared destructor has an exception-specification (15.4 [except.spec]). An explictly defaulted definition has no implicit exception-specification. —end note]
Change 12.8 [class.copy] paragraphs 4-9 as follows:
If the class definition does not explicitly declare a copy constructor, one is declared implicitly implicitly declared as defaulted (8.4 [dcl.fct.def]). Thus...
...An implicitly-declared copy constructor is an inline public member of its class. An implicitly-declared defaulted copy constructor for a class X is defined as deleted if X has: ...
A copy constructor for class X is trivial trivial if it is not neither user-provided nor deleted (8.4 [dcl.fct.def]) and if...
A non-user-provided copy constructor that is defaulted and not defined as deleted is implicitly defined if it is used to initialize an object of its class type from a copy of an object of its class type or of a class type derived from its class type116, or when it is explicitly defaulted after its first declaration. [Note: the copy constructor is implicitly defined even if the implementation elided its use (12.2 [class.temporary]). —end note]
Before the non-user-provided defaulted copy constructor for a class is implicitly defined, all non-user-provided copy constructors...
The implicitly-defined or explicitly-defaulted copy constructor for a non-union class X performs...
The implicitly-defined or explicitly-defaulted copy constructor for a union X copies the object representation (3.9 [basic.types]) of X.
Change 12.8 [class.copy] paragraphs 11-15 as follows:
If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly implicitly declared as defaulted (8.4 [dcl.fct.def])...
...An implicitly-declared defaulted copy assignment operator for class X is defined as deleted if X has:...
A copy assignment operator for class X is trivial if it is not neither user-provided nor deleted and if...
A non-user-provided copy assignment operator that is defaulted and not defined as deleted is implicitly defined when an object of its class type is assigned a value of its class type or a value of a class type derived from its class type, or when it is explicitly defaulted after its first declaration.
Before the non-user-provided defaulted copy assignment operator for a class is implicitly defined...
The implicitly-defined or explicitly-defaulted copy assignment operator for a non-union class X performs...
It is unspecified whether subobjects representing virtual base classes are assigned more than once by the implicitly-defined or explicitly-defaulted copy assignment operator. [Example:...
The implicitly-defined or explicitly-defaulted copy assignment operator for a union X copies the object representation (3.9 [basic.types]) of X.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
Although the term “move constructor” appears multiple times in the library clauses and is referenced in the newly-added text for the lambda feature, it is not defined anywhere.
Notes from the June, 2008 meeting:
The only reference to “move constructor” in the core language clauses of the Standard is in 5.1.2 [expr.prim.lambda] paragraph 10; there are no semantic implications of the term. This issue will be addressed by using a function signature instead of the term, thus allowing the library section to provide a definition that is appropriate for its needs.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into WP at March, 2010 meeting.]
12.8 [class.copy] paragraph 16 details the conditions under which a thrown object can be moved instead of copied. However, the optimization as currently described is unsafe. Consider the following example:
void f() { X x; try { throw x; } catch (...) { } // x may have been moved from but can still be accessed here }
When the operation is a throw, as opposed to a return, there must be a restriction that the object potentially being moved be defined within the innermost enclosing try block.
Notes from the July, 2009 meeting:
It is not clear how important this optimization is in the context of throw: how often is a large object with substantial copying overhead thrown? Also, throwing an exception is already a heavyweight operation, so presumably moving instead of copying an object would not make much difference.
Proposed resolution (October, 2009):
Change 12.8 [class.copy] paragraph 17 second bullet as follows:
[Voted into WP at March, 2010 meeting as document N3053.]
A constructor of the form T::T(T&&) is a candidate function for copy construction; however, the declaration of such a constructor does not inhibit the implicit declaration and definition of a copy constructor. This can lead to surprising results. We should consider suppressing the implicit copy constructor if a move constructor is declared.
[Voted into WP at March, 2010 meeting.]
The terminology used to refer to the parameter for this and its corresponding argument is inconsistent, sometimes using “implied” and sometimes “implicit.” It would be easier to search the text of the Standard if this usage were made regular.
Proposed resolution (February, 2010):
Change the index to refer to “implicit object parameter” and “implied object argument” instead of the current permutations of these terms.
Change 13.3 [over.match] paragraph 1 as follows:
...how well (for non-static member functions) the object matches the implied implicit object parameter...
Change 13.3.1 [over.match.funcs] paragraph 4 as follows:
...For conversion functions, the function is considered to be a member of the class of the implicit implied object argument for the purpose of defining the type of the implicit object parameter...
Change the footnote in 13.3.3 [over.match.best] paragraph 1 bullet 1 as follows:
[Voted into WP at October, 2009 meeting.]
There are several problems with the phrasing of 13.3.1.1 [over.match.call] paragraphs 1 and 3. Paragraph 1 reads,
Recall from 5.2.2 [expr.call], that a function call is a postfix-expression, possibly nested arbitrarily deep in parentheses, followed by an optional expression-list enclosed in parentheses:( ... (opt postfix-expression ) ... )opt ( expression-listopt )
Overload resolution is required if the postfix-expression is the name of a function, a function template (14.5.6 [temp.fct]), an object of class type, or a set of pointers-to-function.
Aside from the fact that directly addressing the reader (“Recall that...”) is stylistically incongruous with the rest of the Standard, as well as the fact that 5.2.2 [expr.call] doesn't mention parentheses at all, this wording does not cover member function calls: a member access expression isn't “the name” of anything. This should perhaps be reworded to refer to being either an id-expression or the id-expression in a member access expression. This could be either by using two lines in the “of the form” citation or in the discussion following the syntax reference.
In addition, paragraph 3 refers to “a postfix-expression of the form &F,” which is an oxymoron: &F is a unary-expression, not a postfix-expression. One possibility would be to explicitly include the parentheses needed in this case, i.e., “a postfix-expression of the form (&F)...”
Proposed resolution (September, 2009):
Replace the entirety of 13.3.1.1 [over.match.call] with the following two paragraphs:
In a function call (5.2.2 [expr.call])
postfix-expression ( expression-listopt )
if the postfix-expression denotes a set of overloaded functions and/or function templates, overload resolution is applied as specified in 13.3.1.1.1 [over.call.func]. If the postfix-expression denotes an object of class type, overload resolution is applied as specified in 13.3.1.1.2 [over.call.object].
If the postfix-expression denotes the address of a set of overloaded functions and/or function templates, overload resolution is applied using that set as described above. If the function selected by overload resolution is a non-static member function, the program is ill-formed. [Note: The resolution of the address of an overload set in other contexts is described in 13.4 [over.over]. —end note]
[Voted into WP at October, 2009 meeting.]
According to 13.3.1.3 [over.match.ctor],
When objects of class type are direct-initialized (8.5 [dcl.init]), or copy-initialized from an expression of the same or a derived class type (8.5 [dcl.init])... [the] argument list is the expression-list within the parentheses of the initializer.
However, in copy initialization (using the “=” notation), there need be no parentheses. What is the argument list in that case?
Proposed resolution (June, 2009):
Change 13.3.1.3 [over.match.ctor] paragraph 1 as follows:
...The argument list is the expression-list or assignment-expression within the parentheses of the initializer initializer.
[Voted into WP at March, 2010 meeting.]
Consider the following example:
struct C { }; struct A { explicit operator int() const; explicit operator C() const; }; struct B { int i; B(const A& a): i(a) { } }; int main() { A a; int i = a; int j(a); C c = a; C c2(a); }
It's clear that the B constructor and the declaration of j are well-formed and the declarations of i and c are ill-formed. But what about the declaration of c2? This is supposed to work, but it doesn't under the current wording.
C c2(a) is direct-initialization of a class, so constructors are considered. The only possible candidate is the default copy constructor. So we look for a conversion from A to const C&. There is a conversion operator to C, but it is explicit and we are now performing copy-initialization of a reference temporary, so it is not a candidate, and the declaration of c2 is ill-formed.
Proposed resolution (October, 2009):
Change 13.3.1.4 [over.match.copy] paragraph 1 second bullet as follows:
[Voted into the WP at the March, 2009 meeting.]
12.3.2 [class.conv.fct] paragraph 1 says,
A conversion function is never used to convert a (possibly cv-qualified) object to the (possibly cv-qualified) same object type (or a reference to it), to a (possibly cv-qualified) base class of that type (or a reference to it), or to (possibly cv-qualified) void.
At what point is this enforced, and how is it enforced?
Consider this test case:
struct abc; struct xyz { xyz(); xyz(xyz &); operator xyz& (); // #1 operator abc& (); // #2 }; struct abc : xyz {}; void foo(xyz &); void bar() { foo (xyz ()); }
If such conversion functions are part of the overload set, #1 is a better conversion than #2 to convert the temporary xyz object to a non-const reference required for foo's operand. If such conversion functions are not part of the overload set, then #2 would be selected, and AFAICT the program would be well formed.
If the conversion functions are not part of the overload set, then it would seem one cannot take their address. For instance, adding the following line to the above test case would find no suitable function:
xyz &(xyz::*ptr) () = &xyz::operator xyz &;
Notes from the October, 2007 meeting:
The intent of 12.3.2 [class.conv.fct] paragraph 1 is that overload resolution not be attempted at all for the listed cases; that is, if the target type is void, the object's type, or a base of the object's type, the conversion is done directly without considering any conversion functions. Consequently, the questions about whether the conversion function is part of the overload set or not are moot. The wording will be changed to make this clearer.
Proposed Resolution (October, 2007):
Change the footnote in 12.3.2 [class.conv.fct] paragraph 1 as follows:
A conversion function is never used to convert a (possibly cv-qualified) object to the (possibly cv-qualified) same object type (or a reference to it), to a (possibly cv-qualified) base class of that type (or a reference to it), or to (possibly cv-qualified) void. [Footnote: These conversions are considered as standard conversions for the purposes of overload resolution (13.3.3.1 [over.best.ics], 13.3.3.1.4 [over.ics.ref]) and therefore initialization (8.5 [dcl.init]) and explicit casts (5.2.9 [expr.static.cast]). A conversion to void does not invoke any conversion function (5.2.9 [expr.static.cast]). Even though never directly called to perform a conversion, such conversion functions can be declared and can potentially be reached through a call to a virtual conversion function in a base class —end footnote]
Additional note (March, 2008):
A slight change to the example above indicates that there is a need for a normative change as well as the clarification of the rationale in the October, 2007 proposed resolution. If the declaration of foo were changed to
void foo(const xyz&);
with the current wording, the call foo(xyz()) would be interpreted as foo(xyz().operator abc&()) instead of binding the parameter directly to the rvalue, which is clearly wrong.
Proposed resolution (March, 2008):
Change the footnote in 12.3.2 [class.conv.fct] paragraph 1 as described in the October, 2007 proposed resolution.
Change 8.5.3 [dcl.init.ref] paragraph 5 as follows:
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
If the initializer expression
is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2,” or
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an lvalue of type “cv3 T3,” where “cv1 T1” is reference-compatible with “cv3 T3” [Footnote: This requires a conversion function (12.3.2 [class.conv.fct]) returning a reference type. —end footnote] (this conversion is selected by enumerating the applicable conversion functions (13.3.1.6 [over.match.ref]) and choosing the best one through overload resolution (13.3 [over.match])),
then...
[Drafting note: this resolution makes the example in the issue description ill-formed.]
[Voted into WP at October, 2009 meeting.]
13.3.2 [over.match.viable] paragraph 3 says,
If the parameter has reference type, the implicit conversion sequence includes the operation of binding the reference, and the fact that a reference to non-const cannot be bound to an rvalue can affect the viability of the function (see 13.3.3.1.4 [over.ics.ref]).
This should say “lvalue reference to non-const,” as is correctly stated in 13.3.3.1.4 [over.ics.ref] paragraph 3.
Proposed resolution (July, 2009):
Change 13.3.2 [over.match.viable] paragraph 3 as follows:
If the parameter has reference type, the implicit conversion sequence includes the operation of binding the reference, and the fact that a an lvalue reference to non-const cannot be bound to an rvalue can affect the viability of the function (see 13.3.3.1.4 [over.ics.ref]).
[Voted into WP at July, 2009 meeting.]
The overload resolution rules for ranking a template against a non-template function differ for conversion functions in a surprising way. 13.3.3 [over.match.best] lists four checks, the last three concern this report. For the non-conversion operator case, checks 2 and 3 are applicable, whereas for the conversion operator case checks 3 and 4 are applicable. Checks 2 and 4 concern the ranking of argument and return value conversion sequences respectively. Check 3 concerns only the templatedness of the functions being ranked, and will prefer a non-template to a template. Notice that this check happens after argument conversion sequence ranking, but before return value conversion sequence ranking. This has the effect of always selecting a non-template conversion operator, as the following example shows:
struct C { inline operator int () { return 1; } template <class T> inline operator T () { return 0; } }; inline long f (long x) { return x; } int main (int argc, char *argv[]) { return f (C ()); }
The non-templated C::operator int function will be selected, rather than the apparently better C::operator long<long> instantiation. This is a surprise, and resulted in a bug report where the user expected the template to be selected. In addition some C++ compilers have implemented the overload ranking as if checks 3 and 4 were transposed.
Is this ordering accidental, or is there a rationale?
Notes from the April, 2005 meeting:
The CWG agreed that the template/non-template distinction should be the final tie-breaker.
Proposed resolution (March, 2007):
In the second bulleted list of 13.3.3 [over.match.best] paragraph 1, move the second and third bullets to the end of the list, to read as follows:
for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that,
the context is an initialization by user-defined conversion (see 8.5 [dcl.init], 13.3.1.5 [over.match.conv], and 13.3.1.6 [over.match.ref]) and the standard conversion sequence from the return type of F1 to the destination type (i.e., the type of the entity being initialized) is a better conversion sequence than the standard conversion sequence from the return type of F2 to the destination type, [Example: ... —end example] or, if not that,
- F1 is a non-template function and F2 is a function template specialization, or, if not that,
F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in 14.5.6.2 [temp.func.order].
[Voted into WP at March, 2010 meeting.]
13.3.3.1 [over.best.ics] paragraph 4 says,
However, when considering the argument of a user-defined conversion function that is a candidate by 13.3.1.3 [over.match.ctor] when invoked for the copying of the temporary in the second step of a class copy-initialization, by 13.3.1.7 [over.match.list] when passing the initializer list as a single argument or when the initializer list has exactly one element and a conversion to some class X or reference to (possibly cv-qualified) X is considered for the first parameter of a constructor of X, or by 13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv], or 13.3.1.6 [over.match.ref] in all cases, only standard conversion sequences and ellipsis conversion sequences are allowed.
This is not quite right, as this applies to constructor arguments, not just arguments of user-defined conversion functions. Furthermore, the word “allowed” might be better replaced by something like,
considered (in particular, for the purposes of determining whether the candidate function (that is either a constructor or a conversion function) is viable)
Proposed resolution (October, 2009):
Change 13.3.3.1 [over.best.ics] paragraph 4 as follows:
However, when considering the argument of a constructor or user-defined conversion function that is a candidate by 13.3.1.3 [over.match.ctor] when invoked for the copying of the temporary in the second step of a class copy-initialization, by 13.3.1.7 [over.match.list] when passing the initializer list as a single argument or when the initializer list has exactly one element and a conversion to some class X or reference to (possibly cv-qualified) X is considered for the first parameter of a constructor of X, or by 13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv], or 13.3.1.6 [over.match.ref] in all cases, only standard conversion sequences and ellipsis conversion sequences are allowed considered.
[Voted into WP at March, 2010 meeting.]
According to 13.3.3.1.4 [over.ics.ref] paragraphs 3-4,
A standard conversion sequence cannot be formed if it requires binding an lvalue reference to non-const to an rvalue (except when binding an implicit object parameter; see the special rules for that case in 13.3.1 [over.match.funcs]). [Note: this means, for example, that a candidate function cannot be a viable function if it has a non-const lvalue reference parameter (other than the implicit object parameter) and the corresponding argument is a temporary or would require one to be created to initialize the lvalue reference (see 8.5.3 [dcl.init.ref]). —end note]
Other restrictions on binding a reference to a particular argument that are not based on the types of the reference and the argument do not affect the formation of a standard conversion sequence, however.
Because this section does not mention attempting to bind an rvalue reference to an lvalue, such a “conversion sequence” might be selected as best and result in an ill-formed program. It should, instead, be treated like trying to bind an lvalue reference to non-const to an rvalue, making the function non-viable.
Proposed resolution (November, 2009):
Change 13.3.3.1.4 [over.ics.ref] paragraph 3 as follows:
A Except for an implicit object parameter, for which see 13.3.1 [over.match.funcs], a standard conversion sequence cannot be formed if it requires binding an lvalue reference to non-const to an rvalue (except when binding an implicit object parameter; see the special rules for that case in 13.3.1 [over.match.funcs]) or binding an rvalue reference to an lvalue. [Note: this means, for example, that a candidate function cannot be a viable function if it has a non-const lvalue reference parameter (other than the implicit object parameter) and the corresponding argument is a temporary or would require one to be created to initialize the lvalue reference (see 8.5.3 [dcl.init.ref]). —end note]
[Voted into WP at July, 2009 meeting.]
We need another bullet in 13.3.3.2 [over.ics.rank], along the lines of:
List-initialization sequence L1 is a better conversion sequence than list-initialization sequence L2 if L1 converts to std::initializer_list<X> for some X and L2 does not.
This is necessary to make the following example work:
#include <initializer_list> struct string { string (const char *) {} template <class Iter> string (Iter, Iter); }; template <class T, class U> struct pair { pair (T t, U u) {} }; template<class T, class U> struct map { void insert (pair<T,U>); void insert (std::initializer_list<pair<T,U> >) {} }; int main() { map<string,string> m; m.insert({ {"this","that"}, {"me","you"} }); }
Proposed resolution (March, 2009):
Add a new top-level bullet at the end of the current list in 13.3.3.2 [over.ics.rank] paragraph 3:
[Voted into WP at March, 2010 meeting.]
Conversion of a pointer or pointer to member to bool is given special treatment as a tiebreaker in overload resolution in 13.3.3.2 [over.ics.rank] paragraph 4, bullet 1:
It would be reasonable to expect a similar provision to apply to conversions of std::nullptr_t to bool.
Proposed resolution (October, 2009):
Change 13.3.3.2 [over.ics.rank] paragraph 4 bullet 1 as follows:
[Voted into WP at March, 2010 meeting.]
The list of overloads for user-defined literal operators given in 13.5.8 [over.literal] paragraph 3 should include signatures for char, wchar_t, char16_t, and char32_t.
Proposed resolution (November, 2009):
Change 13.5.8 [over.literal] paragraph 3 as follows:
The declaration of a literal operator shall have a parameter-declaration-clause equivalent to one of the following:
const char* unsigned long long int long double char wchar_t char16_t char32_t const char*, std::size_t const wchar_t*, std::size_t const char16_t*, std::size_t const char32_t*, std::size_t
[Voted into WP at July, 2009 meeting.]
13.6 [over.built] paragraph 7 posits the existence of built-in candidate operator* functions “for every function type T.” However, only non-static member function types can contain a cv-qualifier or ref-qualifier (8.3.5 [dcl.fct] paragraph 7), and a reference to such a type cannot be initialized (5.2.5 [expr.ref] paragraph 4, bullet 3, sub-bullet 2). (See also _N2914_.14.10.4 [concept.support] paragraph 10, which disallows references to function types with cv-qualifiers but is silent on ref-qualifiers.)
Proposed resolution (March, 2009):
Change 13.6 [over.built] paragraph 7 as follows:
For every function type T that does not have cv-qualifiers or a ref-qualifier, there exist candidate operator functions of the formT & operator*(T*);
Change _N2914_.14.10.4 [concept.support] paragraph 7 as follows:
Requires: for every type T that is an object type, a function type that does not have cv-qualifiers or a ref-qualifier, or cv void, a concept map PointeeType<T> is implicitly defined in namespace std.
Change _N2914_.14.10.4 [concept.support] paragraph 11 as follows:
Requires: for every type T that is an object type, a function type that does not have cv-qualifiers or a ref-qualifier, or a reference type, a concept map ReferentType<T> is implicitly defined in namespace std.
[Voted into WP at October, 2009 meeting.]
13.6 [over.built] paragraph 15 restricts the built-in comparison operators to
every T, where T is an enumeration type or pointer to effective object type
This omits both pointers to function types and pointers to void.
Proposed resolution (July, 2009):
Add a new paragraph following 5.9 [expr.rel] paragraph 2:
Change 5.10 [expr.eq] paragraph 1 as follows:
...Pointers to objects or functions of the same type (after pointer conversions) can be compared for equality...
Change 13.6 [over.built] paragraph 15 as follows:
[Voted into WP at March, 2010 meeting.]
13.6 [over.built] paragraphs 24-25 describe the imaginary built-in conditional operator functions. However, neither paragraph 24 (promoted arithmetic types) nor 25 (pointer and pointer-to-member types) covers scoped enumerations, whose values should be usable in conditional expressions.
(See also issue 835.)
Proposed resolution (October, 2009):
Change 13.6 [over.built] paragraph 25 as follows:
For every type T, where T is a pointer, or pointer-to-member, or scoped enumeration type, there exist candidate operator functions of the form
T operator?(bool, T , T );
[Voted into WP at March, 2010 meeting as document N3065.]
Exported templates were a great idea that is generally understood to have failed. In the decade since the standard was adopted, only one implementation has appeared. No current vendors appear interested in creating another. We tentatively suggest this makes the feature ripe for deprecation. Our main concern with deprecation is that it might turn out that exported constrained templates become an important compile-time optimization, as the constraints would be checked once in the exported definition and not in each translation unit consuming the exported declarations.
Notes from the March, 2010 meeting:
It was decided to remove export altogether, rather than deprecating it.
[Voted into WP at October, 2009 meeting.]
Nontype template parameters are currently allowed to have rvalue reference type (14.1 [temp.param] paragraph 4 bullet 3 just says “reference,” not “lvalue reference”). However, with the change of N2844 voted in (which prohibits rvalue references from binding to lvalues), I can't think of any way to specify a valid template argument for a parameter of rvalue reference type. If that's the case, should we restrict nontype template parameters to lvalue reference types?
Proposed resolution (July, 2009):
Change 14.1 [temp.param] paragraph 4, bullet 3 as follows:
[Voted into WP at March, 2010 meeting.]
5.19 [expr.const] permits literal types with a constexpr conversion function to an integral type to be used in an integral constant expression. However, such conversions are not listed in 14.3.2 [temp.arg.nontype] paragraph 5 bullet 1 among the conversions applied to template-arguments for a non-type template-parameter of integral or enumeration type.
Notes from the March, 2009 meeting:
The original national body comment suggested allowing any literal type as a non-type template argument. The CWG was not in favor of this change, but in the course of discussing the suggestion discovered the problem with template-parameters of integral and enumeration type.
Proposed resolution (October, 2009):
Change 14.3.2 [temp.arg.nontype] paragraph 1 bullet 1 as follows:
[Voted into WP at March, 2010 meeting.]
According to 14.3.3 [temp.arg.template] paragraph 3,
A template-argument matches a template template-parameter (call it P) when each of the template parameters in the template-parameter-list of the template-argument's corresponding class template or template alias (call it A) matches the corresponding template parameter in the template-parameter-list of P. When P's template-parameter-list contains a template parameter pack (14.5.3 [temp.variadic]), the template parameter pack will match zero or more template parameters or template parameter packs in the template-parameter-list of A with the same type and form as the template parameter pack in P (ignoring whether those template parameters are template parameter packs).
The immediately-preceding example, however, assumes that a parameter pack in the parameter will match only a parameter pack in the argument:
template<class T> class A { /* ... */ }; template<class T, class U = T> class B { /* ... */ }; template<class ... Types> class C { /* ... */ }; template<template<class ...> class Q> class Y { /* ... */ }; Y<A> ya; // ill-formed: a template parameter pack does not match a template parameter Y<B> yb; // ill-formed: a template parameter pack does not match a template parameter Y<C> yc; // OK
Proposed resolution (February, 2010):
Change the final three lines of the second example in 14.3.3 [temp.arg.template] paragraph 2 as follows:
Y<A> ya; // ill-formed: a template parameter pack does not match a template parameter OK Y<B> yb; // ill-formed: a template parameter pack does not match a template parameter OK Y<C> yc; // OK
[Voted into WP at March, 2010 meeting.]
Is this allowed?
template<typename T> struct X { static int s[]; int c; }; template<typename T> int X<T>::s[sizeof(X<T>)]; int* p = X<char>::s;
I have a compiler claiming that, for the purpose of sizeof(), X<T> is an incomplete type, when it tries to instantiate X<T>::s. It seems to me that X<char> should be considered complete enough for sizeof even though the size of s isn't known yet.
John Spicer: This is a problematic construct that is currently allowed but which I think should be disallowed.
I tried this with a number of compilers. None of which did the right thing. The EDG front end accepts it, but gives X<...>::s the wrong size.
It appears that most compilers evaluate the "declaration" part of the static data member definition only once when the definition is processed. The initializer (if any) is evaluated for each instantiation.
This problem is solvable, and if it were the only issue with incomplete arrays as template static data members, then it would make sense to solve it, but there are other problems.
The first problem is that the size of the static data member is only known if a template definition of the static data member is present. This is weird to start with, but it also means that sizes would not be available in general for exported templates.
The second problem concerns the rules for specialization. An explicit specialization for a template instance can be provided up until the point that a use is made that would cause an implicit instantiation. A reference like "sizeof(X<char>::s)" is not currently a reference that would cause an implicit instantiation of X<char>::s. This means you could use such a sizeof and later specialize the static data member with a different size, meaning the earlier sizeof gave the wrong result. We could, of course, change the "use" rules, but I'd rather see us require that static data members that are arrays have a size specified in the class or have a size based on their initializer.
Notes from the October 2003 meeting:
The example provided is valid according to the current standard. A static data member must be instantiated (including the processing of its initializer, if any) if there is any reference to it. The compiler need not, however, put out a definition in that translation unit. The standard doesn't really have a concept of a "partial instantiation" for a static data member, and although we considered adding that, we decided that to get all the size information that seems to be available one needs a full instantiation in any case, so there's no need for the concept of a partial instantiation.
Note (June, 2006):
Mark Mitchell suggested the following example:
template <int> void g(); template <typename T> struct S { static int i[]; void f(); }; template <typename T> int S<T>::i[] = { 1 }; template <typename T> void S<T>::f() { g<sizeof (i) / sizeof (int)>(); } template <typename T> int S<int>::i[] = { 1, 2 };
Which g is called from S<int>::f()?
If the program is valid, then surely one would expect g<2> to be called.
If the program is valid, does S<T>::i have a non-dependent type in S<T>::f? If so, is it incomplete, or is it int[1]? (Here, int[1] would be surprising, since S<int>::i actually has type int[2].)
If the program is invalid, why?
For a simpler example, consider:
template <typename T> struct S { static int i[]; const int N = sizeof (i); };
This is only valid if the type of i is dependent, meaning that the sizeof expression isn't evaluated until the class is instantiated.
Proposed resolution (February, 2010):
Add the following as a new paragraph following 14.5.1.3 [temp.static] paragraph 1:
An explicit specialization of a static data member declared as an array of unknown bound can have a different bound from its definition, if any. [Example:
template<class T> struct A { static int i[]; }; template<class T> int A<T>::i[4]; // 4 elements template<> int A<int>::i[] = { 1 }; // 1 element, OK
—end example]
Change 14.6.2.2 [temp.dep.expr] paragraph 3 as follows:
An id-expression is type-dependent if it contains:
an identifier that was declared with a dependent type,
a template-id that is dependent,
a conversion-function-id that specifies a dependent type, or
a nested-name-specifier or a qualified-id that names a member of an unknown specialization.;
or if it names a static data member of the current instantiation that has type “array of unknown bound of T” for some T (14.5.1.3 [temp.static]). Expressions of the following forms are type-dependent only if...
[Voted into WP at March, 2010 meeting.]
Is this code well-formed?
template <typename T> struct A { struct B; }; class C { template <typename T> friend struct A<T>::B; static int bar; }; template <> struct A<char> { struct B { int f() { return C::bar; // Is A<char>::B a friend of C? } }; };
According to 14.5.4 [temp.friend] paragraph 5,
A member of a class template may be declared to be a friend of a non-template class. In this case, the corresponding member of every specialization of the class template is a friend of the class granting friendship.
This would tend to indicate that the example is well-formed. However, technically A<char>::B does not “correspond to” the same-named member of the class template: 14.7.3 [temp.expl.spec] paragraph 4 says,
The definition of an explicitly specialized class is unrelated to the definition of a generated specialization. That is, its members need not have the same names, types, etc. as the members of a generated specialization.
In other words, there are no “corresponding members” in an explicit specialization.
Is this the outcome we want for examples like the preceding? There is diversity among implementations on this question, with some accepting the example and others rejecting it as an access violation.
Notes from the July, 2009 meeting:
The consensus of the CWG was to allow the correspondence of similar members in explicit specializations.
Proposed resolution (October, 2009):
Change 14.5.4 [temp.friend] paragraph 5 as follows:
A member of a class template may be declared to be a friend of a non-template class. In this case, the corresponding member of every specialization of the class template is a friend of the class granting friendship. For explicit specializations the corresponding member is the member (if any) that has the same name, kind (type, function, class template or function template), template parameters, and signature as the member of the class template instantiation that would otherwise have been generated. [Example:
template<class T> struct A { struct B { }; void f(); struct D { void g(); }; }; template<> struct A<int> { struct B { }; int f(); struct D { void g(); }; }; class C { template<class T> friend struct A<T>::B; // grants friendship to A<int>::B even though // it is not a specialization of A<T>::B template<class T> friend void A<T>::f(); // does not grant friendship to A<int>::f() // because its return type does not match template<class T> friend void A<T>::D::g(); // does not grant friendship to A<int>::D::g() // because A<int>::D is not a specialization of A<T>::D };
[Voted into WP at October, 2009 meeting.]
Although it is a reasonable assumption that a template-declaration in which the declaration is an alias-declaration declares a template alias, that is not said explicitly in 14.5.7 [temp.alias] nor, apparently, anywhere else.
Proposed resolution (September, 2009):
Change 14.5.7 [temp.alias] paragraph 1 as follows:
A template-declaration in which the declaration is an alias-declaration (clause 7) declares the identifier to be a template alias. A template alias declares template alias is a name for a family of types. The name of the template alias is a template-name.
[Voted into the WP at the March, 2009 meeting.]
14.6.2 [temp.dep] paragraph 3 reads,
In the definition of a class template or a member of a class template, if a base class of the class template depends on a template-parameter, the base class scope is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member.
This wording applies only to definitions of class templates and members of class templates. That would make the following program ill-formed (but it probably should be well-formed):
struct B{ void f(int); }; template<class T> struct D: B { }; template<class T> void g() { struct B{ void f(); }; struct A: D<T> { B m; }; A a; a.m.f(); // Presumably, we want ::g()::B::f(), not ::B::f(int) } int main () { g<int>(); return 0; }
I suspect the wording should be something like
In the definition of a class template or a class defined (directly or indirectly) within the scope of a class template or function template, if a base class...
That should also include deeply nested classes in templates, local classes of non-template member functions of member classes of class templates, etc.
Proposed resolution (October, 2006):
Change 14.6.2 [temp.dep] paragraph 3 as follows:
In the definition of a class or class template or a member of a class template, if a base class of the class template depends on a template-parameter, the base class scope is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member.
[Voted into WP at March, 2010 meeting.]
14.6.2.2 [temp.dep.expr] paragraph 3 says,
An id-expression is type-dependent if it contains:
- an identifier that was declared with a dependent type...
This treatment seems inadequate with regard to id-expressions in function calls:
According to 14.6.2.1 [temp.dep.type] paragraph 6,
A type is dependent if it is
- ...
- a compound type constructed from any dependent type...
This would apply to the type of a member function of a class template if any of its parameters are dependent, even if the return type is not dependent. However, there is no need for a call to such a function to be a type-dependent expression because the type of the expression is known at definition time.
This wording does not handle the case of overloaded functions, some of which might have dependent types (however defined) and others not.
Notes from the October, 2009 meeting:
The consensus of the CWG was that the first point of the issue is not sufficiently problematic as to require a change.
Proposed resolution (October, 2009):
Change 14.6.2.2 [temp.dep.expr] paragraph 3 as follows:
An id-expression is type-dependent if it contains:
an identifier that was associated by name lookup with one or more declarations declared with a dependent type,
a template-id that is dependent,
a conversion-function-id that specifies a dependent type, or
a nested-name-specifier or a qualified-id that names a member of an unknown specialization.
[Voted into WP at March, 2010 meeting.]
According to 14.6.4.2 [temp.dep.candidate],
For a function call that depends on a template parameter, if the function name is an unqualified-id but not a template-id, the candidate functions are found using the usual lookup rules (3.4.1 [basic.lookup.unqual], 3.4.2 [basic.lookup.argdep]) except that:
For the part of the lookup using unqualified name lookup (3.4.1 [basic.lookup.unqual]), only function declarations with external linkage from the template definition context are found.
For the part of the lookup using associated namespaces (3.4.2 [basic.lookup.argdep]), only function declarations with external linkage found in either the template definition context or the template instantiation context are found.
It is not at all clear why a call using a template-id would be treated differently from one not using a template-id. Furthermore, is it really necessary to exclude internal linkage functions from the lookup? Doesn't the ODR give implementations sufficient latitude to handle this case without another wrinkle on name lookup?
(See also issue 524.)
Notes from the April, 2006 meeting:
The consensus of the group was that template-ids should not be treated differently from unqualified-ids (although it's not clear how argument-dependent lookup works for template-ids), and that internal-linkage functions should be found by the lookup (although they may result in errors if selected by overload resolution).
Note (June, 2006):
Although the notes from the Berlin meeting indicate that argument-dependent lookup for template-ids is under-specified in the Standard, further examination indicates that that is not the case: the note in 14.8.1 [temp.arg.explicit] paragraph 8 clearly indicates that argument-dependent lookup is to be performed for template-ids, and 3.4.2 [basic.lookup.argdep] paragraph 4 describes the lookup performed:
When considering an associated namespace, the lookup is the same as the lookup performed when the associated namespace is used as a qualifier (3.4.3.2 [namespace.qual]) except that:
Any using-directives in the associated namespace are ignored.
Any namespace-scope friend functions declared in associated classes are visible within their respective namespaces even if they are not visible during an ordinary lookup (11.3 [class.friend]).
Proposed resolution (October, 2009):
Change 14.6.2 [temp.dep] paragraph 1 as follows:
In an expression of the form:
postfix-expression ( expression-listopt )
where the postfix-expression is an unqualified-id but not a template-id, the unqualified-id denotes a dependent name if and only if any of the expressions in the expression-list is a type-dependent expression (14.6.2.2 [temp.dep.expr])...
Change 14.6.4.2 [temp.dep.candidate] paragraph 1 as follows:
For a function call that depends on a template parameter, if the function name is an unqualified-id but not a template-id, or if the function is called using operator notation, the candidate functions are found using the usual lookup rules (3.4.1 [basic.lookup.unqual], 3.4.2 [basic.lookup.argdep]) except that:
For the part of the lookup using unqualified name lookup (3.4.1 [basic.lookup.unqual]), only function declarations with external linkage from the template definition context are found.
For the part of the lookup using associated namespaces (3.4.2 [basic.lookup.argdep]), only function declarations with external linkage found in either the template definition context or the template instantiation context are found.
[Voted into WP at March, 2010 meeting.]
Consider this example:
template <class T> struct A { virtual void f() {} }; extern template struct A<int>; int main() { A<int> a; a.f(); }
The intent is that the explicit instantiation declaration will suppress any compiler-generated machinery such as a virtual function table or typeinfo data in this translation unit, and that because of 14.7.2 [temp.explicit] paragraph 10,
An entity that is the subject of an explicit instantiation declaration and that is also used in the translation unit shall be the subject of an explicit instantiation definition somewhere in the program; otherwise the program is ill-formed, no diagnostic required.
the use of A<int> in declaring a requires an explicit instantiation definition in another translation unit that will provide the requisite compiler-generated data.
The existing wording of 14.7.2 [temp.explicit] does not express this intent clearly enough, however.
Suggested resolution:
Change 14.7.2 [temp.explicit] paragraph 7 as follows:
An explicit instantiation that names a class template specialization is also an explicit instantion of the same kind (declaration or definition) of each of its members (not including members inherited from base classes) that has not been previously explicitly specialized in the translation unit containing the explicit instantiation, except as described below.
Change 14.7.2 [temp.explicit] paragraph 9 as follows:
An explicit instantiation declaration that names a class template specialization has no effect on the class template specialization itself (except for perhaps resulting in its implicit instantiation). Except for inline functions and class template specializations, other explicit instantiation declarations have the effect of suppressing the implicit instantiation of the entity to which they refer...
Proposed resolution (October, 2009):
Change 14.7.2 [temp.explicit] paragraphs 7-9 as follows:
An explicit instantiation that names a class template specialization is also an explicit instantion of the same kind (declaration or definition) of each of its members (not including members inherited from base classes) that has not been previously explicitly specialized in the translation unit containing the explicit instantiation, except as described below. [Note: In addition, it will typically be an explicit instantiation of certain implementation-dependent data about the class. —end note]
An explicit instantiation definition that names a class template specialization explicitly instantiates the class template specialization and is only an explicit instantiation definition of only those members whose definition is visible at the point of instantiation.
An explicit instantiation declaration that names a class template specialization has no effect on the class template specialization itself (except for perhaps resulting in its implicit instantiation). Except for inline functions and class template specializations, other explicit instantiation declarations have the effect of suppressing the implicit instantiation of the entity to which they refer...
[Voted into WP at March, 2010 meeting.]
14.7.2 [temp.explicit] paragraph 1 says,
An explicit instantiation of a function template shall not use the inline or constexpr specifiers.
This wording should be revised to apply to member functions of class templates as well.
Proposed resolution (February, 2010):
Change 14.7.2 [temp.explicit] paragraph 1 as follows:
...An explicit instantiation of a function template or member function of a class template shall not use the inline or constexpr specifiers.
[Voted into WP at March, 2010 meeting.]
14.7.2 [temp.explicit] paragraph 5 has an example that reads, in significant part,
namespace N { template<class T> class Y { void mf() { } }; } using N::Y; template class Y<int>; // OK: explicit instantiation in namespace N
In fact, paragraph 2 requires that an explicit instantiation with an unqualified name must appear in the same namespace in which the template was declared, so the example is ill-formed.
Proposed resolution (February, 2010):
Change the example in 14.7.2 [temp.explicit] paragraph 5 as follows:
namespace N { template<class T> class Y { void mf() { } }; } template class Y<int>; // error: class template Y not visible // in the global namespace using N::Y; template class Y<int>; // OK: explicit instantiation in namespace N template class Y<int>; // error: explicit instantiation outside of the // namespace of the template template class N::Y<char*>; // OK: explicit instantiation in namespace N template void N::Y<double>::mf(); // OK: explicit instantiation // in namespace N
[Voted into WP at October, 2009 meeting.]
The list of entities that can be explicitly specialized in 14.7.3 [temp.expl.spec] paragraph 1 includes member templates of class templates but not member templates of non-template classes. This omission could lead to the conclusion that such member templates cannot be explicitly specialized. (Note, however, that paragraph 3 refers to “an explicit specialization for a member template of [a] class or class template.”)
Proposed resolution (July, 2009):
Change 14.7.3 [temp.expl.spec] paragraph 1 as follows:
An explicit specialization of any of the following:
...
member class template of a class or class template
non-deleted member function template of a class or class template
can be declared...
[Voted into WP at October, 2009 meeting.]
14.7.3 [temp.expl.spec] paragraphs 15-16 contain the following note:
[Note: there is no syntax for the definition of a static data member of a template that requires default initialization.template<> X Q<int>::x;This is a declaration regardless of whether X can be default initialized (8.5 [dcl.init]). —end note]
While this note is still accurate, the C++0x list initialization syntax provides a way around the restriction, which could be useful if the class is not copyable or movable but has a default constructor. Perhaps the note should be updated to mention that possibility?
Proposed resolution (July, 2009):
Change 14.7.3 [temp.expl.spec] paragraphs 15-16 as follows:
An explicit specialization of a static data member of a template is a definition if the declaration includes an initializer; otherwise, it is a declaration. [Note: there is no syntax for the The definition of a static data member of a template that requires default initialization. must use a braced-init-list:
template<> X Q<int>::x; // declaration template<> X Q<int>::x (); // error: declares a function template<> X Q<int>::x {}; // definition
This is a declaration regardless of whether X can be default initialized (8.5 [dcl.init]). —end note]
[Voted into WP at March, 2010 meeting.]
According to 14.7.3 [temp.expl.spec] paragraph 14,
An explicit specialization of a function template is inline only if it is explicitly declared to be...
This could be read to require that the inline keyword must appear in the declaration. However, 8.4 [dcl.fct.def] paragraph 10 says that a deleted function is implicitly inline, so it should be made clear that defining an explicit specialization as deleted makes it inline.
Proposed resolution (November, 2009):
Change 14.7.3 [temp.expl.spec] paragraph 14 as follows:
An explicit specialization of a function template is inline only if it is explicitly declared to be with the inline specifier or defined as deleted, and independently of whether its function template is inline. [Example:...
[Voted into WP at October, 2009 meeting.]
A customer of ours recently brought the following example to our attention. There's some question as to whether the Standard adequately addresses this example, and if it does, whether the outcome is what we'd like to see. Here's the example:
struct Abs {
virtual void x() = 0;
};
struct Der: public Abs {
virtual void x();
};
struct Cnvt {
template <typename F> Cnvt(F);
};
void foo(Cnvt a);
void foo(Abs &a);
void f() {
Der d;
Abs *a = &d;
foo(*a); // #1
return 0;
}
The question is how to perform overload resolution for the call at #1. To do that, we need to determine whether foo(Cnvt) is a viable function. That entails deciding whether there is an implicit conversion sequence that converts Abs (the type of *a in the call) to Cnvt (13.3.2 [over.match.viable] paragraph 3), and that involves a recursive invocation of overload resolution.
The initialization of the parameter of foo(Cnvt) is a case of copy-initialization of a class by user-defined conversion, so the candidate functions are the converting constructors of Cnvt (13.3.1.4 [over.match.copy] paragraph 1), of which there are two: the implicitly-declared copy constructor and the constructor template.
According to 14.7.1 [temp.inst] paragraph 8,
If a function template or a member function template specialization is used in a way that involves overload resolution, a declaration of the specialization is implicitly instantiated (14.8.3 [temp.over]).
Template argument deduction results in “synthesizing” (14.8.3 [temp.over] paragraph 1) (or “instantiating,” 14.7.1 [temp.inst] paragraph 8) the declaration
Cnvt::Cnvt(Abs)
Because Abs is an abstract class, this declaration violates the restriction of 10.4 [class.abstract] paragraph 3 (“An abstract class shall not be used as a parameter type...”), and because a parameter of an abstract class type does not cause a deduction failure (it's not in the bulleted list in 14.8.2 [temp.deduct] paragraph 2), the program is ill-formed. This error is reported by both EDG and Microsoft compilers, but not by g++.
It seems unfortunate that the program would be rendered ill-formed by a semantic violation in a declaration synthesized solely for the purpose of overload resolution analysis; foo(Cnvt) would not be selected by overload resolution, so Cnvt::Cnvt(Abs) would not be instantiated.
There's at least some indication that a parameter with an abstract class type should be a deduction failure; an array element of abstract class type is a deduction failure, so one might expect that a parameter would be, also.
(See also issue 339; this question might be addressed as part of the direction described in the notes from the July, 2007 meeting.)
Notes from the June, 2008 meeting:
Paper N2634, adopted at the June, 2008 meeting, replaces the normative list of specific errors accepted as deduction failures by a general statement covering all “invalid types and expressions in the immediate context of the function type and its template parameter types,” so the code is now well-formed. However, the previous list is now a note, and the note should be updated to mention this case.
Proposed resolution (August, 2008):
Add a new bullet following the last bullet of the note in 14.8.2 [temp.deduct] paragraph 8 as follows:
Attempting to create a function type in which a parameter type or the return type is an abstract class type (10.4 [class.abstract]).
[Voted into WP at March, 2010 meeting.]
The adoption of paper N2844 made it ill-formed to attempt to bind an rvalue reference to an lvalue. However, the example in 14.8.2.1 [temp.deduct.call] paragraph 3 still reflects the previous specification:
template <typename T> int f(T&&); int i; int j = f(i); // calls f<int&>(i) template <typename T> int g(const T&&); int k; int n = g(k); // calls g<int>(k)
The last line of that example is now ill-formed, attempting to bind the const int&& parameter of g to the lvalue k.
Proposed resolution (July, 2009):
Replace the example in 14.8.2.1 [temp.deduct.call] paragraph 3 with:
template<typename T> int f(T&&); template<typename T> int g(const T&&); int i; int n1 = f(i); // calls f<int&>(int&) int n2 = f(0); // calls f<int>(int&&) int n3 = g(i); // error: would call g<int>(const int&&), which would // bind an rvalue reference to an lvalue
(See also issue 858.)
[Voted into WP at October, 2009 meeting.]
14.8.2.1 [temp.deduct.call] paragraph 3 says,
If P is of the form T&&, where T is a template parameter, and the argument is an lvalue, the type A& is used in place of A for type deduction.
The type references in that sentence are inconsistent with the normal usage in the Standard; they should instead refer to “an rvalue reference to a cv-unqualified template parameter” and “lvalue reference to A.”
Proposed resolution (July, 2009):
Change 14.8.2.1 [temp.deduct.call] paragraph 3 as follows:
If P is a cv-qualified type, the top level cv-qualifiers of P's type are ignored for type deduction. If P is a reference type, the type referred to by P is used for type deduction. If P is of the form T&&, where T is a template parameter, an rvalue reference to a cv-unqualified template parameter and the argument is an lvalue, the type A& “lvalue reference to A” is used in place of A for type deduction.
[Voted into WP at March, 2010 meeting.]
An expression used in an if statement is implicitly converted to type bool (6.4 [stmt.select]). According to the rules of template argument deduction for conversion functions given in 14.8.2.3 [temp.deduct.conv], the following example is ill-formed:
struct X { template<class T> operator const T&() const; }; int main() { if( X() ) {} }
Following the logic in 14.8.2.3 [temp.deduct.conv], A is bool and P is const T (because cv-qualification is dropped from P before the reference is removed), and deduction fails.
It's not clear whether this is the intended outcome or not.
Notes from the April, 2005 meeting:
The CWG observed that there is nothing special about either bool or the context in the example above; instead, it will be a problem wherever a copy occurs, because cv-qualification is always dropped in a copy operation. This appears to be a case where the conversion deduction rules are not properly symmetrical with the rules for arguments. The example should be accepted.
Proposed resolution (February, 2010):
This issue is resolved by the resolution of issue 976.
[Voted into WP at March, 2010 meeting.]
The rules for deducing function template arguments from a conversion function template include provisions in 14.8.2.3 [temp.deduct.conv] paragraph 2 for array and function return types, even though such types are prohibited and cannot occur in the conversion-type-id of a conversion function template. They should be removed.
Proposed resolution (February, 2010):
This issue is resolved by the resolution of issue 976. In particular, under that resolution, if a conversion function returns a reference to an array or function type, the reference will be dropped prior to the adjustments mentioned in this issue, so they are, in fact, needed.
[Voted into WP at March, 2010 meeting.]
Consider this program:
struct F { template<class T> operator const T&() { static T t; return t; } }; int main() { F f; int i = f; // ill-formed }
It's ill-formed, because according to 14.8.2.3 [temp.deduct.conv], we try to match const T with int.
(The reference got removed from P because of paragraph 3, but the const isn't removed, because paragraph 2 bullet 3 comes before paragraph 3 and thus isn't applied any more.)
Changing the declaration of the conversion operator to
operator T&() { ... }
makes the program compile, which is counter-intuitive to me: I'm in an rvalue (read-only) context, and I can use a conversion to T&, but I can't use a conversion to const T&?
Proposed resolution (February, 2010):
Change 14.8.2.3 [temp.deduct.conv] paragraphs 1-3 as follows, inserting a new paragraph between the current paragraphs 1 and 2:
Template argument deduction is done by comparing the return type of the conversion function template (call it P; see 8.5 [dcl.init], 13.3.1.5 [over.match.conv], and 13.3.1.6 [over.match.ref] for the determination of that type) with the type that is required as the result of the conversion (call it A) as described in 14.8.2.5 [temp.deduct.type].
If P is a reference type, the type referred to by P is used in place of P for type deduction and for any further references to or transformations of P in the remainder of this section.
If A is not a reference type:
If P is an array type, the pointer type produced by the array-to-pointer standard conversion (4.2 [conv.array]) is used in place of P for type deduction; otherwise,
If P is a function type, the pointer type produced by the function-to-pointer standard conversion (4.3 [conv.func]) is used in place of P for type deduction; otherwise,
If P is a cv-qualified type, the top level cv-qualifiers of P's type are ignored for type deduction.
If A is a cv-qualified type, the top level cv-qualifiers of A's type are ignored for type deduction. If A is a reference type, the type referred to by A is used for type deduction. If P is a reference type, the type referred to by P is used for type deduction.
(This resolution also resolves issues 493 and 913.)
[Drafting note: This change intentionally reverses the resolution of issue 322 (and applies it in a different form).]
[Voted into the WP at the March, 2009 meeting.]
According to 15.1 [except.throw] paragraph 3,
The type of the throw-expression shall not be an incomplete type, or a pointer to an incomplete type other than (possibly cv-qualified) void.
This disallows cases like the following, because str has an incomplete type (an array of unknown size):
extern const char str[]; void f() { throw str; }
The array-to-pointer conversion is applied to the operand of throw, so there's no problem creating the exception object, which is the reason for the restriction on incomplete types. I believe this case should be permitted.
Notes from the April, 2005 meeting:
The CWG agreed that the example should be permitted. Note that the reference to throw-expression in the cited text is incorrect; a throw-expression includes the throw keyword and is always of type void. This wording problem is addressed in the proposed resolution for issue 475.
Proposed resolution (October, 2006)
Change 15.1 [except.throw] paragraph 3 as indicated:
...The type of the throw-expression shall not If the type of the exception object would be an incomplete type, or a pointer to an incomplete type other than (possibly cv-qualified) void the program is ill-formed...
[Voted into WP at March, 2010 meeting.]
15.1 [except.throw] paragraph 4 says,
When the last remaining active handler for the exception exits by any means other than throw; the temporary object is destroyed and the implementation may deallocate the memory for the temporary object...
With std::current_exception() (18.8.5 [propagation] paragraph 7), it might be possible to refer to the exception object after its last handler exits (if the exception object is not copied). The text needs to be updated to allow for that possibility.
Proposed resolution (September, 2009):
Change 15.1 [except.throw] paragraph 4 as follows:
The memory for the temporary copy of the exception being thrown exception object is allocated in an unspecified way, except as noted in 3.7.4.1 [basic.stc.dynamic.allocation]. The temporary persists as long as there is a handler being executed for that exception. In particular, if If a handler exits by executing a throw; statement, that passes control rethrowing, control is passed to another handler for the same exception, so the temporary remains. The exception object is destroyed after either When the last remaining active handler for the exception exits by any means other than throw; rethrowing, or the last object of type std::exception_ptr (18.8.5 [propagation]) that refers to the exception object is destroyed, whichever is later. In the former case, the destruction occurs when the handler exits, immediately after the destruction of the object declared in the exception-declaration in the handler, if any. In the latter case, the destruction occurs before the destructor of std::exception_ptr returns. the temporary object is destroyed and the The implementation may then deallocate the memory for the temporary exception object; any such deallocation is done in an unspecified way. The destruction occurs immediately after the destruction of the object declared in the exception-declaration in the handler.
[Voted into WP at March, 2010 meeting as paper N3051.]
Exception specifications have proven close to worthless in practice, while adding a measurable overhead to programs. The feature should be deprecated. The one exception to the rule is the empty throw specification which could serve a legitimate optimizing role if the requirement to call std::unexpected were relaxed in this case.
Notes from the July, 2009 meeting:
The consensus of the CWG was in favor of deprecating exception specifications. Further discussion, and with a wider constituency, is needed to determine a position on the status of throw().
(See also issue 814.)
[Voted into WP at March, 2010 meeting.]
There is no prohibition against specifying a function type in an
exception-specification, and the normal conversion of a
function type to a pointer-to-function type occurs in both
throw-expressions (
Proposed resolution (February, 2010):
Change 15.4 [except.spec] paragraphs 2-3 as follows:
A type denoted in an exception-specification shall not denote an incomplete type. A type denoted in an exception-specification shall not denote a pointer or reference to an incomplete type, other than void*, const void*, volatile void*, or const volatile void*. A type cv T, “array of T,” or “function returning T” denoted in an exception-specification is adjusted to type T, “pointer to T,” or “pointer to function returning T,” respectively.
If any declaration of a function has an exception-specification, all declarations, including the definition and an any explicit specialization, of that function shall have an exception-specification with the same set of type-ids adjusted types. If any declaration of a pointer to function, reference to function, or pointer to member function has an exception-specification, all occurrences of that declaration shall have an exception-specification with the same set of type-ids adjusted types. In an explicit instantiation an exception-specification may be specified, but is not required. If an exception-specification is specified in an explicit instantiation directive, it shall have the same set of type-ids adjusted types as other declarations of that function. A diagnostic is required only if the sets of type-ids adjusted types are different within a single translation unit.
[Voted into the WP at the March, 2009 meeting.]
The destruction of local static objects occurs at the same time as that of non-local objects (3.6.3 [basic.start.term] paragraph 1) and the execution of functions registered with std::atexit (paragraph 3). According to 15.5.1 [except.terminate] paragraph 1, std::terminate is called if a destructor for a non-local object or a function registered with std::atexit exits via an exception, but the Standard is silent about the result of throwing an exception from a destructor for a local static object. Presumably this is an oversight and the same rules should apply to destruction of local static objects.
Proposed resolution (September, 2008):
Change 15.5.1 [except.terminate] paragraph 1, fourth bullet as indicated, and add an additional bullet to follow it:
when construction or destruction of a non-local object with static or thread storage duration exits using an exception (3.6.2 [basic.start.init]), or
when destruction of an object with static or thread storage duration exits using an exception (3.6.3 [basic.start.term]), or
[Voted into WP at October, 2009 meeting.]
The description of preprocessing expressions in 16.1 [cpp.cond] paragraph 4 says,
The resulting tokens comprise the controlling constant expression which is evaluated according to the rules of 5.19 using arithmetic that has at least the ranges specified in 18.3 [support.limits], except that all signed and unsigned integer types act as if they have the same representation as, respectively, intmax_t or uintmax_t (18.3.2).
However, this does not address the type implicitly assigned to integral literals. For example, in an implementation where int is 32 bits and long long is 64 bits, is a literal like 0xffffffff signed or unsigned? WG14 adopted DR 265 to deal with this issue in the essentially-identical wording in C99; we should probably follow suit for C++.
Proposed Resolution (July, 2009):
Change 16.1 [cpp.cond] paragraph 4 as follows:
...and then each preprocessing token is converted into a token. The resulting tokens comprise the controlling constant expression which is evaluated according to the rules of 5.19 [expr.const] using arithmetic that has at least the ranges specified in 18.3 [support.limits], except that. For the purposes of this token conversion and evaluation all signed and unsigned integer types act as if they have the same representation as, respectively, intmax_t or uintmax_t (_N3035_.18.4.2 [stdinth])[Footnote: Thus on an implementation where std::numeric_limits<int>::max() is 0x7FFF and std::numeric_limits<unsigned int>::max() is 0xFFFF, the integer literal 0x8000 is signed and positive within a #if expression even though it is unsigned in translation phase 7 (2.2 [lex.phases]). —end footnote]. This includes interpreting character literals...
[Voted into WP at October, 2009 meeting.]
16.1 [cpp.cond] paragraph 1 states,
The expression that controls conditional inclusion shall be an integral constant expression except that: it shall not contain a cast...
The prohibition of casts is vacuous and misleading: as pointed out in the footnote in that paragraph,
Because the controlling constant expression is evaluated during translation phase 4, all identifiers either are or are not macro names — there simply are no keywords, enumeration constants, and so on.
As a result, there can be no casts, which require either keywords or identifiers that resolve to types in order to be recognized as casts. The wording on casts should be removed and replaced by a note recognizing this implication.
Notes from the April, 2007 meeting:
The CWG agreed with this suggested resolution; however, the reference is in the “Preprocessing Directives” clause, which WG21 intends to keep in as close synchronization as possible with the corresponding wording in the C Standard. Any change here must therefore be done in consultation with WG14. Clark Nelson will fulfill this liaison function.
It was also noted that the imminent introduction of constexpr also has the potential for a similar kind of confusion, so the proposed resolution should address both casts and constexpr.
Proposed resolution (July, 2009):
Change 16.1 [cpp.cond] paragraph 1 as follows:
The expression that controls conditional inclusion shall be an integral constant expression except that: it shall not contain a cast; identifiers (including those lexically identical to keywords)...
[Voted into WP at October, 2009 meeting.]
Clause 16 [cpp] refers in several places to “character string literals” without specifying whether they are narrow or wide strings. For instance, what kind of string does the # operator (16.3.2 [cpp.stringize]) produce?
16.4 [cpp.line] paragraph 1 says,
The string literal of a #line directive, if present, shall be a character string literal.
Is “character string literal” intended to mean a narrow string literal? (Also, there is no string-literal mentioned in the grammatical descriptions of #line; paragraph 4 reads,
which is apparently intended to suggest a string literal but does not use the term.)
16.8 [cpp.predefined] should also specify what kind of character string literals are produced by the various string-valued predefined macros.
Notes from the July, 2007 meeting:
The CWG affirmed that all the string literals mentioned in Clause 16 [cpp] are intended to be narrow strings.
Proposed resolution (September, 2008)
Change the footnote in 16 [cpp] paragraph 1 as follows:
Thus, preprocessing directives are commonly called “lines.” These “lines” have no other syntactic significance, as all white space is equivalent except in certain situations during preprocessing (see the # character string literal creation operator in 16.3.2 [cpp.stringize], for example).
Change 16.3.2 [cpp.stringize] paragraph 2 as follows:
If, in the replacement list, a parameter is immediately preceded by a # preprocessing token, both are replaced by a single character ordinary string literal (2.14.5 [lex.string]) preprocessing token that contains the spelling of the preprocessing token sequence for the corresponding argument... Otherwise, the original spelling of each preprocessing token in the argument is retained in the character ordinary string literal, except for special handling for producing the spelling of string literals and character literals: a \ character is inserted before each " and \ character of a character literal or string literal (including the delimiting " characters). If the replacement that results is not a valid character ordinary string literal, the behavior is undefined. The character ordinary string literal corresponding to an empty argument is "". The order of evaluation of # and ## operators is unspecified.
Change 16.3.5 [cpp.scope] paragraph 6 as follows:
To illustrate the rules for creating character ordinary string literals and concatenating tokens, the sequence... or, after concatenation of the character ordinary string literals...
Change 16.4 [cpp.line] paragraph 1 as follows:
The string literal of a #line directive, if present, shall be a character an ordinary string literal.
Change 16.4 [cpp.line] paragraph 4 as follows:
...and changes the presumed name of the source file to be the contents of the character ordinary string literal.
Change 16.8 [cpp.predefined] paragraph 1 as follows:
__DATE__
The date of translation of the source file (a character an ordinary string literal of the form...
__FILE__
The presumed name of the source file (a character an ordinary string literal).
...
__TIME__
The time of translation of the source file (a character an ordinary string literal of the form...
Notes from the September, 2008 meeting:
The proposed resolution will be discussed with the C Committee before proceeding, as it is expected that the next revision of the C Standard will also adopt new forms of string literals.
Additional notes (May, 2009):
At its most recent meeting, the C Committee decided to keep the existing term, “character string literal.”
One possibility for maintaining compatible phraseology with the C Standard would be to replace the occurrences of “ordinary string literal” in 2.14.5 [lex.string] with “character string literal,” instead of the extensive set of changes above.
Another possibility would be to leave the references in clause 16 [cpp] unchanged and just insert a prefatory comment near the beginning that every occurrence of “character string literal” refers to a string-literal with no prefix. (The use of “ordinary string literal” in the preceding edits is problematic in that the phrase includes raw string literals as well as unprefixed literals.)
Proposed resolution (July, 2009):
Change 16.3.2 [cpp.stringize] paragraph 2 as follows:
A character string literal is a string-literal with no prefix. If, in the replacement list, a parameter is immediately preceded by a # preprocessing token...
Change the fifteenth bullet of Annex B [implimits] paragraph 2 as follows:
[Voted into WP at October, 2009 meeting.]
The limit of 17 recursively-nested template instantiations is too small for modern programming practices such as template metaprogramming. It is unclear, however, whether this is a useful metric; see this paper for an example that honors the limit but results in over 750 billion instantiations.
Notes from the July, 2009 meeting:
The consensus of the CWG was to increase the limit to 1024.
Proposed resolution (September, 2009):
Change B [implimits], the fourth bullet from the end, as follows:
[Moved to DR at the April, 2013 meeting.]
The C++ Standard uses the phrase “indeterminate value” without defining it. C99 defines it as “either an unspecified value or a trap representation.” Should C++ follow suit?
In addition, 4.1 [conv.lval] paragraph 1 says that applying the lvalue-to-rvalue conversion to an “object [that] is uninitialized” results in undefined behavior; this should be rephrased in terms of an object with an indeterminate value.
Proposed resolution (October, 2012):
Change 4.1 [conv.lval] paragraphs 1 and 2 as follows (including changing the running text of paragraph 2 into bullets):
A glvalue (3.10 [basic.lval]) of a non-function, non-array type T can be converted to a prvalue.53 If T is an incomplete type, a program that necessitates this conversion is ill-formed. If the object to which the glvalue refers is not an object of type T and is not an object of a type derived from T, or if the object is uninitialized, a program that necessitates this conversion has undefined behavior. If T is a non-class type, the type of the prvalue is the cv-unqualified version of T. Otherwise, the type of the prvalue is T.54
When an lvalue-to-rvalue conversion occurs in an unevaluated operand or a subexpression thereof (Clause 5 [expr]) the value contained in the referenced object is not accessed. In all other cases, the result of the conversion is determined according to the following rules:
If T is (possibly cv-qualified) std::nullptr_t, the result is a null pointer constant (4.10 [conv.ptr]).
Otherwise, if the glvalue T has a class type, the conversion copy-initializes a temporary of type T from the glvalue and the result of the conversion is a prvalue for the temporary.
Otherwise, if the object to which the glvalue refers contains an invalid pointer value (3.7.4.2 [basic.stc.dynamic.deallocation], 3.7.4.3 [basic.stc.dynamic.safety]), the behavior is implementation-defined.
Otherwise, if T is a (possibly cv-qualified) unsigned character type (3.9.1 [basic.fundamental]), and the object to which the glvalue refers contains an indeterminate value (5.3.4 [expr.new], 8.5 [dcl.init], 12.6.2 [class.base.init]), and that object does not have automatic storage duration or the glvalue was the operand of a unary & operator or it was bound to a reference, the result is an unspecified value. [Footnote: The value may be different each time the lvalue-to-rvalue conversion is applied to the object. An unsigned char object with indeterminate value allocated to a register might trap. —end footnote]
Otherwise, if the object to which the glvalue refers contains an indeterminate value, the behavior is undefined.
Otherwise, if the glvalue has (possibly cv-qualified) type std::nullptr_t, the prvalue result is a null pointer constant (4.10 [conv.ptr]). Otherwise, the value contained in the object indicated by the glvalue is the prvalue result.
Change 5.2.5 [expr.ref] paragraph 4 second bullet as follows:
If E2 is a static data member...
...If E1 is an lvalue, then E1.E2 is an lvalue; if E1 is an xvalue, then otherwise E1.E2 is an xvalue; otherwise, it is a prvalue. Let the notation...
If E2 is a (possibly overloaded) member function...
Change 5.5 [expr.mptr.oper] paragraph 6 as follows:
...The result of a .* expression whose second operand is a pointer to a data member is of the same value category (3.10 [basic.lval]) as its first operand an lvalue if the first operand is an lvalue and an xvalue otherwise. The result of a .* expression whose second operand is a pointer to a member function...
This resolution also resolves issues 129, 240, 312, 623, and 1013.
(See also issue 1213.)
Additional note (August, 2012):
It was observed that the phrase in the fourth bullet of the change to 4.1 [conv.lval] paragraph 2 that reads “is not a local variable” should probably be changed to “does not have automatic storage duration,” because objects with static storage duration are zero-initialized and thus cannot have an indeterminate value. The issue was returned to "review" status for discussion of this point.
[Moved to DR at the April, 2013 meeting.]
The Standard uses the phrase, “user-defined type,” but it is not clear what it is intended to mean. For example, 17.6.4.2.1 [namespace.std] paragraph 1 says,
A program may add a template specialization for any standard library template to namespace std only if the declaration depends on a user-defined type...
Are types defined in the Standard library “user-defined?”
7.1.6.2 [dcl.type.simple] paragraph 2 says,
The auto specifier is a placeholder for a type to be deduced (7.1.6.4 [dcl.spec.auto]). The other simple-type-specifiers specify either a previously-declared user-defined type or one of the fundamental types (3.9.1 [basic.fundamental]).
implying that all non-fundamental types are “user-defined.”
A definition is needed, as well as a survey of uses of the term to ensure consistency with the definition.
Proposed resolution (October, 2012):
Change 7.1.6.2 [dcl.type.simple] paragraph 2 as follows:
The auto specifier is a placeholder for a type to be deduced (7.1.6.4 [dcl.spec.auto]). The other simple-type-specifiers specify either a previously-declared user-defined type, a type determined from an expression, or one of the fundamental types (3.9.1 [basic.fundamental]). Table 10 summarizes the valid combinations of simple-type-specifiers and the types they specify.
Change 4 [conv] paragraph 4 as follows:
[Note: For user-defined class types, user-defined conversions are considered as well; see 12.3 [class.conv]. In general, an implicit conversion sequence (13.3.3.1 [over.best.ics]) consists of a standard conversion sequence followed by a user-defined conversion followed by another standard conversion sequence. —end note]
Change the example in 13.3.1.2 [over.match.oper] paragraph 1 as follows:
... void f(void) { const char* p= "one" + "two"; // ill-formed because neither // operand has user-defined class or enumeration type int I = 1 + 1; // Always evaluates to 2 even if // user-defined class or enumeration types exist which // would perform the operation. }
[Moved to DR at the April, 2013 meeting.]
The verb “access” is used in various places in the Standard (see 3.8 [basic.life] paragraphs 5 and 6 and 3.10 [basic.lval] paragraph 10) but is not defined. C99 defines it as
<execution-time action> to read or modify the value of an object
(See also issue 1530.)
Proposed resolution (March, 2013):
Add the following to 1.3 [intro.defs]:
access
<execution-time action> to read or modify the value of an object
Change 1.9 [intro.execution] paragraph 12 as follows:
Accessing Reading an object designated by a volatile glvalue (3.10 [basic.lval]), modifying an object, calling...
Change 1.10 [intro.multithread] paragraph 4 as follows:
Two expression evaluations conflict if one of them modifies a memory location (1.7 [intro.memory]) and the other one accesses reads or modifies the same memory location.
Change 1.10 [intro.multithread] paragraph 24 as follows:
The implementation may assume that any thread will eventually do one of the following:
...
access read or modify a volatile object, or
...
Change 5.17 [expr.ass] paragraph 8 as follows:
If the value being stored in an object is accessed from read via another object that overlaps in any way the storage of the first object, then the overlap shall be exact and the two objects shall have the same type, otherwise the behavior is undefined. [Note:...
[Moved to DR at the April, 2013 meeting.]
Does the Standard require that an uninitialized auto variable have a stable (albeit indeterminate) value? That is, does the Standard require that the following function return true?
bool f() { unsigned char i; // not initialized unsigned char j = i; unsigned char k = i; return j == k; // true iff "i" is stable }3.9.1 [basic.fundamental] paragraph 1 requires that uninitialized unsigned char variables have a valid value, so the initializations of j and k are well-formed and required not to trap. The question here is whether the value of i is allowed to change between those initializations.
Mike Miller: 1.9 [intro.execution] paragraph 10 says,
An instance of each object with automatic storage duration (3.7.3 [basic.stc.auto] ) is associated with each entry into its block. Such an object exists and retains its last-stored value during the execution of the block and while the block is suspended...I think that the most reasonable way to read this is that the only thing that is allowed to change the value of an automatic (non-volatile?) value is a "store" operation in the abstract machine. There are no "store" operations to i between the initializations of j and k, so it must retain its original (indeterminate but valid) value, and the result of the program is well-defined.
The quibble, of course, is whether the wording "last-stored value" should be applied to a "never-stored" value. I think so, but others might differ.
Tom Plum: 7.1.6.1 [dcl.type.cv] paragraph 8 says,
[Note: volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation. See 1.9 [intro.execution] for detailed semantics. In general, the semantics of volatile are intended to be the same in C++ as they are in C. ]>From this I would infer that non-volatile means "shall not be changed by means undetectable by an implementation"; that the compiler is entitled to safely cache accesses to non-volatile objects if it can prove that no "detectable" means can modify them; and that therefore i shall maintain the same value during the example above.
Nathan Myers: This also has practical code-generation consequences. If the uninitialized auto variable lives in a register, and its value is really unspecified, then until it is initialized that register can be used as a temporary. Each time it's "looked at" the variable has the value that last washed up in that register. After it's initialized it's "live" and cannot be used as a temporary any more, and your register pressure goes up a notch. Fixing the uninit'd value would make it "live" the first time it is (or might be) looked at, instead.
Mike Ball: I agree with this. I also believe that it was certainly never my intent that an uninitialized variable be stable, and I would have strongly argued against such a provision. Nathan has well stated the case. And I am quite certain that it would be disastrous for optimizers. To ensure it, the frontend would have to generate an initializer, because optimizers track not only the lifetimes of variables, but the lifetimes of values assigned to those variables. This would put C++ at a significant performance disadvantage compared to other languages. Not even Java went this route. Guaranteeing defined behavior for a very special case of a generally undefined operation seems unnecessary.
Proposed resolution (February, 2012):
This issue is resolved by the resolution of issue 616.
[Moved to DR at the October, 2012 meeting.]
According to 2.14.3 [lex.ccon] paragraph 1,
A character literal that does not begin with u, U, or L is an ordinary character literal, also referred to as a narrow-character literal. An ordinary character literal that contains a single c-char has type char, with value equal to the numerical value of the encoding of the c-char in the execution character set.
However, the definition of c-char includes as one possibility a universal-character-name. The value of a universal-character-name cannot, in general, be represented as a char, so this specification is impossible to satisfy.
(See also issue 411 for related questions.)
Additional note (February, 2012):
See the discussion in issue 1422 for a possible interpretation of the existing text.
Proposed resolution (February, 2012):
Change 2.14.3 [lex.ccon] paragraph 1 as follows:
...A character literal that does not begin with u, U, or L is an ordinary character literal, also referred to as a narrow-character literal. An ordinary character literal that contains a single c-char representable in the execution character set has type char, with value equal to the numerical value of the encoding of the c-char in the execution character set. An ordinary character literal that contains more than one c-char is a multicharacter literal. A multicharacter literal, or an ordinary character literal containing a single c-char not representable in the execution character set, is conditionally-supported, has type int, and has an implementation-defined value.
This resolution also resolves issue 1024.
[Moved to DR at the October, 2012 meeting.]
There is no limit placed on the number of c-chars in a multicharacter literal or a wide-character literal containing multiple c-chars, either in 2.14.3 [lex.ccon] paragraphs 1-2 or in Annex B [implimits]. Presumably this means that an implementation must accept arbitrarily long literals.
An alternative approach might be to state that these literals are conditionally supported with implementation-defined semantics, allowing an implementation to impose a documented limit that makes sense for the particular architecture.
Proposed resolution (February, 2012):
This issue is resolved by the resolution of issue 912.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
In describing static data members initialized inside the class definition, 9.4.2 [class.static.data] paragraph 3 says,
The member shall still be defined in a namespace scope if it is used in the program...
The definition of “used” is in 3.2 [basic.def.odr] paragraph 1:
An object or non-overloaded function whose name appears as a potentially-evaluated expression is used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19 [expr.const]) and the lvalue-to-rvalue conversion (4.1 [conv.lval]) is immediately applied.
Now consider the following example:
struct S { static const int a = 1; static const int b = 2; }; int f(bool x) { return x ? S::a : S::b; }
According to the current wording of the Standard, this example requires that S::a and S::b be defined in a namespace scope. The reason for this is that, according to 5.16 [expr.cond] paragraph 4, the result of this conditional-expression is an lvalue and the lvalue-to-rvalue conversion is applied to that, not directly to the object, so this fails the “immediately applied” requirement. This is surprising and unfortunate, since only the values and not the addresses of the static data members are used. (This problem also applies to the proposed resolution of issue 696.)
Proposed resolution (August, 2011):
Divide 3.2 [basic.def.odr] paragraph 2 into two paragraphs and change as follows:
An expression is potentially evaluated unless it is an unevaluated operand (Clause 5) or a subexpression thereof. The set of potential results of an expression e is defined as:
if e is an id-expression (5.1.1 [expr.prim.general]), the set whose sole member is e,
if e is a class member access (5.2.5 [expr.ref]), the set of potential results of the object expression,
if e is a pointer-to-member expression (5.5 [expr.mptr.oper]) whose second operand is a constant expression, the set of potential results of the object expression,
if e has the form (e1), the set of potential results of e1,
if e is a glvalue conditional expression (5.16 [expr.cond]), the union of the sets of potential results of the second and third operands,
if e is a comma expression (5.18 [expr.comma]), the set of potential results of the right operand,
otherwise, the empty set.
A variable x whose name appears as a potentially-evaluated expression ex is odr-used unless it x is an object that satisfies the requirements for appearing in a constant expression (5.19 [expr.const]) and ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (4.1 [conv.lval]) is immediately applied to e, or e is a discarded-value expression (Clause 5 [expr]). this is odr-used...
[Drafting note: this wording requires S::a to be defined if it is used in an expression like *&S::a.][Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The current wording of 3.2 [basic.def.odr] paragraph 2 uses the term “overloaded” differently from its definition in 13 [over] paragraph 1. For example, names found by argument-dependent lookup are not “overloaded” if they are not declared in the same scope. The phrasing should be reconciled between the two sections.
Proposed resolution (August, 2011):
Change 3.2 [basic.def.odr] paragraph 2 as follows:
...A non-overloaded function whose name appears as a potentially-evaluated expression is odr-used if it is the unique lookup result or it is the selected or a member of a set of candidate overloaded functions (3.4 [basic.lookup], 13.3 [over.match], 13.4 [over.over]), if selected by overload resolution when referred to from a potentially-evaluated expression, is odr-used, unless it is a pure virtual function and its name is not explicitly qualified...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The requirement in 3.2 [basic.def.odr] paragraph 4 that a type T must be complete if an expression is implicitly converted to a pointer to T or reference to T inadvertently applies to user-defined conversions, although it was intended only to refer to built-in conversions.
Proposed resolution (August, 2011):
Change the indicated bullet of 3.2 [basic.def.odr] paragraph 4 as follows:
an expression that is not a null pointer constant, and has type other than cv void*, is converted to the type pointer to T or reference to T using an implicit a standard conversion (Clause 4 [conv]), a dynamic_cast (5.2.7 [expr.dynamic.cast]) or a static_cast (5.2.9 [expr.static.cast]), or
[Moved to DR at the April, 2013 meeting.]
We have a special case in 3.2 [basic.def.odr] paragraph 2 that variables which satisfy the requirements for appearing in a constant expression are not odr-used if the lvalue-to-rvalue conversion is immediately applied. This special case only applies to objects, and thus does not apply to variables of reference type. This inconsistency seems strange, and there is implementation divergence:
int n; void f() { constexpr int &r = n; [] { return r; }; // error: r is odr-used but not captured }
This code is accepted by g++ but rejected by clang. Should r be odr-used here?
Proposed resolution (October, 2012):
Change 3.2 [basic.def.odr] paragraph 3 as follows:
A variable x whose name appears as a potentially-evaluated expression ex is odr-used unless x is an object that satisfies the requirements for appearing in a constant expression (5.19 [expr.const]) and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (4.1 [conv.lval]) is applied to e, or e is a discarded-value expression (Clause 5 [expr]). this is odr-used...
[Moved to DR at the April, 2013 meeting.]
One of the criteria in 3.2 [basic.def.odr] paragraph 6 for when a entity is allowed to have multiple definitions is:
in each definition of D, corresponding names, looked up according to 3.4 [basic.lookup], shall refer to an entity defined within the definition of D, or shall refer to the same entity, after overload resolution (13.3 [over.match]) and after matching of partial template specialization (14.8.3 [temp.over]), except that a name can refer to a const object with internal or no linkage if the object has the same literal type in all definitions of D, and the object is initialized with a constant expression (5.19 [expr.const]), and the value (but not the address) of the object is used, and the object has the same value in all definitions of D; and
This wording is possibly not sufficiently clear for an example like:
const volatile int n = 0; inline int get() { return n; }
Presumably this code could not appear in multiple translation units, because the requirement that n “has the same value in all definitions” cannot be satisfied (the value of a volatile variable can change “by means undetectable by the implementation,” per 7.1.6.1 [dcl.type.cv] paragraph 7, so the value of n might be different in each translation unit). However, it might be good to make it explicit that “a const object” is not intended to apply to a volatile-qualified object.
Other points that were raised during the discussion of this issue were that it would probably be better to rephrase “the value (but not the address) of the object is used” in terms of the odr-use of the object, as well as questioning why a const volatile variable implicitly has internal linkage.
Proposed resolution (October, 2012):
Change 3.2 [basic.def.odr] paragraph 6 as follows:
There can be more than one definition...
...
in each definition of D, corresponding names, looked up according to 3.4 [basic.lookup], shall refer to an entity defined within the definition of D, or shall refer to the same entity, after overload resolution (13.3 [over.match]) and after matching of partial template specialization (14.8.3 [temp.over]), except that a name can refer to a non-volatile const object with internal or no linkage if the object has the same literal type in all definitions of D, and the object is initialized with a constant expression (5.19 [expr.const]), and the value (but not the address) of the object is used not odr-used, and the object has the same value in all definitions of D; and
...
Change 3.5 [basic.link] paragraph 3 as follows:
A name having namespace scope (3.3.6 [basic.scope.namespace]) has internal linkage if it is the name of
a variable, function or function template that is explicitly declared static; or,
a non-volatile variable that is explicitly declared const or constexpr and neither explicitly declared extern nor previously declared to have external linkage; or
a data member of an anonymous union.
[Moved to DR at the April, 2013 meeting.]
According to 3.3.2 [basic.scope.pdecl] paragraph 2,
The point of declaration for an enumeration is immediately after the identifier (if any) in either its enum-specifier (7.2 [dcl.enum]) or its first opaque-enum-declaration (7.2 [dcl.enum]), whichever comes first.
This would make the following example well-formed:
template<typename T> struct S { typedef char I; }; enum E: S<E>::I { e };
Presumably that would make E an incomplete enumeration type for the instantiation of S<E> (a concept not otherwise found in the Standard). However, some implementations reject this example, presumably making the point of declaration after the enum-base. We either need to make it ill-formed or describe incomplete enumeration types.
See also issue 977.
Proposed resolution (December, 2012):
Change 3.9 [basic.types] paragraph 5 as follows:
A class that has been declared but not defined, an enumeration type in certain contexts (7.2 [dcl.enum]), or an array of unknown size or of incomplete element type, is an incompletely-defined object type.43 Incompletely-defined object types...
Add the following as a new paragraph preceding 7.2 [dcl.enum] paragraph 6:
An enumeration whose underlying type is fixed is an incomplete type from its point of declaration (3.3.2 [basic.scope.pdecl]) to immediately after its enum-base (if any), at which point it becomes a complete type. An enumeration whose underlying type is not fixed is an incomplete type from its point of declaration to immediately after the closing } of its enum-specifier, at which point it becomes a complete type.
For an enumeration whose underlying type is not fixed...
This resolution also resolves issue 977.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The rules regarding class scope and when the class is considered to be complete (normally implemented by deferred parsing of portions of class member declarations) are inconsistent and need to be clarified.
Proposed resolution (August, 2011):
Change 3.3.7 [basic.scope.class] paragraph 1 as follows:
Change 3.4.1 [basic.lookup.unqual] paragraph 7 as follows:
A name used in the definition of a class X outside of a member function body, default argument, brace-or-equal-initializer of a non-static data member, or nested class definition29 shall be declared in one of the following ways:...
Change 3.4.1 [basic.lookup.unqual] paragraph 8 as follows:
A For the members of a class X, a name used in a member function body, in a default argument, in the brace-or-equal-initializer of a non-static data member (9.2 [class.mem]), or in the definition of a class member function (9.3 [class.mfct]) of class X outside of the definition of X, following the function's member's declarator-id [Footnote: That is, an unqualified name that occurs, for instance, in a type or default argument in the parameter-declaration-clause or in the function body exception-specification. —end footnote], or in the brace-or-equal-initializer of a non-static data member (9.2 [class.mem]) of class X shall be declared in one of the following ways:...
[Moved to DR at the April, 2013 meeting.]
In 3.4.3.1 [class.qual] paragraph 2,
In a lookup in which the constructor is an acceptable lookup result and the nested-name-specifier nominates a class C:
if the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C (Clause 9 [class]), or
in a using-declaration (7.3.3 [namespace.udecl]) that is a member-declaration, if the name specified after the nested-name-specifier is the same as the identifier or the simple-template-id's template-name in the last component of the nested-name-specifier,
the name is instead considered to name the constructor of class C.
it is not clear what constitutes “an acceptable lookup result.” For instance, is
struct S { } *sp = new S::S;
well-formed?
The intent of the wording was that S::S would refer to the constructor except in lookups that ignore the names of functions, e.g., in elaborated-type-specifiers and nested-name-specifiers. There doesn't seem to be a good reason to allow a qualified-id naming the injected-class-name. The alternative, i.e., only to find the constructor in a declarator, complicates parsing because the determination of whether the name is a type or a function would require lookahead.
Proposed resolution (August, 2012):
Change 3.4.3.1 [class.qual] paragraph 2 as follows:
In a lookup in which the constructor is an acceptable lookup result function names are not ignored [Footnote: Lookups in which function names are ignored include names appearing in a nested-name-specifier, an elaborated-type-specifier, or a base-specifier. —end footnote] and the nested-name-specifier nominates a class C:
if the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C (Clause 9 [class]), or
in a using-declaration (7.3.3 [namespace.udecl]) that is a member-declaration, if the name specified after the nested-name-specifier is the same as the identifier or the simple-template-id's template-name in the last component of the nested-name-specifier,
the name is instead considered to name the constructor of class C...
[Moved to DR at the October, 2012 meeting.]
There does not appear to be wording that prohibits a block-scope extern object declaration from being a definition.
Proposed resolution (February, 2012):
Add the following as a new paragraph following 8.5 [dcl.init] paragraph 4:
[Note: Default arguments are more restricted; see 8.3.6 [dcl.fct.default].
The order of initialization of variables with static storage duration is described in 3.6 [basic.start] and 6.7 [stmt.dcl]. —end note]
A declaration of a block-scope variable with external or internal linkage that has an initializer is ill-formed.
To zero-initialize an object...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The specification of the forms of the definition of main that an impliementation is required to accept is clear in C99 that the parameter names and the exact syntactic form of the types can vary. Although it is reasonable to assume that a C++ implementation would accept a definition like
int main(int foo, char** bar) { /* ... */ }
instead of the canonical
int main(int argc, char* argv[]) { /* ... */ }
it might be a good idea to clarify the intent using wording similar to C99's.
Proposed resolution (August, 2011):
Change 3.6.1 [basic.start.main] paragraph 2 as follows:
...All implementations shall allow both of the following definitions of main:
int main() { /* ... */ }
and
int main(int argc, char* argv[]) { /* ... */ }
function of () returning int and
function of (int, pointer to pointer to char) returning int
as the type of main (8.3.5 [dcl.fct]. In the latter form, for purposes of exposition, the first function parameter is called argc and the second function parameter is called argv, where argc shall be the number of arguments...
[Moved to DR at the April, 2013 meeting.]
According to 3.6.2 [basic.start.init] paragraph 2,
Constant initialization is performed:
...
if an object with static or thread storage duration is not initialized by a constructor call and if every full-expression that appears in its initializer is a constant expression.
Presumably this would include a value-initialization (i.e., with no expressions) such as
int a[1000]{};
However, we have recently clarified the degenerate cases of other similar rules referencing “every,” so it wouldn't hurt to be more explicit here.
Proposed resolution (October, 2012):
Change 3.6.2 [basic.start.init] paragraph 2 as follows:
Constant initialization is performed:
...
if an object with static or thread storage duration is not initialized by a constructor call and if either the object is value-initialized or every full-expression that appears in its initializer is a constant expression.
[Moved to DR at the April, 2013 meeting.]
3.7.4.2 [basic.stc.dynamic.deallocation] paragraph 4 mentions that the effect of using an invalid pointer value is undefined. However, the standard never says what it means to 'use' a value.
There are a number of possible interpretations, but it appears that each of them leads to undesired conclusions:
int *x = new int(0); delete x; x = 0;into undefined behaviour. As this is a common idiom, this is clearly undesirable.
int *x = new int(0); delete x; x->~int();into undefined behaviour; according to 5.2.4 [expr.pseudo], the variable x is 'evaluated' as part of evaluating the pseudo destructor call. This, in turn, would mean that all containers (23 [containers]) of pointers show undefined behaviour, e.g. 23.3.5.4 [list.modifiers] requires to invoke the destructor as part of the clear() method of the container.
If any other meaning was intended for 'using an expression', that meaning should be stated explicitly.
(See also issue 623.)
Proposed resolution (February, 2012):
This issue is resolved by the resolution of issue 616.
Any use of a pointer to deleted storage, even if the pointer is not dereferenced, produces undefined behavior (3.7.4.2 [basic.stc.dynamic.deallocation] paragraph 4). The reason for this restriction is that, on some historical architectures, deallocating an object might free a memory segment, resulting in a hardware exception if a pointer referring to that segment were loaded into a pointer register, and on those architectures use of a pointer register for moving and comparing pointers was the most efficient mechanism for these operations.
It is not clear whether current or foreseeable architectures still require such a draconian restriction or whether it is feasible to relax it only to forbid a smaller range of operations. Of particular concern is the use of atomic pointers, which might be used in race conditions involving deallocation, where the loser of the race might encounter this undefined behavior.
(See also issue 312.)
Rationale (April, 2007):
The current specification is clear and was well-motivated. Analysis of whether this restriction is still needed should be done via a paper and discussed in the Evolution Working Group rather than being handled by CWG as an issue/defect.
Additional note, February, 2014:
This issue was resolved by the resolution of issue 616, which made use of a pointer to deleted storage implementation-defined behavior.
[Moved to DR at the October, 2012 meeting.]
The current Standard says that any use of an invalid pointer value produces undefined behavior (3.7.4.2 [basic.stc.dynamic.deallocation] paragraph 4). This includes not only dereferencing the pointer but even just fetching its value. The reason for this draconian restriction is that some architectures in the past used dedicated address registers for pointer loads and stores and they could fault if, for example, a segment number in a pointer was not currently mapped.
It is not clear whether such restrictions are necessary with architectures currently in use or reasonably foreseen. This should be investigated to see if the restriction can be loosened to apply only to dereferencing the pointer.
Proposed resolution (February, 2012):
Change 3.7.4.2 [basic.stc.dynamic.deallocation] paragraph 4 as follows:
If the argument given to a deallocation function in the standard library is a pointer that is not the null pointer value (4.10 [conv.ptr]), the deallocation function shall deallocate the storage referenced by the pointer, rendering invalid all pointers referring to any part of the deallocated storage. The effect of using an invalid pointer value (including passing it to a deallocation function) is undefined Indirection through an invalid pointer value and passing an invalid pointer value to a deallocation function have undefined behavior. Any other use of an invalid pointer value has implementation-defined behavior. [Footnote: On some Some implementations, it might define that copying an invalid pointer value causes a system-generated runtime fault. —end footnote]
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
An lvalue referring to an out-of-lifetime non-POD class objects can be used in limited ways, subject to the restrictions in 3.8 [basic.life] paragraph 6:
if the original object will be or was of a non-POD class type, the program has undefined behavior if:
the lvalue is used to access a non-static data member or call a non-static member function of the object, or
the lvalue is implicitly converted (4.10 [conv.ptr]) to a reference to a base class type, or
the lvalue is used as the operand of a static_cast (5.2.9 [expr.static.cast]) except when the conversion is ultimately to cv char& or cv unsigned char& ), or
the lvalue is used as the operand of a dynamic_cast (5.2.7 [expr.dynamic.cast]) or as the operand of typeid.
There are at least a couple of questionable things in this list. First, there is no “implicit conversion to a reference to a base class,” as assumed by the second bullet. Presumably this is intended to say that the lvalue is bound to a reference to a base class, and the cross-reference should be to 8.5.3 [dcl.init.ref], not to 4.10 [conv.ptr] (which deals with pointer conversions). However, even given that adjustment, it is not clear why it is forbidden to bind a reference to a non-virtual base class of an out-of-lifetime object, as that is just an address offset calculation. (Binding to a virtual base, of course, would require access to the value of the object and thus cannot be done outside the object's lifetime.)
The third bullet also appears questionable. It's not clear why static_cast is discussed at all here, as the only permissible static_cast conversions involving reference types and non-POD classes are to references to base or derived classes and to the same type, modulo cv-qualification; if implicit “conversion” to a base class reference is forbidden in the second bullet, why would an explicit conversion be permitted in the third? Was this intended to refer to reinterpret_cast? Also, is there a reason to allow char types but disallow array-of-char types (which are more likely to be useful than a single char)?
Proposed resolution (March, 2008):
Change 3.8 [basic.life] paragraph 5 as follows:
...If the object will be or was of a non-trivial class type, the program has undefined behavior if:
the pointer is used to access a non-static data member or call a non-static member function of the object, or
the pointer is implicitly converted (
4.10 [conv.ptr] ) to a pointer to a virtual base class type, orthe pointer is used as the operand of a static_cast (5.2.9 [expr.static.cast]) (except when the conversion is to void*, or to void* and subsequently to char*, or unsigned char*). pointer to cv void, or to pointer to cv void and subsequently to pointer to cv char or pointer to cv unsigned char, or
the pointer is used as the operand of a dynamic_cast (5.2.7 [expr.dynamic.cast])...
Change 3.8 [basic.life] paragraph 6 as follows:
...if the original object will be or was of a non-trivial class type, the program has undefined behavior if:
the lvalue is used to access a non-static data member or call a non-static member function of the object, or
the lvalue is implicitly converted (4.10 [conv.ptr]) bound to a reference to a virtual base class type (8.5.3 [dcl.init.ref]), or
the lvalue is used as the operand of a static_cast (5.2.9 [expr.static.cast]) except when the conversion is ultimately to cv char& or cv unsigned char&, or
the lvalue is used as the operand of a dynamic_cast (5.2.7 [expr.dynamic.cast]) or as the operand of typeid.
[Drafting notes: Paragraph 5 was changed to track the changes to paragraph 6. See also the resolution for issue 658.]
[Moved to DR at the April, 2013 meeting.]
In 3.9 [basic.types] paragraph 10, the standard makes it quite clear that volatile qualified types are PODs:
Arithmetic types (3.9.1 [basic.fundamental]), enumeration types, pointer types, and pointer to member types (3.9.2 [basic.compound]), and cv-qualified versions of these types (3.9.3 [basic.type.qualifier]) are collectively called scalar types. Scalar types, POD-struct types, POD-union types (clause 9 [class]), arrays of such types and cv-qualified versions of these types (3.9.3 [basic.type.qualifier]) are collectively called POD types.
However in 3.9 [basic.types] paragraph 3, the standard makes it clear that PODs can be copied “as if” they were a collection of bytes by memcpy:
For any POD type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base-class subobject, if the value of obj1 is copied into obj2, using the std::memcpy library function, obj2 shall subsequently hold the same value as obj1.
The problem with this is that a volatile qualified type may need to be copied in a specific way (by copying using only atomic operations on multithreaded platforms, for example) in order to avoid the “memory tearing” that may occur with a byte-by-byte copy.
I realise that the standard says very little about volatile qualified types, and nothing at all (yet) about multithreaded platforms, but nonetheless this is a real issue, for the following reason:
The forthcoming TR1 will define a series of traits that provide information about the properties of a type, including whether a type is a POD and/or has trivial construct/copy/assign operations. Libraries can use this information to optimise their code as appropriate, for example an array of type T might be copied with a memcpy rather than an element-by-element copy if T is a POD. This was one of the main motivations behind the type traits chapter of the TR1. However it's not clear how volatile types (or POD's which have a volatile type as a member) should be handled in these cases.
Notes from the April, 2005 meeting:
It is not clear whether the volatile qualifier actually guarantees atomicity in this way. Also, the work on the memory model for multithreading being done by the Evolution Working Group seems at this point likely to specify additional semantics for volatile data, and that work would need to be considered before resolving this issue.
Proposed resolution, October, 2012:
Change 3.9 [basic.types] paragraph 9 as follows:
...Scalar types, trivially copyable class types (Clause 9 [class]), arrays of such types, and cv-qualified non-volatile const-qualified versions of these types (3.9.3 [basic.type.qualifier]) are collectively called trivially copyable types. Scalar types, trivial class types...
Change 7.1.6.1 [dcl.type.cv] paragraphs 6-7 as follows:
What constitutes an access to an object that has volatile-qualified type is implementation-defined. If an attempt is made to refer to an object defined with a volatile-qualified type through the use of a glvalue with a non-volatile-qualified type, the program behavior is undefined.
[Note: volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation. Furthermore, for some implementations, volatile might indicate that special hardware instructions are required to access the object. See 1.9 [intro.execution] for detailed semantics. In general, the semantics of volatile are intended to be the same in C++ as they are in C. —end note]
Change 12.8 [class.copy] paragraph 12 as follows:
A copy/move constructor for class X is trivial if it is not user-provided, its declared parameter type is the same as if it had been implicitly declared, and if
class X has no virtual functions (10.3 [class.virtual]) and no virtual base classes (10.1 [class.mi]), and
class X has no non-static data members of volatile-qualified type, and
...
Change 12.8 [class.copy] paragraph 25 as follows:
A copy/move assignment operator for class X is trivial if it is not user-provided, its declared parameter type is the same as if it had been implicitly declared, and if
class X has no virtual functions (10.3 [class.virtual]) and no virtual base classes (10.1 [class.mi]), and
class X has no non-static data members of volatile-qualified type, and
...
The requirement in 3.8 [basic.life] paragraph 10 that
is mostly redundant with the constexpr constructor requirements in 7.1.5 [dcl.constexpr] paragraph 4 (although 12.6.2 [class.base.init] does not establish a strict equivalence between brace-or-equal-initializers and mem-initializers).
Proposed resolution (April, 2013):
This issue is resolved by the changes in N3652, adopted at the April, 2013 (Bristol) meeting.
[Moved to DR at the April, 2013 meeting.]
Currently, literal class types can have mutable members. It is not clear whether that poses any particular problems with constexpr objects and constant expressions, and if so, what should be done about it.
Proposed resolution (February, 2012):
Change 5.19 [expr.const] paragraph 2 as follows:
...
an lvalue-to-rvalue conversion (4.1 [conv.lval]) unless it is applied to
a glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression, or
a glvalue of literal type that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable sub-object of such an object, or
a glvalue of literal type that refers to a non-volatile temporary object whose lifetime has not ended, initialized with a constant expression;
...
[Moved to DR at the October, 2012 meeting.]
Can a literal class have a volatile member? For example,
struct S { constexpr S() : n(0) { } volatile int n; }; constexpr S s; // causes volatile write to S::n
Proposed resolution (February, 2012):
Change 3.9 [basic.types] paragraph 10 as follows:
A type is a literal type if it is:
...
a class type (Clause 9 [class]) that has all the following properties:
...
all of its non-static data members and base classes are of non-volatile literal types.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
There is no normative requirement on the ranges of the integral types, although the footnote in 3.9.1 [basic.fundamental] paragraph 2 indicates the intent (for int, at least) that they match the values given in the <climits> header. Should there be an explicit requirement of some sort?
(See also paper N1693.)
Proposed resolution (August, 2011):
Change 3.9.1 [basic.fundamental] paragraph 3 as follows:
...collectively called the extended integer types. The signed and unsigned integral types shall satisfy the constraints given in ISO C 5.2.4.2.1.
[Moved to DR at the October, 2012 meeting.]
The list of acceptable uses of an expression of type void in 3.9.1 [basic.fundamental] paragraph 9 does not, but should, include an operand of the noexcept operator.
Proposed resolution (August, 2011):
Change 3.9.1 [basic.fundamental] paragraph 9 as follows:
An expression of type void shall be used only as an expression statement (6.2 [stmt.expr]), as an operand of a comma expression (5.18 [expr.comma]), as a second or third operand of ?: (5.16 [expr.cond]), as the operand of typeid, noexcept, or decltype, as the expression in a return statement (6.6.3 [stmt.return]) for a function with the return type void, or as the operand of an explicit conversion to type cv void.
[Moved to DR at the April, 2013 meeting.]
According to 3.9.1 [basic.fundamental] paragraph 4,
Unsigned integers, declared unsigned, shall obey the laws of arithmetic modulo 2n where n is the number of bits in the value representation of that particular size of integer.
It is not clear whether this wording intentionally excludes types like char16_t and char32_t (and, possibly, types char and wchar_t, if those types are unsigned in a given implementation), since the unsigned keyword is not used in their declaration.
Proposed resolution (October, 2012):
Change 3.9.1 [basic.fundamental] paragraph 4 as follows:
Unsigned integers, declared unsigned, shall obey the laws of arithmetic modulo 2n where n is the number of bits in the value representation of that particular size of integer.46
[Moved to DR at the April, 2013 meeting.]
The term character type is used in the Standard without definition. It should be defined; however, the use of the term is divergent between the core and library clauses: in the former, it means narrow character types, while in the latter it includes wchar_t, char16_t, and char32_t, so care must be taken in ensuring that no inadvertent changes are implied.
Proposed resolution (October, 2012):
Change 3.7.4.3 [basic.stc.dynamic.safety] paragraph 1 as follows:
A traceable pointer object is
...
a sequence of elements in an array of narrow character type (3.9.1 [basic.fundamental]), where the size and alignment of the sequence match those of some object pointer type.
Change 3.9.1 [basic.fundamental] paragraph 1 as follows:
Objects declared as characters (char) shall be large enough to store any member of the implementation's basic character set. If a character from this set is stored in a character object, the integral value of that character object is equal to the value of the single character literal form of that character. It is implementation-defined whether a char object can hold negative values. Characters can be explicitly declared unsigned or signed. Plain char, signed char, and unsigned char are three distinct types, collectively called narrow character types. A char, a signed char, and an unsigned char occupy the same amount of storage and have the same alignment requirements (3.11 [basic.align]); that is, they have the same object representation. For narrow character types, all bits of the object representation participate in the value representation. For unsigned narrow character types, all possible bit patterns of the value representation represent numbers. These requirements do not hold for other types. In any particular implementation, a plain char object can take on either the same values as a signed char or an unsigned char; which one is implementation-defined.
Change 3.11 [basic.align] paragraph 6 as follows:
The alignment requirement of a complete type can be queried using an alignof expression (5.3.6 [expr.alignof]). Furthermore, the types char, signed char, and unsigned char narrow character types (3.9.1 [basic.fundamental]) shall have the weakest alignment requirement. [Note: This enables the narrow character types to be used as the underlying type for an aligned memory area (7.6.2 [dcl.align]). —end note]
Change 8.5.2 [dcl.init.string] paragraph 1 as follows:
A char array (whether plain char, signed char, or unsigned char) An array of narrow character type (3.9.1 [basic.fundamental]), char16_t array, char32_t array, or wchar_t array can be initialized by a narrow character string literal, char16_t string literal, char32_t string literal, or wide string literal, respectively, or by an appropriately-typed string literal enclosed in braces (2.14.5 [lex.string]). Successive characters of the value of the string literal initialize the elements of the array. [Example:
[Moved to DR at the October, 2012 meeting.]
In spite of the resolution of issue 112, the exact relationship between cv-qualifiers and array types is not clear. There does not appear to be a definitive normative statement answering the question of whether an array with a const-qualified element type is itself const-qualified; the statement in 3.9.3 [basic.type.qualifier] paragraph 5,
Cv-qualifiers applied to an array type attach to the underlying element type, so the notation “cv T,” where T is an array type, refers to an array whose elements are so-qualified. Such array types can be said to be more (or less) cv-qualified than other types based on the cv-qualification of the underlying element types.
hints at an answer but is hardly decisive. For example, is the following example well-formed?
template <class T> struct is_const { static const bool value = false; }; template <class T> struct is_const<const T> { static const bool value = true; }; template <class T> void f(T &) { char checker[is_const<T>::value]; } int const arr[1] = {}; int main() { f(arr); }
Also, when 3.10 [basic.lval] paragraph 4 says,
Class prvalues can have cv-qualified types; non-class prvalues always have cv-unqualified types.
does this apply to array rvalues, as it appears? That is, given
struct S { const int arr[10]; };
is the array rvalue S().arr an array of int or an array of const int?
(The more general question is, when the Standard refers to non-class types, should it be considered to include array types? Or perhaps only arrays of non-class types?)
Proposed resolution (December, 2011):
Change 3.9.3 [basic.type.qualifier] paragraph 5 as follows:
...Cv-qualifiers applied to an array type attach to the underlying element type, so the notation “cv T,” where T is an array type, refers to an array whose elements are so-qualified. Such array types can be said to be more (or less) cv-qualified than other types based on the cv-qualification of the underlying element types. An array type whose elements are cv-qualified is also considered to have the same cv-qualification as its elements. [Example:typedef char CA[5]; typedef const char CC; CC arr1[5] = { 0 }; const CA arr2 = { 0 };
The type of both arr1 and arr2 is “array of 5 const char,” and the array type is considered to be const-qualified. —end example]
Change 3.10 [basic.lval] paragraph 4 as follows:
Class and array prvalues can have cv-qualified types; non-class other prvalues always have cv-unqualified types. Unless otherwise indicated...
[Moved to DR at the October, 2012 meeting.]
The definition of “const object” in 3.9.3 [basic.type.qualifier] paragraph 1 is:
The presence of a const specifier in a decl-specifier-seq declares an object of const-qualified object type; such object is called a const object.
Because the type of an object created by a new-expression is given by a type-id or new-type-id rather than with a decl-specifier-seq, this definition gives the false impression that objects of dynamic storage duration cannot be const objects. The wording should be adjusted to make it clear that they can.
Proposed resolution (February, 2012):
Change 3.9.3 [basic.type.qualifier] paragraph 1 as follows:
The term object type (1.8 [intro.object]) includes the cv-qualifiers specified in the decl-specifier-seq (7.1 [dcl.spec]), declarator (Clause 8 [dcl.decl]), type-id (8.1 [dcl.name]), or new-type-id (5.3.4 [expr.new]) when the object is created. The presence of a const specifier in a decl-specifier-seq declares an object of const-qualified object type; such object is called a const object. The presence of a volatile specifier in a decl-specifier-seq declares an object of volatile-qualified object type; such object is called a volatile object. The presence of both cv-qualifiers in a decl-specifier-seq declares an object of const-volatile-qualified object type; such object is called a const volatile object.
A const object is an object of type const T or a non-mutable subobject of such an object.
A volatile object is an object of type volatile T, a subobject of such an object, or a mutable subobject of a const volatile object.
A const volatile object is an object of type const volatile T, a non-mutable subobject of such an object, a const subobject of a volatile object, or a non-mutable volatile subobject of a const object.
The cv-qualified or cv-unqualified versions of a type are distinct types; however, they shall have the same representation and alignment requirements (3.9 [basic.types]).51
Change 3.9.3 [basic.type.qualifier] paragraph 3 as follows:
Each non-static, non-mutable, non-reference data member of a const-qualified class object is const-qualified, each non-static, non-reference data member of a volatile-qualified class object is volatile-qualified and similarly for members of a const-volatile class. See 8.3.5 [dcl.fct] and 9.3.2 [class.this] regarding function types...
[Moved to DR at the April, 2013 meeting.]
4.1 [conv.lval] paragraph 1 says,
If the object to which the lvalue refers is not an object of type T and is not an object of a type derived from T, or if the object is uninitialized, a program that necessitates this conversion has undefined behavior.
I think there are at least three related issues around this specification:
Presumably assigning a valid value to an uninitialized object allows it to participate in the lvalue-to-rvalue conversion without undefined behavior (otherwise the number of programs with defined behavior would be vanishingly small :-). However, the wording here just says "uninitialized" and doesn't mention assignment.
There's no exception made for unsigned char types. The wording in 3.9.1 [basic.fundamental] was carefully crafted to allow use of unsigned char to access uninitialized data so that memcpy and such could be written in C++ without undefined behavior, but this statement undermines that intent.
It's possible to get an uninitialized rvalue without invoking the lvalue-to-rvalue conversion. For instance:
struct A { int i; A() { } // no init of A::i }; int j = A().i; // uninitialized rvalue
There doesn't appear to be anything in the current IS wording that says that this is undefined behavior. My guess is that we thought that in placing the restriction on use of uninitialized objects in the lvalue-to-rvalue conversion we were catching all possible cases, but we missed this one.
In light of the above, I think the discussion of uninitialized objects ought to be removed from 4.1 [conv.lval] paragraph 1. Instead, something like the following ought to be added to 3.9 [basic.types] paragraph 4 (which is where the concept of "value" is introduced):
Any use of an indeterminate value (5.3.4 [expr.new], 8.5 [dcl.init], 12.6.2 [class.base.init]) of any type other than char or unsigned char results in undefined behavior.
John Max Skaller:
A().i had better be an lvalue; the rules are wrong. Accessing a member of a structure requires it be converted to an lvalue, the above calculation is 'as if':
struct A { int i; A *get() { return this; } }; int j = (*A().get()).i;
and you can see the bracketed expression is an lvalue.
A consequence is:
int &j= A().i; // OK, even if the temporary evaporates
j now refers to a 'destroyed' value. Any use of j is an error. But the binding at the time is valid.
Proposed Resolution (November, 2006):
Add the indicated words to 3.9 [basic.types] paragraph 4:
... For trivial types, the value representation is a set of bits in the object representation that determines a value, which is one discrete element of an implementation-defined set of values. Any use of an indeterminate value (5.3.4 [expr.new], 8.5 [dcl.init], 12.6.2 [class.base.init]) of a type other than unsigned char results in undefined behavior.
Change 4.1 [conv.lval] paragraph 1 as follows:
If the object to which the lvalue refers is not an object of type T and is not an object of a type derived from T, or if the object is uninitialized, a program that necessitates this conversion has undefined behavior.
Additional note (May, 2008):
The C committee is dealing with a similar issue in their DR338. According to this analysis, they plan to take almost the opposite approach to the one described above by augmenting the description of their version of the lvalue-to-rvalue conversion. The CWG did not consider that access to an unsigned char might still trap if it is allocated in a register and needs to reevaluate the proposed resolution in that light. See also issue 129.
Proposed resolution (February, 2012):
This issue is resolved by the resolution of issue 616.
[Moved to DR at the April, 2013 meeting.]
According to 4.1 [conv.lval] paragraph 1, an lvalue-to-rvalue conversion on an uninitialized object produces undefined behavior. Since there is only one “value” of type std::nullptr_t, an lvalue-to-rvalue conversion on a std::nullptr_t glvalue does not need to fetch the value from storage. Is there any need for undefined behavior in this case?
Proposed resolution (February, 2012):
This issue is resolved by the resolution of issue 616.
[Moved to DR at the October, 2012 meeting.]
The resolution of issue 654 (found in paper N2656) enabled conversion of rvalues of type std::nullptr_t to bool. It appears that the use cases for this conversion are primarily or exclusively the “contextually converted to bool” cases, with some possibility for inadvertent misuse in other contexts. Paper N2656 mentioned the idea of limiting the conversions to the direct initialization contexts; that possibility should be reexamined.
Proposed resolution (February, 2012):
Change 4.12 [conv.bool] paragraph 1 as follows:
...any other value is converted to true. A For direct-initialization (8.5 [dcl.init]), a prvalue of type std::nullptr_t can be converted to a prvalue of type bool; the resulting value is false.
[Moved to DR at the October, 2012 meeting.]
Proposed resolution (December, 2011):
Change 3.10 [basic.lval] paragraph 4 as follows (supersedes the corresponding change in the resolution of issue 1059):
Class prvalues can have cv-qualified types; non-class prvalues always have cv-unqualified types. Unless otherwise indicated (5.2.2 [expr.call]), prvalues shall always have complete types or the void type; in addition to these types, glvalues can also have incomplete types. [Note: class and array prvalues can have cv-qualified types; other prvalues always have cv-unqualified types. See Clause 5 [expr]. —end note]
Add a new paragraph following 5 [expr] paragraph 5:
If an expression initially has the type “reference to T”...
If a prvalue initially has the type “cv T,” where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.
Change 5.2.2 [expr.call] paragraph 3 as follows:
If the postfix-expression designates a destructor (12.4 [class.dtor]), the type of the function call expression is void; otherwise, the type of the function call expression is the return type of the statically chosen function (i.e., ignoring the virtual keyword), even if the type of the function actually called is different. This return type shall be an object type, a reference type or the type cv void.
Change 5.2.3 [expr.type.conv] paragraph 2 as follows:
...[Note: if T is a non-class type that is cv-qualified, the cv-qualifiers are ignored discarded when determining the type of the resulting prvalue (3.10 [basic.lval] Clause 5 [expr]). —end note]
Change 5.4 [expr.cast] paragraph 1 as follows:
...[Note: if T is a non-class type that is cv-qualified cv-qualified, the cv-qualifiers are ignored discarded when determining the type of the resulting prvalue; see 3.10 [basic.lval] Clause 5 [expr]. —end note]
[Moved to DR at the October, 2012 meeting.]
There are some points in the description discarded-value expressions that need clarification:
Does this require the lvalue-to-rvalue conversion in the listed cases or only prohibit it in all other cases (“only if” vs “if and only if”)?
Does this apply only to built-in operations or to overloaded operators?
Does this apply to non-POD types?
Suggested resolution:
In some contexts, an expression only appears for its side effects. Such an expression is called a discarded-value expression. The expression is evaluated and its value is discarded. The array-to-pointer (4.2 [conv.array]) and function-to-pointer (4.3 [conv.func]) standard conversions are not applied. The lvalue-to-rvalue conversion (4.1 [conv.lval]) is applied if and only if the expression is an lvalue of volatile-qualified type and it has one of the following forms:
id-expression (5.1.1 [expr.prim.general]),
subscripting subscript operation (5.2.1 [expr.sub]),
class member access (5.2.5 [expr.ref]),
indirection operation (5.3.1 [expr.unary.op]),
pointer-to-member operation (5.5 [expr.mptr.oper]),
conditional expression operation (5.16 [expr.cond]) where both the second and the third operands are one of the above these forms, or
comma expression operation (5.18 [expr.comma]) where the right operand is one of the above these forms.
[Note: Expressions invoking user-defined operators are not the operations above. Discarded-value expressions apply to class types, which will be ill-formed if there is no volatile copy constructor with which to initialize the temporary. —end note]
Proposed resolution (February, 2012):
Change 5 [expr] paragraph 10 as follows:
...The lvalue-to-rvalue conversion (4.1 [conv.lval]) is applied if and only if the expression is an lvalue of volatile-qualified type and it has is one of the following forms:
( expression ), where expression is one of these expressions,
id-expression (5.1.1 [expr.prim.general]),
...
conditional expression (5.16 [expr.cond]) where both the second and the third operands are one of the above these expressions, or
comma expression (5.18 [expr.comma]) where the right operand is one of the above these expressions.
[Note: Using an overloaded operator causes a function call; the above covers only operators with built-in meaning. If the lvalue is of class type, it must have a volatile copy constructor to initialize the temporary that is the result of the lvalue-to-rvalue conversion. —end note]
Additional note (February, 2012):
A problem was discovered that was not addressed by the proposed resolution that was reviewed at the February, 2012 meeting, so the issue has been moved back to "review" status with revised wording.
[Moved to DR at the October, 2012 meeting.]
The current wording of the Standard does not describe what happens when a decltype-specifier is used as a nested-name-specifier and the type denoted by the decltype-specifier is neither a class type nor an enumeration type. Such nested-name-specifiers should be prohibited, presumably somewhere around paragraphs 8-10 of 5.1.1 [expr.prim.general]. (The corresponding prohibition for named types is handled as part of lookup in 3.4.3 [basic.lookup.qual] paragraph 1.)
Proposed resolution (February, 2012):
Add the following immediately after the grammar in 5.1.1 [expr.prim.general] paragraph 8 and move the text following that point into a new paragraph:
The type denoted by a decltype-specifier in a nested-name-specifier shall be a class or enumeration type.
A nested-name-specifier that denotes a class...
In the current specification of lambda expressions, a name appearing in a lambda-capture must refer to a local variable or reference with automatic storage duration (5.1.2 [expr.prim.lambda] paragraph 3). This restriction seems unnecessary and possibly confusing.
One possibility would be to extend the syntax of the lambda-capture to be something like
v = expr
with the meaning that the closure object would have a member named v initialized with the value expr. With this extension, the current syntax could be viewed as an abbreviation for
v = v
Rationale, March, 2009:
This idea was discussed and rejected by the EWG.
This issue was addressed by the adoption of N3648, adopted at the April, 2013 (Bristol) meeting.
[Moved to DR status at the April, 2013 meeting.]
There does not appear to be any technical difficulty that would require the restriction in 5.1.2 [expr.prim.lambda] paragraph 5 against default arguments in lambda-expressions.
Proposed resolution (August, 2011):
Delete the following sentence from 5.1.2 [expr.prim.lambda] paragraph 5:
Default arguments (8.3.6 [dcl.fct.default]) shall not be specified in the parameter-declaration-clause of a lambda-declarator.
Additional note (February, 2012):
EWG requested that the adoption of this proposed resolution be postponed to allow them to discuss it. The issue has thus been returned to "review" status pending EWG action.
[Moved to DR status at the April, 2013 meeting as part of paper N3638.]
There does not appear to be any technical difficulty that would require the current restriction that the return type of a lambda can be deduced only if the body of the lambda consists of a single return statement. In particular, multiple return statements could be permitted if they all return the same type.
Proposed resolution (August, 2011):
Change 5.1.2 [expr.prim.lambda] paragraph 4 as follows:
...If a lambda-expression does not include a trailing-return-type, it is as if the trailing-return-type denotes the following type:
if the compound-statement is of the form
{ attribute-specifier-seqopt return expression ;
the type of the returned expression after lvalue-to-rvalue conversion (4.1 [conv.lval]), array-to-pointer conversion (4.2 [conv.array]), and function-to-pointer conversion (4.3 [conv.func]);
otherwise, void.
if there are no return statements in the compound-statement, or all return statements return either an expression of type void or no expression or braced-init-list, the type void;
otherwise, if all return statements return an expression and the types of the returned expressions after lvalue-to-rvalue conversion (4.1 [conv.lval]), array-to-pointer conversion (4.2 [conv.array]), and function-to-pointer conversion (4.3 [conv.func]) are the same, that common type;
otherwise, the program is ill-formed.
[Example:
auto x1 = [](int i){ return i; }; // OK: return type is int auto x2 = []{ return { 1, 2 }; }; // error: the return type is void (a // braced-init-list is not an expression) struct A { int fn1(); const int& fn2(); }; template <class T> void f () { [](T t, bool b){ if (b) return t.fn1(); else return t.fn2(); }; } template void f<A>(); // OK: lambda return type is int—end example]
Additional note (February, 2012):
EWG requested that the adoption of this proposed resolution be postponed to allow them to discuss it. The issue has thus been returned to "review" status pending EWG action.
[Moved to DR at the April, 2013 meeting.]
5.1.2 [expr.prim.lambda] paragraph 6 does not specify the language linkage of the function type of the closure type's conversion function.
Proposed resolution (October, 2012):
Change 5.1.2 [expr.prim.lambda] paragraph 6 as follows:
The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function with C++ language linkage (7.5 [dcl.link]). having the same parameter and return types as the closure type's function call operator. The value returned...
[Moved to DR status at the April, 2013 meeting.]
Because the subscripting operation is defined as indirection through a pointer value, the result of a subscript operator applied to an xvalue array is an lvalue, not an xvalue. This could be surprising to some.
Proposed resolution (December, 2012):
Change 5.2.1 [expr.sub] paragraph 1 as follows:
A postfix expression followed by an expression in square brackets is a postfix expression. One of the expressions shall have the type “array of T” or “pointer to T” and the other shall have unscoped enumeration or integral type. The result is an lvalue of type “T.” The type “T” shall be a completely-defined object type.62 The expression E1[E2] is identical (by definition) to *((E1)+(E2)) [Note: see 5.3 [expr.unary] and 5.7 [expr.add] for details of * and + and 8.3.4 [dcl.array] for details of arrays. —end note], except that in the case of an array operand, the result is an lvalue if that operand is an lvalue and an xvalue otherwise.
(See also issue 616.)
Additional note (January, 2013):
The preceding resolution differs from that discussed in the December, 2012 drafting review teleconference by the deletion of the words “an lvalue” in the second sentence. The issue has consequently been moved to "review" status instead of "tentatively ready."
[Moved to DR at the April, 2013 meeting.]
The terms “virtual function call” and “virtual call” are used in the Standard but are not defined.
Proposed resolution (August, 2012):
Change 5.2.2 [expr.call] paragraph 1 as follows:
If the selected function is non-virtual, or if the id-expression in the class member access expression is a qualified-id, that function is called. Otherwise, its final overrider (10.3 [class.virtual]) in the dynamic type of the object expression is called; such a call is referred to as a virtual function call.
[Moved to DR at the October, 2012 meeting.]
5.2.7 [expr.dynamic.cast] paragraph 2 allows an expression of any value category when the target type is an rvalue reference. However, paragraph 6 requires that the operand be an lvalue if the runtime check is to be applied. This requirement should presumably be relaxed to require only a glvalue when the target type is an rvalue reference.
Proposed resolution (August, 2011):
Change 5.2.7 [expr.dynamic.cast] paragraph 6 as follows:
Otherwise, v shall be a pointer to or an lvalue a glvalue of a polymorphic type (10.3 [class.virtual]).
Additional note, January, 2012:
An objection has been raised to the proposed resolution on the basis that it unnecessarily weakens the distinction between rvalues and lvalues, making it easier to create dangling references. Its status has therefore been changed back to "review" to allow further discussion.
[Moved to DR at the October, 2012 meeting.]
The requirement in 5.2.8 [expr.typeid] paragraph 5 that
The top-level cv-qualifiers of the glvalue expression or the type-id that is the operand of typeid are always ignored
could be misinterpreted as referring to cv-qualifiers in a function type, even though it is clear that a function type is never cv-qualified. A note emphasizing the fact that that is not the case would be helpful.
Proposed resolution (February, 2012):
Change 5.2.8 [expr.typeid] paragraph 5 as follows:
The top-level cv-qualifiers of the glvalue expression or the type-id that is the operand of typeid are always ignored. If the type of the expression or type-id is a cv-qualified type, the result of the typeid expression refers to a std::type_info object representing the cv-unqualified type. [Example:...
[Moved to DR at the April, 2013 meeting.]
The specification of static_cast (5.2.9 [expr.static.cast]) does not describe conversion of a scoped enumeration value to bool. Presumably it should be handled as for unscoped enumerations, with a zero value becoming false and a non-zero value becoming true.
Proposed resolution (August, 2012):
Change 5.2.9 [expr.static.cast] paragraph 9 as follows:
A value of a scoped enumeration type (7.2 [dcl.enum]) can be explicitly converted to an integral type. The When that type is cv bool, the resulting value is false if the original value is zero and true for all other values. For the remaining integral types, the value is unchanged if the original value can be represented by the specified type. Otherwise, the resulting value is unspecified.
[Moved to DR at the April, 2013 meeting.]
The definedness of various pointer conversions (see 5.2.10 [expr.reinterpret.cast] paragraph 7, 9.2 [class.mem] paragraph 20, 5.2.9 [expr.static.cast] paragraph 13) relies on the properties of the types involved, such as whether they are standard-layout types and their intrinsic alignment. This creates contradictions and unnecessary unspecified behavior when the actual values of the pointer involved would actually permit the operations. Recasting the specification in terms of the memory model instead of the types of the objects provides a more coherent specification.
Proposed resolution (February, 2012):
Change 4.10 [conv.ptr] paragraph 2 as follows:
A prvalue of type “pointer to cv T,” where T is an object type, can be converted to a prvalue of type “pointer to cv void”. The result of converting a “pointer to cv T” non-null pointer value of a pointer to object type to a “pointer to cv void” points to the start of the storage location where the object of type T resides, as if the object is a most derived object (1.8 [intro.object]) of type T (that is, not a base class subobject) represents the address of the same byte in memory as the original pointer value. The null pointer value is converted to the null pointer value of the destination type.
Change 5.2.9 [expr.static.cast] paragraph 13 as follows:
A prvalue of type “pointer to cv1 void” can be converted to a prvalue of type “pointer to cv2 T,” where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. The null pointer value is converted to the null pointer value of the destination type. If the original pointer value represents the address A of a byte in memory and A satisfies the alignment requirement of T, then the resulting pointer value represents the same address as the original pointer value, that is, A. The result of any other such pointer conversion is unspecified. A value of type pointer to object converted to “pointer to cv void” and back, possibly with different cv-qualification, shall have its original value...
Change 5.2.10 [expr.reinterpret.cast] paragraph 7 as follows:
An object pointer can be explicitly converted to an object pointer of a different type.70 When a prvalue v of object pointer type “pointer to T1” is converted to the object pointer type “pointer to cv T2”, the result is static_cast<cv T2*>(static_cast<cv void*>(v)) if both T1 and T2 are standard-layout types (3.9 [basic.types]) and the alignment requirements of T2 are no stricter than those of T1, or if either type is void. Converting a prvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value. The result of any other such pointer conversion is unspecified.
[Moved to DR at the October, 2012 meeting.]
According to 5.2.9 [expr.static.cast] paragraph 3,
A glvalue of type “cv1 T1” can be cast to type “rvalue reference to cv2 T2,” if “cv2 T2” is reference-compatible with “cv1 T1” (8.5.3 [dcl.init.ref]). The result refers to the object or the specified base class subobject thereof.
This specification fails to allow for a bit-field lvalue operand, since the reference cannot refer to a bit-field. Presumably a temporary should be formed and the reference be bound to it.
Proposed resolution (February, 2012):
Change 5.2.9 [expr.static.cast] paragraphs 3-4 as follows:
A glvalue of type “cv1 T1” can be cast to type “rvalue reference to cv2 T2” if “cv2 T2” is reference-compatible with “cv1 T1” (8.5.3 [dcl.init.ref]). The If the glvalue is not a bit-field, the result refers to the object or the specified base class subobject thereof; otherwise, the lvalue-to-rvalue conversion (4.1 [conv.lval]) is applied to the bit-field and the resulting prvalue is used as the expression of the static_cast for the remainder of this section. If T2 is an inaccessible (Clause 11 [class.access]) or ambiguous (10.2 [class.member.lookup]) base class of T1, a program that necessitates such a cast is ill-formed.
Otherwise, an An expression e can be explicitly converted...
[Moved to DR at the October, 2012 meeting.]
5.2.10 [expr.reinterpret.cast] paragraph 11, dealing with casting to reference types, only allows an lvalue operand. Presumably it should allow a glvalue operand when the target is an rvalue reference type.
Proposed resolution (August, 2011):
Change 5.2.10 [expr.reinterpret.cast] paragraph 11:
An lvalue A glvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. The result refers to the same object as the source glvalue, but with the specified type. [Note: That is, for lvalues, a reference cast reinterpret_cast<T&>(x) has the same effect as the conversion *reinterpret_cast<T*>(&x) with the built-in & and * operators (and similarly for reinterpret_cast<T&&>(x)). —end note] The result refers to the same object as the source lvalue, but with a different type. The result is an lvalue for an lvalue reference type or an rvalue reference to function type and an xvalue for an rvalue reference to object type. No temporary is created,...
Additional note, January, 2012:
An objection has been raised to the proposed resolution on the basis that it unnecessarily weakens the distinction between rvalues and lvalues, making it easier to create dangling references. Its status has therefore been changed back to "review" to allow further discussion.
[Moved to DR at the October, 2012 meeting.]
Split off from issue 315.
Incidentally, another thing that ought to be cleaned up is the inconsistent use of "indirection" and "dereference". We should pick one.
Proposed resolution (December, 2006):
Change 5.3.1 [expr.unary.op] paragraph 1 as follows:
The unary * operator performs indirection dereferences a pointer value: the expression to which it is applied shall be a pointer...
Change 8.3.4 [dcl.array] paragraph 8 as follows:
The results are added and indirection applied values are added and the result is dereferenced to yield an array (of five integers), which in turn is converted to a pointer to the first of the integers.
Change 8.3.5 [dcl.fct] paragraph 9 as follows:
The binding of *fpi(int) is *(fpi(int)), so the declaration suggests, and the same construction in an expression requires, the calling of a function fpi, and then using indirection through dereferencing the (pointer) result to yield an integer. In the declarator (*pif)(const char*, const char*), the extra parentheses are necessary to indicate that indirection through dereferencing a pointer to a function yields a function, which is then called.
Change the index for * and “dereferencing” no longer to refer to “indirection.”
[Drafting note: 26.6.9 [template.indirect.array] requires no change. Many more places in the current wording use “dereferencing” than “indirection.”]
Notes from the August, 2011 meeting:
CWG prefers use of the term “indirection” instead of “dereferencing.” This would be consistent with the usage in the C Standard and would avoid entanglement with the C++ concept of a reference type.
Proposed resolution (February, 2012):
Change 3.7.4.1 [basic.stc.dynamic.allocation] paragraph 2 as follows:
...The effect of dereferencing indirecting through a pointer returned as a request for zero size is undefined.
Change 3.7.4.3 [basic.stc.dynamic.safety] paragraph 2 as follows:
the value returned by a call to the C ++ standard library implementation of ::operator new(std:: size_t); [Footnote: This section does not impose restrictions on dereferencing indirection through pointers to memory not allocated by ::operator new. This maintains the ability of many C++ implementations to use binary libraries and components written in other languages. In particular, this applies to C binaries, because dereferencing indirection through pointers to memory allocated by std::malloc is not restricted. —end footnote]
the result of taking the address of an object (or one of its subobjects) designated by an lvalue resulting from dereferencing indirection through a safely-derived pointer value;
...
Change 3.8 [basic.life] paragraph 5 as follows:
...Such Indirection through such a pointer may be dereferenced is permitted but the resulting lvalue may only be used in limited ways...
Change 4.11 [conv.mem] paragraph 2 as follows:
...Since the result has type “pointer to member of D of type cv T”, it can be dereferenced indirection through it with a D object is valid. The result is the same as if indirecting through the pointer to member of B were dereferenced with the B subobject of D. The null member pointer value...
Change 5.2.9 [expr.static.cast] paragraph 12 as follows:
...[Note: although class B need not contain the original member, the dynamic type of the object on with which indirection through the pointer to member is dereferenced performed must contain the original member; see 5.5 [expr.mptr.oper]. —end note]
Change 5.3.1 [expr.unary.op] paragraph 1 as follows:
...[Note: indirection through a pointer to an incomplete type (other than cv void) can be dereferenced is valid. The lvalue thus obtained...
Change 5.10 [expr.eq] paragraph 2 as follows:
...Otherwise they compare equal if and only if they would refer to the same member of the same most derived object (1.8 [intro.object]) or the same subobject if they were dereferenced indirection with a hypothetical object of the associated class type were performed. [Example:...
Change 7.5 [dcl.link] paragraph 8:
[Note: Because the language linkage is part of a function type, when indirecting through a pointer to C function (for example) is dereferenced, the function to which it the resulting lvalue refers is considered a C function. —end note]
Change 8.3.2 [dcl.ref] paragraph 5 as follows:
...[Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by dereferencing indirection through a null pointer, which causes undefined behavior. As described...
Change 17.6.3.5 [allocator.requirements] table 27:
Variable Definition ... c a dereferenceable pointer of type C* through which indirection is valid ...
Change 20.7.3.2 [pointer.traits.functions] as follows:
Returns: The first member function returns a dereferenceable pointer to r obtained by calling Ptr::pointer_to(r) through which indirection is valid; an instantiation of this function is ill-formed...
Change 20.7.4 [util.dynamic.safety] paragraph 10 as follows:
Effects: The n bytes starting at p no longer contain traceable pointer locations, independent of their type. Hence pointers indirection through a pointer located there may not be dereferenced is undefined if the object they point it points to was created by global operator new and not previously declared reachable...
Change 20.7.12 [specialized.algorithms] paragraph 1 as follows:
...is required to have the property that no exceptions are thrown from increment, assignment, comparison, or dereference of indirection through valid iterators...
Change 22.4.5.1.2 [locale.time.get.virtuals] paragraph 11 as follows:
Requires: t shall be dereferenceable point to an object.
Change 23.4.4.2 [map.cons] paragraph 3 as follows:
Requires: If the iterator's dereference indirection operator returns an lvalue or a const rvalue pair<key_type, mapped_type>, then both key_type and mapped_type shall be CopyConstructible.
Change 23.4.5.2 [multimap.cons] paragraph 3 as follows:
Requires: If the iterator's dereference indirection operator returns an lvalue or a const rvalue pair<key_type, mapped_type>, then both key_type and mapped_type shall be CopyConstructible.
Change 23.4.6.2 [set.cons] paragraph 4 as follows:
Requires: If the iterator's dereference indirection operator returns an lvalue or a non-const rvalue, then Key shall be CopyConstructible.
Change 23.4.7.2 [multiset.cons] paragraph 3 as follows:
Requires: If the iterator's dereference indirection operator returns an lvalue or a const rvalue, then Key shall be CopyConstructible.
Change 24.5.3 [move.iterators] paragraph 1 as follows:
Class template move_iterator is an iterator adaptor with the same behavior as the underlying iterator except that its dereference indirection operator implicitly converts the value returned by the underlying iterator's dereference indirection operator to an rvalue reference...
Change the title of 28.12.1.3 [re.regiter.deref] as follows:
regex_iterator dereference indirection
Change the title of 28.12.2.3 [re.tokiter.deref] as follows:
regex_token_iterator dereference indirection
[Moved to DR at the October, 2012 meeting.]
According to 5.3.1 [expr.unary.op] paragraph 5,
The address of an object of incomplete type can be taken, but if the complete type of that object is a class type that declares operator&() as a member function, then the behavior is undefined (and no diagnostic is required).
This should actually be “ill-formed, no diagnostic required” instead of undefined behavior, since the problem could be detected by whole-program analysis. Also, it's not clear what this means for constant expressions.
Proposed resolution (February, 2012):
Change 5.3.1 [expr.unary.op] paragraph 5 as follows:
The address of an object of incomplete type can be taken, but if the complete type of that object is a class type that declares operator&() as a member function, then the behavior is undefined (and no diagnostic is required). If & is applied to an lvalue of incomplete class type and the complete type declares operator&(), it is unspecified whether the operator has the built-in meaning or the operator function is called. The operand of & shall not be a bit-field.
[Moved to DR at the April, 2013 meeting.]
According to 5.3.3 [expr.sizeof] paragraph 1,
The sizeof operator shall not be applied... to an lvalue that designates a bit-field.
Xvalues can also designate bit-fields and thus should presumably be addressed here as well.
Proposed resolution (October, 2012):
Change 5.3.3 [expr.sizeof] paragraph 1 as follows:
The sizeof operator yields the number of bytes in the object representation of its operand. The operand is either an expression, which is an unevaluated operand (Clause 5 [expr]), or a parenthesized type-id. The sizeof operator shall not be applied to an expression that has function or incomplete type, to an enumeration type whose underlying type is not fixed before all its enumerators have been declared, to the parenthesized name of such types, or to an lvalue a glvalue that designates a bit-field. sizeof(char)...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to the C++ Standard section 5.3.4 [expr.new] paragraph 21 it is unspecified whether the allocation function is called before evaluating the constructor arguments or after evaluating the constructor arguments but before entering the constructor.
On top of that paragraph 17 of the same section insists that
If any part of the object initialization described above [Footnote: This may include evaluating a new-initializer and/or calling a constructor.] terminates by throwing an exception and a suitable deallocation function is found, the deallocation function is called to free the memory in which the object was being constructed... If no unambiguous matching deallocation function can be found, propagating the exception does not cause the object's memory to be freed...
Now suppose we have:
struct copy_throw { copy_throw(const copy_throw&) { throw std::logic_error("Cannot copy!"); } copy_throw(long, copy_throw) { } copy_throw() { } };
int main() try { copy_throw an_object, /* undefined behaviour */ * a_pointer = ::new copy_throw(0, an_object); return 0; } catch(const std::logic_error&) { }
Here the new-expression '::new copy_throw(0, an_object)' throws an exception when evaluating the constructor's arguments and before the allocation function is called. However, 5.3.4 [expr.new] paragraph 17 prescribes that in such a case the implementation shall call the deallocation function to free the memory in which the object was being constructed, given that a matching deallocation function can be found.
So a call to the Standard library deallocation function '::operator delete(void*)' shall be issued, but what argument is an implementation supposed to supply to the deallocation function? As per 5.3.4 [expr.new] paragraph 17 - the argument is the address of the memory in which the object was being constructed. Given that no memory has yet been allocated for the object, this will qualify as using an invalid pointer value, which is undefined behaviour by virtue of 3.7.4.2 [basic.stc.dynamic.deallocation] paragraph 4.
Suggested resolution:
Change the first sentence of 5.3.4 [expr.new] paragraph 17 to read:
If the memory for the object being created has already been successfully allocated and any part of the object initialization described above...
Proposed resolution (March, 2008):
Change 5.3.4 [expr.new] paragraph 18 as follows:
If any part of the object initialization described above [Footnote: ...] terminates by throwing an exception, storage has been obtained for the object, and a suitable deallocation function can be found, the deallocation function is called...
[Accepted at the April, 2013 meeting.]
Currently, 5.3.4 [expr.new] paragraph 7 requires that an attempt to allocate an array with a negative length be diagnosed:
If the value of that expression is less than zero or such that the size of the allocated object would exceed the implementation-defined limit, or if the new-initializer is a braced-init-list for which the number of initializer-clauses exceeds the number of elements to initialize, no storage is obtained and the new-expression terminates by throwing an exception of a type that would match a handler (15.3 [except.handle]) of type std::bad_array_new_length (18.6.2.2 [new.badlength]).
Checking for a negative bound will be lost, however, upon the adoption of paper N3323, as the expression will be converted to std::size_t, an unsigned type. Although the result of this conversion will likely also cause the check to fail (and will always do so when scaled by an element size larger than 1), it is not inconceivable that an implementation could provide a heap that capable of providing more than half the addressable range of std::size_t, and a request for a character array (with an element size of 1) with a negative bound close to LONG_MIN (assuming std::size_t is unsigned long) might actually succeed.
The wording of 5.3.4 [expr.new] paragraph 7 should be changed so that the test for a negative bound is applied to the value before conversion to std::size_t, or some other mechanism should be invented to preserve the check for a negative bound.
Additional note (August, 2012):
The goal for addressing this issue should be that an attempt to use an invalid bound (negative, greater than the maximum allowed, or more than the number implied by the initializer) will be ill-formed when the bound is a compile-time constant and will result in an exception otherwise.
Proposed resolution (October, 2012):
Change 3.9.2 [basic.compound] paragraph 2 as follows:
These methods of constructing types can be applied recursively; restrictions are mentioned in 8.3.1 [dcl.ptr], 8.3.4 [dcl.array], 8.3.5 [dcl.fct], and 8.3.2 [dcl.ref]. Constructing a type such that the number of bytes in its object representation exceeds the maximum value representable in the type std::size_t (18.2 [support.types]) is ill-formed.
Change 5.3.4 [expr.new] paragraph 7 as follows:
The expression in a noptr-new-declarator is erroneous if:
the expression is of non-class type and its value before converting to std::size_t is less than zero;
the expression is of class type and its value before application of the second standard conversion (13.3.3.1.2 [over.ics.user]) [Footnote: If the conversion function returns a signed integer type, the second standard conversion converts to the unsigned type std::size_t and thus thwarts any attempt to detect a negative value afterwards. —end footnote] is less than zero;
its value is such that the size of the allocated object would exceed the implementation-defined limit (annex B [implimits]); or
the new-initializer is a braced-init-list and the number of array elements for which initializers are provided (including the terminating '\0' in a string literal (2.14.5 [lex.string])) exceeds the number of elements to initialize.
If the expression, after converting to std::size_t, is a core constant expression and the expression is erroneous, the program is ill-formed. Otherwise, a new-expression with an erroneous expression does not call an allocation function and terminates by throwing an exception of a type that would match a handler (15.3 [except.handle]) of type std::bad_array_new_length (18.6.2.2 [new.badlength]). When the value of the expression in a noptr-new-declarator is zero, the allocation function is called to allocate an array with no elements. If the value of that expression is less than zero or such that the size of the allocated object would exceed the implementation-defined limit, or if the new-initializer is a braced-init-list for which the number of initializer-clauses exceeds the number of elements to initialize, no storage is obtained and the new-expression terminates by throwing an exception of a type that would match a handler (15.3 [except.handle]) of type std::bad_array_new_length (18.6.2.2 [new.badlength]).
(This resolution also resolves issue 1559.)
[Moved to DR at the April, 2013 meeting.]
According to 5.3.4 [expr.new] paragraph 7,
if the new-initializer is a braced-init-list for which the number of initializer-clauses exceeds the number of elements to initialize, no storage is obtained and the new-expression terminates by throwing an exception of a type that would match a handler (15.3 [except.handle]) of type std::bad_array_new_length (18.6.2.2 [new.badlength]).
This wording does not, but presumably should, require an exception to be thrown in a case like
void f() { int x = 3; new char[x]{"abc"}; }
(See also issue 1464.)
Proposed resolution (October, 2012):
This issue is resolved by the resolution of issue 1464.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to 5.3.6 [expr.alignof] paragraph 1,
An alignof expression yields the alignment requirement of its operand type. The operand shall be a type-id representing a complete object type or an array thereof or a reference to a complete object type.
This (presumably unintentionally) excludes a reference to an array with an unknown bound but a complete element type; the bound is not needed to determine the alignment of the array.
Proposed resolution (August, 2011):
Change 5.3.6 [expr.alignof] paragraph 1 as follows:
An alignof expression yields the alignment requirement of its operand type. The operand shall be a type-id representing a complete object type or an array thereof or a reference to a complete object type one of those types.
[Moved to DR at the October, 2012 meeting.]
The result of the noexcept operator does not consider possible exceptions thrown by the destructors for temporaries created in the operand expression.
Proposed resolution (February, 2012):
Change 1.9 [intro.execution] paragraph 10 as follows:
A full-expression is an expression that is not a subexpression of another expression. [Note: in some contexts such as unevaluated operands, a syntactic subexpression is considered a full-expression (Clause 5 [expr]). —end note] If a language construct...
Change 5 [expr] paragraph 7 as follows:
...An unevaluated operand is not evaluated. An unevaluated operand is considered a full-expression. [Note:...
[Drafting note: This uniformly handles sizeof(A()), noexcept(A()), typeid(A()), and decltype(A()) with regard to the semantic requirements on ~A (accessible and not deleted), which might be checked via SFINAE. A programmer can use decltype(new A) to avoid considering the destructor. If this is undesired, an alternative change just addresses the noexecept issue:]
[Editing note: all the occurrences of “potentially evaluated” in 5.3.7 [expr.unary.noexcept] paragraph 3 should be hyphenated.]
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
Both the .* and ->* operators (5.5 [expr.mptr.oper]) require that the class of the second operand be a complete object type. Current implementations do not enforce this requirement, and it is not clear that there is a need for it.
Proposed resolution (August, 2011):
Change 5.5 [expr.mptr.oper] paragraph 2 as follows:
The binary operator .* binds its second operand, which shall be of type “pointer to member of T” (where T is a completely-defined class type) to its first operand...
Change 5.5 [expr.mptr.oper] paragraph 3 as follows:
The binary operator ->* binds its second operand, which shall be of type “pointer to member of T” (where T is a completely-defined class type) to its first operand...
[Moved to DR at the October, 2012 meeting.]
Issue 614 adopted the corresponding C99 wording for 5.6 [expr.mul] paragraph 4,
...if the quotient a/b is representable in the type of the result, (a/b)*b + a%b is equal to a.
in an attempt to ensure that INT_MAX % -1 produces undefined behavior (because the result is not specified by the Standard). However, the new C draft makes the undefined behavior explicit:
If the quotient a/b is representable, the expression (a/b) * b + a%b shall equal a; otherwise, the behavior of both a/b and a%b is undefined.
Should C++ adopt similar wording?
Proposed resolution (February, 2012):
Change 5.6 [expr.mul] paragraph 4 as follows:
...If the second operand of / or % is zero the behavior is undefined. For integral operands the / operator yields the algebraic quotient with any fractional part discarded;81 if the quotient a/b is representable in the type of the result, (a/b)*b + a%b is equal to a; otherwise, the behavior of both a/b and a%b is undefined.
[Moved to DR at the April, 2013 meeting.]
The current wording is not sufficiently clear that a pointer to a base class subobject of an array element cannot be used in pointer arithmetic.
Proposed resolution (October, 2012):
Add the following as a new paragraph before 5.7 [expr.add] paragraph 7:
For addition or subtraction, if the expressions P or Q have type “pointer to cv T", where T is different from the cv-unqualified array element type, the behavior is undefined. [Note: In particular, a pointer to a base class cannot be used for pointer arithmetic when the array contains objects of a derived class type. —end note]
If the value 0 is added to or subtracted...
[Moved to DR at the October, 2012 meeting.]
The current wording of 5.8 [expr.shift] paragraph 2 makes it undefined behavior to create the most-negative integer of a given type by left-shifting a (signed) 1 into the sign bit, even though this is not uncommonly done and works correctly on the majority of (twos-complement) architectures:
...if E1 has a signed type and non-negative value, and E1 ⨯ 2E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.
As a result, this technique cannot be used in a constant expression, which will break a significant amount of code.
Proposed resolution (February, 2012):
Change 5.8 [expr.shift] paragraph 2 as follows:
...if E1 has a signed type and non-negative value, and E1 ⨯ 2E2 is representable in the corresponding unsigned type of the result type, then that value, converted to the result type, is the resulting value; otherwise, the behavior is undefined.
[Moved to DR status at the April, 2013 meeting as paper N3624.]
In C, this is ill-formed (cf C99 6.5.8):
void f(char* s) { if (s < 0) { } }
...but in C++, it's not. Why? Who would ever need to write (s > 0) when they could just as well write (s != 0)?
This has been in the language since the ARM (and possibly earlier); apparently it's because the pointer conversions (4.10 [conv.ptr]) need to be performed on both operands whenever one of the operands is of pointer type. So it looks like the "null-ptr-to-real-pointer-type" conversion is hitching a ride with the other pointer conversions.
Proposed resolution (April, 2013):
This issue is resolved by the resolution of issue 1512.
[Moved to DR status at the April, 2013 meeting as paper N3624.]
According to 5.9 [expr.rel] paragraph 2, describing pointer comparisons,
Pointer conversions (4.10 [conv.ptr]) and qualification conversions (4.4 [conv.qual]) are performed on pointer operands (or on a pointer operand and a null pointer constant, or on two null pointer constants, at least one of which is non-integral) to bring them to their composite pointer type.
This would appear to make the following example ill-formed,
bool foo(int** x, const int** y) {
return x < y; // valid ?
}
because int** cannot be converted to const int**, according to the rules of 4.4 [conv.qual] paragraph 4. This seems too strict for pointer comparison, and current implementations accept the example.
Proposed resolution (November, 2012):
The proposed wording is found in document N3478.
(This resolution also resolves issue 583.)
[Moved to DR at the April, 2013 meeting.]
The current wording of 5.16 [expr.cond] paragraph 2 says,
If either the second or the third operand has type void, then the lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), and function-to-pointer (4.3 [conv.func]) standard conversions are performed on the second and third operands, and one of the following shall hold:
The second or the third operand (but not both) is a throw-expression (15.1 [except.throw]); the result is of the type of the other and is a prvalue.
Both the second and the third operands have type void; the result is of type void and is a prvalue. [Note: This includes the case where both operands are throw-expressions. —end note]
A parenthesized throw-expression is a primary-expression, not a throw-expression. Should a parenthesized throw-expression be considered a throw-expression for this purpose?
Proposed resolution (October, 2012):
Change 5.16 [expr.cond] paragraph 2 as follows:
If either the second or the third operand has type void, then the lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), and function-to-pointer (4.3 [conv.func]) standard conversions are performed on the second and third operands, and one of the following shall hold:
The second or the third operand (but not both) is a (possibly parenthesized) throw-expression (15.1 [except.throw]); the result is of the type and value category of the other and is a prvalue.
...
(This resolution also resolves issue 1560.)
[Moved to DR at the April, 2013 meeting.]
A glvalue appearing as one operand of a conditional-expression in which the other operand is a throw-expression is converted to a prvalue, regardless of how the conditional-expression is used:
If either the second or the third operand has type void, then the lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), and function-to-pointer (4.3 [conv.func]) standard conversions are performed on the second and third operands, and one of the following shall hold:
The second or the third operand (but not both) is a throw-expression (15.1 [except.throw]); the result is of the type of the other and is a prvalue.
This seems to be gratuitous and surprising.
Proposed resolution (October, 2012):
This issue is resolved by the resolution of issue 1550.
[Moved to DR at the April, 2013 meeting.]
According to 5.17 [expr.ass] paragraph 9,
A braced-init-list may appear on the right-hand side of
an assignment to a scalar...
an assignment defined by a user-defined assignment operator, in which case the initializer list is passed as the argument to the operator function.
Presumably the phrase “user-defined” is not intended to forbid an example like
struct A { A(); A ( std::initializer_list<int> ) ; }; void f() { A a; a = {37}; }
which relies on an implicitly-declared assignment operator.
Proposed resolution (August, 2012):
Change 5.17 [expr.ass] paragraph 9 as follows:
A braced-init-list may appear on the right-hand side of
an assignment to a scalar, in which case the initializer list shall have at most a single element. The meaning of x={v}, where T is the scalar type of the expression x, is that of x=T(v) except that no narrowing conversion (8.5.4) is allowed. The meaning of x={} is x=T().
an assignment defined by a user-defined assignment operator to an object of class type, in which case the initializer list is passed as the argument to the assignment operator function selected by overload resolution (13.5.3 [over.ass], 13.3 [over.match]).
[Moved to DR at the April, 2013 meeting.]
According to 5.17 [expr.ass] paragraph 9,
The meaning of x={v}, where T is the scalar type of the expression x, is that of x=T(v) except that no narrowing conversion (8.5.4 [dcl.init.list]) is allowed. The meaning of x={} is x=T().
This definition adds a gratuitous C-style cast to the right-hand operand, inadvertently allowing such things as base-to-derived conversions and circumvention of access checking.
Proposed resolution (October, 2012):
Change 5.17 [expr.ass] paragraph 9 as follows:
The meaning of x={v}, where T is the scalar type of the expression x, is that of x=T(v) except that no narrowing conversion (8.5.4 [dcl.init.list]) is allowed x=T{v}. The meaning of x={} is x=T() x=T{}.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
Proposed resolution (August, 2011):
This issue is resolved by the resolution of issue 1369.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
It is not clear whether a string literal can be used in a constant expression.
Proposed resolution (August, 2011):
Change 5.19 [expr.const] paragraph 2 as follows:
an lvalue-to-rvalue conversion (4.1 [conv.lval]) unless it is applied to
a glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression [Note: a string literal (2.14.5 [lex.string]) corresponds to an array of such objects. —end note], or
...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The current wording of 5.19 [expr.const] paragraph 2 does not, but should, prohibit use of volatile glvalues in constant expressions.
Proposed resolution (August, 2011):
Change 5.19 [expr.const] paragraph 2 as follows:
an lvalue-to-rvalue conversion (4.1 [conv.lval]) unless it is applied to
a non-volatile glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression, or
a non-volatile glvalue of literal type that refers to a non-volatile object defined with constexpr, or that refers to a sub-object of such an object, or
a non-volatile glvalue of literal type that refers to a non-volatile temporary object whose lifetime has not ended, initialized with a constant expression;
[Moved to DR at the October, 2012 meeting.]
Although a reinterpret_cast is prohibited in a constant expression, casting to and from void* can achieve the same effect.
Proposed resolution (August, 2011):
Change 5.19 [expr.const] paragraph 2 as follows:
an lvalue-to-rvalue conversion (4.1 [conv.lval]) unless it is applied to...
an lvalue-to-rvalue conversion (4.1 [conv.lval]) that is applied to a glvalue of type cv1 T that refers to an object of type cv2 U, where T and U are neither the same type nor similar types (4.4 [conv.qual]);
an lvalue-to-rvalue conversion (4.1 [conv.lval]) that is applied to a glvalue that refers to a non-active member...
Note, January, 2012:
Additional discussion has occurred, so this issue has been returned to "review" status to allow further consideration.
Proposed resolution (February, 2012):
Change 5.19 [expr.const] paragraph 2 as follows:
...
an id-expression that refers to a variable or data member of reference type unless...
a conversion from type cv void * to a pointer-to-object type;
a dynamic cast (5.2.7 [expr.dynamic.cast]);
...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The requirements for constant expressions do not currently, but should, exclude expressions that have undefined behavior, such as pointer arithmetic when the pointers do not point to elements of the same array.
Proposed resolution (August, 2011):
Change 5.19 [expr.const] paragraph 2 as follows:
...
a result that is not mathematically defined or not in the range of representable values for its type;
an operation that would have undefined behavior [Note: including, for example, signed integer overflow (Clause 5 [expr]), certain pointer arithmetic (5.7 [expr.add]), division by zero (5.6 [expr.mul]), or certain shift operations (5.8 [expr.shift]) —end note];
...
a subtraction (5.7 [expr.add]) where both operands are pointers;
...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
Use of a parameter in a constexpr function appears to be ill-formed, because the lvalue-to-rvalue conversion on the parameter is not one of those permitted in a constant expression.
Proposed resolution (August, 2011):
Change the indicated bullet of 5.19 [expr.const] paragraph 2 as follows:
an invocation of a constexpr constructor with arguments that, when substituted by function invocation substitution (7.1.5 [dcl.constexpr]), do not produce all constant expressions for the constructor calls and full-expressions in the mem-initializers (including conversions); [Example:...
Delete the final bullet of 7.1.5 [dcl.constexpr] paragraph 3 and move the deleted "." to the preceding sub-bullet:
every constructor call and implicit conversion used in initializing the return value (6.6.3 [stmt.return], 8.5 [dcl.init]) shall be one of those allowed in a constant expression (5.19 [expr.const]).
Delete the final bullet of 7.1.5 [dcl.constexpr] paragraph 4 and change the preceding bullet as follows:
every assignment-expression that is an initializer-clause appearing directly or indirectly within a brace-or-equal-initializer for a non-static data member that is not named by a mem-initializer-id shall be a constant expression; and.
every implicit conversion used in converting a constructor argument to the corresponding parameter type and converting a full-expression to the corresponding member type shall be one of those allowed in a constant expression.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The current wording appears to allow calling a constexpr function that is never defined within the body of a constexpr function. (The wording was intended to allow mutually-recursive constexpr functions but require that the not-yet-defined function be defined before it would be needed in an actual constant expression.)
Proposed resolution (August, 2011):
Change the indicated bullet of 5.19 [expr.const] paragraph 2 as follows:
an invocation of an undefined constexpr function or an undefined constexpr constructor outside the definition of a constexpr function or a constexpr constructor;
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The provisions allowing the use of this in a constant expression appear to be unnecessary, as any uses of this in a constant expression that are valid will be replaced by function invocation substitution.
Proposed resolution (August, 2011):
This issue is resolved by the resolution of issue 1369.
[Moved to DR at the October, 2012 meeting.]
The current wording incorrectly appears to make the following example ill-formed:
constexpr const int &f(const int &n) { return n; } constexpr int k = f(0); // ill-formed
Proposed resolution (February, 2012):
Change 5.19 [expr.const] paragraph 2 as follows:
A conditional-expression is a core constant expression unless it involves one of the following...
...
an invocation of a constexpr function with arguments that, when substituted by function invocation substitution (7.1.5 [dcl.constexpr]), do not produce a core constant expression; [Example:...
an invocation of a constexpr constructor with arguments that, when substituted by function invocation substitution (7.1.5 [dcl.constexpr]), do not produce all core constant expressions for the constructor calls and full-expressions in the mem-initializers; [Example:...
...
an lvalue-to-rvalue conversion (4.1 [conv.lval]) unless it is applied to
...
a glvalue of literal type that refers to a non-volatile temporary object whose lifetime has not ended, initialized with a core constant expression;
...
an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization, initialized with a constant expression; and either
it is initialized with a constant expression or
it is a non-static data member of a temporary object whose lifetime has not ended and is initialized with a core constant expression;
...
Change 5.19 [expr.const] paragraph 3 as follows, dividing it into two paragraphs:
A literal constant expression is a prvalue core constant expression of literal type, but not pointer type. An integral constant expression is a literal constant an expression of integral or unscoped enumeration type, implicitly converted to a prvalue, where the converted expression is a core constant expression. [Note: Such expressions may be used as array bounds (8.3.4 [dcl.array], 5.3.4 [expr.new]), as bit-field lengths (9.6 [class.bit]), as enumerator initializers if the underlying type is not fixed (7.2 [dcl.enum]), as null pointer constants (4.10 [conv.ptr]), and as alignments (7.6.2 [dcl.align]). —end note] A converted constant expression of type T is a literal constant an expression, implicitly converted to a prvalue of type T, where the implicit conversion (if any) is permitted in a literal converted expression is a core constant expression and the implicit conversion sequence contains only user-defined conversions, lvalue-to-rvalue conversions (4.1 [conv.lval]), integral promotions (4.5 [conv.prom]), and integral conversions (4.7 [conv.integral]) other than narrowing conversions (8.5.4 [dcl.init.list]). [Note: such expressions may be used as case expressions (6.4.2 [stmt.switch]), as enumerator initializers if the underlying type is fixed (7.2 [dcl.enum]), and as integral or enumeration non-type template arguments (14.3 [temp.arg]). —end note]
A literal constant expression is a prvalue core constant expression of literal type, but not pointer type (after conversions as required by the context). For a literal constant expression of array or class type, each subobject of its value shall have been initialized by a constant expression. A reference constant expression is an lvalue core constant expression that designates an object with static storage duration or a function. An address constant expression is a prvalue core constant expression (after conversions as required by the context) of type std::nullptr_t or of pointer type that evaluates to the address of an object with static storage duration, to the address of a function, or to a null pointer value, or a prvalue core constant expression of type std::nullptr_t. Collectively, literal constant expressions, reference constant expressions, and address constant expressions are called constant expressions.
Change the second example 7.1.5 [dcl.constexpr] paragraph 5 as follows:
constexpr int f(bool b) { return b ? throw 0 : 0; } // OK constexpr int f() { throw 0 return f(true); } // ill-formed, no diagnostic required ...
This resolution also resolves issue 1455.
[Moved to DR at the October, 2012 meeting.]
A "converted constant expression" must be a literal constant expression, which is a prvalue, and thus can't be an lvalue. This is unintended; the lvalue-to-rvalue conversion should be applied as necessary.
Proposed resolution (February, 2012):
This issue is resolved by the resolution of issue 1454.
[Moved to DR at the April, 2013 meeting.]
Currently an address constant expression cannot designate the address one past the end of an array. This seems unfortunate.
Proposed resolution (August, 2012):
Change 5.19 [expr.const] paragraph 3 as follows:
...An address constant expression is a prvalue core constant expression of pointer type that evaluates to the address of an object with static storage duration, to the address one past the last element of an array with static storage duration, to the address of a function, or to a null pointer value, or a prvalue core constant expression of type std::nullptr_t...
The following initializations appear to be well-formed:
struct C { int m; constexpr C(int m ) : m{m} {}; constexpr int get() { return m; }; }; C&& rr = C{1}; constexpr int k1 = rr.get(); const C& cl = C{1}; constexpr int k2 = cl.get();
They appear to fall under the bullet of 5.19 [expr.const] paragraph 2,
an lvalue-to-rvalue conversion (4.1 [conv.lval]) unless it is applied to
...
a non-volatile glvalue of literal type that refers to a non-volatile temporary object whose lifetime has not ended, initialized with a constant expression;
The problem in this example is that the referenced temporary object is not a constant, so it would be well-defined for intervening code to modify its value before it was used in the later initialization.
Additional note (February, 2013):
The intent is that non-const references to temporaries should be allowed within constant expressions, e.g., across constexpr function calls, but not as the result of a constant expression.
Proposed resolution (April, 2013):
This issue was rendered moot by paper N3652, adopted at the April, 2013 meeting.
[Moved to DR at the April, 2013 meeting.]
One of the criteria in 5.19 [expr.const] paragraph 2 for disqualifying an expression from being a constant expression is:
a typeid expression (5.2.8 [expr.typeid]) whose operand is of a polymorphic class type;
on the basis that a runtime test for the dynamic type is inconsistent with a constant expression. However, it is only glvalues of polymorphic type that require a runtime test; type-ids and prvalues with a polymorphic type could (and should) be permitted in constant expressions.
Proposed resolution (October, 2012):
Change 5.19 [expr.const] paragraph 2 as follows:
...
a typeid expression (5.2.8 [expr.typeid]) whose operand is a glvalue of a polymorphic class type;
[Moved to DR at the April, 2013 meeting.]
According to the note in 5.19 [expr.const] paragraph 4,
[Note: Although in some contexts constant expressions must be evaluated during program translation, others may be evaluated during program execution. Since this International Standard imposes no restrictions on the accuracy of floating-point operations, it is unspecified whether the evaluation of a floating-point expression during translation yields the same result as the evaluation of the same expression (or the same operations on the same values) during program execution.
With the advent of narrowing rules, which require the compiler to evaluate constant expressions in more contexts than was the case in C++03 in order to determine whether an expression is well formed or not, this wording is not sufficiently clear in stating that even in cases where the computation must be done at compile time, the implementation is free to use the result of a runtime calculation rather than preserving the one computed at compile time.
Proposed resolution (October, 2012):
Change 5.19 [expr.const] paragraph 4 as follows:
[Note: Although in some contexts constant expressions must be evaluated during program translation, others may be evaluated during program execution. Since this International Standard imposes no restrictions on the accuracy of floating-point operations, it is unspecified whether the evaluation of a floating-point expression during translation yields the same result as the evaluation of the same expression (or the same operations on the same values) during program execution. [Footnote: Nonetheless, implementations are encouraged to provide consistent results, irrespective of whether the evaluation was actually performed during translation and/or during program execution. —end footnote] [Example:...
[Moved to DR at the October, 2012 meeting.]
6.4.1 [stmt.if] is silent about whether the else clause of an if statement is executed if the condition is not evaluated. (This could occur via a goto or a longjmp.) C99 covers the goto case with the following provision:
If the first substatement is reached via a label, the second substatement is not executed.
It should probably also be stated that the condition is not evaluated when the “then” clause is entered directly.
Proposed resolution (February, 2012):
Change 6.4.1 [stmt.if] paragraph 1 as follows:
If the condition (6.4 [stmt.select]) yields true the first substatement is executed. If the else part of the selection statement is present and the condition yields false, the second substatement is executed. If the first substatement is reached via a label, the condition is not evaluated and the second substatement is not executed. In the second form...
[Moved to DR at the April, 2013 meeting.]
It is not clear whether the reference to argument-dependent lookup in 6.5.4 [stmt.ranged] paragraph 1 bullet 3 should be “pure” argument-dependent lookup (with no unqualified name lookup component) or the usual lookup that is invoked when argument-dependent lookup is done, i.e., unqualified lookup, potentially augmented by the associated namespaces.
Proposed resolution (October, 2012):
Change 6.5.4 [stmt.ranged] paragraph 1 bullet 3 as follows:
otherwise, begin-expr and end-expr are begin(__range) and end(__range), respectively, where begin and end are looked up with argument-dependent lookup in the associated namespaces (3.4.2 [basic.lookup.argdep]). For the purposes of this name lookup, namespace std is an associated namespace. [Note: Ordinary unqualified lookup (3.4.1 [basic.lookup.unqual]) is not performed. —end note]
[Moved to DR at the April, 2013 meeting.]
According to 6.6.3 [stmt.return] paragraph 3,
A return statement with neither an expression nor a braced-init-list can be used only in functions that do not return a value, that is, a function with the return type void, a constructor (12.1 [class.ctor]), or a destructor (12.4 [class.dtor]).
However, paragraph 3 allows a return type of cv void in cases where the expression in the return statement has type void. Should paragraph 2 follow suit?
Proposed resolution (October, 2012):
Change 6.6.3 [stmt.return] paragraph 2 as follows:
A return statement with neither an expression nor a braced-init-list can be used only in functions that do not return a value, that is, a function with the return type cv void, a constructor (12.1 [class.ctor]), or a destructor (12.4 [class.dtor]). A return statement with an expression of non-void type...
[Moved to DR at the April, 2013 meeting.]
There is a contradiction in the specification of the linkage of members of the unnamed namespace, for example
namespace { int x; }
According to 3.5 [basic.link] paragraph 4,
An unnamed namespace or a namespace declared directly or indirectly within an unnamed namespace has internal linkage. All other namespaces have external linkage. A name having namespace scope that has not been given internal linkage above has the same linkage as the enclosing namespace if it is the name of
a variable; or
...
which would give x internal linkage. However, 7.1.1 [dcl.stc] paragraph 7 says,
A name declared in a namespace scope without a storage-class-specifier has external linkage unless it has internal linkage because of a previous declaration and provided it is not declared const.
This would give x external linkage. (This appears to have been a missed edit from the resolution of issue 1113.)
Proposed resolution (October, 2012):
Delete 7.1.1 [dcl.stc] paragraph 7:
A name declared in a namespace scope without a storage-class-specifier has external linkage unless it has internal linkage because of a previous declaration and provided it is not declared const. Objects declared const and not explicitly declared extern have internal linkage.
[Moved to DR at the April, 2013 meeting.]
Consider the following:
struct S { int i; }; using A alignas(alignof(long long)) = S;
7.6.2 [dcl.align] paragraph 1 allows an alignment-specifier to be applied to the declaration of a class or enumeration type, which A arguably is. The specification should be clarified to indicate that such a usage is not permitted, however.
Proposed resolution (October, 2012):
Change 7.6.2 [dcl.align] paragraph 1 as follows:
An alignment-specifier may be applied to a variable or to a class data member, but it shall not be applied to a bit-field, a function parameter, the formal parameter of a catch clause an exception-declaration (15.3 [except.handle]), or a variable declared with the register storage class specifier. An alignment-specifier may also be applied to the declaration of a class or enumeration type or definition of a class (in an elaborated-type-specifier (7.1.6.3 [dcl.type.elab]) or class-head (Clause 9 [class]), respectively) and to the declaration or definition of an enumeration (in an opaque-enum-declaration or enum-head, respectively (7.2 [dcl.enum])). An alignment-specifier with an ellipsis is a pack expansion (14.5.3 [temp.variadic]).
[Moved to DR status at the April, 2013 meeting.]
The permission granted implementations in 7.1.5 [dcl.constexpr] paragraph 5 to diagnose definitions of constexpr functions that can never be used in a constant expression should not apply to an instantiated constexpr function template.
Notes from the August, 2011 meeting:
int f(); // not constexpr
struct A {
int m;
constexpr A(int i = f()) : m(i) { }
};
struct B {
A a;
} b;
This is ill-formed, no diagnostic required, because the defaulted default constructor of B will be declared constexpr but can never be invoked in a constant expression. See issue 1360.
Proposed resolution (February, 2012):
Change 7.1.5 [dcl.constexpr] paragraph 5 as follows:
... —end example]
For a non-template, non-defaulted constexpr function, if no function argument values exist such that the function invocation substitution would produce a constant expression (5.19 [expr.const]), the program is ill-formed; no diagnostic required. For a non-template, non-defaulted, non-inheriting constexpr constructor, if no argument values exist such that after function invocation substitution, every constructor call and full-expression in the mem-initializers would be a constant expression (including conversions), the program is ill-formed; no diagnostic required. For a constexpr function template or member function of a class template, if no instantiation would be well-formed when considered as a non-template constexpr function, the program is ill-formed; no diagnostic required. [Example:...
Delete 7.1.5 [dcl.constexpr] paragraph 6:
If the instantiated template specialization of a constexpr function template or member function of a class template would fail to satisfy the requirements for a constexpr function or constexpr constructor, that specialization is not a constexpr function or constexpr constructor. [Note: If the function is a member function it will still be const as described below. —end note] If no specialization of the template would yield a constexpr function or constexpr constructor, the program is ill-formed; no diagnostic required.
Additional notes, February, 2012:
The proposed resolution inadvertently removes the provision allowing specializations of constexpr templates to violate the requirements of 7.1.5 [dcl.constexpr]. It is being retained in "drafting" status pending additional work.
Proposed resolution (October, 2012):
Change 7.1.5 [dcl.constexpr] paragraphs 5-6 as follows:
...For a non-template, non-defaulted constexpr function, if no function argument values exist such that the function invocation substitution would produce a constant expression (5.19 [expr.const]), the program is ill-formed; no diagnostic required. For a non-template, non-defaulted, non-inheriting constexpr constructor, if no argument values exist such that after function invocation substitution, every constructor call and full-expression in the mem-initializers would be a constant expression (including conversions), the program is ill-formed; no diagnostic required. [Example: ... —end example]
If the instantiated template specialization of a constexpr function template or member function of a class template would fail to satisfy the requirements for a constexpr function or constexpr constructor, that specialization is not still a constexpr function or constexpr constructor, even though a call to such a function cannot appear in a constant expression. [Note: If the function is a member function it will still be const as described below. —end note] If no specialization of the template would yield satisfy the requirements for a constexpr function or constexpr constructor when considered as a non-template function or constructor, the program template is ill-formed; no diagnostic required.
Additional note (January, 2013):
Questions arose in the discussion of issue 1581 as to whether this approach — making the specialization of a constexpr function template or member function of a class template still constexpr but unable to be invoked in a constant context — is correct. The implication is that class types might be categorized as literal but not be able to be instantiated at compile time. This issue is therefore returned to "review" status to allow further consideration of this question.
[Moved to DR at the October, 2012 meeting.]
A constexpr constructor is required to initialize all non-static data members (7.1.5 [dcl.constexpr] paragraph 4), which conflicts with the requirement that a constructor for a union is permitted to initialize only a single non-static data member (12.6.2 [class.base.init] paragraph 8).
Proposed resolution (February, 2012):
Change 7.1.5 [dcl.constexpr] paragraph 4 as follows:
In a definition of a constexpr constructor, each of the parameter types shall be a literal type. In addition, either its function-body shall be = delete or = default or it shall satisfy the following constraints:
...
every non-variant non-static data member and base class sub-object shall be initialized (12.6.2 [class.base.init]);
if the class is a non-empty union, or for each non-empty anonymous union member of a non-union class, exactly one non-static data member shall be initialized;
...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The requirement that a class with a constexpr constructor cannot have a virtual base only applies to constructors with non-deleted and non-defaulted function-bodys. This seems like an oversight.
Proposed resolution (August, 2011):
Change 7.1.5 [dcl.constexpr] paragraph 4 as follows:
In a The definition of a constexpr constructor, each of the parameter types shall be a literal type. In addition, either its function-body shall be = delete or = default or it shall satisfy the following constraints:
the class shall not have any virtual base classes;
each of the parameter types shall be a literal type;
its function-body shall not be a function-try-block;
In addition, either its function-body shall be = delete or it shall satisfy the following constraints:
either its function-body shall be = default or the compound-statement of its function-body shall contain only...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
Function invocation substitution (7.1.5 [dcl.constexpr] paragraph 5) seems underspecified with respect to this.
Proposed resolution (August, 2011):
Change the indicated bullet of 5.19 [expr.const] paragraph 2 as follows:
this (5.1 [expr.prim] 5.1.1 [expr.prim.general]) unless it appears as the postfix-expression in a class member access expression, including the result of the implicit transformation in the body of a non-static member function (9.3.1 [class.mfct.non-static]) [Note: when evaluating a constant expression, function invocation substitution (7.1.5 [dcl.constexpr]) replaces each occurrence of this in a constexpr member function with a pointer to the class object. —end note];
Change 7.1.5 [dcl.constexpr] paragraph 5 as follows (converting the running text into a bulleted list):
Function invocation substitution for a call of a constexpr function or of a constexpr constructor means:
implicitly converting each argument to the corresponding parameter type as if by copy-initialization,91
substituting that converted expression for each use of the corresponding parameter in the function-body,
in a member function, substituting for each use of this (9.3.2 [class.this]) a prvalue pointer whose value is the address of the object for which the member function is called, and
for in a constexpr functions, implicitly converting the resulting returned expression or braced-init-list to the return type of the function as if by copy-initialization.
Such substitution...
This resolution also resolves issues 1264 and 1367.
[Addressed by the adoption of paper N3652 at the April, 2013 meeting.]
One of the examples in 7.1.5 [dcl.constexpr] paragraph 3 reads,
constexpr int prev(int x)
{ return --x; } // error: use of decrement
According to paragraph 5, this ill-formed, no diagnostic required:
For a constexpr function, if no function argument values exist such that the function invocation substitution would produce a constant expression (5.19 [expr.const]), the program is ill-formed; no diagnostic required.
However, the surrounding errors in the example have required diagnostics, potentially leading the reader to the mistaken conclusion that this error must be diagnosed as well. The example should be removed or the comment updated to reflect its true status.
Proposed resolution (June, 2013):
This issue is no longer relevant after the adoption of the changes to constexpr in paper N3652.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The constraints on type-specifiers given in 7.1.6 [dcl.type] paragraphs 2 and 3 (at most one type-specifier except as specified, at least one type-specifier, no redundant cv-qualifiers) are couched in terms of decl-specifier-seqs and declarations. However, they should also apply to constructs that are not syntactically declarations and that are defined to use type-specifier-seqs, including 5.3.4 [expr.new], 6.6 [stmt.jump], 8.1 [dcl.name], and 12.3.2 [class.conv.fct].
Proposed resolution (August, 2011):
Change 7.1.6 [dcl.type] paragraph 3 as follows:
At Except in a declaration of a constructor, destructor, or conversion function, at least one type-specifier that is not a cv-qualifier is required in a declaration unless it declares a constructor, destructor or conversion function shall appear in a complete type-specifier-seq or a complete decl-specifier-seq.92 A type-specifier-seq shall not define...
(Note: paper N2546, voted into the Working Draft in February, 2008, addresses part of this issue.)
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The current wording of 7.1.6.4 [dcl.spec.auto] does not appear to forbid using the auto specifier for both a function declaration with a trailing return type and a variable definition in the same declaration, e.g.,
auto f() -> int, i = 0;
(See also issue 1347.)
Proposed resolution (August, 2011):
Change 7.1.6.4 [dcl.spec.auto] paragraph 7 as follows:
If the list of declarators contains more than one declarator, they shall all form declarations of variables. The the type of each declared variable is determined as described above. If, and if the type deduced for the template parameter U is not the same in each deduction, the program is ill-formed. [Example:...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
7.1.6.4 [dcl.spec.auto] does not address the case when the initializer for an auto variable is a parenthesized expression-list.
Proposed resolution (August, 2011):
Change 7.1.6.4 [dcl.spec.auto] paragraph 3 as follows:
...auto shall appear as one of the decl-specifiers in the decl-specifier-seq and the decl-specifier-seq shall be followed by one or more init-declarators, each of which shall have a non-empty initializer. In an initializer of the form
( expression-list )
the expression-list shall be a single assignment-expression. [Example:...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The intent of 7.1.6.4 [dcl.spec.auto] paragraph 7 appears to have been that the type represented by auto should be the same for each declarator in the declaration. However, the current wording does not achieve that goal. For example, in
auto a = 0, b = { 1, 2, 2 };
the auto specifier represents int in the first declarator and std::initializer_list<int> in the second. (See also issue 1265.)
Proposed resolution (August, 2011):
Move the example in 7.1.6.4 [dcl.spec.auto] paragraph 7 into that of paragraph 6 and change paragraph 7 as follows:
...[Example:
auto x1 = { 1, 2 }; // decltype(x1) is std::initializer_list<int> auto x2 = { 1, 2.0 }; // error: cannot deduce element type const auto &i = expr;The type of i is the deduced type of the parameter u in the call f(expr) of the following invented function template:
template <class U> void f(const U& u);
—end example]
If the list of declarators init-declarator-list contains more than one declarator init-declarator, the type of each declared variable is determined as described above. If the type deduced for the template parameter U that replaces the occurrence of auto is not the same in each deduction, the program is ill-formed.
[Example:
const auto &i = expr;
The type of i is the deduced type of the parameter u in the call f(expr) of the following invented function template:
template <class U> void f(const U& u); auto x = 5, *y = &x; // OK: auto is int auto a = 5, b = { 1, 2 }; // error: different types for auto—end example]
In an example like
const auto x = 3;
the intent, clearly, is to make const int the type of x. It is not clear, however, that the current wording accomplishes this. Because the deduction is based on that of function calls, and because top-level cv-qualifiers are ignored in such deduction, it appears that 7.1.6.4 [dcl.spec.auto] paragraph 6,
The type deduced for the variable d is then the deduced A determined using the rules of template argument deduction from a function call (14.8.2.1 [temp.deduct.call]), where P is a function template parameter type and the initializer for d is the corresponding argument.
incorrectly gives x the type int.
Proposed resolution (April, 2013):
This issue is resolved by the wording changes in N3638, adopted at the April, 2013 (Bristol) meeting.
[Moved to DR at the April, 2013 meeting.]
There is disagreement among implementations as to when an enumeration type is complete. For example,
enum E { e = E() };
is rejected by some and accepted by another. The Standard does not appear to resolve this question definitively.
See also issue 1482.
Proposed resolution (December, 2012):
This issue is resolved by the resolution of issue 1482.
[Moved to DR at the October, 2012 meeting.]
According to 7.3.1.2 [namespace.memdef] paragraph 3,
If a friend declaration in a non-local class first declares a class or function95 the friend class or function is a member of the innermost enclosing namespace. The name of the friend is not found by unqualified lookup (3.4.1 [basic.lookup.unqual]) or by qualified lookup (3.4.3 [basic.lookup.qual]) until a matching declaration is provided in that namespace scope (either before or after the class definition granting friendship).
This wording does not, but probably should, apply to friend declarations of function templates and class templates as well.
Proposed resolution (February, 2012):
Change 3.4.2 [basic.lookup.argdep] paragraph 1 as follows:
...in those namespaces, namespace-scope friend function or function template declarations (11.3) not otherwise visible may be found...
Change 7.3.1.2 [namespace.memdef] paragraph 3 as follows:
Every name first declared in a namespace is a member of that namespace. If a friend declaration in a non-local class first declares a class, or function, class template, or function template95 the friend class or function is a member of the innermost enclosing namespace. The name of the friend is not found by unqualified lookup (3.4.1 [basic.lookup.unqual]) or by qualified lookup (3.4.3 [basic.lookup.qual]) until a matching declaration is provided in that namespace scope (either before or after the class definition granting friendship). If a friend function or function template is called, its name may be found by the name lookup that considers functions from namespaces and classes associated with the types of the function arguments (3.4.2 [basic.lookup.argdep]). If the name in a friend declaration...
[Moved to DR at the April, 2013 meeting.]
According to 7.3.1.2 [namespace.memdef] paragraph 3,
Every name first declared in a namespace is a member of that namespace. If a friend declaration in a non-local class first declares a class or function95 the friend class or function is a member of the innermost enclosing namespace. The name of the friend is not found by unqualified lookup (3.4.1 [basic.lookup.unqual]) or by qualified lookup (3.4.3 [basic.lookup.qual]) until a matching declaration is provided in that namespace scope (either before or after the class definition granting friendship).
Taken literally, that would mean the following example is ill-formed:
namespace N { struct A { friend int f(); }; } int N::f() { return 0; } int i = N::f(); // ill-formed: N::f not found
because the definition of N::f appears in global scope rather than in namespace scope.
Proposed resolution (October, 2012):
Change 7.3.1.2 [namespace.memdef] paragraph 3 as follows:
Every name first declared in a namespace is a member of that namespace. If a friend declaration in a non-local class first declares a class or function95 the friend class or function is a member of the innermost enclosing namespace. The name of the friend is not found by The friend declaration does not by itself make the name visible to unqualified lookup (3.4.1 [basic.lookup.unqual]) or by qualified lookup (3.4.3 [basic.lookup.qual]). [Note: The name of the friend will be visible in its namespace if until a matching declaration is provided in that at namespace scope (either before or after the class definition granting friendship). —end note] If a friend function is called...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The Standard does not appear to specify what happens for code like the following:
namespace one { template<typename T> void fun(T); } using one::fun; template<typename T> void fun(T);
7.3.3 [namespace.udecl] paragraph 13 does not appear to apply because it deals only with functions, not function templates:
If a function declaration in namespace scope or block scope has the same name and the same parameter types as a function introduced by a using-declaration, and the declarations do not declare the same function, the program is ill-formed.
John Spicer: For function templates I believe the rule should be that if they have the same function type (parameter types and return type) and have identical template parameter lists, the program is ill-formed.
Proposed resolution (August, 2011):
Change 7.3.3 [namespace.udecl] paragraph 14 as follows:
If a function declaration in namespace scope or block scope has the same name and the same parameter types parameter-type-list (8.3.5 [dcl.fct]) as a function introduced by a using-declaration, and the declarations do not declare the same function, the program is ill-formed. If a function template declaration in namespace scope has the same name, parameter-type-list, return type, and template parameter list as a function template introduced by a using-declaration, the program is ill-formed. [Note: Two using-declarations may introduce functions with the same name and the same parameter types parameter-type-list. If, for a call to an unqualified function name, function overload resolution selects the functions introduced by such using-declarations, the function call is ill-formed. [Example:...
[Moved to DR at the April, 2013 meeting.]
The example in 7.6.4 [dcl.attr.depend] paragraph 4 reads,
/* Translation unit A. */ struct foo { int* a; int* b; }; std::atomic<struct foo *> foo_head[10]; int foo_array[10][10]; [[carries_dependency]] struct foo* f(int i) { return foo_head[i].load(memory_order_consume); } [[carries_dependency]] int g(int* x, int* y) { return kill_dependency(foo_array[*x][*y]); } /* Translation unit B. */ [[carries_dependency]] struct foo* f(int i); [[carries_dependency]] int* g(int* x, int* y); int c = 3; void h(int i) { struct foo* p; p = f(i); do_something_with(g(&c, p->a)); do_something_with(g(p->a, &c)); }
There appear to be two errors in this example. First, g is declared as returning int in TU A but int* in TU B. Second, paragraph 6 says,
Function g's second argument has a carries_dependency attribute
but that is not reflected in the example.
Proposed resolution (August, 2012):
Change the example in 7.6.4 [dcl.attr.depend] paragraph 4 as follows:
/* Translation unit A. */ struct foo { int* a; int* b; }; std::atomic<struct foo *> foo_head[10]; int foo_array[10][10]; [[carries_dependency]] struct foo* f(int i) { return foo_head[i].load(memory_order_consume); } [[carries_dependency]] int g(int* x, int* y [[carries_dependency]]) { return kill_dependency(foo_array[*x][*y]); } /* Translation unit B. */ [[carries_dependency]] struct foo* f(int i); [[carries_dependency]] int* int g(int* x, int* y [[carries_dependency]]); int c = 3; void h(int i) { struct foo* p; p = f(i); do_something_with(g(&c, p->a)); do_something_with(g(p->a, &c)); }
Change 7.6.4 [dcl.attr.depend] paragraph 6 as follows:
Function g's second argument parameter has a carries_dependency attribute, but its first argument parameter does not. Therefore, function h's first call to g carries a dependency into g, but its second call does not. The implementation might need to insert a fence prior to the second call to g.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
There is a contradiction between the grammar of 8 [dcl.decl] paragraph 4 and that of 8.3.5 [dcl.fct] paragraphs 1 and 2 and 8.4.1 [dcl.fct.def.general] paragraph 2 regarding the placement of the optional exception-specification: in the former, it immediately follows the parameter-declaration-clause, while in the latter it follows the exception-specification.
Proposed resolution (August, 2011):
Change the grammar in 8 [dcl.decl] paragraph 4 as follows:
Change the grammar snippet in 13.3.1.1.2 [over.call.object] paragraph 2 as follows:
[Moved to DR at the October, 2012 meeting.]
Issue 147 changed the name lookup rules so that a lookup that would have found the injected-class-name of a class will refer to the constructor. However, there still appear to be vestiges of the earlier specification that were not removed by the resolution. For example, the grammar in 8 [dcl.decl] paragraph 4 contains,
It would seem that there is no longer any need for the second line, since a lookup for a declarator-id will not produce a class-name. Similarly, 5.1.1 [expr.prim.general] paragraph 8 still contains the sentence,
Where class-name :: class-name is used, and the two class-names refer to the same class, this notation names the constructor (12.1 [class.ctor]).
Proposed resolution (February, 2012):
Change 8 [dcl.decl] paragraph 4 as follows:
...
declarator-id:...opt id-expression
nested-name-specifieropt class-nameA class-name has special meaning in a declaration of the class of that name and when qualified by that name using the scope resolution operator :: (, 12.1 [class.ctor], 12.4 [class.dtor]).
Change 5.1.1 [expr.prim.general] paragraph 8 as follows:
...[Note: a class member can be referred to using a qualified-id at any point in its potential scope (3.3.7 [basic.scope.class]). —end note] Where class-name :: class-name is used, and the two class-names refer to the same class, this notation names the constructor (12.1 [class.ctor]). Where class-name ::~ class-name is used...
[Moved to DR at the April, 2013 meeting.]
There is no restriction against repeated cv-qualifiers in declarators, although there is (in 7.1.6 [dcl.type] paragraph 2) for those appearing in a decl-specifier-seq. However, most or all current implementations reject such code. Is a change needed?
Proposed resolution (October, 2012):
Change 7.1.6.1 [dcl.type.cv] paragraph 1 as follows:
There are two cv-qualifiers, const and volatile. Each cv-qualifier shall appear at most once in a cv-qualifier-seq. If a cv-qualifier appears in a decl-specifier-seq, the init-declarator-list of the declaration shall not be empty. [Note:...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to 8.3 [dcl.meaning] paragraph 1,
A declarator-id shall not be qualified except for the definition of a member function (9.3 [class.mfct]) or static data member (9.4 [class.static]) outside of its class, the definition or explicit instantiation of a function or variable member of a namespace outside of its namespace, or the definition of a previously declared explicit specialization outside of its namespace, or the declaration of a friend function that is a member of another class or namespace (11.3 [class.friend]). When the declarator-id is qualified, the declaration shall refer to a previously declared member of the class or namespace to which the qualifier refers...
This restriction prohibits examples like the following:
void f(); void ::f(); // error: qualified declarator namespace N { void f(); void N::f() { } // error: qualified declarator }
There doesn't seem to be any good reason for disallowing such declarations, and a number of implementations accept them in spite of the Standard's prohibition. Should the Standard be changed to allow them?
Notes from the April, 2006 meeting:
In discussing issue 548, the CWG agreed that the prohibition of qualified declarators inside their namespace should be removed.
Proposed resolution (October, 2006):
Remove the indicated words from 8.3 [dcl.meaning] paragraph 1:
...An unqualified-id occurring in a declarator-id shall be a simple identifier except for the declaration of some special functions (12.3 [class.conv], 12.4 [class.dtor], 13.5 [over.oper]) and for the declaration of template specializations or partial specializations (). A declarator-id shall not be qualified except for the definition of a member function (9.3 [class.mfct]) or static data member (9.4 [class.static]) outside of its class, the definition or explicit instantiation of a function or variable member of a namespace outside of its namespace, or the definition of a previously declared explicit specialization outside of its namespace, or the declaration of a friend function that is a member of another class or namespace (11.3 [class.friend]). When the declarator-id is qualified, the declaration shall refer to a previously declared member of the class or namespace to which the qualifier refers, and the member shall not have been introduced by a using-declaration in the scope of the class or namespace nominated by the nested-name-specifier of the declarator-id...
[Drafting note: The omission of “outside of its class” here does not give permission for redeclaration of class members; that is still prohibited by 9.2 [class.mem] paragraph 1. The removal of the enumeration of the kinds of declarations in which a qualified-id can appear does allow a typedef declaration to use a qualified-id, which was not permitted before; if that is undesirable, the prohibition can be reinstated here.]
[Moved to DR at the April, 2013 meeting.]
The status of a declaration like the following is unclear:
template<typename T> struct A { A<T>(); };
8.3 [dcl.meaning] paragraph 1 appears to say that it is not allowed, but it is not clear.
Proposed resolution (October, 2012):
Change 8.3 [dcl.meaning] paragraph 1 as follows:
A list of declarators appears after an optional (Clause 7 [dcl.dcl]) decl-specifier-seq (7.1 [dcl.spec]). Each declarator contains exactly one declarator-id; it names the identifier that is declared. An unqualified-id occurring in a declarator-id shall be a simple identifier except for the declaration of some special functions (12.1 [class.ctor], 12.3 [class.conv], 12.4 [class.dtor], 13.5 [over.oper]) and for the declaration of template specializations or partial specializations (14.7 [temp.spec]). A declarator-id shall not...
Change 12.1 [class.ctor] paragraph 1 as follows:
Constructors do not have names. A special declarator syntax is used to declare or define the constructor. The syntax uses:
an optional decl-specifier-seq in which each decl-specifier is either a function-specifier or constexpr,
the constructor's class name, and
a parameter list
in that order. In such a declaration, optional parentheses around the constructor class name are ignored. A declaration of a constructor uses a function declarator (8.3.5 [dcl.fct]) of the form
ptr-declarator ( parameter-declaration-clause ) exception-specificationopt attribute-specifier-seqopt
where the ptr-declarator consists solely of an id-expression, an optional attribute-specifier-seq, and optional surrounding parentheses, and the id-expression has one of the following forms:
in a member-declaration that belongs to the member-specification of a class but is not a friend declaration (11.3 [class.friend]), the id-expression is the injected-class-name (Clause 9 [class]) of the immediately-enclosing class;
in a member-declaration that belongs to the member-specification of a class template but is not a friend declaration, the id-expression is a class-name that names the current instantiation (14.6.2.1 [temp.dep.type]) of the immediately-enclosing class template; or
in a declaration at namespace scope or in a friend declaration, the id-expression is a qualified-id that names a constructor (3.4.3.1 [class.qual]).
The class-name shall not be a typedef-name. In a constructor declaration, each decl-specifier in the optional decl-specifier-seq shall be friend, inline, explicit, or constexpr. [Example:...
Delete 12.1 [class.ctor] paragraph 3:
A typedef-name shall not be used as the class-name in the declarator-id for a constructor declaration.
Change 12.1 [class.ctor] paragraph 4 as follows:
A constructor shall not be virtual (10.3 [class.virtual]) or static (9.4 [class.static]). A constructor can be invoked for a const, volatile or const volatile object. A constructor shall not be declared const, volatile, or const volatile (9.3.2 [class.this]). const and volatile semantics (7.1.6.1 [dcl.type.cv]) are not applied on an object under construction. They come into effect when the constructor for the most derived object (1.8 [intro.object]) ends. A constructor shall not be declared with a ref-qualifier.
Change 12.1 [class.ctor] paragraph 9 as follows:
No return type (not even void) shall be specified for a constructor. A return statement in the body of a constructor shall not specify a return value. The address of a constructor shall not be taken.
Change 12.4 [class.dtor] paragraphs 1-2 as follows:
A special declarator syntax using an optional function-specifier (7.1.2 [dcl.fct.spec]) followed by ~ followed by the destructor's class name followed by an empty parameter list is used to declare the destructor in a class definition. In such a declaration, the ~ followed by the destructor's class name can be enclosed in optional parentheses; such parentheses are ignored. A typedef-name shall not be used as the class-name following the ~ in the declarator for a destructor declaration. A declaration of a destructor uses a function declarator (8.3.5 [dcl.fct]) of the form
ptr-declarator ( parameter-declaration-clause ) exception-specificationopt attribute-specifier-seqopt
where the ptr-declarator consists solely of an id-expression, an optional attribute-specifier-seq, and optional surrounding parentheses, and the id-expression has one of the following forms:
in a member-declaration that belongs to the member-specification of a class but is not a friend declaration (11.3 [class.friend]), the id-expression is ~class-name and the class-name is the injected-class-name (Clause 9 [class]) of the immediately-enclosing class;
in a member-declaration that belongs to the member-specification of a class template but is not a friend declaration, the id-expression is ~class-name and the class-name names the current instantiation (14.6.2.1 [temp.dep.type]) of the immediately-enclosing class template; or
in a declaration at namespace scope or in a friend declaration, the id-expression is nested-name-specifier ~class-name and the class-name names the same class as the nested-name-specifier.
The class-name shall not be a typedef-name. A destructor shall take no arguments (8.3.5 [dcl.fct]). In a destructor declaration, each decl-specifier of the optional decl-specifier-seq shall be friend, inline, or virtual.
A destructor is used to destroy objects of its class type. A destructor takes no parameters, and no return type can be specified for it (not even void). The address of a destructor shall not be taken. A destructor shall not be static. A destructor can be invoked for a const, volatile or const volatile object. A destructor shall not be declared const, volatile or const volatile (9.3.2 [class.this]). const and volatile semantics (
7.1.6.1 [dcl.type.cv] ) are not applied on an object under destruction. They stop being in effect when the destructor for the most derived object (1.8 [intro.object]) starts. A destructor shall not be declared with a ref-qualifier.
This resolution also resolves issue 344.
[Moved to DR at the April, 2013 meeting.]
According to 8.3.2 [dcl.ref] paragraph 1,
Cv-qualified references are ill-formed except when the cv-qualifiers are introduced through the use of a typedef (7.1.3 [dcl.typedef]) or of a template type argument (14.3 [temp.arg]), in which case the cv-qualifiers are ignored.
There does not appear to be a good reason not to extend this to apply to apply to decltype, as well.
Proposed resolution (October, 2012):
Change 8.3.2 [dcl.ref] paragraph 1 as follows:
...Cv-qualified references are ill-formed except when the cv-qualifiers are introduced through the use of a typedef typedef-name (7.1.3 [dcl.typedef], 14.1 [temp.param]) or of a template type argument (14.3 [temp.arg]) decltype-specifier (7.1.6.2 [dcl.type.simple]), in which case the cv-qualifiers are ignored. [Example:...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
8.3.5 [dcl.fct]/2 restricts the use of void as parameter type, but does not mention CV qualified versions. Since void f(volatile void) isn't a callable function anyway, 8.3.5 [dcl.fct] should also ban cv-qualified versions. (BTW, this follows C)
Suggested resolution:
A possible resolution would be to add (cv-qualified) before void in
The parameter list (void) is equivalent to the empty parameter list. Except for this special case, (cv-qualified) void shall not be a parameter type (though types derived from void, such as void*, can).
Proposed resolution (August, 2011):
This issue is resolved by the resolution of issue 577.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
8.3.5 [dcl.fct] paragraph 2 says,
The parameter list (void) is equivalent to the empty parameter list.
This special case is intended for C compatibility, but C99 describes it differently (6.7.5.3 paragraph 10):
The special case of an unnamed parameter of type void as the only item in the list specifies that the function has no parameters.
The C99 formulation allows typedefs for void, while C++ (and C90) accept only the keyword itself in this role. Should the C99 approach be adopted?
Notes from the October, 2006 meeting:
The CWG did not take a formal position on this issue; however, there was some concern expressed over the treatment of function templates and member functions of class templates if the C++ rule were changed: for a template parameter T, would a function taking a single parameter of type T become a no-parameter function if it were instantiated with T = void?
Proposed resolution (August, 2011):
Change 8.3.5 [dcl.fct] paragraph 4 as follows:
...If the parameter-declaration-clause is empty, the function takes no arguments. The parameter list (void) A parameter list consisting of a single unnamed parameter of non-dependent type void is equivalent to the an empty parameter list. Except for this special case, a parameter shall not have type cv void shall not be a parameter type (though types derived from void, such as void*, can). If the parameter-declaration-clause terminates...
This resolution also resolves issue 332.
[Moved to DR at the October, 2012 meeting.]
Although 8.3.5 [dcl.fct] paragraph 9 forbids defining a type in a parameter declaration, and a template parameter declaration is syntactically a parameter-declaration, the context in 8.3.5 [dcl.fct] function declarators. It's therefore not completely clear that that prohibition applies to template parameter declarations as well. This should be clarified.
Proposed resolution (February, 2012):
Change 14.1 [temp.param] paragraph 2 as follows:
...A storage class shall not be specified in a template-parameter declaration. Types shall not be defined in a template-parameter declaration. [Note:...
[Moved to DR at the October, 2012 meeting.]
Currently, 8.3.5 [dcl.fct] paragraph 9 requires that
The type of a parameter or the return type for a function definition shall not be an incomplete class type (possibly cv-qualified) unless the function definition is nested within the member-specification for that class (including definitions in nested classes defined within the class).
There is no reason for this requirement for a function with a deleted definition, and it would be useful to relax this prohibition in such cases.
Proposed resolution (February, 2012):
Change 8.3.5 [dcl.fct] paragraph 9 as follows:
Types shall not be defined in return or parameter types. The type of a parameter or the return type for a function definition shall not be an incomplete class type (possibly cv-qualified) unless the function is deleted (8.4.3 [dcl.fct.def.delete]) or the definition is nested within the member-specification for that class (including definitions in nested classes defined within the class).
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to the new wording of 8.3.6 [dcl.fct.default] paragraph 5,
A default argument is implicitly converted (Clause 4 [conv]) to the parameter type.
This is incorrect when the default argument is a braced-init-list. That sentence doesn't seem to be necessary, but if it is kept, it should be recast in terms of initialization rather than conversion.
Proposed resolution (August, 2011):
Delete the first sentence of 8.3.6 [dcl.fct.default] paragraph 5:
A default argument is implicitly converted (Clause 4 [conv]) to the parameter type.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The grammar for defaulted and deleted functions in 8.4.2 [dcl.fct.def.default] and 8.4.3 [dcl.fct.def.delete] does not provide for virt-specifiers. Is there a reason for this omission, or was it inadvertent?
Proposed resolution (August, 2011):
Change 8.4.2 [dcl.fct.def.default] paragraph 1 as follows:
A function definition of the form:
attribute-specifier-seqopt decl-specifier-seqopt declarator virt-specifier-seqopt = default ;
is called an explicitly-defaulted definition...
Change 8.4.3 [dcl.fct.def.delete] paragraph 1 as follows:
A function definition of the form:
attribute-specifier-seqopt decl-specifier-seqopt declarator virt-specifier-seqopt = delete ;
is called a deleted definition...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
Paragraph 1 of 8.4.2 [dcl.fct.def.default] allows an explicitly-defaulted copy constructor or copy assignment operator to have a parameter type that is a reference to non-const, even if the corresponding implicitly-declared function would have a reference to const. However, paragraph 2 says that a copy constructor or copy assignment operator that is defaulted on its first declaration, the parameter type must be exactly the same. Is there a good reason for the stricter rule for a function that is defaulted on its first declaration?
Proposed resolution (August, 2011):
Change 8.4.2 [dcl.fct.def.default] paragraph 2 as follows:
...If a function is explicitly defaulted on its first declaration,
...
in the case of a copy constructor, move constructor, copy assignment operator, or move assignment operator, it shall have the same parameter type as if it had been implicitly declared.
Change 12.8 [class.copy] paragraph 12 as follows:
A copy/move constructor for class X is trivial if it is not user-provided, its declared parameter type is the same as if it had been implicitly declared, and if...
Change 12.8 [class.copy] paragraph 25 as follows:
A copy/move assignment operator for class X is trivial if it is not user-provided, its declared parameter type is the same as if it had been implicitly declared, and if...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The definition of “user-provided” given in 8.4.2 [dcl.fct.def.default] paragraph 4 applies only to special member functions, while the definition of an aggregate in 8.5.1 [dcl.init.aggr] paragraph 1 relies on that term in identifying constructors that make a class a non-aggregate. As a result, a class with a non-special constructor is considered an aggregate.
Proposed resolution (August, 2011):
Change 8.4.2 [dcl.fct.def.default] paragraph 4 as follows:
A special member function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration...
[Drafting note: This makes a class with only a deleted initializer-list constructor an aggregate.]
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
8.5 [dcl.init] paragraph 7 only describes how to initialize objects:
To value-initialize an object of type T means:
However, 5.2.3 [expr.type.conv] paragraph 2 calls for value-initializing prvalues, which in the case of scalar types are not objects:
The expression T(), where T is a simple-type-specifier or typename-specifier for a non-array complete object type or the (possibly cv-qualified) void type, creates a prvalue of the specified type, which is value-initialized (8.5 [dcl.init]; no initialization is done for the void() case).
Proposed resolution (August, 2011):
Change 5.2.3 [expr.type.conv] paragraph 2 as follows:
The expression T(), where T is a simple-type-specifier or typename-specifier for a non-array complete object type or the (possibly cv-qualified) void type, creates a prvalue of the specified type,which is value-initialized (8.5 [dcl.init] type, whose value is that produced by value-initializing (8.5 [dcl.init]) an object of type T; no initialization is done for the void() case). [Note:...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to 8.5 [dcl.init] paragraph 7,
To value-initialize an object of type T means:
if T is a (possibly cv-qualified) class type (Clause 9 [class]) with a user-provided constructor (12.1 [class.ctor]), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
if T is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object is zero-initialized and, if T's implicitly-declared default constructor is non-trivial, that constructor is called.
if T is an array type, then each element is value-initialized;
otherwise, the object is zero-initialized.
This suggests that for
struct A { A() = delete; }; union B { A a }; int main() { B(); }
a B temporary is created and zero-initialized, even though its default constructor is deleted. We should strike "non-union" and also the "if...non-trivial" condition, since we can have a trivial deleted constructor.
Proposed resolution (August, 2011):
Change 8.5 [dcl.init] paragraph 7 as follows:
To value-initialize an object of type T means:
if T is a (possibly cv-qualified) class type (Clause 9 [class]) with either no default constructor (12.1 [class.ctor]) or a default constructor that is user-provided or deleted constructor (12.1 [class.ctor]), then the object is default-initialized default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
if T is a (possibly cv-qualified) non-union class type without a user-provided or deleted default constructor, then the object is zero-initialized and, if T's implicitly-declared default constructor is T has a non-trivial default constructor, that constructor is called. default-initialized;
...
Change 8.5.4 [dcl.init.list] paragraph 3 as follows:
List-initialization of an object or reference of type T is defined as follows:
If the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
Otherwise, if If T is an aggregate, aggregate initialization is performed (8.5.1 [dcl.init.aggr]). [Example:...
Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
...
This resolution also resolves issues 1324 and 1368.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
One would expect that an example like
struct B { B(const B&) = default; }; B b{};
would invoke value-initialization, but (because it does not have a default constructor), the logic ladder of 8.5.4 [dcl.init.list] paragraph 3 makes it aggregate initialization instead:
If the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
Otherwise, if T is an aggregate, aggregate initialization is performed (8.5.1 [dcl.init.aggr]).
Proposed resolution (August, 2011):
This issue is resolved by the resolution of issue 1301.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to the current rules of 8.5 [dcl.init], given a class like
struct A { int i; A() = default; A(int i): i(i) { } };
value-initialization leaves A::i uninitialized. This seems like an oversight.
Proposed resolution (August, 2011):
This issue is resolved by the resolution of issue 1301.
[Moved to DR at the April, 2013 meeting.]
According to 8.5 [dcl.init] paragraph 7,
To value-initialize an object of type T means:
if T is a (possibly cv-qualified) class type (Clause 9 [class]) with either no default constructor (12.1 [class.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
if T is a (possibly cv-qualified) non-union class type without a user-provided or deleted default constructor, then the object is zero-initialized and, if T has a non-trivial default constructor, default-initialized;
if T is an array type, then each element is value-initialized;
otherwise, the object is zero-initialized.
In an example like
union U { int a = 5; }; int main() { return U().a; }
this means that the value returned is 0, because none of the first three bullets apply. Should the “non-union” restriction be dropped from the second bullet?
Proposed resolution (October, 2012):
Change 8.5 [dcl.init] paragraph 7 as follows:
To value-initialize an object of type T means:
...
if T is a (possibly cv-qualified) non-union class type without a user-provided or deleted default constructor, then the object is zero-initialized and, if T has a non-trivial default constructor, default-initialized;
...
[Moved to DR at the April, 2013 meeting.]
According to 8.5 [dcl.init] paragraph 7, a trivial default constructor is not used in value initialization, so the following example would appear to be well-formed:
struct A { private: A() = default; }; int main() { A(); }
Proposed resolution (February, 2013):
Change 8.5 [dcl.init] paragraph 7 as follows:
To default-initialize an object of type T means:
if T is a (possibly cv-qualified) class type (Clause 9 [class]), the default constructor (12.1 [class.ctor]) for T is called (and the initialization is ill-formed if T has no accessible default constructor default constructor or overload resolution (13.3 [over.match]) results in an ambiguity or in a function that is deleted or inaccessible from the context of the initialization);
...
Change 8.5 [dcl.init] paragraph 8 as follows:
To value-initialize an object of type T means:
...
if T is a (possibly cv-qualified) non-union class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;
...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
Consider the following example:
struct X { unsigned bitfield : 4; }; int main() { X x = { 1 }; unsigned const &ref = static_cast<X &&>(x).bitfield; }
According to 8.5.3 [dcl.init.ref] paragraph 5, ref is bound to the bit-field xvalue.
Proposed resolution (August, 2011):
Change the indicated sub-bullet of 8.5.3 [dcl.init.ref] paragraph 5 as follows:
is an xvalue (but is not a bit-field), class prvalue, array prvalue or function lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or
[Moved to DR at the April, 2013 meeting.]
According to 13.3.1.6 [over.match.ref] paragraph 1, the determination of the candidate functions is based on whether 8.5.3 [dcl.init.ref] requires an lvalue result or an rvalue result. It is not sufficiently clear exactly what this means, particularly with respect to function lvalues and rvalues.
Proposed resolution (August, 2011):
Change 13.3.1.6 [over.match.ref] paragraph 1 as follows:
The conversion functions of S and its base classes are considered, except that for copy-initialization, only the non-explicit conversion functions are considered. Those that are not hidden within S and yield type “lvalue reference to cv2 T2” (when 8.5.3 [dcl.init.ref] requires an lvalue result initializing an lvalue reference or an rvalue reference to function) or “cv2 T2” or “rvalue reference to cv2 T2” (when 8.5.3 [dcl.init.ref] requires an rvalue result initializing an rvalue reference or an lvalue reference to function), where “cv1 T” is reference-compatible (8.5.3 [dcl.init.ref]) with “cv2 T2”, are candidate functions.
Change 13.3.3 [over.match.best] paragraph 1 as follows:
...
the context is an initialization by user-defined conversion... or if not that,
the context is an initialization by conversion function for direct reference binding (13.3.1.6 [over.match.ref]) of a reference to function type, the return type of F1 is the same kind of reference (i.e. lvalue or rvalue) as the reference being initialized, and the return type of F2 is not [Example:
template <class T> struct A {
operator T&(); // #1
operator T&&(); // #2
};
typedef int Fn();
A<Fn> a;
Fn& lf = a; // calls #1
Fn&& rf = a; // calls #2
—end example] or, if not that,
F1 is a non-template function...
[Moved to DR at the October, 2012 meeting.]
The definition of reference-compatible types in 8.5.3 [dcl.init.ref] paragraph 4 allows the types to differ in top-level cv-qualification, but it does not encompass the deeper added cv-qualification permitted for “similar types” (4.4 [conv.qual]). This seems surprising and could lead to errors resulting from the fact that the reference will be bound to a temporary and not to the original object in the initializer.
Proposed resolution (February, 2012):
Change 8.5.3 [dcl.init.ref] paragraph 4 as follows:
Given types “cv1 T1” and “cv2 T2,” “cv1 T1” is reference-related to “cv2 T2” if T1 is the same type as T2, or T1 is a base class of T2. “cv1 T1” is reference-compatible with “cv2 T2” if T1 is reference-related to T2 and cv1 is the same cv-qualification as, or greater cv-qualification than, cv2. For purposes of overload resolution, cases for which cv1 is greater cv-qualification than cv2 are identified as reference-compatible with added qualification (see 13.3.3.2 [over.ics.rank]). In all cases...
Delete 13.3.3.1.4 [over.ics.ref] paragraph 5:
The binding of a reference to an expression that is reference-compatible with added qualification influences the rank of a standard conversion; see 13.3.3.2 [over.ics.rank] and 8.5.3 [dcl.init.ref].
[Drafting note: CWG decided not to make a substantive change for this issue, but the investigation discovered that the term defined by these two citations is not actually used and could be removed.]
[Moved to DR at the October, 2012 meeting.]
Issue 1232 extended the language to allow creation of array temporaries using initializer lists. However, such initializer lists must be “completely braced;” the elision of braces described in 8.5.1 [dcl.init.aggr] paragraph 11 applies only
In a declaration of the form
T x = { a };
This restriction prevents plausible uses like
array<int, 3> f() { return { 1, 2, 3 }; }
Proposed resolution (February, 2012):
Change 8.5.1 [dcl.init.aggr] paragraph 11 as follows:
In a declaration of the form
T x = { a };
braces Braces can be elided in an initializer-list as follows. [Footnote: Braces cannot be elided in other uses of list-initialization. —end footnote]
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
One might expect that in an example like
int i; int & ir{i};
ir would bind directly to i. However, according to 8.5.4 [dcl.init.list] paragraph 3, this example creates a temporary of type int and binds the reference to that temporary:
...
Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary...
Otherwise, if the initializer list has a single element, the object or reference is initialized from that element...
Also, the “or reference” in the last bullet is dead code, as a reference initialization is always handled by the preceding bullet.
Proposed resolution (August, 2011):
Change 8.5.4 [dcl.init.list] paragraph 3 as follows:
...
Otherwise, if T is a class type, constructors are considered...
Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary. [Note: As usual, the binding will fail and the program is ill-formed if the reference type is an lvalue reference to a non-const type. —end note] [Example: ... —end example]
Otherwise, if the initializer list has a single element of type E and either T is not a reference type or its referenced type is reference-related to E, the object or reference is initialized from that element; if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed. [Example:...
Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary. [Note: As usual, the binding will fail and the program is ill-formed if the reference type is an lvalue reference to a non-const type. —end note] [Example: ... —end example]
Otherwise, if the initializer list has no elements...
...
[Moved to DR at the October, 2012 meeting.]
A question has arisen over expected behavior when an initializer_list is a non-static data member of a class. Initialization of an initializer_list is defined in terms of construction from an implicitly allocated array whose lifetime "is the same as that of the initializer_list object". That would mean that the array needs to live as long as the initializer_list does, which would on the face of it appear to require the array to be stored in something like a std::unique_ptr<T[]> within the same class (if the member is initialized in this manner).
It would be surprising if that was the intent, but it would make initializer_list usable in this context.
It would also be reasonable if this behaved similarly to binding temporaries to reference members (i.e., "temporary bound to a reference member in a constructor's ctor-initializer (12.6.2 [class.base.init]) persists until the constructor exits."), though this approach would probably prevent use of an initializer_list member in that context.
Proposed resolution (February, 2012):
Change 8.5.4 [dcl.init.list] paragraphs 5-6 as follows:
An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation allocated an a temporary array of N elements of type E, where...
The lifetime of the array is the same as that of the initializer_list object. The array has the same lifetime as any other temporary object (12.2 [class.temporary]), except that initializing an initializer_list object from the array extends the lifetime of the array exactly like binding a reference to a temporary. [Example:
typedef std::complex<double> cmplx; std::vector<cmplx> v1 = { 1, 2, 3 }; void f() { std::vector<cmplx> v2{ 1, 2, 3 }; std::initializer_list<int> i3 = { 1, 2, 3 }; } struct A { std::initializer_list<int> i4; A(): i4{1,2,3} { } // creates an A with a dangling reference };
For v1 and v2, the initializer_list object is a parameter in a function call, so the and array created for { 1, 2, 3 } have has full-expression lifetime. For i3, the initializer_list object is a variable, so the and array have automatic persists for the lifetime of the variable. For i4, the initializer_list object is initialized in a constructor's ctor-initializer, so the array persists only until the constructor exits, and so any use of the elements of i4 after the constructor exits produces undefined behavior. —end example] [Note: The implementation is free to allocate the array in read-only memory if an explicit array with the same initializer could be so allocated. —end note]
Change 12.2 [class.temporary] paragraph 5 as follows:
The second context is when a reference is bound to a temporary. [Footnote: The same rules apply to initialization of an initializer_list object (8.5.4 [dcl.init.list]) with its underlying temporary array. —end footnote] The temporary to which...
[Moved to DR at the October, 2012 meeting.]
According to 8.5.4 [dcl.init.list] paragraph 5, the elements of the backing array for an object of type std::initializer_list<E> are of type E. This is contradicted by the wording of 18.9 [support.initlist] paragraph 2.
Proposed resolution (February, 2012):
Change 8.5.4 [dcl.init.list] paragraph 5 as follows:
An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation allocated an array of N elements of type const E, where N is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list, and the std::initializer_list<E> object is constructed to refer to that array. If a narrowing conversion is required to initialize any of the elements, the program is ill-formed. [Example:
struct X { X(std::initializer_list<double> v); }; X x{ 1,2,3 };The initialization will be implemented in a way roughly equivalent to this:
const double __a[3] = {double{1}, double{2}, double{3}}; X x(std::initializer_list<double>(__a, __a+3));
assuming that the implementation can construct an initializer_list object with a pair of pointers. —end example]
[Moved to DR at the October, 2012 meeting.]
According to 8.5.4 [dcl.init.list] paragraph 7, an implicit conversion
from an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted back to the original type.
As is made plain in the examples in that paragraph, a conversion of a negative value to an unsigned type is intended to be a narrowing conversion; however, the phrase “actual value after conversion” makes this intent unclear, especially since the round-trip conversion between signed and unsigned types might well yield the original value.
Proposed resolution (February, 2012):
Change 8.5.4 [dcl.init.list] paragraph 7 as follows:
A narrowing conversion is an implicit conversion
...
from an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, except where the source is a constant expression and the actual value after conversion whose value after integral promotions will fit into the target type and will produce the original value when converted back to the original type.
[Note:...
[Moved to DR at the April, 2013 meeting.]
One of the bullets in 8.5.4 [dcl.init.list] paragraph 3 reads,
Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary.
This does not say whether the initialization of the temporary is direct-list-initialization, copy-list-initialization, or the same kind of list-initialization as the top-level initialization.
Proposed resolution (December, 2012):
Change 8.5.4 [dcl.init.list] paragraph 3 as follows:
...
Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is list-initialized copy-list-initialized or direct-list-initialized, depending on the kind of initialization for the reference, and the reference is bound to that temporary. [Note: As usual...
...
[Moved to DR at the April, 2013 meeting.]
One of the bullets in 8.5.4 [dcl.init.list] paragraph 3 says,
Otherwise, if T is a specialization of std::initializer_list<E>, an initializer_list object is constructed as described below and used to initialize the object according to the rules for initialization of an object from a class of the same type (8.5 [dcl.init]).
This does not, but should, say whether the initializer_list object is treated as an lvalue or prvalue for the purpose of the 8.5 [dcl.init] initialization.
Proposed resolution (October, 2012):
Change 8.5.4 [dcl.init.list] paragraph 3 as follows:
List-initialization of an object or reference of type T is defined as follows:
...
Otherwise, if T is a specialization of std::initializer_list<E>, an a prvalue initializer_list object is constructed as described below and used to initialize the object according to the rules for initialization of an object from a class of the same type (8.5 [dcl.init]).
...
[Moved to DR at the April, 2013 meeting.]
In constructing an initializer_list object from an initializer list, 8.5.4 [dcl.init.list] paragraph 5 says of the underlying array,
Each element of that array is copy-initialized with the corresponding element of the initializer list
It would probably be good to mention that the copy/move constructor for this copy must be accessible in the context in which the initialization occurs.
Proposed resolution (October, 2012):
Change 8.5.4 [dcl.init.list] paragraph 4 as follows:
...Each element of that array is copy-initialized with the corresponding element of the initializer list, and the std::initializer_list<E> object is constructed to refer to that array. [Note: A constructor or conversion function selected for the copy shall be accessible (Clause 11 [class.access]) in the context of the initializer list. —end note] If a narrowing conversion is required...
[Moved to DR at the April, 2013 meeting.]
The ambiguity in an example like
struct A final { };
is resolved by 2.11 [lex.name] paragraph 2 to be the declaration of a variable named final:
any ambiguity as to whether a given identifier has a special meaning is resolved to interpret the token as a regular identifier.
Similarly, in an example like
struct C { constexpr operator int() { return 5; } }; struct A { struct B final : C{}; };
final is taken as the name of a bit-field member rather than as the name of a nested class.
Proposed resolution (October, 2012):
Change 2.11 [lex.name] paragraph 2 as follows:
The identifiers in Table 3 have a special meaning when appearing in a certain context. When referred to in the grammar, these identifiers are used explicitly rather than using the identifier grammar production. Unless otherwise specified, any ambiguity as to whether a given identifier has a special meaning is resolved to interpret the token as a regular identifier.
Change 9 [class] paragraph 3 as follows:
If a class is marked with the class-virt-specifier final and it appears as a base-type-specifier in a base-clause (Clause 10 [class.derived]), the program is ill-formed. Whenever a class-key is followed by a class-head-name, the identifier final, and a colon or left brace, final is interpreted as a class-virt-specifier. [Example:
struct A; struct A final {}; // OK: definition of struct A, // not value-initialization of variable final struct X { struct C { constexpr operator int() { return 5; } }; struct B final : C{}; // OK: definition of nested class B, // not declaration of a bit-field member final };
—end example]
[Moved to DR at the October, 2012 meeting.]
The requirements for a trivial class include having “a trivial default constructor” (9 [class] paragraph 6). However, with an explicitly-defaulted default constructor and other constructors with default arguments, it is possible to have multiple default constructors. Such a class cannot be default-initialized and thus should probably be considered non-trivial.
Proposed resolution (February, 2012):
Change 9 [class] paragraph 6 as follows:
...A trivial class is a class that has a trivial default constructor (12.1 [class.ctor]), has no non-trivial default constructors, and is trivially copyable...
[Moved to DR at the April, 2013 meeting.]
The resolution of issue 355 failed to update the grammar for class-head-name in 9 [class] paragraph 1 and removed the optional :: from class-or-decltype in 10 [class.derived] paragraph 1, with the result that it is still not possible to globally-qualify a class name in a class definition, and the ability to globally-qualify a base class name has been lost.
Proposed resolution (February, 2012):
Change the grammar in 5.1.1 [expr.prim.general] paragraph 8 as follows:
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to 9.2 [class.mem] paragraph 2,
A class is considered a completely-defined object type (3.9 [basic.types]) (or complete type) at the closing } of the class-specifier. Within the class member-specification, the class is regarded as complete within function bodies, default arguments, exception-specifications, and brace-or-equal-initializers for non-static data members (including such things in nested classes). Otherwise it is regarded as incomplete within its own class member-specification.
With the advent of the noexcept operator, treating the class type as complete in exception-specifications is obviously not possible, e.g.,
struct X { // should X be considered as complete here? static void create() noexcept(noexcept(X())); X() noexcept(!noexcept(X::create())); };
Proposed resolution (August, 2011):
Change 9.2 [class.mem] paragraph 2 as follows:
A class is considered a completely-defined object type (3.9 [basic.types]) (or complete type) at the closing } of the class-specifier. Within the class member-specification, the class is regarded as complete within function bodies, default arguments, exception-specifications, and brace-or-equal-initializers for non-static data members (including such things in nested classes). Otherwise it is regarded as incomplete within its own class member-specification.
Change 15.4 [except.spec] paragraph 2 as follows:
...A type denoted in an exception-specification shall not denote an incomplete type other than a class currently being defined. A type denoted in an exception-specification shall not denote a pointer or reference to an incomplete type, other than cv void*, const void*, volatile void*, or const volatile void* or a pointer or reference to a class currently being defined. A type cv T, “array of T”, or “function returning T” denoted in an exception-specification is adjusted to type T, “pointer to T”, or “pointer to function returning T”, respectively.
Note:
This change was subsequently removed by the resolution of issue 1330.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The grammar allows a brace-or-equal-initializer for any class member with a member-declarator, including typedef members and member function declarations, and there is no semantic restriction forbidding those forms, either.
Proposed resolution (August, 2011):
In 9.2 [class.mem], delete paragraph 4 and change paragraph 5 as follows:
A member can be initialized using a constructor; see 12.1 [class.ctor]. [Note: See Clause 12 [special] for a description of constructors and other special member functions. —end note]
A member can be initialized using a brace-or-equal-initializer shall appear only in the declaration of a data member. (For static data members, see 9.4.2 [class.static.data]; for non-static data members, see 12.6.2 [class.base.init]).
[Moved to DR at the April, 2013 meeting.]
According to 9.2 [class.mem] paragraph 20,
A pointer to a standard-layout struct object, suitably converted using a reinterpret_cast, points to its initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa.
Given a standard-layout struct in which the non-static data members are in a base class (and hence the derived class is empty), it is not clear what the “initial member” is. Presumably the intent behind allowing such standard-layout classes was to treat the base class object and its first non-static data member as the initial member of the derived class, but this does not appear to be spelled out anywhere.
Proposed resolution (February, 2012):
Change 9.2 [class.mem] paragraph 20 as follows:
A pointer to a standard-layout struct object, suitably converted using a reinterpret_cast, points to its initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa. If a standard-layout class object has any non-static data members, its address is the same as the address of its first non-static data member. Otherwise, its address is the same as the address of its first base class subobject (if any). [Note:...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to 9.3.2 [class.this] paragraph 2,
In a const member function, the object for which the function is called is accessed through a const access path; therefore, a const member function shall not modify the object and its non-static data members.
This is clearly overstating the case: mutable members can be modified, a const_cast can be used, and class member access expressions not involving this can also allow the object to be modified. The effect of the cv-qualification of a member function on the type of *this is clear from the preceding paragraph; this statement appears both unnecessary and incorrect.
Proposed resolution (August, 2011):
Merge 9.3.2 [class.this] paragraphs 1 and 2 and change the text as follows:
In the body of a non-static (9.3 [class.mfct]) member function, the keyword this is a prvalue expression whose value is the address of the object for which the function is called. The type of this in a member function of a class X is X*. If the member function is declared const, the type of this is const X*, if the member function is declared volatile, the type of this is volatile X*, and if the member function is declared const volatile, the type of this is const volatile X*. In [Note: thus in a const member function, the object for which the function is called is accessed through a const access path; therefore, a const member function shall not modify the object and its non-static data members. —end note] [Example:...
[Moved to DR at the October, 2012 meeting.]
According to 9.5 [class.union] paragraph 7,
A union for which objects or pointers are declared is not an anonymous union.
This should also apply to references, which are now possible because decltype allows writing an initializer for an unnamed union:
char buf[100]; union { int i; } &r = (decltype(r)) buf;
Proposed resolution (February, 2012):
Change 9.5 [class.union] paragraph 7:
A union for which objects, or pointers, or references are declared is not an anonymous union. [Example:
void f() { union { int aa; char* p; } obj, *ptr = &obj; aa = 1; // error ptr->aa = 1; // OK }
[Moved to DR at the October, 2012 meeting.]
Is the signedness of x in the following example implementation-defined?
template <typename T> struct A { T x : 7; }; template struct A<long>;
A similar example could be created with a typedef.
Lawrence Crowl: According to 9.6 [class.bit] paragraph 3,
It is implementation-defined whether a plain (neither explicitly signed nor unsigned) char, short, int or long bit-field is signed or unsigned.
This clause is conspicuously silent on typedefs and template parameters.
Clark Nelson: At least in C, the intention is that the presence or absence of this redundant keyword is supposed to be remembered through typedef declarations. I don't remember discussing it in C++, but I would certainly hope that we don't want to do something different. And presumably, we would want template type parameters to work the same way.
So going back to the original example, in an instantiation of A<long>, the signedness of the bit-field is implementation-defined, but in an instantiation of A<signed long>, the bit-field is definitely signed.
Peter Dimov: How can this work? Aren't A<long> and A<signed long> the same type?
(See also issue 739.)Proposed resolution (February, 2012):
This issue is resolved by the resolution of issue 739.[Moved to DR at the October, 2012 meeting.]
9.6 [class.bit] paragraph 3 says,
It is implementation-defined whether a plain (neither explicitly signed nor unsigned) char, short, int or long bit-field is signed or unsigned.
The implications of this permission for an implementation that chooses to treat plain bit-fields as unsigned are not clear. Does this mean that the type of such a bit-field is adjusted to the unsigned variant or simply that sign-extension is not performed when the value is fetched? C99 is explicit in specifying the former (6.7.2 paragraph 5: “for bit-fields, it is implementation-defined whether the specifier int designates the same type as signed int or the same type as unsigned int”), while C90 takes the latter approach (6.5.2.1: “Whether the high-order bit position of a (possibly qualified) 'plain' int bit-field is treated as a sign bit is implementation-defined”).
(See also issue 675 and issue 741.)Additional note, May, 2009:
As an example of the implications of this question, consider the following declaration:
struct S { int i: 2; signed int si: 2; unsigned int ui: 2; } s;
Is it implementation-defined which expression, cond?s.i:s.si or cond?s.i:s.ui, is an lvalue (the lvalueness of the result depends on the second and third operands having the same type, per 5.16 [expr.cond] paragraph 4)?
Proposed resolution (August, 2011):
Change 9.6 [class.bit] paragraph 3 as follows:
A bit-field shall not be a static member. A bit-field shall have integral or enumeration type (3.9.1 [basic.fundamental]). It is implementation-defined whether a plain (neither explicitly signed nor unsigned) char, short, int, long, or long long bit-field is signed or unsigned. For a bit-field with a non-dependent type (14.6.2.1 [temp.dep.type]) that is specified to be plain (neither explicitly signed nor unsigned) short, int, long, or long long or a typename-name that is so defined (possibly through multiple levels of typedefs), it is implementation-defined whether the type of the bit-field is the corresponding signed or unsigned type. [Example:
struct B { long x : 3; typedef signed int si; si y : 1; typedef int i; i z : 1; }; template<class T> struct A { T x : 7; };
It is implementation-defined whether B::x has type signed long or unsigned long. B::y has type signed int. It is implementation-defined whether B::z has type signed int or unsigned int. A<int>::x and A<signed int>::x designate the same entity of type signed int. A<unsigned int>::x has type unsigned int. —end example]
A bool value...
This resolution also resolves issue 675.
Note, January, 2012:
Additional questions have been raised about the proposed resolution, so the status was returned to "review" to allow further discussion.
Proposed resolution (February, 2012):
Change 9.6 [class.bit] paragraph 3 as follows:
A bit-field shall not be a static member. A bit-field shall have integral or enumeration type (3.9.1 [basic.fundamental]). It is implementation-defined whether a plain (neither explicitly signed nor unsigned) char, short, int, long, or long long bit-field is signed or unsigned. A bool value can successfully be stored...
Add the following as a new section in C.1.8 [diff.class]:
9.6 [class.bit]
Change: Bit-fields of type plain int are signed.
Rationale: Leaving the choice of signedness to implementations could lead to inconsistent definitions of template specializations. For consistency, the implementation freedom was eliminated for non-dependent types, too.
Effect on original feature: The choice is implementation-defined in C, but not so in C++.
Difficulty of converting: Syntactic transformation.
How widely used: Seldom.
This resolution also resolves issue 675.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to 10.3 [class.virtual] paragraph 8,
If the return type of D::f differs from the return type of B::f, the class type in the return type of D::f shall be complete at the point of declaration of D::f or shall be the class type D.
This provision was intended to deal with covariant return types but inadvertently affects types that vary only in cv-qualification:
struct A; struct B { virtual const A* f(); }; struct D : B { A* f(); // ill-formed };
Proposed resolution (August, 2011):
Change 10.3 [class.virtual] paragraph 8 as follows:
If the class type in the covariant return type of D::f differs from the return type that of B::f, the class type in the return type of D::f shall be complete at the point of declaration of D::f or shall be the class type D. When the overriding function...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
Split off from issue 86.
Should binding a reference to the result of a "," operation whose second operand is a temporary extend the lifetime of the temporary?
const SFileName &C = ( f(), SFileName("abc") );
Notes from the March 2004 meeting:
We think the temporary should be extended.
Proposed resolution (October, 2004):
Change 12.2 [class.temporary] paragraph 2 as indicated:
... In all these cases, the temporaries created during the evaluation of the expression initializing the reference, except the temporary that is the overall result of the expression [Footnote: For example, if the expression is a comma expression (5.18 [expr.comma]) and the value of its second operand is a temporary, the reference is bound to that temporary.] and to which the reference is bound, are destroyed at the end of the full-expression in which they are created and in the reverse order of the completion of their construction...
[Note: this wording partially resolves issue 86. See also issue 446.]
Notes from the April, 2005 meeting:
The CWG suggested a different approach from the 10/2004 resolution, leaving 12.2 [class.temporary] unchanged and adding normative wording to 5.18 [expr.comma] specifying that, if the result of the second operand is a temporary, that temporary is the result of the comma expression as well.
Proposed Resolution (November, 2006):
Add the indicated wording to 5.18 [expr.comma] paragraph 1:
... The type and value of the result are the type and value of the right operand; the result is of the same value category as its right operand, and is a bit-field if its right operand is a glvalue and a bit-field. If the value of the right operand is a temporary (12.2 [class.temporary]), the result is that temporary.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
Although the normative wording of 12.3.1 [class.conv.ctor] paragraph 1 defining a converting constructor says
A constructor declared without the function-specifier explicit specifies a conversion from the types of its parameters to the type of its class.
implying that a constructor with multiple parameters can be a converting constructor, it would be helpful if the example contained such a constructor.
Proposed resolution (August, 2011):
Change the example in 12.3.1 [class.conv.ctor] paragraph 1 as follows:
struct X { X(int); X(const char*, int =0); X(int, int); }; void f(X arg) { X a = 1; // a = X(1) X b = "Jessie"; // b = X("Jessie",0) a = 2; // a = X(2) f(3); // f(X(3)) f({1, 2}); // f(X(1,2)) }
Change the example in 12.3.1 [class.conv.ctor] paragraph 2 as follows:
struct Z { explicit Z(); explicit Z(int); explicit Z(int, int); }; Z a; // OK: default-initialization performed Z a1 = 1; // error: no implicit conversion Z a3 = Z(1); // OK: direct initialization syntax used Z a2(1); // OK: direct initialization syntax used Z* p = new Z(1); // OK: direct initialization syntax used Z a4 = (Z)1; // OK: explicit cast used Z a5 = static_cast<Z>(1); // OK: explicit cast used Z a6 = { 3, 4 }; // error: no implicit conversion
Note that destructors suffer from similar problems as those of constructors dealt with in issue 194 and in 263 (constructors as friends). Also, the wording in 12.4 [class.dtor], paragraph 1 does not permit a destructor to be defined outside of the memberlist.
Change 12.4 [class.dtor], paragraph 1 from
...A special declarator syntax using an optional function-specifier (7.1.2 [dcl.fct.spec]) followed by ~ followed by the destructor's class name followed by an empty parameter list is used to declare the destructor in a class definition. In such a declaration, the ~ followed by the destructor's class name can be enclosed in optional parentheses; such parentheses are ignored....
to
...A special declarator syntax using an optional sequence of function-specifiers (7.1.2 [dcl.fct.spec]), an optional friend keyword, an optional sequence of function-specifiers (7.1.2 [dcl.fct.spec]) followed by an optional :: scope-resolution-operator followed by an optional nested-name-specifier followed by ~ followed by the destructor's class name followed by an empty parameter list is used to declare the destructor. The optional nested-name-specifier shall not be specified in the declaration of a destructor within the member-list of the class of which the destructor is a member. In such a declaration, the optional :: scope-resolution-operator followed by an optional nested-name-specifier followed by ~ followed by the destructor's class name can be enclosed in optional parentheses; such parentheses are ignored....
Proposed resolution:
This issue is resolved by the resolution of issue 1435.
[Moved to DR at the April, 2013 meeting.]
According to 12.4 [class.dtor] paragraph 13,
The invocation of a destructor is subject to the usual rules for member functions (9.3 [class.mfct]), that is, if the object is not of the destructor's class type and not of a class derived from the destructor's class type, the program has undefined behavior (except that invoking delete on a null pointer has no effect).
While true, the final parenthetical comment concerns a delete-expression, not an explicit destructor call. Its presence here could mislead a careless reader to think that invoking a destructor with a null pointer is harmless. It should be deleted.
Proposed resolution (February, 2013):
Change 12.4 [class.dtor] paragraph 13 as follows:
In an explicit destructor call, the destructor name appears as a ~ followed by a type-name or decltype-specifier that denotes the destructor's class type. The invocation of a destructor is subject to the usual rules for member functions (9.3 [class.mfct]),; that is, if the object is not of the destructor's class type and not of a class derived from the destructor's class type (including when the destructor is invoked via a null pointer value), the program has undefined behavior (except that invoking delete on a null pointer has no effect). [Note: invoking delete on a null pointer does not call the destructor; see 5.3.5 [expr.delete]. —end note] [Example:...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
12.6.2 [class.base.init] paragraph 8 appears to indicate that a class member that is an anonymous union is to be default initilized.
Proposed resolution (August, 2011):
Change the indicated bullet of 12.6.2 [class.base.init] paragraph 8 as follows:
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
Footnote 112 (12.8 [class.copy] paragraph 2) says,
Because a template constructor is never a copy constructor, the presence of such a template does not suppress the implicit declaration of a copy constructor. Template constructors participate in overload resolution with other constructors, including copy constructors, and a template constructor may be used to copy an object if it provides a better match than other constructors.
However, many of the stipulations about copy construction are phrased to refer only to “copy constructors.” For example, 12.8 [class.copy] paragraph 14 says,
A program is ill-formed if the copy constructor... for an object is implicitly used and the special member function is not accessible (clause 11 [class.access]).
Does that mean that using an inaccessible template constructor to copy an object is permissible, because it is not a “copy constructor?” Obviously not, but each use of the term “copy constructor” in the Standard should be examined to determine if it applies strictly to copy constructors or to any constructor used for copying. (A similar issue applies to “copy assignment operators,” which have the same relationship to assignment operator function templates.)
Proposed Resolution (August, 2011):
Change 3.2 [basic.def.odr] paragraph 2 as follows:
...[Note: This covers calls to named functions (5.2.2 [expr.call]), operator overloading (Clause 13 [over]), user-defined conversions (12.3.2 [class.conv.fct]), allocation function for placement new (5.3.4 [expr.new]), as well as non-default initialization (8.5 [dcl.init]). A copy constructor or move constructor selected to copy or move an object of class type is odr-used even if the call is actually elided by the implementation (12.8 [class.copy]). —end note] ... A copy-assignment function for a class An assignment operator function in a class is odr-used by an implicitly-defined copy-assignment or move-assignment function for another class as specified in 12.8 [class.copy]. A move-assignment function for a class is odr-used by an implicitly-defined move-assignment function for another class as specified in 12.8 [class.copy]. A default constructor...
Delete 12.1 [class.ctor] paragraph 9:
A copy constructor (12.8 [class.copy]) is used to copy objects of class type. A move constructor (12.8 [class.copy]) is used to move the contents of objects of class type.
Change 12.2 [class.temporary] paragraph 1 as follows:
...[Note: even if there is no call to the destructor or copy/move constructor, all the semantic restrictions, such as accessibility (Clause 11 [class.access]) and whether the function is deleted (8.4.3 [dcl.fct.def.delete]), shall be satisfied. this includes accessibility (11 [class.access]) and whether it is deleted, for the constructor selected and for the destructor. However, in the special case of a function call...
Change 12.8 [class.copy] paragraph 13 as follows:
A copy/move constructor that is defaulted and not defined as deleted is implicitly defined if it is odr-used (3.2 [basic.def.odr]) to initialize an object of its class type from a copy of an object of its class type or of a class type derived from its class type [Footnote: See 8.5 [dcl.init] for more details on direct and copy initialization. —end footnote] or when it is explicitly defaulted...
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor selected for the copy/move operation and/or the destructor for the object have side effects...
Change 13.3.3.1.2 [over.ics.user] paragraph 4 as follows:
A conversion of an expression of class type to the same class type is given Exact Match rank, and a conversion of an expression of class type to a base class of that type is given Conversion rank, in spite of the fact that a copy/move constructor (i.e., a user-defined conversion function) is called for those cases.
Change 15.1 [except.throw] paragraph 3 as follows:
A throw-expression copy-initializes (8.5 [dcl.init]) a temporary object, called the exception object...
Change 15.1 [except.throw] paragraph 5 as follows:
When the thrown object is a class object, the copy/move constructor selected for the copy-initialization and the destructor shall be accessible, even if the copy/move operation is elided (12.8 [class.copy]).
[Drafting note: 5.17 [expr.ass] paragraph 4, 9 [class] paragraph 4, 9.5 [class.union] paragraph 1, 12.2 [class.temporary] paragraph 2, 12.8 [class.copy] paragraphs 1-2, and 15.4 [except.spec] paragraph 14 do not require any changes.]
[Moved to DR status at the April, 2013 meeting as paper N3667.]
Paragraphs 11 and 23 of 12.8 [class.copy] make a defaulted move constructor and assignment operator, respectively, deleted if there is a subobject with no corresponding move function and for which the copy operation is non-trivial. This seems excessive and unnecessary. For example:
template<typename T> struct wrap { wrap() = default; #ifdef USE_DEFAULTED_MOVE wrap(wrap&&) = default; #else wrap(wrap&& w) : t(static_cast<T&&>(w.t)) { } #endif wrap(const wrap&) = default; T t; }; struct S { S(){} S(const S&){} S(S&&){} }; typedef wrap<const S> W; W get() { return W(); } // Error, if USE_DEFAULTED_MOVE is defined, else OK
In this example the defaulted move constructor of wrap is selected by overload resolution, but this move-constructor is deleted, because S has no trivial copy-constructor.
I think that we overshoot here with the delete rules: I see no problem for the defaulted move-constructor in this example. Our triviality-deduction rules already cover this case (12.8 [class.copy] paragraph 12: W::W(W&&) is not trivial) and our exception-specification rules (15.4 [except.spec] paragraph 14) already correctly deduce a noexcept(false) specification for W::W(W&&).
It would still be OK to prevent that a move-constructor would be generated for the following example where no user-declared defaulted copy/move members are present:
template<typename T>
struct wrap_2
{
wrap_2() = default;
T t;
};
typedef wrap_2<const S> W2;
W2 get() { return W2(); } // OK, selects copy constructor
if we want. This would mean that we add a new bullet to 12.8 [class.copy] paragraph 9 and paragraph 20.
Proposed resolution (February, 2012):
Change 12.8 [class.copy] paragraph 9 as follows:
If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if
...
X does not have a user-declared destructor, and
the move constructor would not be implicitly defined as deleted., and
each of X's non-static data members and direct or virtual base classes has a type that either has a move constructor or is trivially copyable.
[Note:...
Change 12.8 [class.copy] paragraph 11 as follows:
An implicitly-declared copy/move constructor is an inline public member of its class. A defaulted copy/move constructor for a class X is defined as deleted (8.4.3 [dcl.fct.def.delete]) if X has:
...
any direct or virtual base class or non-static data member of a type with a destructor that is deleted or inaccessible from the defaulted constructor, or
for the copy constructor, a non-static data member of rvalue reference type., or
for the move constructor, a non-static data member or direct or virtual base class with a type that does not have a move constructor and is not trivially copyable.
Change 12.8 [class.copy] paragraph 20 as follows:
If the definition of a class X does not explicitly declare a move assignment operator, one will be implicitly declared as defaulted if and only if
...
X does not have a user-declared destructor, and
the move assignment operator would not be implicitly defined as deleted.,
X has no direct or indirect virtual base class with a non-trivial move assignment operator, and
each of X's non-static data members and direct or virtual base classes has a type that either has a move assignment operator or is trivially copyable.
Example:...
Change 12.8 [class.copy] paragraph 23 as follows:
A defaulted copy/move assignment operator for class X is defined as deleted if X has:
...
a direct or virtual base class B that cannot be copied/moved because overload resolution (13.3 [over.match]), as applied to B's corresponding assignment operator, results in an ambiguity or a function that is deleted or inaccessible from the defaulted assignment operator., or
for the move assignment operator, a non-static data member or direct base class with a type that does not have a move assignment operator and is not trivially copyable, or any direct or indirect virtual base class.
Additional notes (August, 2012):
The proposed resolution was extensively discussed and additional alternatives were suggested. A paper is being produced for the October, 2012 meeting describing the various options, so the issue has been returned to "review" status to wait for the outcome of that deliberation.
See also the discussion of issue 1491 for additional considerations.
Proposed resolution (December, 2012):
Change 12.8 [class.copy] paragraph 9 as follows:
If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if
X does not have a user-declared copy constructor,
X does not have a user-declared copy assignment operator,
X does not have a user-declared move assignment operator, and
X does not have a user-declared destructor., and
the move constructor would not be implicitly defined as deleted.
[Note:...
Change 12.8 [class.copy] paragraph 11 as follows:
...A defaulted copy/move constructor for a class X is defined as deleted (8.4.3 [dcl.fct.def.delete]) if X has:
a variant member with a non-trivial corresponding constructor and X is a union-like class,
a non-static data member of class type M (or array thereof) that cannot be copied/moved because overload resolution (13.3 [over.match]), as applied to M's corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor,
a direct or virtual base class B that cannot be copied/moved because overload resolution (13.3 [over.match]), as applied to B's corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor,
any direct or virtual base class or non-static data member of a type with a destructor that is deleted or inaccessible from the defaulted constructor, or
for the copy constructor, a non-static data member of rvalue reference type., or
for the move constructor, a non-static data member or direct or virtual base class with a type that does not have a move constructor and is not trivially copyable.
A defaulted move constructor that is defined as deleted is ignored by overload resolution (13.3 [over.match]). [Note: A deleted move constructor would otherwise interfere with initialization from an rvalue which can use the copy constructor instead. —end note]
Change 12.8 [class.copy] paragraph 20 as follows:
If the definition of a class X does not explicitly declare a move assignment operator, one will be implicitly declared as defaulted if and only if
X does not have a user-declared copy constructor,
X does not have a user-declared move constructor,
X does not have a user-declared copy assignment operator, and
X does not have a user-declared destructor., and
the move assignment operator would not be implicitly defined as deleted.
[Example:...
Change 12.8 [class.copy] paragraph 23 as follows:
A defaulted copy/move assignment operator for class X is defined as deleted if X has:
a variant member with a non-trivial corresponding assignment operator and X is a union-like class, or
a non-static data member of const non-class type (or array thereof), or
a non-static data member of reference type, or
a non-static data member of class type M (or array thereof) that cannot be copied/moved because overload resolution (13.3 [over.match]), as applied to M's corresponding assignment operator, results in an ambiguity or a function that is deleted or inaccessible from the defaulted assignment operator, or
a direct or virtual base class B that cannot be copied/moved because overload resolution (13.3 [over.match]), as applied to B's corresponding assignment operator, results in an ambiguity or a function that is deleted or inaccessible from the defaulted assignment operator., or
for the move assignment operator, a non-static data member or direct base class with a type that does not have a move assignment operator and is not trivially copyable, or any direct or indirect virtual base class.
A defaulted move assignment operator that is defined as deleted is ignored by overload resolution (13.3 [over.match], 13.4 [over.over]).
This resolution also resolves issue 1491.
According to 12.8 [class.copy] paragraph 11, the last bullet, a defaulted move constructor for a class is defined as deleted if
a non-static data member or direct or virtual base class with a type that does not have a move constructor and is not trivially copyable.
This means that an example like
struct S { S(); int &&r; } s{S()};
is ill-formed. This is probably not intended.
(Note that the February, 2012 proposed resolution for issue 1402 also makes this example ill-formed, because the move constructor is not declared and the copy constructor is defined as deleted because of the rvalue-reference member.)
Additional note, February, 2014:
This issue is resolved by the resolution of issue 1402, which removed the problematic sentence.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to 12.9 [class.inhctor] paragraph 3, the exception specification for an inheriting constructor has the same exception specification as the inherited constructor. This ignores the exception specifications of default constructors for base classes and nonstatic data members and of functions called in brace-or-equals-initializers of nonstatic data members.
Proposed resolution (August, 2011):
Delete the indicated bullet of 12.9 [class.inhctor] paragraph 2:
Change 12.9 [class.inhctor] paragraph 3 as follows:
...[Note: Default arguments are not inherited. An exception-specification is implied as specified in 15.4 [except.spec]. —end note]
Change 15.4 [except.spec] paragraph 14 as follows:
An inheriting constructor (12.9 [class.inhctor]) and an implicitly declared special member function (Clause 12 [special]) shall have an exception-specification. If f is an inheriting constructor or an implicitly declared default constructor, copy constructor, move constructor, destructor, copy assignment operator, or move assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f's implicit definition; f shall allow all exceptions if any function it directly invokes allows all exceptions, and f shall allow no exceptions if every function it directly invokes allows no exceptions. [Note: an instantiation of an inheriting constructor template has an implied exception-specification as if it were a non-template inheriting constructor. —end note] [Example:...
[Moved to DR at the April, 2013 meeting.]
According to 12.9 [class.inhctor] paragraph 3,
For each non-template constructor in the candidate set of inherited constructors other than a constructor having no parameters or a copy/move constructor having a single parameter, a constructor is implicitly declared with the same constructor characteristics unless there is a user-declared constructor with the same signature in the class where the using-declaration appears.
It is not clear whether that determination is intended to include constructors declared after the point of the using-declaration or not.
Proposed resolution (February, 2013):
Change 9.2 [class.mem] paragraph 2 as follows:
A class is considered a completely-defined object type (3.9 [basic.types]) (or complete type) at the closing } of the class-specifier. Within the class member-specification, the class is regarded as complete within function bodies, default arguments, using-declarations introducing inheriting constructors (12.9 [class.inhctor]), and brace-or-equal-initializers for non-static data members (including such things in nested classes). Otherwise it is regarded as incomplete within its own class member-specification.
Change 12 [special] paragraph 1 as follows:
...See 12.1 [class.ctor], 12.4 [class.dtor] and 12.8 [class.copy]. —end note] An implicitly-declared special member function is declared at the closing } of the class-specifier. Programs shall not define implicitly-declared special member functions.
Change 12.9 [class.inhctor] paragraph 3 as follows:
For each non-template constructor in the candidate set of inherited constructors other than a constructor having no parameters or a copy/move constructor having a single parameter, a constructor is implicitly declared with the same constructor characteristics unless there is a user-declared constructor with the same signature in the complete class where the using-declaration appears. Similarly, for each constructor template in the candidate set of inherited constructors, a constructor template is implicitly declared with the same constructor characteristics unless there is an equivalent user-declared constructor template (14.5.6.1 [temp.over.link]) in the complete class where the using-declaration appears. [Note: Default arguments are not inherited. An exception-specification is implied as specified in 15.4 [except.spec]. —end note]
Additional note (January, 2013):
A question has been raised as to whether it is necessary to prohibit inheriting constructors from base classes that are also enclosing classes when the derived class is defined outside the member-specification of the enclosing class. This issue has been returned to "review" status to allow discussion of this question.
Additional note (February, 2013):
It was observed that it is not permitted to derive from an incomplete class, which prevents the problem intended to be addressed by the prohibition of inheriting constructors from an enclosing class without disallowing such usage when the nested class is defined outside its enclosing class. That restriction has been removed from the proposed resolution.
[Moved to DR at the October, 2012 meeting.]
In 13.3.1.1.2 [over.call.object] paragraph 2, the non-explicit conversion functions considered for producing surrogate call functions are those of the form
This (presumably inadvertently) excludes conversion functions with a ref-qualifier and/or an exception-specification.
Proposed resolution (February, 2012):
Change 13.3.1.1.2 [over.call.object] paragraph 2 as follows:
In addition, for each non-explicit conversion function declared in T of the form
operator conversion-type-id () attribute-specifier-seqopt cv-qualifier
ref-qualifieropt exception-specificationopt attribute-specifier-seqopt ;
where cv-qualifier is the same...
[Moved to DR at the April, 2013 meeting.]
Issue 899 added wording to the second bullet of 13.3.1.4 [over.match.copy] paragraph 1 permitting use of explicit conversion functions when calling a copy constructor in a direct-initialization context. Issue 1087 addressed the problem in the earlier resolution that the phrase “copy constructor” did not include move constructors and template constructors. However, the term “copy constructor” implicitly (and correctly) restricted the constructor parameter to be the constructor's class, i.e., the class of the object being directly initialized. The new phrasing, “a constructor that takes a reference to possibly cv-qualified T as its first argument,” incorrectly assumed that T referred to the type of the object being initialized; however, that is not the case, and a converting constructor from T to the type of the object being initialized is inadvertently permitted. The wording needs a further tweak to restore the intended context.
Proposed resolution (October, 2012):
Change the second bullet of 13.3.1.4 [over.match.copy] paragraph 1 as follows:
...Assuming that “cv1 T” is the type of the object being initialized, with T a class type, the candidate functions are selected as follows:
The converting constructors (12.3.1 [class.conv.ctor]) of T are candidate functions.
When the type of the initializer expression is a class type “cv S”, the non-explicit conversion functions of S and its base classes are considered. When initializing a temporary to be bound to the first parameter of a constructor that takes a reference to possibly cv-qualified T as its first argument, called with a single argument in the context of direct-initialization of an object of type “cv2 T”, explicit conversion functions are also considered. Those that are not hidden within S and yield a type whose cv-unqualified version is the same type as T or is a derived class thereof are candidate functions. Conversion functions that return “reference to X” return lvalues or xvalues, depending on the type of reference, of type X and are therefore considered to yield X for this process of selecting candidate functions.
[Moved to DR at the October, 2012 meeting.]
In 13.3.1.5 [over.match.conv], dealing with non-reference initialization, direct initialization considers as candidate functions only those that
yield type T or a type that can be converted to type T with a qualification conversion
By contrast, 13.3.1.6 [over.match.ref], dealing with reference binding, requires only that the type returned be reference-compatible with the target, permitting both qualification conversions and derived-to-base conversions. This discrepancy is presumably unintentional.
Proposed resolution (February, 2012):
Change 13.3.1.6 [over.match.ref] paragraph 1 as follows:
...the candidate functions are selected as follows:
The conversion functions of S and its base classes are considered, except that for copy-initialization, only the non-explicit conversion functions are considered. Those non-explicit conversion functions that are not hidden within S and yield type “lvalue reference to cv2 T2” (when 8.5.3 [dcl.init.ref] requires an lvalue result) or “cv2 T2” or “rvalue reference to cv2 T2” (when 8.5.3 [dcl.init.ref] requires an rvalue result), where “cv1 T” is reference-compatible (8.5.3 [dcl.init.ref]) with “cv2 T2”, are candidate functions. For direct-initialization, those explicit conversion functions that are not hidden within S and yield type “lvalue reference to cv2 T2,” or “cv2 T2 or “rvalue reference to cv2 T2,” respectively, where T2 is the same type as T or can be converted to type T with a qualification conversion (4.4 [conv.qual]), are also candidate functions.
[Moved to DR at the October, 2012 meeting.]
Both paragraphs 3 and 4 (for non-aggregate and aggregate types, respectively) of 13.3.3.1.5 [over.ics.list] say that the implicit conversion sequence is a user-defined conversion sequence, but neither specifies that the second standard conversion sequence is the identity conversion, as is presumably intended. This makes ranking by 13.3.3.2 [over.ics.rank] paragraph 3 bullet 2 unncessarily unclear.
Proposed resolution (February, 2012):
Change 13.3.3.1.5 [over.ics.list] paragraphs 3-4 as follows:
Otherwise, if the parameter is a non-aggregate class X and overload resolution per 13.3.1.7 [over.match.list] chooses a single best constructor of X to perform the initialization of an object of type X from the argument initializer list, the implicit conversion sequence is a user-defined conversion sequence with the second standard conversion sequence an identity conversion. If multiple constructors are viable but none is better than the others, the implicit conversion sequence is the ambiguous conversion sequence...
Otherwise, if the parameter has an aggregate type which can be initialized from the initializer list according to the rules for aggregate initialization (8.5.1 [dcl.init.aggr]), the implicit conversion sequence is a user-defined conversion sequence with the second standard conversion sequence an identity conversion. [Example:...
[Moved to DR at the April, 2013 meeting.]
According to 13.3.3.1.5 [over.ics.list] paragraph 6, when passing an initializer-list argument to a non-class parameter,
if the initializer list has no elements, the implicit conversion sequence is the identity conversion.
However, there is no similar provision for an empty initializer list passed to a specialization of std::initializer_list or an array, as described in paragraph 2:
If the parameter type is std::initializer_list<X> or “array of X”134 and all the elements of the initializer list can be implicitly converted to X, the implicit conversion sequence is the worst conversion necessary to convert an element of the list to X.
It is not clear what the result should be for a list with no elements. For example, given
void f(int) { printf("int\n"); } void f(std::initializer_list<int>) { printf("init list\n"); } int main() { f({}); }
current implementations result in init list being printed, presumably on the basis of the last bullet of 13.3.3.2 [over.ics.rank] paragraph 3:
List-initialization sequence L1 is a better conversion sequence than list-initialization sequence L2 if L1 converts to std::initializer_list<X> for some X\and L2 does not.
That would imply that both conversion sequences are the identity conversion, which is reasonable, but it should be stated clearly if that is the intent.
Proposed resolution (October, 2012):
Change 13.3.3.1.5 [over.ics.list] paragraph 2 as follows:
If the parameter type is std::initializer_list<X> or “array of X”134 and all the elements of the initializer list can be implicitly converted to X, the implicit conversion sequence is the worst conversion necessary to convert an element of the list to X, or if the initializer list has no elements, the identity conversion. This conversion can be a user-defined conversion even in the context of a call to an initializer-list constructor. [Example:
void f(std::initializer_list<int>); f( {} ); // OK: f(initializer_list<int>) identity conversion f( {1,2,3} ); // OK: f(initializer_list<int>) identity conversion f( {'a','b'} ); // OK: f(initializer_list<int>) integral promotion f( {1.0} ); // error: narrowing ...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The following example appears in 13.3.3.2 [over.ics.rank] paragraph 3:
template<class T> int f(T&);
template<class T> int f(T&&);
void g();
int i1 = f(g); // calls f(T&)
This is not correct. Because of the special deduction rule for rvalue reference parameters in 14.8.2.1 [temp.deduct.call] paragraph 3 and the reference-collapsing rules of 8.3.2 [dcl.ref] paragraph 6, the parameter type for both will be void(&)().
Proposed resolution (August, 2011):
Change the example in 13.3.3.2 [over.ics.rank] paragraph 3 bullet 1 sub-bullet 5 as follows:
template<class T> int f(T&); template<class T> int f(T&&); int f(void(&)()); // #1 int f(void(&&)()); // #2 void g(); int i1 = f(g); // calls f(T&) #1
[Moved to DR at the April, 2013 meeting.]
The rule in 13.3.3.2 [over.ics.rank] paragraph 3 for ranking based on a difference in qualification conversion applies only if they "differ only in their qualification conversion".
It is unclear as to whether the property of being a reference binding is a factor in determining if there is a difference between conversion sequences. Notice that 13.3.3.1.4 [over.ics.ref] maps reference bindings to other forms of implicit conversion sequences, but does not state that the property of being a reference binding is preserved; however, 13.3.3.2 [over.ics.rank] has cases which depend on whether certain standard conversion sequences are reference bindings or not and on the specifics of the bindings.
In the following, picking T2 && would bind an rvalue to an rvalue reference. Picking T1 & would bind an rvalue to an lvalue reference, but the qualification conversion to T1 is "better". Which is better?
typedef int * * *const *const T1; typedef int *const *const *const *const T2; void foo(T1 &); void foo(T2 &&) { } int main() { foo((int ****)0); return 0; }
Notes from the February, 2012 meeting:
The CWG agreed that bullets 3 and 4 should be reversed, to check the reference binding first and then for qualification conversion.
Proposed resolution (February, 2012):
Move 13.3.3.2 [over.ics.rank] paragraph 3, first bullet, third sub-bullet, after the current fifth sub-bullet, as follows:
Two implicit conversion sequences of the same form are indistinguishable conversion sequences unless one of the following rules applies:
Standard conversion sequence S1 is a better conversion sequence...
S1 is a proper subsequence of S2...
the rank of S1 is better...
S1 and S2 differ only in their qualification conversion... —end example] or if not that,
S1 and S2 are reference bindings (8.5.3 [dcl.init.ref]) and neither refers... or if not that,
S1 and S2 are reference bindings (8.5.3 [dcl.init.ref]) and S1 binds... —end example] or if not that,
S1 and S2 differ only in their qualification conversion... —end example] or if not that,
S1 and S2 are reference bindings (8.5.3 [dcl.init.ref]), and the types to which the references refer...
- User-defined conversion sequence U1...
[Moved to DR at the October, 2012 meeting.]
Bullet 2 of 13.3.3.2 [over.ics.rank] paragraph 3 reads,
User-defined conversion sequence U1 is a better conversion sequence than another user-defined conversion sequence U2 if they contain the same user-defined conversion function or constructor or aggregate initialization and the second standard conversion sequence of U1 is better than the second standard conversion sequence of U2.
It is not clear what “the same aggregate initialization” means — does this require that the same aggregate type is the target type?
Proposed resolution (February, 2012):
Change 13.3.3.2 [over.ics.rank] paragraph 3 bullet 2 as follows:
Standard conversion sequence S1 is a better conversion sequence...
User-defined conversion sequence U1 is a better conversion sequence than another user-defined conversion sequence U2 if they contain the same user-defined conversion function or constructor or they initialize the same class in an aggregate initialization and in either case the second standard conversion sequence of U1 is better than the second standard conversion sequence of U2. [Example:...
[Moved to DR at the October, 2012 meeting.]
In bullet 3 of paragraph 4 of 13.3.3.2 [over.ics.rank] are two sub-bullets dealing with overload tiebreakers:
binding of an expression of type C to a reference of type B& is better than binding an expression of type C to a reference of type A&,
...
binding of an expression of type B to a reference of type A& is better than binding an expression of type C to a reference of type A&,
Presumably both of these tiebreakers should apply to rvalue references as well as lvalue references.
Proposed resolution (February, 2012):
Change 13.3.3.2 [over.ics.rank] paragraph 4 bullet 3 as follows:
If class B is derived directly or indirectly from class A and class C is derived directly or indirectly from B,
conversion of C* to B* is better...
binding of an expression of type C to a reference of to type B& is better than binding an expression of type C to a reference of to type A&,
...
binding of an expression of type B to a reference of to type A& is better than binding an expression of type C to a reference of to type A&,
...
[Moved to DR at the April, 2013 meeting.]
Presumably, 13.4 [over.over] paragraph 1,
A use of an overloaded function name without arguments is resolved in certain contexts to a function, a pointer to function or a pointer to member function for a specific function from the overload set. A function template name is considered to name a set of overloaded functions in such contexts. The function selected is the one whose type is identical to the function type of the target type required in the context. [Note: That is, the class of which the function is a member is ignored when matching a pointer-to-member-function type. —end note] The target can be
an object or reference being initialized (8.5 [dcl.init], 8.5.3 [dcl.init.ref]),
...
should apply to an example like
double bar(double) { return 0.0; } float bar(float) { return 0.0f; } using fun = double(double); fun &foo{bar};
However, there is implementation variance in whether the use of bar is accepted or not, and the omission of a cross-reference to 8.5.4 [dcl.init.list] might be read as indicating that list-initialization is not included.
Proposed resolution (February, 2013):
Change 13.4 [over.over] paragraph 1 as follows:
...The target can be
an object or reference being initialized (8.5 [dcl.init], 8.5.3 [dcl.init.ref], 8.5.4 [dcl.init.list]),
...
[Moved to DR at the April, 2013 meeting.]
The phrase in 13.5.7 [over.inc] paragraph 1,
a non-member function with one parameter of class or enumeration type
inadvertently excludes reference parameters, and in fact, no mention of the type is necessary in light of 13.5 [over.oper] paragraph 6:
An operator function shall either be a non-static member function or be a non-member function and have at least one parameter whose type is a class, a reference to a class, an enumeration, or a reference to an enumeration.
Proposed resolution (August, 2012):
Change 13.5.7 [over.inc] paragraph 1 as follows:
The user-defined function called operator++ implements the prefix and postfix ++ operator. If this function is a member function with no parameters, or a non-member function with one parameter of class or enumeration type, it defines the prefix increment operator ++ for objects of that type. If the function...
[Moved to DR at the April, 2013 meeting.]
The current grammar requires that there be no whitespace between the literal and the ud-suffix, e.g., ""_abc, but that there be whitespace between the "" and the identifier in a literal-operator-id, e.g., operator "" _abc. This seems unfortunate. Would it be possible to provide an alternate production,
with the requirement that the string-literal be empty?
The current wording is also unclear regarding interactions with phases of translation. We have the following production:
As this is after translation phase 6, would something like
operator "" "" _foo
be accepted?
Proposed resolution (October, 2012):
Change 13.5.8 [over.literal] as follows:
Change 13.5.8 [over.literal] paragraph 1 as follows:
The string-literal or user-defined-string-literal in a literal-operator-id shall have no encoding-prefix and shall contain no characters other than the implicit terminating '\0'. The ud-suffix of the user-defined-string-literal or the identifier in a literal-operator-id is called a literal suffix identifier. [Note: some literal suffix identifiers are reserved for future standardization; see 17.6.4.3.5 [usrlit.suffix]. —end note]
Change the example in 13.5.8 [over.literal] paragraph 8 as follows:
void operator "" _km(long double); // OK string operator "" _i18n(const char*, std::size_t); // OK template <char...> int operator "" \u03C0(); // OK: UCN for lowercase pi float operator ""E(const char*); // error: ""E (with no intervening space) // is a single token OK float operator " " B(const char*); // error: non-adjacent quotesempty string-literal string operator "" 5X(const char*, std::size_t); // error: invalid literal suffix identifier double operator "" _miles(double); // error: invalid parameter-declaration-clause template <char...> int operator "" _j(const char*); // error: invalid parameter-declaration-clause
[Moved to DR at the April, 2013 meeting.]
It appears that an example like
int operator"" _a (const char *, std::size_t = 0); int operator"" _a (const char *); int i = 123_a;
is ambiguous: although only the second declaration is a raw literal operator, the corresponding call
operator"" _a ("123")
could match either declaration. Should default arguments for literal operators be prohibited?
Proposed resolution (October, 2012):
Change 13.5.8 [over.literal] paragraph 3 as follows:
The declaration of a literal operator shall have a parameter-declaration-clause equivalent to one of the following:
...
If a parameter has a default argument (8.3.6 [dcl.fct.default]), the program is ill-formed.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The following example from 14.1 [temp.param] paragraph 11 is incorrect:
// U cannot be deduced or specified template<class... T, class... U> void f() { } template<class... T, class U> void g() { }
In fact, U can be deduced to an empty sequence by 14.8.1 [temp.arg.explicit] paragraph 3:
A trailing template parameter pack (14.5.3 [temp.variadic]) not otherwise deduced will be deduced to an empty sequence of template arguments.
Proposed resolution (August, 2011):
Change 14.1 [temp.param] paragraph 11 as follows:
...A template parameter pack of a function template shall not be followed by another template parameter unless that template parameter can be deduced from the parameter-type-list of the function template or has a default argument (14.8.2 [temp.deduct]). [Example:
template<class T1 = int, class T2> class B; // error // U cannot be deduced from the parameter-type-list or specified template<class... T, class... U> void f() { } // error template<class... T, class U> void g() { } // error—end example]
[Moved to DR at the October, 2012 meeting.]
Although 14.1 [temp.param] paragraph 4 explicitly allows non-type template parameters of type std::nullptr_t, they cannot actually be used: 14.3.2 [temp.arg.nontype] paragraph 1 does not allow a template-argument of type std::nullptr_t, and paragraph 5 does not describe any conversions from other types to std::nullptr_t.
Proposed resolution (February, 2012):
Add the following new bullet in 14.3.2 [temp.arg.nontype] paragraph 1:
...
a pointer to member expressed as described in 5.3.1 [expr.unary.op].
an address constant expression of type std::nullptr_t.
[Moved to DR at the April, 2013 meeting.]
The list of pack expansion contexts in 14.5.3 [temp.variadic] paragraph 4 includes a mem-initializer-list, with no restriction on whether the mem-initializer corresponds to a base class or a member. However, it appears from 12.6.2 [class.base.init] paragraph 15 that such a pack expansion is intended for bases:
A mem-initializer followed by an ellipsis is a pack expansion (14.5.3 [temp.variadic]) that initializes the base classes specified by a pack expansion in the base-specifier-list for the class.
This is not conclusive, however, and use of a pack expansion with a mem-initializer for a member could be used with packs containing zero or one element:
class S { }; template<typename... T> class X { public: X(T ...args) : data_(args)... { } private: S data_; }; int main() { S s; X<> x1; X<S> x2(s); }
The Standard should be clarified as to whether such a usage is permitted or not.
Proposed resolution (October, 2012):
Change 14.5.3 [temp.variadic] paragraph 4 as follows:
...Pack expansions can occur in the following contexts:
...
In a mem-initializer-list (12.6.2 [class.base.init]) for a mem-initializer whose mem-initializer-id denotes a base class; the pattern is a the mem-initializer.
...
[Moved to DR at the April, 2013 meeting.]
Consider an example like
template <int B, typename Type1, typename... Types> struct A; template<typename... Types> struct A<0, Types...> { }; A<0,int,int> t;
In this case, the partial specialization seems well-formed by the rules in 14.5.5 [temp.class.spec], but it is not more specialized than the primary template. However, 14.5.5.1 [temp.class.spec.match] says that if exactly one matching specialization is found, it is used, which suggests that the testcase is well-formed. That seems undesirable; I think a partial specialization that is not more specialized than the primary template should be ill-formed.
If the example is rewritten so that both versions are partial specializations, i.e.,
template <int B, typename... Types> struct A; template <int B, typename Type1, typename... Types> struct A<B, Type1, Types...> { } template<typename... Types> struct A<0, Types...> { }; A<0,int,int> t;
There is implementation variance, with gcc and clang reporting an ambiguity and EDG choosing the second specialization.
Proposed resolution (October, 2012):
Add the following as a new bullet in 14.5.5 [temp.class.spec] paragraph 8:
...
The argument list of the specialization shall not be identical to the implicit argument list of the primary template.
The specialization shall be more specialized than the primary template (14.5.5.2 [temp.class.order]).
...
[Moved to DR at the October, 2012 meeting.]
Consider the following example:
int g(int); template <class T> decltype(g(T())) f(); int g(); template <class T> decltype(g(T())) f() { return g(T()); } int i = f<int>();
Do the two fs declare the same function template? According to 14.5.6.1 [temp.over.link] paragraph 5,
Two expressions involving template parameters are considered equivalent if two function definitions containing the expressions would satisfy the one definition rule (3.2 [basic.def.odr]), except that the tokens used to name the template parameters may differ as long as a token used to name a template parameter in one expression is replaced by another token that names the same template parameter in the other expression.
The relevant portion of 3.2 [basic.def.odr] paragraph 5 says,
in each definition of D, corresponding names, looked up according to 3.4 [basic.lookup], shall refer to an entity defined within the definition of D, or shall refer to the same entity, after overload resolution (13.3 [over.match]) and after matching of partial template specialization (14.8.3 [temp.over]), except that a name can refer to a const object with internal or no linkage if the object has the same literal type in all definitions of D, and the object is initialized with a constant expression (5.19 [expr.const]), and the value (but not the address) of the object is used, and the object has the same value in all definitions of D
This could be read either way, since overload resolution isn't done at this point. Either we consider the result of the unqualified name lookup and say that the expressions aren't equivalent or we need a new rule for equivalence and merging of dependent calls.
Proposed resolution (December, 2011):
Change 14.5.6.1 [temp.over.link] paragraph 5 as follows:
Two expressions involving template parameters are considered equivalent if two function definitions containing the expressions would satisfy the one definition rule (3.2 [basic.def.odr]), except that the tokens used to name the template parameters may differ as long as a token used to name a template parameter in one expression is replaced by another token that names the same template parameter in the other expression. For determining whether two dependent names (14.6.2 [temp.dep]) are equivalent, only the name itself is considered, not the result of name lookup in the context of the template. If multiple declarations of the same function template differ in the result of this name lookup, the result for the first declaration is used. [Example:
template <int I, int J> void f(A<I+J>); // #1 template <int K, int L> void f(A<K+L>); // same as #1 template <class T> decltype(g(T())) h(); int g(int); template <class T> decltype(g(T())) h() // redeclaration of h() uses the earlier lookup { return g(T()); } // ...although the lookup here does find g(int) int i = h<int>(); // template argument substitution fails; g(int) // was not in scope at the first declaration of h()—end example] Two expressions...
Change 14.6.2 [temp.dep] paragraph 1 as follows:
...In an expression of the form:
postfix-expression ( expression-listopt )
where the postfix-expression is an id-expression unqualified-id, the id-expression unqualified-id denotes a dependent name if
any of the expressions in the expression-list is a pack expansion (14.5.3 [temp.variadic]),
any of the expressions in the expression-list is a type-dependent expression (14.6.2.2 [temp.dep.expr]), or
if the unqualified-id of the id-expression is a template-id in which any of the template arguments depends on a template parameter.
if an operand...
Change 14.6.4.2 [temp.dep.candidate] paragraph 1 as follows:
For a function call that depends on a template parameter where the postfix-expression is a dependent name, the candidate functions are found using the usual lookup rules (3.4.1 [basic.lookup.unqual], 3.4.2 [basic.lookup.argdep], 3.4.3 [basic.lookup.qual]) except that:
For the part of the lookup using unqualified name lookup (3.4.1 [basic.lookup.unqual]) or qualified name lookup (3.4.3 [basic.lookup.qual]), only function declarations from the template definition context are found.
For the part of the lookup using associated namespaces (3.4.2 [basic.lookup.argdep]), only function declarations found in either the template definition context or the template instantiation context are found.
If the function name is an unqualified-id and the call would be ill-formed or would find a better match had the lookup within the associated namespaces considered all the function declarations with external linkage introduced in those namespaces in all translation units, not just considering those declarations found in the template definition and template instantiation contexts, then the program has undefined behavior.
[Moved to DR at the October, 2012 meeting.]
In describing the partial ordering of function templates, 14.5.6.2 [temp.func.order] paragraph 3 says,
If only one of the function templates is a non-static member, that function template is considered to have a new first parameter inserted in its function parameter list. The new parameter is of type “reference to cv A,” where cv are the cv-qualifiers of the function template (if any) and A is the class of which the function template is a member. [Note: This allows a non-static member to be ordered with respect to a nonmember function and for the results to be equivalent to the ordering of two equivalent nonmembers. —end note]
The Standard appears to be silent as to whether the reference is an lvalue or rvalue reference; presumably that should be determined by the ref-qualifier of the member function, if any.
Proposed resolution (February, 2012):
Change 14.5.6.2 [temp.func.order] paragraph 3 as follows:
To produce the transformed template, for each type, non-type, or template template parameter (including template parameter packs (14.5.3 [temp.variadic]) thereof) synthesize a unique type, value, or class template respectively and substitute it for each occurrence of that parameter in the function type of the template. If only one of the function templates is a non-static member of some class A, that function template is considered to have a new first parameter inserted in its function parameter list. The Given cv as the cv-qualifiers of the function template (if any), the new parameter is of type “rvalue reference to cv A,” if the optional ref-qualifier of the function template is &&, or of type “lvalue reference to cv A” otherwise where cv are the cv-qualifiers of the function template (if any) and A is the class of which the function template is a member. [Note:...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
According to 14.6 [temp.res] paragraph 8,
Knowing which names are type names allows the syntax of every template definition to be checked. No diagnostic shall be issued for a template definition for which a valid specialization can be generated. If no valid specialization can be generated for a template definition, and that template is not instantiated, the template definition is ill-formed, no diagnostic required. If every valid specialization of a variadic template requires an empty template parameter pack, the template definition is ill-formed, no diagnostic required. If a type used in a non-dependent name...
It seems that these points could and should apply to template declarations that are not definitions, as well.
Proposed resolution (August, 2011):
Change 14.6 [temp.res] paragraph 8 as follows:
Knowing which names are type names allows the syntax of every template definition to be checked. No diagnostic shall be issued for a template definition for which a valid specialization can be generated. If no valid specialization can be generated for a template definition, and that template is not instantiated, the template definition is ill-formed, no diagnostic required. If every valid specialization of a variadic template requires an empty template parameter pack, the template definition is ill-formed, no diagnostic required. If a type used...
[Moved to DR at the April, 2013 meeting.]
Even though A::C is a nested type and member of the current instantiation, and thus dependent by the rules of 14.6.2.1 [temp.dep.type] paragraph 8, there does not seem to be a good reason for making it so:
struct B { struct C { }; }; template<typename T> struct A : B { A::C c; };
Proposed resolution (October, 2012):
[Some existing uses of the term “member of the current instantiation” are consistent with the definition in 14.6.2.1 [temp.dep.type] paragraph 4; others are intended to refer to members of the “current instantiation,” as defined in paragraph 1. The following resolution changes the latter to use the phrase “member of a class that is the current instantiation.”]
Change 14.6.2.1 [temp.dep.type] paragraph 4 as follows:
A name is a member of the current instantiation if it is
An unqualified name that, when looked up, refers to at least one member of a class that is the current instantiation or a non-dependent base class thereof. [Note: This can only occur when looking up a name in a scope enclosed by the definition of a class template. —end note]
A qualified-id in which the nested-name-specifier refers to the current instantiation and that, when looked up, refers to at least one member of a class that is the current instantiation or a non-dependent base class thereof. [Note: if no such member is found, and the current instantiation has any dependent base classes, then the qualified-id is a member of an unknown specialization; see below. —end note]
An id-expression denoting the member in a class member access expression (5.2.5 [expr.ref]) for which the type of the object expression is the current instantiation, and the id-expression, when looked up (3.4.5 [basic.lookup.classref]), refers to at least one member of a class that is the current instantiation or a non-dependent base class thereof. [Note: if no such member is found, and the current instantiation has any dependent base classes, then the id-expression is a member of an unknown specialization; see below. —end note]
[Example: ... —end example]
A name is a dependent member of the current instantiation if it is a member of the current instantiation which, when looked up, refers to at least one member of a class that is the current instantiation.
Change 14.6.2.1 [temp.dep.type] paragraph 5 as follows:
A name is a member of an unknown specialization if it is
A qualified-id in which the nested-name-specifier names a dependent type that is not the current instantiation.
A qualified-id in which the nested-name-specifier refers to the current instantiation, the current instantiation has at least one dependent base class, and name lookup of the qualified-id does not find any member of a class that is the current instantiation or a non-dependent base class thereof.
An id-expression denoting the member in a class member access expression (5.2.5 [expr.ref]) in which either
the type of the object expression is the current instantiation, the current instantiation has at least one dependent base class, and name lookup of the id-expression does not find a member of a class that is the current instantiation or a non-dependent base class thereof; or
the type of the object expression is dependent and is not the current instantiation.
Change 14.6.2.1 [temp.dep.type] paragraph 8 as follows:
A type is dependent if it is
...
a nested class or enumeration that is a dependent member of the current instantiation,
...
Change 14.6.2.2 [temp.dep.expr] paragraph 3 as follows:
An id-expression is type-dependent if it contains
...
or if it names a static data dependent member of the current instantiation that has is a static data member of type “array of unknown bound of T” for some T (14.5.1.3 [temp.static]). Expressions of the following forms...
Change 14.6.2.3 [temp.dep.constexpr] paragraph 2 as follows (assumes the base text is as modified by issue 1413):
An id-expression is value-dependent if:
it is a name declared with a dependent type,
it is the name of a non-type template parameter,
it names a member of an unknown specialization,
it names a static data member of the current instantiation that is a dependent member of the current instantiation and is not initialized in a member-declarator,
it names a static member function that is a dependent member of the current instantiation, or
it is a constant with literal type and is initialized with an expression that is value-dependent.
Expressions of the following form...
Change 14.6.2.3 [temp.dep.constexpr] paragraph 5 as follows (assumes the base text is as modified by issue 1413):
An expression of the form &qualified-id where the qualified-id's nested-name-specifier names a dependent member of the current instantiation is value-dependent.
[Moved to DR status at the April, 2013 meeting.]
Consider the following example:
void f(int*); void f(...); template <int N> void g() { f(N); } int main() { g<0>(); g<1>(); }
The call to f in g is not type-dependent, so the overload resolution must be done at definition time rather than at instantiation time. As a result, both of the calls to g will result in calls to f(...), i.e., N will not be a null pointer constant, even if the value of N is 0.
It would be most consistent to adopt a rule that a value-dependent expression can never be a null pointer constant, even in cases like
template <int N> void g() { int* p = N; }
This would always be ill-formed, even when N is 0.
John Spicer: It's clear that this treatment is required for overload resolution, but it seems too expansive given that there are other cases in which the value of a template parameter can affect the validity of the program, and an implementation is forbidden to issue a diagnostic on a template definition unless there are no possible valid specializations.
Notes from the July, 2009 meeting:
There was a strong consensus among the CWG that only the literal 0 should be considered a null pointer constant, not any arbitrary zero-valued constant expression as is currently specified.
Proposed resolution (October, 2012):
Change 4.10 [conv.ptr] paragraph 1 as follows:
A null pointer constant is an integral constant expression (5.19 [expr.const]) prvalue of integer type that evaluates to integer literal (2.14.2 [lex.icon]) with value zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted...
Change 5.19 [expr.const] paragraph 3 as follows:
...[Note: Such expressions may be used as array bounds (8.3.4 [dcl.array], 5.3.4 [expr.new]), as bit-field lengths (9.6 [class.bit]), as enumerator initializers if the underlying type is not fixed (7.2 [dcl.enum]), as null pointer constants (4.10 [conv.ptr]), and as alignments (7.6.2 [dcl.align]). —end note]...
Change 8.5 [dcl.init] paragraph 5 as follows:
To zero-initialize an object or reference of type T means:
if T is a scalar type (3.9 [basic.types]), the object is set to the value 0 (zero), taken as an integral constant expression, converted initialized to the value obtained by converting integer literal 0 (zero) to T; [Footnote: As specified in 4.10 [conv.ptr], converting an integral constant expression integer literal whose value is 0 to a pointer type results in a null pointer value. —end footnote]
...
Change 14.3.2 [temp.arg.nontype] paragraph 5 as follows:
...
for a non-type template-parameter of type pointer to object, qualification conversions (4.4 [conv.qual]) and the array-to-pointer conversion (4.2 [conv.array]) are applied; if the template-argument is of type std::nullptr_t, the null pointer conversion (4.10 [conv.ptr]) is applied. [Note: In particular, neither the null pointer conversion for a zero-valued integral constant expression integer literal (4.10 [conv.ptr]) nor the derived-to-base conversion (4.10 [conv.ptr]) are applied. Although 0 is...
...
Change 15.3 [except.handle] paragraph 3 as follows:
...[Note: A throw-expression whose operand is an integral constant expression of integer type that evaluates to integer literal with value zero does not match a handler of pointer or pointer to member type. —end note]. [Example: ...
Add a new section to C.2 [diff.cpp03] as follows:
C.2.x Clause 4: standard conversions [diff.cpp03.conv] 4.10 [conv.ptr]
Change: Only literals are integer null pointer constants
Rationale: Removing surprising interactions with templates and constant expressions
Effect on original feature: Valid C++ 2003 code may fail to compile or produce different results in this International Standard, as the following example illustrates:
void f(void *); // #1 void f(...); // #2 template<int N> void g() { f(0*N); // calls #2; used to call #1 }
Additional note (January, 2013):
Concerns were raised at the Portland (October, 2012) meeting that the value false has been used in existing code as a null pointer constant, and such code would be broken by this change. This issue has been returned to "review" status to allow discussion of whether to accommodate such code or not.
[Moved to DR at the April, 2013 meeting.]
The list of cases in 14.6.2.3 [temp.dep.constexpr] paragraph 2 in which an identifier is value-dependent should also include:
an entity with reference type and is initialized with an expression that is value-dependent
a member function or a static data member of the current instantiation
Proposed resolution (October, 2012):
Change 14.6.2.3 [temp.dep.constexpr] paragraph 2 as follows and move the text following the bulleted list into a new paragraph:
An identifier id-expression is value-dependent if it is:
it is a name declared with a dependent type,
it is the name of a non-type template parameter,
it names a member of an unknown specialization,
it names a static data member of the current instantiation that is not initialized in a member-declarator,
it names a static member function that is a member of the current instantiation, or
it is a constant with literal type and is initialized with an expression that is value-dependent.
Expressions of the following form...
Change 14.6.2.3 [temp.dep.constexpr] paragraph 5 as follows:
An id-expression is value-dependent if it names a member of an unknown specialization. An expression of the form &qualified-id where the qualified-id's nested-name-specifier names the current instantiation is value-dependent.
[Moved to DR at the April, 2013 meeting.]
According to 14.7.2 [temp.explicit] paragraph 8,
An explicit instantiation that names a class template specialization is also an explicit instantiation of the same kind (declaration or definition) of each of its members (not including members inherited from base classes) that has not been previously explicitly specialized in the translation unit containing the explicit instantiation, except as described below.
This could be read as an indication that member class templates and member function templates are instantiated (as templates) when their containing class template is instantiated. For example,
template<typename T> struct A { template<typename U> void f() { T t; t.f(); } }; template struct A<int>;
In this view, the result would be a member function template definition in class A<int> equivalent to
template<typename U> void f() { int t; t.f(); }
Such a template could never be validly instantiated and thus would presumably fall under the rule in 14.6 [temp.res] paragraph 8,
If no valid specialization can be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic required.
The wording of 14.7.2 [temp.explicit] paragraph 1 appears not to allow member templates to be instantiated as templates, however, mentioning only a “member template specialization” as a possibility:
A class, a function or member template specialization can be explicitly instantiated from its template. A member function, member class or static data member of a class template can be explicitly instantiated from the member definition associated with its class template.
This appears to be a contradiction, and although a diagnostic for a member template such as the example above would be helpful, most or all current implementations do not do so. Either the wording of paragraph 1 should be changed to allow explicit instantiation of a member template as a template, analogous to explicitly specializing a member template as a template, or paragraph 8 should be clarified to exclude member templates from the members explicitly instantiated when the containing class template is explicitly instantiated.
Proposed resolution (October, 2012):
Change 14.7.2 [temp.explicit] paragraph 8 as follows:
An explicit instantiation that names a class template specialization is also an explicit instantiation of the same kind (declaration or definition) of each of its members (not including members inherited from base classes and members that are templates) that has not been previously explicitly specialized in the translation unit containing the explicit instantiation, except as described below. [Note: In addition, it will typically be an explicit instantiation of certain implementation-dependent data about the class. —end note]
[Moved to DR at the October, 2012 meeting.]
Consider the following example:
template <int> struct X { typedef int type; }; template <class T> struct Y { }; template <class T> struct Z { static int const value = Y<T>::value; }; template <class T> typename X<Y<T>::value + Z<T>::value>::type f(T); int f(...); int main() { sizeof f(0); }
The problem here is that there is a combination of an invalid expression in the immediate context (Y<T>::value) and in the non-immediate context (within Z<T> when evaluating Z<T>::value). The Standard does not appear to state clearly whether this program is well-formed (because the error in the immediate context causes deduction failure) or ill-formed (because of the error in the non-immediate context).
Notes from the March, 2011 meeting:
Some members expressed a desire to allow implementations latitude in whether examples like this should be deduction failure or a diagnosable error, just as the order of evaluation of arithmetic operands is largely unconstrained. Others felt that specifying something like a depth-first left-to-right traversal of the expression or declaration would be better. Another possibility suggested was to enforce ordering only at comma operators. No consensus was achieved.
CWG agreed that the arguments should be processed in left-to-right order. Some popular existing code (e.g., Boost) depends on this ordering.
Proposed resolution (February, 2012):
Change 14.8.2 [temp.deduct] paragraph 7 as follows:
The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. The expressions include not only constant expressions such as those that appear in array bounds or as nontype template arguments but also general expressions (i.e., non-constant expressions) inside sizeof, decltype, and other contexts that allow non-constant expressions. The substitution proceeds in lexical order and stops when a condition that causes deduction to fail is encountered. [Note: The equivalent substitution in exception specifications is done only when the function is instantiated, at which point a program is ill-formed if the substitution results in an invalid type or expression. —end note] [Example:
template <class T> struct A { using X = typename T::X; }; template <class T> typename T::X f(typename A<T>::X); template <class T> void f(...) { } template <class T> auto g(typename A<T>::X) -> typename T::X; template <class T> void g(...) { } void h() { f<int>(0); // OK, substituting return type causes deduction to fail g<int>(0); // error, substituting parameter type instantiates A<int> }
—end example]
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
Proposed resolution (August, 2011):
Change 14.8.2 [temp.deduct] paragraph 5 as follows:
The resulting substituted and adjusted function type is used as the type of the function template for template argument deduction. If a template argument has not been deduced, its default template argument, if any, is used and its corresponding template parameter has a default argument, the template argument is determined by substituting the template arguments determined for preceding template parameters into the default argument. If the substitution results in an invalid type, as described above, type deduction fails. [Example:...
[Moved to DR at the April, 2013 meeting.]
The use of noexcept specifiers can cause instantiation of classes and functions that are not actually needed in the program, just to be able to complete the declaration. The actual value of the expression in the noexcept-specification is only needed if the function is defined (i.e., instantiated) or called, so it would significantly reduce the number of instantiations (and avoid certain kinds of errors when the value is currently required before a class is complete) if exception-specifications were treated like default arguments and only instantiated when they are actually needed.
Proposed resolution (February, 2012):
Change 14.5 [temp.decls] paragraph 2 as follows:
For purposes of name lookup and instantiation, default arguments and exception-specifications of function templates and default arguments and exception-specifications of member functions of class templates are considered definitions; each default argument or exception-specification is a separate definition which is unrelated to the function template definition or to any other default arguments or exception-specifications.
Change 14.6 [temp.res] paragraph 11 as follows:
[Note: For purposes of name lookup, default arguments and exception-specifications of function templates and default arguments and exception-specifications of member functions of class templates are considered definitions (14.5). —end note]
Add a new paragraph following 14.6.4.1 [temp.point] paragraph 2:
If a function template or member function of a class template is called in a way which uses the definition of a default argument of that function template or member function, the point of instantiation of the default argument is the point of instantiation of the function template or member function specialization.
For an exception-specification of a function template specialization or specialization of a member function of a class template, if the exception-specification is implicitly instantiated because it is needed by another template specialization and the context that requires it depends on a template parameter, the point of instantiation of the exception-specification is the point of instantiation of the specialization that requires it. Otherwise, the point of instantiation for such an exception-specification immediately follows the namespace scope declaration or definition that requires the exception-specification.
Change 14.7.1 [temp.inst] paragraph 1 as follows:
...The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions, or default arguments, or exception-specifications, of the class member functions, member classes...
Add a new paragraph following 14.7.1 [temp.inst] paragraph 13:
Each default argument is instantiated independently. [Example: ... —end example]
If the exception-specification of a specialization of a function template or member function of a class template has not yet been instantiated, but is needed (e.g., to instantiate the function definition, to evaluate a noexcept-expression (5.3.7 [expr.unary.noexcept]), or to compare against the exception-specification of another declaration), the dependent names are looked up, the semantic constraints are checked, and the instantiation of any template used in the exception-specification is done as if it were being done as part of instantiating the declaration of the specialization. An exception-specification is not instantiated in order to calculate the exception-specification of a defaulted function in a derived class until the exception-specification of the derived member function becomes necessary.
Change the note 14.8.2 [temp.deduct] paragraph 7 as follows:
...[Note: The equivalent substitution in exception specifications is done only when the function exception-specification is instantiated, at which point a program is ill-formed if the substitution results in an invalid type or expression. —end note]
Add a new paragraph following 15.4 [except.spec] paragraph 15:
A deallocation function (3.7.4.2 [basic.stc.dynamic.deallocation]) with no explicit exception-specification is treated as if it were specified with noexcept(true).
The exception-specification of a function template specialization is not instantiated along with the function declaration; it is instantiated when needed (14.7.1 [temp.inst]). The exception-specification of an implicitly-declared special member function is also evaluated as needed. [Note: Therefore, an implicit declaration of a member function of a derived class does not require the exception-specification of a base member function to be instantiated. —end note]
Notes from the February, 2012 meeting:
There should be a specific definition of when an exception-specification is needed and must thus be instantiated.
Additional discussion (September, 2012):
Daveed Vandevoorde brought up two additional points. First, the rule should be crafted so that an example like the following is ill-formed:
template<class T> T f() noexcept(sizeof(T) < 4); int main() { decltype(f<void>()) *p; }
Even though the exception-specification is not needed here, it should be instantiated (because of the unevaluated reference to f) in order to catch the ill-formed sizeof(T).
In addition, the proposed change creates an asymmetry between class templates and ordinary classes:
struct S {
void f() noexcept(sizeof(g()) < 8); // Invalid forward reference.
static int g();
};
but
template<typename> struct X {
void f() noexcept(sizeof(g()) < 8); // Okay.
static int g();
};
If the proposed change is adopted, the rules for exception-specifications in ordinary classes should be revised to make the parallel usage well-formed.
Proposed resolution (October, 2012):
Change 3.3.7 [basic.scope.class] paragraph 1 #1 as follows:
The potential scope of a name declared in a class consists not only of the declarative region following the name's point of declaration, but also of all function bodies, default arguments, exception-specifications, and brace-or-equal-initializers of non-static data members in that class (including such things in nested classes).
Change 3.4.1 [basic.lookup.unqual] paragraph 7 as follows:
A name used in the definition of a class X outside of a member function body, default argument, exception-specification, brace-or-equal-initializer of a non-static data member, or nested class definition29 shall be declared in one of the following ways:...
Change 3.4.1 [basic.lookup.unqual] paragraph 8 as follows:
For the members of a class X, a name used in a member function body, in a default argument, in an exception-specification, in the brace-or-equal-initializer of a non-static data member (9.2 [class.mem]), or in the definition of a class member outside of the definition of X, following the member's declarator-id31 , shall be declared in one of the following ways:...
Change 9.2 [class.mem] paragraph 2 as follows:
A class is considered a completely-defined object type (3.9 [basic.types]) (or complete type) at the closing } of the class-specifier. Within the class member-specification, the class is regarded as complete within function bodies, default arguments, exception-specifications, and brace-or-equal-initializers for non-static data members (including such things in nested classes). Otherwise it is regarded as incomplete within its own class member-specification.
Change 14.5 [temp.decls] paragraph 2 as follows:
For purposes of name lookup and instantiation, default arguments and exception-specifications of function templates and default arguments and exception-specifications of member functions of class templates are considered definitions; each default argument or exception-specification is a separate definition which is unrelated to the function template definition or to any other default arguments or exception-specifications.
Change 14.6 [temp.res] paragraph 11 as follows:
[Note: For purposes of name lookup, default arguments and exception-specifications of function templates and default arguments and exception-specifications of member functions of class templates are considered definitions (14.5 [temp.decls]). —end note]
Add the following as a new paragraph after 14.6.4.1 [temp.point] paragraph 2:
If a function template or member function of a class template is called in a way which uses the definition of a default argument of that function template or member function, the point of instantiation of the default argument is the point of instantiation of the function template or member function specialization.
For an exception-specification of a function template specialization or specialization of a member function of a class template, if the exception-specification is implicitly instantiated because it is needed by another template specialization and the context that requires it depends on a template parameter, the point of instantiation of the exception-specification is the point of instantiation of the specialization that requires it. Otherwise, the point of instantiation for such an exception-specification immediately follows the namespace scope declaration or definition that requires the exception-specification.
Change 14.7.1 [temp.inst] paragraph 1 as follows:
Unless a class template specialization has been explicitly instantiated (14.7.2 [temp.explicit]) or explicitly specialized (14.7.3 [temp.expl.spec]), the class template specialization is implicitly instantiated when the specialization is referenced in a context that requires a completely-defined object type or when the completeness of the class type affects the semantics of the program. The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions, or default arguments, or exception-specifications of the class member functions, member classes, scoped member enumerations, static data members and member templates; and it causes the implicit instantiation of the definitions of unscoped member enumerations and member anonymous unions. However, for the purpose...
Insert the following as a new paragraph immediately preceding 14.7.1 [temp.inst] paragraph 14:
The exception-specification of a function template specialization is not instantiated along with the function declaration; it is instantiated when needed (15.4 [except.spec]). If such an exception-specification is needed but has not yet been instantiated, the dependent names are looked up, the semantics constraints are checked, and the instantiation of any template used in the exception-specification is done as if it were being done as part of instantiating the declaration of the specialization at that point.
[Note: 14.6.4.1 [temp.point] defines the point of instantiation of a template specialization. —end note]
Change 14.8.2 [temp.deduct] paragraph 7 as follows:
The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. The expressions include not only constant expressions such as those that appear in array bounds or as nontype template arguments but also general expressions (i.e., non-constant expressions) inside sizeof, decltype, and other contexts that allow non-constant expressions. [Note: The equivalent substitution in exception specifications is done only when the function exception-specification is instantiated, at which point a program is ill-formed if the substitution results in an invalid type or expression. —end note]
Change 15.4 [except.spec] paragraph 2 as follows:
...A type denoted in an exception-specification shall not denote an incomplete type other than a class currently being defined. A type denoted in an exception-specification shall not denote a pointer or reference to an incomplete type, other than cv void* or a pointer or reference to a class currently being defined. A type cv T, “array of T”, or “function returning T” denoted in an exception-specification is adjusted to type T, “pointer to T”, or “pointer to function returning T” respectively.
Add the following as a new paragraph following 15.4 [except.spec] paragraph 15:
A deallocation function (3.7.4.2 [basic.stc.dynamic.deallocation]) with no explicit exception-specification is treated as if it were specified with noexcept(true).
An exception-specification is considered to be needed when:
in an expression, the function is the unique lookup result or the selected member of a set of overloaded functions (3.4 [basic.lookup], 13.3 [over.match], 13.4 [over.over]);
the function is odr-used (3.2 [basic.def.odr]) or, if it appears in an unevaluated operand, would be odr-used if the expression were potentially-evaluated;
the exception-specification is compared to that of another declaration (e.g. an explicit specialization or an overriding virtual function);
the function is defined; or
the exception-specification is needed for a defaulted special member function that calls the function. [Note: A defaulted declaration does not require the exception-specification of a base member function to be evaluated until the implicit exception-specification of the derived function is needed, but an explicit exception-specification needs the implicit exception-specification to compare against. —end note]
The exception-specification of a defaulted special member function is evaluated as described above only when needed; similarly, the exception-specification of a specialization of a function template or member function of a class template is instantiated only when needed.
[Note: this resolution reverses the decision in issue 1308.]
[Moved to DR at the April, 2013 meeting.]
The relationship between errors that render a program ill-formed but for which no diagnostic is required and things that cause deduction failure is not clearly specified. Presumably failures that need not be diagnosed cannot be the basis for SFINAE, lest different implementations perform deduction differently depending on how thoroughly they handle such cases. This should be spelled out explicitly.
Proposed resolution (October, 2012):
Change 14.8.2 [temp.deduct] paragraph 8 as follows:
If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written using the substituted arguments. [Note: If no diagnostic is required, the program is still ill-formed. Access checking is done as part of the substitution process. —end note] Only invalid types and expressions...
[Moved to DR at the October, 2012 meeting.]
Presumably 14.8.2.5 [temp.deduct.type] paragraph 5 should include a bullet for a function parameter or function parameter pack that follows a function parameter pack.
Proposed resolution (February, 2012):
Change 14.8.2.1 [temp.deduct.call] paragraph 1 as follows:
...For a function parameter pack that does not occur at the end of the parameter-declaration-list, the type of the parameter pack is a non-deduced context. When a function parameter pack appears in a non-deduced context (14.8.2.5 [temp.deduct.type]), the type of that parameter pack is never deduced. [Example:
template<class ... Types> void f(Types& ...); template<class T1, class ... Types> void g(T1, Types ...); template<class T1, class ... Types> void g1(Types ..., T1); void h(int x, float& y) { const int z = x; f(x, y, z); // Types is deduced to int, float, const int g(x, y, z); // T1 is deduced to int; Types is deduced to float, int g1(x, y, z); // error: Types is not deduced g1<int, int, int>(x, y, z); // OK, no deduction occurs }—end example]
This resolution also resolves issue 1399.
[Moved to DR at the October, 2012 meeting.]
Consider:
template <class... T> void f(T..., int, T...) { } int main() { f(0); // OK f<int>(0,0,0); // OK f(0,0,0); // error }
It seems clear that the third call is ill-formed because by the time we get to the second function parameter pack we've already assumed that T is empty, so deducing anything for T would be nonsensical. But I don't think this is expressed anywhere in the standard.
One way to handle this would be to say that a template parameter pack is not deducible if it is used in a function parameter pack not at the end of the parameter list.
Proposed resolution (February, 2012):
This issue is resolved by the resolution of issue 1388.
[Moved to DR at the October, 2012 meeting.]
According to 14.8.2.3 [temp.deduct.conv] paragraph 1,
Template argument deduction is done by comparing the return type of the conversion function template (call it P; see 8.5 [dcl.init], 13.3.1.5 [over.match.conv], and 13.3.1.6 [over.match.ref] for the determination of that type) with the type that is required as the result of the conversion (call it A) as described in 14.8.2.5 [temp.deduct.type].
It would seem that the cross-references should apply to the determination of the type “required as the result of the conversion” (i.e., A) instead of the return type of the conversion function.
Proposed resolution (February, 2012):
Change 14.8.2.3 [temp.deduct.conv] paragraph 1 as follows:
Template argument deduction is done by comparing the return type of the conversion function template (call it P; see 8.5 [dcl.init], 13.3.1.5 [over.match.conv], and 13.3.1.6 [over.match.ref] for the determination of that type) with the type that is required as the result of the conversion (call it A; see 8.5 [dcl.init], 13.3.1.5 [over.match.conv], and 13.3.1.6 [over.match.ref] for the determination of that type) as described in 14.8.2.5 [temp.deduct.type].
[Moved to DR at the October, 2012 meeting.]
Presumably 14.8.2.5 [temp.deduct.type] paragraph 5 should include a bullet for a decltype-specifier whose expression references a template parameter.
Proposed resolution (February, 2012):
Change 14.8.2.5 [temp.deduct.type] paragraph 5 as follows:
The non-deduced contexts are:
The nested-name-specifier of a type that was specified using a qualified-id.
The expression of a decltype-specifier.
A non-type template argument...
[Moved to DR at the October, 2012 meeting.]
There are a number of places in the Standard that appear to assume that exceptions are only thrown by throw-expressions. Various other constructs, such as dynamic_casts, typeid, new-expressions, etc., can also throw exceptions, so a more general term should be coined and applied in place of throw-expression wherever necessary.
Proposed resolution (February, 2012):
Change 3.7.4.1 [basic.stc.dynamic.allocation] paragraph 3 as follows:
...Any other allocation function that fails to allocate storage shall indicate failure only by throwing an exception (15.1 [except.throw]) of a type that would match a handler (15.3 [except.handle]) of type std::bad_alloc (18.6.2.1 [bad.alloc]).
Change 3.7.4.1 [basic.stc.dynamic.allocation] paragraph 4 as follows:
...[Note: In particular, a global allocation function is not called to allocate storage for objects with static storage duration (3.7.1 [basic.stc.static]), for objects or references with thread storage duration (3.7.2 [basic.stc.thread]), for objects of type std::type_info (5.2.8 [expr.typeid]), or for the copy of an object thrown by a throw expression an exception object (15.1 [except.throw]). —end note]
Change 5.2.7 [expr.dynamic.cast] paragraph 9 as follows:
The value of a failed cast to pointer type is the null pointer value of the required result type. A failed cast to reference type throws an exception (15.1 [except.throw]) of a type that would match a handler (15.3 [except.handle]) of type std::bad_cast (18.7.2 [bad.cast]).
Change 5.2.8 [expr.typeid] paragraph 2 as follows:
...If the glvalue expression is obtained by applying the unary * operator to a pointer68 and the pointer is a null pointer value (4.10 [conv.ptr]), the typeid expression throws the an exception (15.1 [except.throw]) of a type that would match a handler of type std::bad_typeid exception (18.7.3 [bad.typeid]).
Change 5.3.4 [expr.new] paragraph 7 as follows:
When the value of the expression in a noptr-new-declarator is zero, the allocation function is called to allocate an array with no elements. If the value of that expression is less than zero or such that the size of the allocated object would exceed the implementation-defined limit, or if the new-initializer is a braced-init-list for which the number of initializer-clauses exceeds the number of elements to initialize, no storage is obtained and the new-expression terminates by throwing throws an exception (15.1 [except.throw]) of a type that would match a handler (15.3 [except.handle]) of type std::bad_array_new_length (18.6.2.2 [new.badlength]).
Change 15 [except] paragraph 1 as follows:
...A handler will be invoked only by a throw-expression invoked throwing an exception in code executed in the handler's try block or in functions called from the handler's try block ...
Change 15 [except] paragraph 2 as follows:
A try-block is a statement (Clause 6 [stmt.stmt]). A throw-expression is of type void. Code that executes a throw-expression is said to “throw an exception;” code that subsequently gets control is called a “handler.” [Note:...
Change 15.1 [except.throw] paragraph 1 as follows:
Throwing an exception transfers control to a handler. [Note: An exception can be thrown from one of the following contexts: throw-expression (see below), allocation functions (3.7.4.1 [basic.stc.dynamic.allocation]), dynamic_cast (5.2.7 [expr.dynamic.cast]), typeid (5.2.8 [expr.typeid]), new-expression (5.3.4 [expr.new]), and standard library functions (17.5.1.4 [structure.specifications]). —end note] An object is passed and the type of that object determines which handlers can catch it. [Example:...
Change 15.1 [except.throw] paragraph 3 as follows:
A throw-expression Throwing an exception copy-initializes (8.5 [dcl.init],12.8 [class.copy] ) a temporary object, called the exception object, the type of which is determined by removing any top-level cv-qualifiers from the static type of the operand of throw and adjusting the type from “array of T” or “function returning T” to “pointer to T” or “pointer to function returning T”, respectively. The temporary is an lvalue and is used to initialize the variable named in the matching handler (15.3 [except.handle]). If the type of the exception object would be an incomplete type or a pointer to an incomplete type other than (possibly cv-qualified) void the program is ill-formed. Except for these restrictions and the restrictions on type matching mentioned in 15.3 [except.handle], the operand of throw is treated exactly as a function argument in a call (5.2.2 [expr.call]) or the operand of a return statement. Evaluating a throw-expression with an operand throws an exception; the type of the exception object is determined by removing any top-level cv-qualifiers from the static type of the operand and adjusting the type from “array of T” or “function returning T” to “pointer to T” or “pointer to function returning T,” respectively.
Change 15.1 [except.throw] paragraph 4 as follows:
...[Note: an a thrown exception thrown by a throw-expression does not propagate to other threads unless caught, stored, and rethrown using appropriate library functions; see 18.8.5 [propagation] and 30.6 [futures]. —end note]
Change 15.1 [except.throw] paragraph 8 as follows:
A throw-expression with no operand rethrows the currently handled exception (15.3 [except.handle]). The exception is reactivated with the existing temporary exception object; no new temporary exception object is created. The exception is no longer considered to be caught; therefore, the value of std::uncaught_exception() will again be true. [Example:...
Change 15.2 [except.ctor] paragraph 1 as follows:
As control passes from a throw-expression the point where an exception is thrown to a handler, destructors are invoked for all automatic objects constructed since the try block was entered...
Change 15.2 [except.ctor] paragraph 3 as follows:
The process of calling destructors for automatic objects constructed on the path from a try block to a throw-expression the point where an exception is thrown is called “stack unwinding.” If a destructor...
Change 15.3 [except.handle] paragraph 17 as follows:
When the handler declares a non-constant an object, any changes to that object will not affect the temporary object that was initialized by execution of the throw-expression exception object. When the handler declares a reference to a non-constant an object, any changes to the referenced object are changes to the temporary object initialized when the throw-expression was executed exception object and will have effect should that object be rethrown.
Change 18.8.3.4 [terminate] paragraph 1 as follows:
Remarks: Called by the implementation when exception handling must be abandoned for any of several reasons (15.5.1 [except.terminate]), in effect immediately after evaluating the throw-expression (18.8.3.1 [terminate.handler]) throwing the exception. May also be called directly by the program.
[Moved to DR at the April, 2013 meeting.]
According to 15.1 [except.throw] paragraph 7,
If the exception handling mechanism, after completing evaluation of the expression to be thrown but before the exception is caught, calls a function that exits via an exception, std::terminate is called (15.5.1 [except.terminate]).
This wording was overlooked in the resolution for issue 475 and should be changed, along with the following example, to indicate that std::terminate will be called for an uncaught exception only after initialization of the exception object is complete.
Proposed resolution (August, 2012):
Change 15.1 [except.throw] paragraph 7 as follows:
If the exception handling mechanism, after completing evaluation of the expression to be thrown the initialization of the exception object but before the exception is caught activation of a handler for the exception, calls a function that exits via an exception, std::terminate is called (15.5.1 [except.terminate]). [Example:
struct C { C() { } C(const C&) { throw 0; } if (std::uncaught_exception()) { throw 0; // throw during copy to handler's exception-declaration object (15.3 [except.handle]) } } }; int main() { try { throw C(); // calls std::terminate() if construction of the handler's // exception-declaration object is not elided (12.8 [class.copy]) } catch(C) { } }—end example]
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
I have a question about exception handling with respect to derived to base conversions of pointers caught by reference.
What should the result of this program be?
struct S {}; struct SS : public S {}; int main() { SS ss; int result = 0; try { throw &ss; // throw object has type SS* // (pointer to derived class) } catch (S*& rs) // (reference to pointer to base class) { result = 1; } catch (...) { result = 2; } return result; }
The wording of 15.3 [except.handle] paragraph 3 would seem to say that the catch of S*& does not match and so the catch ... would be taken.
All of the compilers I tried (EDG, g++, Sun, and Microsoft) used the catch of S*& though.
What do we think is the desired behavior for such cases?
My initial reaction is that this is a bug in all of these compilers, but the fact that they all do the same thing gives me pause.
On a related front, if the handler changes the parameter using the reference, what is caught by a subsequent handler?
extern "C" int printf(const char *, ...); struct S {}; struct SS : public S {}; SS ss; int f() { try { throw &ss; } catch (S*& rs) // (reference to pointer to base class) { rs = 0; throw; } catch (...) { } return 0; } int main() { try { f(); } catch (S*& rs) { printf("rs=%p, &ss=%p\n", rs, &ss); } }
EDG, g++, and Sun all catch the original (unmodified) value. Microsoft catches the modified value. In some sense the EDG/g++/Sun behavior makes sense because the later catch could catch the derived class instead of the base class, which would be difficult to do if you let the catch clause update the value to be used by a subsequent catch.
But on this non-pointer case, all of the compilers later catch the modified value:
extern "C" int printf(const char *, ...); int f() { try { throw 1; } catch (int& i) { i = 0; throw; } catch (...) { } return 0; } int main() { try { f(); } catch (int& i) { printf("i=%p\n", i); } }
To summarize:
(See also issue 729.)
Notes from the October, 2009 meeting:
The consensus of the CWG was that it should not be possible to catch a pointer to a derived class using a reference to a base class pointer, and that a handler that takes a reference to non-const pointer should allow the pointer to be modified by the handler.
Proposed resolution (March, 2010):
Change 15.3 [except.handle] paragraph 3 as follows:
A handler is a match for an exception object of type E if
The handler is of type cv T or cv T& and E and T are the same type (ignoring the top-level cv-qualifiers), or
the handler is of type cv T or cv T& and T is an unambiguous public base class of E, or
the handler is of type cv1 T* cv2 or const T& where T is a pointer type and E is a pointer type that can be converted to the type of the handler T by either or both of
a standard pointer conversion (4.10 [conv.ptr]) not involving conversions to pointers to private or protected or ambiguous classes
a qualification conversion
the handler is of type cv T or const T& where T is a pointer or pointer to member type and E is std::nullptr_t.
(This resolution also resolves issue 729.)
Notes from the March, 2011 meeting:
This resolution would require an ABI change and was thus deferred for further consideration.
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
Given the following example:
int f() { try { /* ... */ } catch(const int*&) { return 1; } catch(int*&) { return 2; } return 3; }
can f() return 2? That is, does an int* exception object match a const int*& handler?
According to 15.3 [except.handle] paragraph 3, it does not:
A handler is a match for an exception object of type E if
The handler is of type cv T or cv T& and E and T are the same type (ignoring the top-level cv-qualifiers), or
the handler is of type cv T or cv T& and T is an unambiguous public base class of E, or
the handler is of type cv1 T* cv2 and E is a pointer type that can be converted to the type of the handler by either or both of
a standard pointer conversion (4.10 [conv.ptr]) not involving conversions to pointers to private or protected or ambiguous classes
a qualification conversion
the handler is a pointer or pointer to member type and E is std::nullptr_t.
Only the third bullet allows qualification conversions, but only the first bullet applies to a handler of reference-to-pointer type. This is consistent with how other reference bindings work; for example, the following is ill-formed:
int* p; const int*& r = p;
(The consistency is not complete; the reference binding would be permitted if r had type const int* const &, but a handler of that type would still not match an int* exception object.)
However, implementation practice seems to be in the other direction; both EDG and g++ do match an int* with a const int*&, and the Microsoft compiler issues an error for the presumed hidden handler in the code above. Should the Standard be changed to reflect existing practice?
(See also issue 388.)
Notes from the October, 2009 meeting:
The CWG agreed that matching the exception object with a handler should, to the extent possible, mimic ordinary reference binding in cases like this.
Proposed resolution (February, 2010):
This issue is resolved by the resolution of issue 388.
[Moved to DR at the October, 2012 meeting.]
The types that may appear in an exception-specification (15.4 [except.spec] paragraph 2) include rvalue reference types, although they are excluded as handler types (15.3 [except.handle] paragraph 1). This appears to have been an oversight.
Proposed resolution (February, 2012):
Change 15.4 [except.spec] paragraph 2 as follows:
...A type denoted in an exception-specification shall not denote an incomplete type or an rvalue reference type. A type denoted in an exception-specification shall not denote a pointer or reference...
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
It is not clear whether the unexpected handler will be invoked in the following example:
#include <iostream> #include <exception> struct A { ~A() throw() { } }; struct B { ~B() noexcept { } }; struct C : A, B { ~C() { throw 0; } }; void unexpected_observer() { std::cerr << "unexpected called" << std::endl; std::terminate(); } int main() { std::set_unexpected(unexpected_observer); C c; }
The problem is 15.4 [except.spec] paragraph 14 only says that the exception-specification of C::~C “shall allow no exceptions,” which could mean either throw() or noexcept(true).
Proposed resolution (August, 2011):
Change 15.4 [except.spec] paragraph 14 as follows:
An implicitly declared special member function (Clause 12 [special]) shall have has an exception-specification. If f is an implicitly declared default constructor, copy constructor, move constructor, destructor, copy assignment operator, or move assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f's implicit definition; f shall allow allows all exceptions if any function it directly invokes allows all exceptions, and f shall allow no exceptions has the exception-specification noexcept(true) if every function it directly invokes allows no exceptions. [Example:
struct A { A(); A(const A&) throw(); A(A&&) throw(); ~A() throw(X); }; struct B { B() throw(); B(const B&) throw(); B(B&&) throw(Y); ~B() throw(Y); }; struct D : public A, public B { // Implicit declaration of D::D(); // Implicit declaration of D::D(const D&) throw() noexcept(true); // Implicit declaration of D::D(D&&) throw(Y); // Implicit declaration of D::~D() throw(X, Y); };Furthermore, if...
[Moved to DR at the October, 2012 meeting.]
According to 15.4 [except.spec] paragraph 14,
An implicitly declared special member function (Clause 12 [special]) shall have an exception-specification. If f is an implicitly declared default constructor, copy constructor, move constructor, destructor, copy assignment operator, or move assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f's implicit definition; f shall allow all exceptions if any function it directly invokes allows all exceptions, and f shall allow no exceptions if every function it directly invokes allows no exceptions.
It would be clearer if this description made explicit the intent that special member functions that invoke no other functions are to allow no exceptions.
Proposed resolution (February, 2012):
Change 15.4 [except.spec] paragraph 14 as follows:
...and f has the exception-specification noexcept(true) if every function it directly invokes allows no exceptions. [Note: It follows that f has the exception-specification noexcept(true) if it invokes no other functions. —end note] [Example:
struct A { A(); A(const A&) throw(); A(A&&) throw(); ~A() throw(X); }; struct B { B() throw(); B(const B&) throw() = default; // Declaration of B::B(const B&) noexcept(true) B(B&&) throw(Y); ~B() throw(Y); }; ...
[Moved to DR at the October, 2012 meeting.]
16.3 [cpp.replace] paragraph 12 says,
If there is a ... in the identifier-list in the macro definition...
However, an identifier-list cannot contain an ellipsis according to the grammar in 16 [cpp] paragraph 1.
Proposed resolution (February, 2012):
Change 16.3 [cpp.replace] paragraph 12 as follows:
If there is a ... in the identifier-list immediately preceding the ) in the function-like macro definition, then the trailing arguments, including any separating comma preprocessing tokens, are merged to form a single item: the variable arguments variable arguments. The number of arguments so combined is such that, following merger, the number of arguments is one more than the number of parameters in the macro definition (excluding the ...).
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
It is not clear whether the implementation limit on recursion in template instantiation applies only to instantiation itself or also to recursion that occurs during template argument deduction.
Proposed resolution (August, 2011):
Change B [implimits] as follows:
Recursively nested template instantiations, including substitution during template argument deduction (14.8.2 [temp.deduct]) [1 024].
[Voted into the WP at the February, 2012 meeting; moved to DR at the October, 2012 meeting.]
The incompatibility described appears not to exist.
Proposed resolution (August, 2011):
Delete the second entry in C.1.3 [diff.conv], i.e., the one headed by
Change: Only pointers to non-const and non-volatile objects may be implicitly converted to void*
[Moved to DR at the April, 2013 meeting.]
During the discussion of issues 167 and 174, it became apparent that there was no consensus on the meaning of deprecation. Some thought that deprecating a feature reflected an intent to remove it from the language. Others viewed it more as an encouragement to programmers not to use certain constructs, even though they might be supported in perpetuity.
There is a formal-sounding definition of deprecation in Annex D [depr] paragraph 2:
deprecated is defined as: Normative for the current edition of the Standard, but not guaranteed to be part of the Standard in future revisions.However, this definition would appear to say that any non-deprecated feature is "guaranteed to be part of the Standard in future revisions." It's not clear that that implication was intended, so this definition may need to be amended.
This issue is intended to provide an avenue for discussing and resolving those questions, after which the original issues may be reopened if that is deemed desirable.
Proposed resolution (August, 2012):
Change D [depr] paragraph 2 as follows:
These are deprecated features, where deprecated is defined as: Normative for the current edition of the Standard, but not guaranteed to be part of the Standard in having been identified as a candidate for removal from future revisions.
The Lao character 0e0d should be 0e8d. 0e0d is both out of order and already used in the Thai characters.
Proposed resolution (10/99): As suggested.
22.4.1.1.2 [locale.ctype.virtuals] paragraph 13 states a constraint on the values of the characters representing the decimal digits in the execution character set:
for any digit character c, the expression (do_narrow( c, dfault)-'0') evaluates to the digit value of the character.This requirement is not reflected in the description of the execution character set (2.3 [lex.charset] paragraph 3).
Proposed resolution (10/00):
In 2.3 [lex.charset] paragraph 3, after the sentence
For each basic execution character set, the values of the members shall be non-negative and distinct from one another.insert the following:
In both the source and execution basic character sets, the value of each character after 0 in the above list of decimal digits shall be one greater than the value of the previous.
Footnotes 26 and 29 both use the phrase "following the function declarator" incorrectly: the function declarator includes the parameter list, but the footnotes make clear that they intend what's said to apply to names inside the parameter list. Presumably the phrase should be "following the function declarator-id."
Proposed Resolution (04/99): Change the text in 3.4.1 [basic.lookup.unqual] paragraph 6 from:
A name used in the definition of a function [footnote: This refers to unqualified names following the function declarator; such a name may be used as a type or as a default argument name in the parameter-declaration-clause, or may be used in the function body. end footnote] that is ...to:
A name used in the definition of a function following the function's declarator-id [footnote: This refers to unqualified names that occur, for instance, in a type or default argument expression in the parameter-declaration-clause or used in the function body. end footnote] that is ...Change the text in 3.4.1 [basic.lookup.unqual] paragraph 8 from:
A name used in the definition of a function that is a member function (9.3 [class.mfct] ) [footnote: That is, an unqualified name following the function declarator; such a name may be used as a type or as a default argument name in the parameter-declaration-clause, or may be used in the function body, or, if the function is a constructor, may be used in the expression of a mem-initializer. end footnote] of class X shall be ...to:
A name used in the definition of a member function (9.3 [class.mfct] ) of class X following the function's declarator-id [footnote: That is, an unqualified name that occurs, for instance, in a type or default argument expression in the parameter-declaration-clause, in the function body, or in an expression of a mem-initializer in a constructor definition. end footnote] shall be ...
If an argument used for lookup is the address of a group of overloaded functions, are there any associated namespaces or classes? What if it's the address of a function template?
My inclination is to say no to both.
From Mike Miller:
We discussed this on the reflector a few weeks ago. I'll leave the template case for the Core III experts, but I'd find it surprising if the overload case weren't handled as the obvious generalization of the single-function case. For a single function, the associated namespaces are those of the types used in the parameters and return type; I would expect that using an overloaded function name would simply be the union of the namespaces from the members of the overload set. That would be the simplest and most intuitive, IMHO — is there an argument for doing it differently?
Proposed Resolution (04/99): In 3.4.2 [basic.lookup.argdep] paragraph 2, add following the last bullet in the list of associated classes and namespaces for various argument types (not a bullet itself because overload sets and templates do not have a type):
In addition, if the argument is the name or address of a set of overloaded functions and/or function templates, its associated classes and namespaces are the union of those associated with each of the members of the set: the namespace in which the function or function template is defined and the classes and namespaces associated with its (non-dependent) parameter types and return type.
Section 3.4.2 [basic.lookup.argdep] includes the following:
struct A { union U {}; friend void f(U); }; struct B { struct S {}; friend void f(S); }; int main() { A::U u; f(u); // okay: A is an associated class B::S s; f(s); // error: no matching f(), B is not an associated class }Certainly the enclosing class should also be an associated class for nested class types, shouldn't it?
Proposed Resolution (10/99): Change the two referenced bullets to read:
The description of Koenig lookup in 3.4.2 [basic.lookup.argdep] paragraph 1 says,
...other namespaces not considered during the usual unqualified lookup (3.4.1 [basic.lookup.unqual] ) may be searched.Does this mean that Koenig lookup does not search namespaces that were already searched during the usual unqualified lookup? The answer is academic except for the two-stage lookup during template instantiation. If a given namespace is searched in the context of the template definition, are declarations in that namespace in the instantiation context ignored during the Koenig lookup? For instance,
void f(int); template <class T> void g(T t) { f(t); } enum E { e }; void f(E); void h() { g(e); }In this example, the call f(t) in the template function will resolve to f(E) if Koenig lookup reexamines already-searched namespaces and to f(int) if not.
Proposed Resolution (10/00):
Immediately preceding the example at the end of 3.4.2 [basic.lookup.argdep] paragraph 2, add the following:
[Note: the namespaces and classes associated with the argument types can include namespaces and classes already considered by the ordinary unqualified lookup.]
In 3.4.4 [basic.lookup.elab] paragraph 3, there is the example
struct Base { // ... struct Data { /* ... */ }; // Defines nested Data struct Data; // OK: Redeclares nested Data };The final redeclaration is invalid according to 9.2 [class.mem] paragraph 1 last sentence.
Proposed resolution (10/00): Remove the line
struct Data; // OK: Redeclares nested Data
See also Core issue 36 and Core issue 56.
A reference is rebindable. This is surprising and unnatural. This can also cause subtle optimizer bugs.Example:
struct T { int& ri; T (int& r) : ri (r) { } }; void bar (T*); void foo () { int i; T x (i); x.ri = 3; // the optimizer understands that this is really i = 3 bar (&x); x.ri = 4; // optimizer assumes that this writes to i, but this is incorrect } int gi; void bar (T* p) { p->~T (); new (p) T (gi); }If we replace T& with T* const in the example then undefined behavior result and the optimizer is correct.Proposal: make T& equivalent to T* const by extending the scope of 3.8 [basic.life] paragraph 9 to references.
(See also J16/99-0005 = WG21 N1182, "Proposed Resolutions for Core Language Issues 6, 14, 20, 40, and 89")
In addition, Lisa Lippincott pointed out the following example:
void f( const bool * ); void g(); int main() { const bool *b = new const bool( false ); f(b); if (*b) g(); } void f( const bool *b ) { new ( const_cast<bool *>(b) ) const bool( true ); }
The proposed wording in the paper would still permit this usage and thus prevent an optimizer from eliminating the call to g().
Proposed Resolution (10/00):
Add a new bullet to the list of restrictions in 3.8 [basic.life] paragraph 7, following the second bullet ("the new object is of the same type..."):
The text of 3.8 [basic.life] paragraph 2 currently reads,
The phrase "an object of type" is obviously incorrect. I believe it should read "an object of POD type." Does anyone disagree?
Proposed Resolution (10/99): As suggested.
Can you use memcpy on non-member POD subobjects of non-POD objects?
In 3.9 [basic.types] paragraphs 2 and 3 we have:
For any complete POD object type T, whether or not the object holds a valid value of type T, the underlying bytes (1.7 [intro.memory] ) making up the object can be copied into an array of char or unsigned char*. If the content of the array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value. [Example elided]Paragraph 3 doesn't repeat the restriction of paragraph 2. Should it be assumed? Otherwise only complete POD types are copyable to an array of char and back, but scribbling over subobjects is OK. (Or perhaps a "distinct T object" is a complete object...)*[Footnote: By using, for example, the library functions (17.6.1.2 [headers] ) memcpy or memmove. end footnote]For any POD type T, if two pointers to T point to distinct T objects obj1 and obj2, if the value of obj1 is copied into obj2, using the memcpy library function, obj2 shall subsequently hold the same value as obj1.
Proposed Resolution (04/99): Change the text in 3.9 [basic.types] paragraph 2 from:
For any complete POD object type T, ...to:
For any object (other than a base class subobject) of POD type T, ...Change the text in 3.9 [basic.types] paragraph 3 from:
For any POD type T, if two pointers to T point to distinct T objects obj1 and obj2,to:
For any POD type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base class subobject, ...
The Standard uses confusing terminology when referring to accessibility in connection with ambiguity. For instance:
4.10 [conv.ptr] paragraph 3:
If B is an inaccessible or ambiguous base ...5.2.7 [expr.dynamic.cast] paragraph 8:
... has an unambiguous public base ...10.3 [class.virtual] paragraph 5:
... is an unambiguous direct or indirect base ... and is accessible ...15.3 [except.handle] paragraph 3:
not involving conversions to pointers to private or protected or ambiguous classes
The phrase "unambiguous public base" is unfortunate as it could mean either "an unambiguous base not considering accessibility, which is public" or "an unambiguous base considering only the publicly accessible bases." I believe the former interpretation correct, as accessibility is applied after visibility (11 [class.access] paragraph 4) and ambiguity is described in terms of visibility (10.2 [class.member.lookup] paragraph 2).
Suggested Resolution: Use the phrases "public and unambiguous," "accessible and unambiguous," "non-public or ambiguous," or "inaccessible or ambiguous" as appropriate.
Proposed resolution (10/00):
A nested-name-specifier that names a class, optionally followed by the keyword template (14.8.1 [temp.arg.explicit] ), ...The use of the template keyword in this context is discussed in 14.2 [temp.names] , not 14.8.1 [temp.arg.explicit] .
From paper J16/99-0010 = WG21 N1187.
5.1.1 [expr.prim.general] paragraph 7 says that class-name::class-name names the constructor when both class-name refer to the same class. (Note the different perspective, at least, in 12.1 [class.ctor] paragraph 1, in which constructors have no names and are recognized by syntactic context rather than by name.)
This formulation does not address the case of classes in which a function template is declared as a constructor, for example:
template <class T> struct A { template <class T2> A(T2); }; template<> template<> A<int>::A<int>(int);
Here there is an ambiguity as to whether the second template argument list is for the injected class name or for the constructor.
Suggested resolution: restate the rule as a component of name lookup. Specifically, if when doing a qualified lookup in a given class you look up a name that is the same as the name of the class, the entity found is the constructor and not the injected class name. In all other cases, the name found is the injected class name. For example:
class B { }; class A: public B { A::B ab; // B is the inherited injected B A::A aa; // Error: A::A is the constructor };
Without this rule some very nasty backtracking is needed. For example, if the injected class name could be qualified by its own class name, the following code would be well-formed:
template <class T> struct A { template <class T2> A(T2); static A x; }; template<> A<int>::A<int>(A<int>::x);
Here the declarator for the definition of the static data member has redundant parentheses, and it's only after seeing the declarator that the parser can know that the second A<int> is the injected class name rather than the constructor.
Proposed resolution (10/00):
In 9 [class] paragraph 2, change
The class-name is also inserted into the scope of the class itself. For purposes of access checking the inserted class name...
to
The class-name is also inserted into the scope of the class itself; this is known as the injected-class-name. For purposes of access checking, the injected-class-name...
Also, in 3.4.3.1 [class.qual], add the following before paragraph 2:
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. Such a constructor name shall only be used in the declarator-id of a constructor definition that appears outside of the class definition. [Example:struct A { A(); }; struct B: public A { B(); }; A::A() { } B::B() { } B::A ba; // object of type A A::A a; // error, A::A is not a type name—end example]
Also, change 3.4 [basic.lookup] paragraph 3 from
Because the name of a class is inserted in its class scope (clause 9 [class]), the name of a class is also considered a member of that class for the purposes of name hiding and lookup.
to
The injected-class-name of a class (clause 9 [class]) is also considered to be a member of that class for the purposes of name hiding and lookup.
(See also issue 194.)
5.2.5 [expr.ref] paragraph 4 should make it clear that when a nonstatic member is referenced in a member selection operation, the type of the left operand is implicitly cast to the naming class of the member. This allows for the detection of access and ambiguity errors on that implicit cast.
Proposed Resolution (10/00):
In 11.2 [class.access.base] paragraph 4, remove the following from the second note:
If the member m is accessible when named in the naming class according to the rules below, the access to m is nonetheless ill-formed if the type of p cannot be implicitly converted to type T (for example, if T is an inaccessible base class of p's class).
Add the following as a new paragraph 5 of 11.2 [class.access.base]:
If a class member access operator, including an implicit "this->," is used to access a nonstatic data member or nonstatic member function, the reference is ill-formed if the left operand (considered as a pointer in the "." operator case) cannot be implicitly converted to a pointer to the naming class of the right operand. [Note: this requirement is in addition to the requirement that the member be accessible as named.]
In 11.2 [class.access.base] paragraph 4, fix a typographical error by adding the missing right parenthesis following the text
(including cases where an implicit "this->" is added
Add following the first sentence of 5.2.2 [expr.call] paragraph 4:
If the function is a nonstatic member function, the "this" parameter of the function (9.3.2 [class.this]) shall be initialized with a pointer to the object of the call, converted as if by an explicit type conversion (5.4 [expr.cast]). [Note: there is no access checking on this conversion; the access checking is done as part of the (possibly implicit) class member access operator. See 11.2 [class.access.base].]
Section 5.2.9 [expr.static.cast] paragraph 6 should make it clear that when any of the "inverse of any standard conversion sequence" static_casts are done, the operand undergoes the lvalue-to-rvalue conversions first.
Proposed Resolution (10/00):
In 5.2.9 [expr.static.cast] paragraph 6, change
can be performed explicitly using static_cast subject to the restriction that the explicit conversion does not cast away constness (5.2.11 [expr.const.cast]), ...
to
can be performed explicitly using static_cast. The lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), and function-to-pointer (4.3 [conv.func]) conversions are applied to the operand. Such a static_cast is subject to the restriction that it does not cast away constness (5.2.11 [expr.const.cast]), ...
According to 7.2 [dcl.enum] paragraph 9, it is permitted to convert from one enumeration type to another. However, neither 5.2.9 [expr.static.cast] nor 5.4 [expr.cast] allows this conversion.
Proposed resolution (10/00): Change the first two sentences of 5.2.9 [expr.static.cast] paragraph 7 to read
A value of integral or enumeration type can be explicitly converted to an enumeration type. The value is unchanged if the original value is within the range of the enumeration values (7.2 [dcl.enum] ).
According to 5.2.9 [expr.static.cast] paragraph 10,
An rvalue of type "pointer to cv void" can be explicitly converted to a pointer to object type.No requirements are stated regarding the cv-qualification of the pointer to object type. Contrast this with the formula used in paragraphs 5, 8, and 9, where the treatment of cv-qualification is explicit, requiring that the target type be at least as cv-qualified as the source. There is an apparently general requirement on all forms of static_cast in 5.2.9 [expr.static.cast] paragraph 1 that it "shall not cast away constness." Assuming that this restriction applies to paragraph 10, since there is no explicit exception to the general rule, that still leaves open the question of whether one can "cast away volatility" in a conversion from volatile void* to a pointer to object type. Should 5.2.9 [expr.static.cast] paragraph 10 be rewritten to handle cv-qualification in the same way as paragraphs 5, 8, and 9?
Proposed resolution (10/00):
Change the first sentence of 5.2.9 [expr.static.cast] paragraph 10 to
An rvalue of type "pointer to cv1 void" can be converted to an rvalue of type "pointer to cv2 T", where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1.
5.3.4 [expr.new] paragraph 6 says:
The expression in a direct-new-declarator shall have integral type (3.9.1 [basic.fundamental] ) with a non-negative value.I assume the intent was to also allow enumeral types, as we do in 5.2.1 [expr.sub] ?
Proposed Resolution (10/99): Replace "integral type" by "integral or enumeration type" in 5.3.4 [expr.new] paragraph 6.
If a placement allocation function has default arguments for all its parameters except the first, it can be called using non-placement syntax. In such a case, it is not clear whether the deallocation function to be called if the constructor terminates by throwing an expression is determined on the basis of the syntax of the new-expression (i.e., a non-placement deallocation function) or the declaration of the selected (placement) allocation function. 5.3.4 [expr.new] paragraph 19 indicates that the deallocation function must match the declaration of the allocation function. However, 15.2 [except.ctor] says that the distinction is based on whether the new-expression contains a new-placement or not.
Proposed resolution (10/00):
In 15.2 [except.ctor] paragraph 2, replace
If the object or array was allocated in a new-expression and the new-expression does not contain a new-placement, the deallocation function (3.7.4.2 [basic.stc.dynamic.deallocation], 12.5 [class.free]) is called to free the storage occupied by the object; the deallocation function is chosen as specified in 5.3.4 [expr.new]. If the object or array was allocated in a new-expression and the new-expression contains a new-placement, the storage occupied by the object is deallocated only if an appropriate placement operator delete is found, as specified in 5.3.4 [expr.new].
with
If the object or array was allocated in a new-expression, the matching deallocation function (3.7.4.2 [basic.stc.dynamic.deallocation], 5.3.4 [expr.new], 12.5 [class.free]), if any, is called to free the storage occupied by the object.
See also issue 429.
5.7 [expr.add] paragraph 8 explicitly allows subtraction of two pointers to functions:
If two pointers point to the same object or function... and the two pointers are subtracted...However, 5.7 [expr.add] paragraph 2 requires that two pointers that are subtracted be pointers to an object type; function pointers are not allowed.
Being able to subtract two pointers to functions doesn't seem terribly useful, especially considering that subtracting two pointers to different functions appears to produce undefined behavior rather than simply a non-zero result, according to paragraph 6:
Unless both pointers point to elements of the same array object, or one past the last element of the array object, the behavior is undefined.
Proposed resolution (10/00):
Remove the words or function from paragraph 8.
Nathan Myers: In 5.10 [expr.eq] , we have:
Pointers to objects or functions of the same type (after pointer conversions) can be compared for equality. Two pointers of the same type compare equal if and only if they are both null, both point to the same object or function, or both point one past the end of the same array.What does this say, when we have
int i[1]; int j[1];about the expression (i+1 == j) ? It seems to require padding between i[0] and j[0] so that the comparison will come out false.
Mike Miller: I think this is reading more into the statement in 5.10 [expr.eq] paragraph 1 than is actually there. What does it mean for a pointer to "point to" an object? I can't find anything that definitively says that i+1 cannot "point to" j[0] (although it's obviously not required to do so). If i+1 is allowed to "point to" j[0], then i+1==j is allowed to be true, and there's no defect. There are places where aliasing is forbidden, but the N+1th element of an array doesn't appear to be one of them.
To put it another way, "points to" is undefined in the Standard. The only definition I can think of that encompasses the possible ways in which a pointer can get its value (e.g., the implementation-defined conversion of an arbitrary integer value to a pointer) is that it means "having the same value representation as would be produced by applying the (builtin) & operator to an lvalue expression designating that object". In other words, if the bits are right, it doesn't matter how you produced the value, as long as you didn't perform any operations that have undefined results. The expression i+1 is not undefined, so if the bits of i+1 are the same as those of &j[0], then i+1 "points to" j[0] and i+i==j is allowed to be true.
Tom MacDonald: C9X contains the following words for the "==" operator:
Two pointers compare equal if both are null pointers, both are pointers to the same object (including a pointer to an object and a subobject at its beginning) or function, both are pointers to one past the last element of the same array object, or one is a pointer to one past the end of one array object and the other is a pointer to the start of a different array object that happens to immediately follow the first array object in the address space.Matt Austern: I don't think there's anything wrong with saying that the result of
int x[1]; int y[1]; std::cout << (y == x + 1) << std::endl;is implementation defined, or even that it's undefined.
Mike Miller: A similar question could be raised about different objects that (sequentially) share the same storage. Consider the following:
struct B { virtual void f(); }; struct D1: B { }; struct D2: B { }; void g() { B* bp1 = new D1; B* bp2 = new (bp1) D2; bp1 == bp2; // ??? }Section 3.8 [basic.life] paragraph 5 does not list this kind of comparison among the pointer operations that cause undefined behavior, so presumably the comparison is allowed. However, 5.10 [expr.eq] paragraph 1 describes pointer comparison in terms of "[pointing] to the same object," which bp1 and bp2 clearly do not do. How should we describe the result of this comparison?
Jason Merrill: When you consider comparing pointers to void, this seems to suggest that no two objects can have the same address, depending on your interpretation of "point to the same object." This would cripple the empty base optimization.
3.9.2 [basic.compound] refers to 'pointers to void or objects or functions'. In that case, 5.10 [expr.eq] does not allow you to compare them; it only allows comparing pointers to objects and functions.
Proposed Resolution (10/00):
A valid value of an object pointer type represents either the address of a byte in memory (1.7 [intro.memory]) or a null pointer (4.10 [conv.ptr]). If an object of type T is located at an address A, a pointer of type cv T* whose value is the address A is said to point to that object, regardless of how the value was obtained. [Note: for instance, the address one past the end of an array (5.7 [expr.add]) would be considered to point to an unrelated object of the array's element type that might be located at that address.]
Two pointers of the same type compare equal if and only if they are both null, both point to the same function, or both represent the same address (3.9.2 [basic.compound]).
(See also paper J16/00-0011 = WG21 N1234.)
Given
char arr[100]; sizeof(0,arr);
What does the sizeof expression return? According to 5.18 [expr.comma] paragraph 1, the comma operator yields an lvalue if the second argument is an lvalue. Since 4.2 [conv.array] paragraph 1 says that the array-to-pointer conversion yields an rvalue, it seems that sizeof should see an array type and give the answer 100. If so, the value of the sizeof expression would be different from that of the corresponding expression in C, but there is nothing in Annex C [diff] to indicate that an incompatible change was intended.
Proposed resolution (10/00):
Add the following as paragraph 3 of C.1.4 [diff.expr]:
5.16, 5.17, 5.18
Change: The result of a conditional expression, an assignment expression, or a comma expression may be an lvalue.
Rationale: C++ is an object-oriented language, placing relatively more emphasis on lvalues. For example, functions may return lvalues.
Effect on original feature: Change to semantics of well-defined feature. Some C expressions that implicitly rely on lvalue-to-rvalue conversions will yield different results. For example,char arr[100]; sizeof(0, arr)yields 100 in C++ and sizeof(char*) in C.
Difficulty of converting: Programs must add explicit casts to the appropriate rvalue.
How widely used: Rare.
struct S { static const int c = 5; }; int a[S::c]; // error: S::c not in scopeIs this restriction intentional? If so, what was the rationale for the restriction?
Bjarne Stroustrup: I think that once you have said S::, c is in scope so that
int a[S::c];is ok.
Mike Miller: I'd like to think that's what it meant, but I don't believe that's what it said. According to 3.3 [basic.scope] paragraph 1, the scope of a name is the region "in which that name may be used as an unqualified name." You can, indeed, use a qualified name to refer to a name that is not in scope, but that only goes to reinforce my point that "S::c" is not in scope at the point where the expression containing it is used. I think the phrase "within its scope" is at best misleading and should be removed. (Unless there's a reason I'm missing for restricting the use of static member constants to their scope.)
As far as I can tell from 5.19 [expr.const] paragraph 2, "arithmetic constant expressions" (as distinct from "integral constant expressions") are used only in static initializers to distinguish between static and dynamic initialization. They include floating point types and exclude non-type template parameters, as well as the const variables and static data members.
There is a minor error in 5.19 [expr.const] paragraph 2. The first sentence says, "Other expressions are considered constant expressions only for the purpose of non-local static object initialization." However, 6.7 [stmt.dcl] paragraph 4 appears to rely on the same definition dealing with the initialization of local static objects. I think that the words "non-local" should be dropped and a cross reference to 6.7 [stmt.dcl] added.
I'm guessing that should be "non-static member," like the similar prohibition in 12.7 [class.cdtor] regarding out-of-lifetime access to members of non-POD class objects.
Proposed resolutions (10/00):
Remove the phrase "within its scope" in 9.4.2 [class.static.data] paragraph 4.
An arithmetic constant expression shall satisfy the requirements for an integral constant expression, except that
- floating literals need not be cast to integral or enumeration type, and
- conversions to floating point types are permitted.
This is not a defect; no change is required. The suggested wording would be more accurate, but since the effect on local initialization is unobservable the current wording is adequate.
Change the referenced sentence in 5.19 [expr.const] paragraph 4 to "An expression that designates the address of a subobject of a non-POD class object is not an address constant expression."
The wording of 6.4 [stmt.select] paragraph 1 is misleading. Instead of
The substatement in a selection-statement (both substatements, in the else form of the if statement) implicitly defines a local scope (3.3 [basic.scope]).
it should say
... each substatement, in the else form...
As is, one is left with the impression that both "then" and "else" clauses together form a single scope.
Proposed resolution (10/00): As suggested.
Mike Ball: I cannot find anything in the standard that tells me the meaning of a storage-class-specifier on a function template declaration. In particular, there is no indication what effect, if any, it has on the storage class of the instantiations.
There is an explicit prohibition of storage-class-specifiers on explicit specializations.
For example, if we have
template<class T> static int foo(T) { return sizeof(T); }does this generate static functions for all instantiations? By 7.1.1 [dcl.stc] the storage class applies to the name declared in the declarator, which is the template foo, not an instantiation of foo, which is named with a template-id. There is a statement in clause 14 that template names have linkage, which supports the contention that "static" applies to the template, not to instantiations.
So what does the specifier mean? Lacking a direct statement in the standard, I see the following posibilities, in my preference order.
From John Spicer
The standard does say that a namespace scope template has external linkage unless it is a function template declared "static". It doesn't explicitly say that the linkage of the template is also the linkage of the instantiations, but I believe that is the intent. For example, a storage class is prohibited on an explicit specialization to ensure that a specialization cannot be given a different storage class than the template on which it is based.
Mike: This makes sense, but I couldn't find much support in the document. Sounds like yet another interpretation to add to the list.The standard does not talk about the linkage of instantiations, because only "names" are considered to have linkage, and instances are not really names. So, from an implementation point of view, instances have linkage, but from a language point of view, only the template from which the instances are generated has linkage.John: Agreed.
Mike: Which is why I think it would be cleaner to eliminate storage class specifiers entirely and rely on the unnamed namespace. There is a statement that specializations go into the namespace of the template. No big deal, it's not something it says, so we live with what's there."export" is an additional attribute that is separate from linkage, but that can only be applied to templates with external linkage.John: That would mean prohibiting static function templates. I doubt those are common, but I don't really see much motivation for getting rid of them at this point.
Mike: I can't find that restriction in the standard, though there is one that templates in an unnamed namespace can't be exported. I'm pretty sure that we intended it, though.John: I can't find it either. The "inline" case seems to be addressed, but not static. Surely this is an error as, by definition, a static template can't be used from elsewhere.
Proposed resolution (10/00):
Change the text in 14 [temp] paragraph 4 from:A template name may have linkage (3.5 [basic.link]).to:
A template name has linkage (3.5 [basic.link]). A non-member function template can have internal linkage; any other template name shall have external linkage. Entities generated from a template with internal linkage are distinct from all entities generated in other translation units.
Can a typedef redeclaration be done within a class?
class X { typedef int I; typedef int I; };See also 9.2 [class.mem] , Core issue 36, and Core issue 85.
Proposed Resolution (10/99): Change 7.1.3 [dcl.typedef] paragraph 2 from "In a given scope" to "In a given non-class scope."
The following code does not compile with the EDG compiler:
volatile const int a = 5; int b[a];The standard, 7.1.6.1 [dcl.type.cv] , says:
A variable of const-qualified integral or enumeration type initialized by an integral constant expression can be used in integral constant expressions.This doesn't say it can't be const volatile-qualified, although I think that was what was intended.
Proposed Resolution (10/99): Change the referenced text in paragraph 2 of 7.1.6.1 [dcl.type.cv] to read:
I can't find the answer to the following in the standard. Does anybody have a reference?
The syntax for elaborated type specifier is
class foo<int> // foo is a templateOn the other hand, a friend declaration seems to require this production,
An elaborated-type-specifier shall be used in a friend declaration for a class.*And in 14.5.4 [temp.friend] we find the example[Footnote: The class-key of the elaborated-type-specifier is required. —end footnote]
[Example:Is there some special dispensation somewhere to allow the syntax in this context? Is there something I've missed about elaborated-type-specifier? Is it just another bug in the standard?template<class T> class task; template<class T> task<T>* preempt(task<T>*); template<class T> class task { // ... friend void next_time(); friend void process(task<T>*); friend task<T>* preempt<T>(task<T>*); template<class C> friend int func(C); friend class task<int>; template<class P> friend class frd; // ... };
An additional problem was reported via comp.std.c++: the grammar does not allow the following example:
namespace A{ class B{}; }; namespace B{ class A{}; class C{ friend class ::A::B; }; };
Proposed resolution (10/00):
Change the grammar in 7.1.6.3 [dcl.type.elab] to read
7.3 [basic.namespace] paragraph 2 says:
A name declared outside all named namespaces, blocks (6.3 [stmt.block] ) and classes (clause 9 [class] ) has global namespace scope (3.3.6 [basic.scope.namespace] ).But 3.3.6 [basic.scope.namespace] paragraph 3 says:
A name declared outside all named or unnamed namespaces (7.3 [basic.namespace] ), blocks (6.3 [stmt.block] ), function declarations (8.3.5 [dcl.fct] ), function definitions (8.4 [dcl.fct.def] ) and classes (clause 9 [class] ) has global namespace scope (also called global scope).7.3 [basic.namespace] should evidently be changed to match the wording in 3.3.6 [basic.scope.namespace] — the unnamed namespace is not global scope.
Proposed resolution (10/00):
Replace the first sentence of 3.3.6 [basic.scope.namespace] paragraph 3 with
The outermost declarative region of a translation unit is also a namespace, called the global namespace. A name declared in the global namespace has global namespace scope (also called global scope).
In the last sentence of the same paragraph, change "Names declared in the global namespace scope" to "Names with global namespace scope."
Replace 7.3 [basic.namespace] paragraph 2 with
The outermost declarative region of a translation unit is a namespace; see 3.3.6 [basic.scope.namespace].
John Spicer: I believe the standard is not clear with respect to this example:
namespace N { template <class T> void f(T); namespace M { struct A { friend void f<int>(int); // okay - refers to N::f }; } }At issue is whether the friend declaration refers to N::f, or whether it is invalid.
A note in 3.3.2 [basic.scope.pdecl] paragraph 6 says
friend declarations refer to functions or classes that are members of the nearest enclosing namespace ...I believe it is intended to mean unqualified friend declarations. Certainly friend void A::B() need not refer to a member of the nearest enclosing namespace. Only when the declarator is unqualified (i.e., it is a declaration and not a reference) does this rule need to apply. The presence of an explicit template argument list requires that a previous declaration be visible and renders this a reference and not a declaration that is subject to this rule.
Mike Miller: 7.3.1.2 [namespace.memdef] paragraph 3 says,
When looking for a prior declaration of a class or a function declared as a friend, scopes outside the innermost enclosing namespace scope are not considered.On the other hand, the friend declaration would be a syntax error if f weren't declared as a template name; it would seem very strange not to find the declaration that made the friend declaration syntactically correct. However, it also seems strange to treat this case differently from ordinary functions and from templates:
namespace N { template <class T> void f(T); void g(); namespace M { struct A { friend void f<int>(int); // N::f template <class T> friend void f(T); // M::f friend void g(); // M::g }; } }
John Spicer: This section refers to "looking for a prior declaration". This gets back to an earlier discussion we've had about the difference between matching two declarations of the same name and doing name lookup. I would maintain that in f<int> the f is looked up using a normal lookup. In practice, this is really how it has to be done because the declaration could actually be f<int>::x.
Proposed resolution (10/00):
In 7.3.1.2 [namespace.memdef] paragraph 3, change
When looking for a prior declaration of a class or a function declared as a friend, scopes outside the innermost enclosing namespace scope are not considered.to
When looking for a prior declaration of a class or a function declared as a friend, and when the name of the friend class or function is neither a qualified name nor a template-id, scopes outside the innermost enclosing namespace scope are not considered.Also, change the example in that paragraph as follows:
void h(int); template <class T> void f2(T); namespace A { class X { friend void f(X); // A::f(X) is a friend friend void f2<>(int); // ::f2<>(int) is a friend ...
(See also issues 95, 136, 138, 139, 143, and 165.)
Consider the following:
extern "C" void f(); namespace N { extern "C" void f(); } using N::f;According to 7.3.3 [namespace.udecl] paragraph 11, the using-declaration is an error:
If a function declaration in namespace scope or block scope has the same name and the same parameter types as a function introduced by a using-declaration, the program is ill-formed.Based on the context (7.3.3 [namespace.udecl] paragraph 10 simply reiterates the requirements of 3.3 [basic.scope] ), one might wonder if the failure to exempt extern "C" functions was intentional or an oversight. After all, there is only one function f() involved, because it's extern "C", so ambiguity is not a reason to prohibit the using-declaration.
This also breaks the relatively strong parallel between extern "C" functions and typedefs established in our discussion of Core issue 14 in Santa Cruz. There the question was for using-directives:
typedef unsigned int size_t; extern "C" int f(); namespace N { typedef unsigned int size_t; extern "C" int f(); } using namespace N; int i = f(); // ambiguous "f"? size_t x; // ambiguous "size_t"?We decided for both that there was no ambiguity because each pair of declarations declares the same entity. (According to 3 [basic] paragraph 3, a typedef name is not an entity, but a type is; thus the declarations of size_t declare the same entity "unsigned int".)
In the context of using-declarations, there is no explicit extension of the restrictions in 3.3 [basic.scope] paragraph 4 except as noted above for function declarations; thus the parallel scenario for a typedef is not ill-formed:
typedef unsigned int size_t; namespace N { typedef unsigned int size_t; }; using N::size_t; // okay, both declarations // refer to the same entityI think the first sentence of 7.3.3 [namespace.udecl] paragraph 11 ought to be rewritten as:
If a function declaration in namespace scope or block scope has the same name and the same parameter types as a function introduced by a using-declaration, and the declarations do not declare the same function, the program is ill-formed.
Proposed Resolution (10/99): As suggested.
Section 7.3.4 [namespace.udir] paragraph 3 uses the term extended-namespace-definition three times:
If a namespace is extended by an extended-namespace-definition after a using-directive for that namespace is given, the additional members of the extended namespace and the members of namespaces nominated by using-directives in the extended-namespace-definition can be used after the extended-namespace-definition.I think the intent is clear, but unfortunately I cannot find any other mention (or definition) of this term.
Mike Miller: True enough; in Section 7.3.1 [namespace.def] [the grammar] it's called an extension-namespace-definition.
Proposed Resolution (10/99): Systematically replace "extended-namespace-definition" by "extension-namespace-definition".
(From J16/99-0005 = WG21 N1182, "Proposed Resolutions for Core Language Issues 6, 14, 20, 40, and 89")
There are two sub-issues. The first concerns the statement in 8.3 [dcl.meaning] paragraph 1,
The id-expression of a declarator-id shall be a simple identifier except for the declaration of some special functions (12.3 [class.conv] , 12.4 [class.dtor] , 13.5 [over.oper] ) and for the declaration of template specializations or partial specializations (14.7 [temp.spec] ).The second sub-issue is regarding another statement in the same paragraph:
A declarator-id shall not be qualified except for the definition of a member function (9.3 [class.mfct] ) or static data member (9.4 [class.static] ) or nested class (9.7 [class.nest] ) outside of its class, the definition or explicit instantiation of a function, variable or class member of a namespace outside of its namespace, or...Analysis
The problem in the first sub-issue is that the wrong syntactic non-terminal is mentioned. The relevant portions of the grammar are:
If an unqualified-id is used as the id-expression of a declarator-id, it shall be a simple identifier except...However, it does not appear that this restriction has any meaning; all of the possible cases of unqualified-ids are represented in the list of exceptions! Rather than recasting the sentence into a correct but useless form, it would be better to remove it altogether.
The second sub-issue deals with the conditions under which a qualified-id can be used in a declarator, including "the definition of a...nested class" and "the definition or explicit instantiation of a...class member of a namespace." However, the name in a class definition is not part of a declarator; these constructs do not belong in a list of declarator contexts.
Proposed Resolution for sub-issue 1 (04/99):
The suggested resolution for the first sub-issue overlooked the fact that the existing wording has the additional effect of prohibiting the use of the non-identifier syntax for declaring other than the listed entities. Thus the proposed wording for the first sub-issue is:
Change 8.3 [dcl.meaning] paragraph 1 from:
The id-expression of a declarator-id shall be a simple identifier except...to:
An unqualified-id occurring in a declarator-id shall be a simple identifier except...
Proposed Resolution for sub-issue 2 (10/99):
Change 8.3 [dcl.meaning] paragraph 1 from:
A declarator-id shall not be qualified except for the definition of a member function (9.3 [class.mfct] ) or static data member (9.4 [class.static] ) or nested class (9.7 [class.nest] ) outside of its class, the definition or explicit instantiation of a function, variable or class member of a namespace outside of its namespace, or...to
A declarator-id shall not be qualified except for the definition of a member function (9.3 [class.mfct] ) or static data member (9.4 [class.static] ) outside of its class, the definition or explicit instantiation of a function or variable member of a namespace outside of its namespace, or...
8.3 [dcl.meaning] paragraph 1 says:
In the qualified declarator-id for a class or namespace member definition that appears outside of the member's class or namespace, the nested-name-specifier shall not name any of the namespaces that enclose the member's definition.This results in the following behavior:
namespace N { namespace M { void f(); void g(); } void M::f(){} // okay void N::M::g(){} // error }I was very surprised when this rule was pointed out to me. The change appears to have been introduced around the time of the first Santa Cruz meeting, but I don't recall discussion of it and could not find a motion related to it.
Regardless of where it came from, I also can't understand why it is there. Certainly it shouldn't matter how you name a given class or namespace.
For example, the standard permits:
namespace N { namespace M { void f(); void g(); } namespace X = M; namespace Y = N::M; void X::f(){} // okay void Y::g(){} // okay }So, it is okay to use an alias for N::M, but not to use N::M directly. Note that it is okay to use N::M in any other context at this point in the program (i.e., the rule is a specific restriction on declarator names, not a general rule on the use of qualified names).
Does anyone recall the intent of this rule or any rationale for its existence?
Notes from 04/00 meeting:
There was some question as to whether this issue actually constituted a defect in the Standard. John Spicer suggested that machine-generated source code would be likely to run afoul of this prohibition. Francis Glassborow expressed support for a rule that would allow full qualification, or qualification relative to the namespace containing the definition, but not qualification relative to a containing namespace. There was no consensus for moving forward with a DR at this point, so the issue was left in "review" status.
Proposed resolution (10/00):
Remove the last sentence of 8.3 [dcl.meaning] paragraph 1 (cited above) and the example that follows.
3.2 [basic.def.odr] paragraph 4 and 8.3.5 [dcl.fct] paragraph 6 indicate that the return type and parameter types must be complete in a function definition. However, when 9.2 [class.mem] paragraph 2 lists the contexts in a class member-specification in which the class is considered complete, the return type and parameter types of a member function defined in the class definition are not included. It thus appears that the following example is ill-formed:
struct S { S f() { return S(); } // error: incomplete return type void g(S) { } // error: incomplete parameter type };Jack Rouse: I suggest supplementing the text in 8.3.5p6 with something like:
The type of a parameter or the return type for a function definition shall not be an incomplete class type unless the function definition is nested in the member-specification for that class (including definitions in nested classes defined within the class).
Proposed resolution (10/00): Replace the last sentence of 8.3.5 [dcl.fct] paragraph 6 with
The type of a parameter or the return type for a function definition shall not be an incomplete class type (possibly cv-qualified) unless the function definition is nested within the member-specification for that class (including definitions in nested classes defined within the class).
3.3 [basic.scope] paragraph 4 says:
Given a set of declarations in a single declarative region, each of which specifies the same unqualified name,8.3.6 [dcl.fct.default] paragraph 9 says:
- they shall all refer to the same entity, or all refer to functions ...
When a declaration of a function is introduced by way of a using-declaration (7.3.3 [namespace.udecl]), any default argument information associated with the declaration is imported as well.This is not really clear regarding what happens in the following case:
namespace A { extern "C" void f(int = 5); } namespace B { extern "C" void f(int = 7); } using A::f; using B::f; f(); // ???Proposed resolution (10/00):
Add the following at the end of 13.3.3 [over.match.best]:
If the best viable function resolves to a function for which multiple declarations were found, and if at least two of these declarations — or the declarations they refer to in the case of using-declarations — specify a default argument that made the function viable, the program is ill-formed. [Example:
namespace A {
extern "C" void f(int = 5);
}
namespace B {
extern "C" void f(int = 5);
}using A::f;
using B::f;void use() {
f(3); // OK, default argument was not used for viability
f(); // Error: found default argument twice
}—end example]
Proposed Resolution (04/99): Change the text in the example of section 8.3.6 [dcl.fct.default] paragraph 5 from:
... g will be called with the value f(1).to:
... g will be called with the value f(2).
According to 8.3.6 [dcl.fct.default] paragraphs 4 and 6,
For non-template functions, default arguments can be added in later declarations of a function in the same scope.
The default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition.
This would appear to allow the following example, in which a default argument is added to a non-template member function of a class template:
template <class T> struct S { void foo (int); }; template <class T> void S<T>::foo (int = 0) { }
John Spicer: The wording "non-template functions" is somewhat unclear with respect to member functions of class templates, but I know that this was intended to include them because it originates from issue 3.13 of the template issues list that I maintained for several years.
Having said that, the rationale for this restriction has since been made obsolete, so this could (in theory) be changed in the standard if it is problematic for users.
(See also issue 205.)
Proposed resolution (10/00):
In 8.3.6 [dcl.fct.default] paragraph 6, replace
The default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition.
with
Except for member functions of class templates, the default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition. Default arguments for a member function of a class template must be specified on the initial declaration of the member function within the class template.
Given:
struct S1 { int x; }; struct S2 { int x; double y; }; struct S3 { int x; double y; string s; };Once upon a time, we went through a fairly protracted discussion to ensure that S1().x would be guaranteed to be 0. Note that if we declare
void f() { S1 s1; // ... }there is no guarantee of the value of s1.x, and that is intentional. But S1().x is different, because S1() is an rvalue, and unless all of its members are defined, the effect of copying it is undefined.
Similarly, S2().x and S2().y are also defined to be equal to zero, and here it really matters for many implementations, because if S2().y is just a bunch of random bits, it is entirely possible that trying to copy S2().y will yield a floating-point trap.
However, rather to my surprise, the standard does not define the value of S3().x or S3().y, because S3 is not a POD. It does define S3().s (by running the string constructor), but once a structure is no longer a POD, the values of uninitialized members are no longer guaranteed in expressions of the form T().
In my opinion, this definition is a mistake, and the committee's intention was to zero-initialize all members that do not have an explicitly defined constructor, whether or not the class is a POD.
See also paper J16/99-0014 = WG21 N1191.
[Note: this issue is resolved by the resolution of issue 178.]
In 3.6.2 [basic.start.init] paragraph 1 and 8.5 [dcl.init] paragraphs 5 and 6, the terms "memory" and "storage" are used in connection with zero-initialization. This is inaccurate; it is the variables that are zero-initialized, not the storage. (An all-zero bit pattern in the storage may, in fact, not correspond to the representation of zero converted to the appropriate type, and it is the latter that is being described.)
Suggested resolution: remove the words "storage" and "memory" in these contexts.
Proposed resolution (10/00):
Delete the words "The storage for" from the first sentence of 3.6.2 [basic.start.init] paragraph 1.
[Note: Revised wording in 8.5 [dcl.init] relating to this issue is also found in issue 178.]
When the Committee considered issue 35, another context in which value initialization might be relevant was overlooked: mem-initializers. It would seem reasonable that if T() as an expression invokes value initialization, that the same syntactic construct in a mem-initializer-list would do the same, and the usefulness of value initialization in that context is at least as great as the standalone case.
Proposed resolution (10/00):
[Note: this resolution supersedes the resolution to issue 35.]
In 5.2.3 [expr.type.conv] paragraph 2, replace "whose value is determined by default-initialization" by "which is value-initialized".
In 5.3.4 [expr.new] paragraph 15,
Replace 8.5 [dcl.init] paragraph 5 by:
To zero-initialize an object of type T means:
- if T is a scalar type (3.9 [basic.types]), the object is set to the value of 0 (zero) converted to T;
- if T is a non-union class type, each non-static data member and each base-class subobject is zero-initialized;
- if T is a union type, the object's first named data member [Footnote: This member must not be static, by virtue of the requirements in 9.5 [class.union]. end footnote] is zero-initialized;
- if T is an array type, each element is zero-initialized;
- if T is a reference type, no initialization is performed.
To default-initialize an object of type T means:
- if T is a non-POD class type (clause 9 [class]), the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
- if T is an array type, each element is default-initialized;
- otherwise, the object is zero-initialized.
To value-initialize an object of type T means:
- if T is a class type (clause 9 [class]) with a user-declared constructor (12.1 [class.ctor]), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
- if T is a non-union class type without a user-declared constructor, then every non-static data member and base-class component of T is value-initialized;
- if T is an array type, then each element is value-initialized;
- otherwise, the object is zero-initialized.
A program that calls for default-initialization of an entity of reference type is ill-formed. If T is a cv-qualified type, the cv-unqualified version of T is used for these definitions of zero-initialization, default-initialization, and value-initialization.
In 8.5 [dcl.init] paragraph 6, change "The memory occupied by any" to "Every".
In 8.5 [dcl.init] paragraph 7, replace "default-initialized" by "value-initialized".
In 8.5.1 [dcl.init.aggr] paragraph 7, replace "default-initialized" by "value-initialized".
In 12.3.1 [class.conv.ctor] paragraph 2, insert "or value-initialization" after the first occurrence of "default-initialization".
In 12.6 [class.init] paragraph 1, replace the note by "The object is default-initialized if there is no initializer, or value-initialized if the initializer is ()" [i.e., replace the non-normative note by different, normative text].
In 12.6.1 [class.expl.init] paragraph 2, replace "default-initialized" by "value-initialized".
In 12.6.2 [class.base.init] paragraph 3, replace "default-initialized" by "value-initialized" in the first bulleted item.
In 12.6.2 [class.base.init] paragraph 4, replace "default-initialized, nor initialized" by "default-initialized, nor value-initialized, nor assigned".
Another glitch in the TC1/core issue 178 definition of value-initialization: it's no longer an error to value-initialize a reference. That makes an example like
typedef struct { int &r; } S; int main() { S(); // Error in C++98, okay in TC1! }valid, which has got to be wrong. See 8.5 [dcl.init] paragraph 5, where there is wording that forbids default-initialization of a reference, but not value-initialization thereof. As noted in issue 302, if the default constructor were required to be generated when a value-initialization is done, that would force an error.
Proposed resolution (10/01):
Add the indicated wording to the indicated sentence in 8.5 [dcl.init] paragraph 5:
A program that calls for default-initialization or value-initialization of an entity of reference type is ill-formed.
8.5.1 [dcl.init.aggr] paragraph 2 says,
When an aggregate is initialized the initializer can be an initializer-clause consisting of a brace-enclosed, comma-separated list of initializers for the members of the aggregate.Neither of these uses of the syntactic nonterminal initializer corresponds to the grammar:
Proposed resolution (10/99): replace the quoted words with:
When an aggregate is initialized the initializer can contain an initializer-clause consisting of a brace-enclosed, comma-separated list of initializer-clauses for the members of the aggregate.
3.9 [basic.types] paragraph 10 defines pointer to member types to be scalar types. It also defines scalar types to be one of the POD types.
9 [class] paragraph 4 defines a POD struct as an aggregate class with no non-static data members of type pointer to member.
It seems contradictory that a type can be POD, yet a class containing that type is non-POD.
Suggested resolution: Alter 9 [class] paragraph 4 to allow pointer to member objects as non-static data members of POD class.
Proposed resolution (10/00):
In 9 [class] paragraph 4, remove all occurrences of "pointer to member."
There is some controversy about whether class name injection applies to class templates. If it does apply, what is injected? Is a class name injected or is the thing that is injected actually a template?
Clause 9 [class] paragraph 2 says,
The class-name is also inserted into the scope of the class itself.In general, clause 9 applies to both classes and class templates, so I would take this to mean that class name imjection does indeed apply to class templates. One problem with this is that clause 9 uses the syntactic term class-name, which I would take to imply that the inserted name is always a class. This is clearly unacceptable for class templates as it makes the template itself unusable from with the template. For example:
template <class T> struct A { A<T*> ptr; // Invalid: A refers to a class };
Clearly the injected name must be usable as both a class and a class template. This kind of magic already exists in the standard. In 14.6.1 [temp.local] it says,
Within the scope of a class template, when the name of the template is neither qualified nor followed by <, it is equivalent to the name of the template followed by the template-parameters enclosed in <>.
The proposal here is that we clarify that name injection does indeed apply to class templates, and that it is the injected name that has the special property of being usable as both a class and a template name (as described in 14.6.1 [temp.local] ). This would eliminate the need for special wording regarding the qualification of the name, but would achieve the same result. This would also make this "special" name available to a derived class of a class template — something which is necessary if the benefits of class name injection are to be made uniformly available for class templates, too.
template <class T> struct Base { Base* p; Base<T*>* p2; ::Base* p3; // Error: only injected name usable as class }; template <class T> struct Derived: public Base<T> { Base* p; // Now okay Base<T*>* p2; // Still okay Derived::Base* p3; // Now okayNote that by giving the special attribute of being usable as both a class and a template to the injected name it is now clear where this attribute can and cannot be used.
(See paper J16/99-0010 = WG21 N1187.)
Proposed resolution (10/00):
[Note: these changes depend on the resolution for issue 147.]
Replace 14.6.1 [temp.local] paragraphs 1 and 2 with the following:
Like normal (non-template) classes, class templates have an injected-class-name (clause 9 [class]). The injected-class-name can be used with or without a template-argument-list. When it is used without a template-argument-list, it is equivalent to the injected-class-name followed by the template-parameters of the class template enclosed in <>. When it is used with a template-argument-list, it refers to the specified class template specialization, which could be the current specialization or another specialization.
Within the scope of a class template specialization or partial specialization, when the injected-class-name is not followed by a <, it is equivalent to the injected-class-name followed by the template-arguments of the class template specialization or partial specialization enclosed in <>. [Example:
template<class T> class Y; template<> class Y<int> { Y* p; // meaning Y<int> Y<char>* q; // meaning Y<char> };—end example]
The injected-class-name of a class template or class template specialization can be used either with or without a template-argument-list wherever it is in scope. [Example:
template <class T> struct Base { Base* p; }; template <class T> struct Derived: public Base<T> { typename Derived::Base* p; // meaning Derived::Base<T> };—end example]
A lookup that finds an injected-class-name (10.2 [class.member.lookup]) can result in an ambiguity in certain cases (for example, if it is found in more than one base class). If all of the injected-class-names that are found refer to specializations of the same class template, and if the name is followed by a template-argument-list, the reference refers to the class template itself and not a specialization thereof, and is not ambiguous. [Example:
template <class T> struct Base { }; template <class T> struct Derived: Base<int>, Base<char> { typename Derived::Base b; // error: ambiguous typename Derived::Base<double> d; // OK };—end example]
When the normal name of the template (i.e., the name from the enclosing scope, not the injected-class-name) is used without a template-argument-list, it refers to the class template itself and not a specialization of the template. [Example:
template <class T> class X { X* p; // meaning X<T> X<T>* p2; X<int>* p3; ::X* p4; // error: missing template argument list // ::X does not refer to the injected-class-name };—end example]
The standard says, in 9.2 [class.mem] paragraph 4:
A member-declarator can contain a constant-initializer only if it declares a static member (9.4 [class.static] ) of integral or enumeration type, see 9.4.2 [class.static.data] .But later, in the section on static class data member initialization, 9.4.2 [class.static.data] paragraph 4, it says:
If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be an integral constant expression (5.19 [expr.const] ). In that case, the member can appear in integral constant expressions within its scope.The first paragraph should be modified to make it clear that it is not possible to initialize a static data member in-line with a constant-initializer if that data member is of integral (or enumeration) type, and yet not const.
Proposed Resolution (10/99): Change the sentence in 9.2 [class.mem] paragraph 4 to read:
A member-declarator can contain a constant-initializer only if it declares a static member (9.4 [class.static] ) of const integral or const enumeration type, see 9.4.2 [class.static.data] .
Between the May '96 and September '96 working papers, the text in 9.2 [class.mem] paragraph 13:
If T is the name of a class, then each of the following shall have a name different from T:was changed by removing the word 'static'. Looking over the meeting minutes from Stockholm, none of the proposals seem to include this change, which breaks C compatibility and is not mentioned in the compatibility annex. Was this change actually voted in by the committee?
- every static data member of class T;
Specifically, this breaks /usr/include/netinet/in.h under Linux, in which "struct ip_opts" shares its name with one of its members.
Proposed resolution (10/00):
In addition, if class T has a user-declared constructor (12.1 [class.ctor] ), every nonstatic data member of class T shall have a name different from T.
The definition of layout-compatible POD-struct types in 9.2 [class.mem] paragraph 14 requires that the two types
have the same number of members, and corresponding members (in order) have layout-compatible types (3.9).There does not appear to be any reason for including member functions and static data members in this requirement. It would be more logical to require only that the non-static data members of the two types must match.
The characteristics of layout-compatible types are not well described in the current wording, either. Apart from their use in 9.2 [class.mem] paragraph 16 to define the term "common initial sequence," there appears to be nothing said about which operations are possible between objects of layout-compatible types. For example, 3.9 [basic.types] paragraphs 2-3 give certain guarantees regarding use of memcpy on objects of the same type; it might be reasonable to assume that the same kinds of guarantees might apply to objects of layout-compatible types, but that is not said. Similarly, 3.10 [basic.lval] paragraph 15 describes permissible "type punning" but does not mention layout-compatible types.
Proposed resolution (10/00):
In 9.2 [class.mem] paragraphs 14 and 15, change all occurrences of "members" to "nonstatic data members."
Paragraph 2 says that "the object-expression is always evaluated" when the class member syntax is used to refer to a static member. This presumably should say that the object expression is evaluated if the member access is performed, i.e., not if the overall expression is the operand of sizeof or the unevaluated branch of ?:, ||, or &&.
Proposed Resolution (10/99): Replace "is always evaluated" by "is evaluated" in 9.4 [class.static] paragraph 2.
Also see section: 3.2 [basic.def.odr] .
Originally, all static data members still had to be defined outside the class whether they were used or not.
But that restriction was supposed to be lifted so that static data members need not be defined outside the class unless they are used in a manner which requires their definition, in the same manner as namespace-scope variables. In particular, if an integral/enum const static data member is initialized within the class, and its address is never taken, we agreed that no namespace-scope definition was required.
For example:
struct A { static const int size = 10; int array[size]; }; int main() { A a; return 0; }However, 9.4.2 [class.static.data] paragraph 4 says:
The member shall still be defined in a namespace scope if it is used in the program and the namespace scope definition shall not contain an initializer.A narrow interpreration of "used" in this rule would make the example ill-formed because there is no namespace-scope definition of "size". A better wording for this rule would be:
The member shall still be defined in a namespace scope if it is used in the program in the manner described in 3.2 [basic.def.odr] . The namespace scope definition shall not contain an initializer.Also, the wording in 3.2 [basic.def.odr] paragraph 2:
An expression is potentially evaluated unless either it is the operand of the sizeof operator (5.3.3 [expr.sizeof] ), or it is the operand of the typeid operator and does not designate an lvalue of polymorphic class type (5.2.8 [expr.typeid] ).is incomplete because it does not mention the use of a compile-time constant as an array bound or template argument. It should say something like:
An expression is potentially evaluated unless it is the operand of the sizeof operator (5.3.3 [expr.sizeof] ), the operand of the typeid operator, an integral constant-expression used as an array bound or an integral constant-expression used as a template-argument for a non-reference template-parameter; and the expression does not designate an lvalue of polymorphic class type (5.2.8 [expr.typeid] ).
Proposed Resolution (04/99): Change the first sentence of 3.2 [basic.def.odr] paragraph 2 from:
An expression is potentially evaluated unless either it is the operand of the sizeof operator (5.3.3 [expr.sizeof] ), or it is the operand of the typeid operator and does not designate an lvalue of polymorphic class type (5.2.8 [expr.typeid] ).to:
An expression is potentially evaluated unless it appears where an integral constant expression is required (see 5.19 [expr.const] ), is the operand of the sizeof operator (5.3.3 [expr.sizeof] ), or is the operand of the typeid operator and the expression does not designate an lvalue of polymorphic class type (5.2.8 [expr.typeid] ).
In the example in paragraph 3 of 11.2 [class.access.base] , all the references to B in DD::f() should be replaced by ::B. The reason is that the class name B is private in D and thus inaccessible in DD. (The example was probably not updated when class name injection was added.)
Proposed resolution (10/00):
Replace the example in 11.2 [class.access.base] paragraph 3 with:
class B { public: int mi; // nonstatic member static int si; // static member }; class D: private B { }; class DD: public D { void f(); }; void DD::f() { mi = 3; // error: mi is private in D si = 3; // error: si is private in D ::B b; b.mi = 3; // OK (b.mi is different from this->mi) b.si = 3; // OK (b.si is different from this->si) ::B::si = 3; // OK ::B* bp1 = this; // error: B is a private base class ::B* bp2 = (::B*)this; // OK with cast bp2->mi = 3; // OK: access through a pointer to B }
11.4 [class.protected] paragraph 1 begins:
When a friend or a member function of a derived class references a protected nonstatic member of a base class, an access check applies in addition to those described earlier in clause 11 [class.access] .
This was intended to refer to nonstatic member functions and nonstatic data members. However, a protected nested type declared in a base class is, by some definition of the word, a "nonstatic" member, and therefore subject to this additional access check.
Proposed resolution (10/99): change "protected nonstatic member" in the above to "protected nonstatic member function or protected nonstatic data member" to make the intent clear.
According to 12.1 [class.ctor] paragraph 1, the syntax used in declaring a constructor allows at most one function-specifier. It is thus not permitted to declare a constructor both inline and explicit. This seems overly restrictive.
On a related note, there doesn't seem to be any explicit prohibition against member functions with the same name as the class. (Such a prohibition might reasonably be expected to occur in 9.2 [class.mem] paragraph 13, but member functions are not listed there.)
One possible interpretation would be that such member functions would violate the restrictions in 3.3.7 [basic.scope.class] paragraph 1, because the class name would refer to the class at some points in the class scope and to the member function at others. However, this seems a bit tenuous. Is an explicit prohibition needed?
(See also issue 147.)
Proposed resolution (10/00):
Add to 9.2 [class.mem] paragraph 13
- every member function of class T [Note: this restriction does not apply to constructors, which do not have names (12.1 [class.ctor]). ];
immediately following the line
- every data member of class T;
Change 12.1 [class.ctor] paragraph 1 from
A special declarator syntax using an optional function-specifier (7.1.2 [dcl.fct.spec])...
to
A special declarator syntax using an optional sequence of function-specifiers (7.1.2 [dcl.fct.spec])...
Can a copy-constructor declared as explicit be used to copy class values implicitly? For example,
struct X { X(); explicit X(const X&); }; void f(X); int main() { X x; f(x); }According to 12.3.1 [class.conv.ctor] paragraphs 2-3,
An explicit constructor constructs objects just like non-explicit constructors, but does so only where the direct-initialization syntax (8.5 [dcl.init] ) or where casts (5.2.9 [expr.static.cast] , 5.4 [expr.cast] ) are explicitly used... A copy-constructor (12.8 [class.copy] ) is a converting constructor. An implicitly-declared copy constructor is not an explicit constructor; it may be called for implicit type conversions.This passage would appear to indicate that the call in the example is ill-formed, since it uses neither the direct-initialization syntax nor an explicit cast. The last sentences are especially interesting in this regard, indicating that explicit and non-explicit copy constructors are handled differently.
On the other hand, 8.5 [dcl.init] paragraph 14, bullet 4, sub-bullet 2 says,
If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination... [the] applicable constructors are enumerated (13.3.1.3 [over.match.ctor] )...The cited passage says that
The candidate functions are all the constructors of the class of the object being initialized.
Notes from 04/01 meeting:
After the issue was accepted as a DR with the proposed resolution to change 13.3.1.3 [over.match.ctor] paragraph 1 as described below, it was noticed that 12.3.1 [class.conv.ctor] paragraph 3 states that:
A copy-constructor (12.8 [class.copy]) is a converting constructor.
In addition to making the proposed resolution for this issue ineffectual, the wording of paragraph 3 also contradicts that of paragraph 1:
A constructor declared without the function-specifier explicit that can be called with a single parameter specifies a conversion from the type of its first parameter to the type of its class. Such a constructor is called a converting constructor.
These considerations led to the addition of the second point of the proposed resolution.
Proposed resolution (04/01):
Change the first two sentences of 13.3.1.3 [over.match.ctor] paragraph 1 to
When objects of class type are direct-initialized (8.5 [dcl.init]), or copy-initialized from an expression of the same or a derived class type (8.5 [dcl.init]), overload resolution selects the constructor. For direct-initialization, the candidate functions are all the constructors of the class of the object being initialized. For copy-initialization, the candidate functions are all the converting constructors (12.3.1 [class.conv.ctor] ) of that class.
Change the first sentence of 12.3.1 [class.conv.ctor] paragraph 3 to read:
A non-explicit copy constructor (12.8 [class.copy]) is a converting constructor.
The Standard is not clear whether automatic objects in a destructor are destroyed before or after the destruction of the class's base and member subobjects. That is, given
struct S { ~S(); }; struct T { S x; ~T() { S y; }; };
which will be destroyed first, x or y?
Proposed resolution (10/00):
In 12.4 [class.dtor] paragraph 6, change
A destructor for class X calls the destructors for X's direct members, ...to
After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X's direct members, ...
In 12.6.2 [class.base.init] paragraph 4 we read:
After the call to a constructor for class X has completed, if a member of X is neither specified in the constructor's mem-initializers, nor default-initialized, nor initialized during execution of the body of the constructor, the member has indeterminate value.
Using the term "initialized" to describe setting the value of a member inside the body of a constructor is a misuse of the term: only by use of a placement new expression can a member be initialized "during the execution of the body of the constructor."
Suggested resolution: Change "initialized" to "given a value."
Proposed resolution (10/00): As suggested.
Issue 1
12.8 [class.copy] (From J16/99-0005 = WG21 N1182, "Proposed Resolutions for Core Language Issues 6, 14, 20, 40, and 89")
There are three related sub-issues in this issue, all dealing with the elision of copy constructors as described in 12.8 [class.copy] paragraph 15:
After discussion in Santa Cruz, the core group decided that sub-issue #1 required no change; the necessity of an accessible and unambiguous copy constructor is made clear in 12.2 [class.temporary] paragraph 1 and need not be repeated in this text. The remaining two sub-issues appear to be valid criticisms and should be addressed.
Proposed Resolution (10/99):
[Note: a small portion of this wording is superseded by the resolution of issue 185.]
The paragraph in question should be rewritten as follows. In addition, references to this section should be added to the index under "temporary, elimination of," "elimination of temporary," and "copy, constructor elision."
in a return statement in a function with a class return type, where the expression is the name of a non-volatile automatic object with the same cv-unqualified type as the function return type, the copy operation can be omitted by constructing the automatic object directly into the function's return value
class Thing { public: Thing(); ~Thing(); Thing(const Thing&); }; Thing f() { Thing t; return t; } Thing t2 = f();Here the criteria for elision can be combined to eliminate two calls to the copy constructor of class Thing: the copying of the local automatic object t into the temporary object for the return value of function f() and the copying of that temporary object into object t2. Effectively, the construction of the local object t can be viewed as directly initializing the global object t2, and that object's destruction will occur at program exit. —end example]
12.8 [class.copy] paragraph 15 refers only to "temporary class objects." It needs to be made clear that these provisions do not apply to temporaries that have been bound to references. For instance,
struct A { mutable int value; explicit A(int i) : value(i) {} void mutate(int i) const { value = i; } }; int foo() { A const& t = A(1); A n(t); // can this copy be elided? t.mutate(2); return n.value; // can this return 2? }The current wording seems to allow an implementation not to perform the copy in A N(t) because the source object is a temporary (created explicitly by A(1)).
Proposed resolution (10/00):
Change the wording proposed in the resolution of issue 20 from
- when a temporary class object (12.2 [class.temporary]) would be copied to a class object...
to
- when a temporary class object that has not been bound to a reference (12.2 [class.temporary]) would be copied to a class object...
Sections 13.3.1.4 [over.match.copy] and 13.3.1.5 [over.match.conv] should be clarified regarding the treatment of conversion functions which return reference types.
Proposed resolution (10/99):
In 13.3.1.4 [over.match.copy] paragraph 1, change
Conversion functions that return "reference to T" return lvalues of type T and are therefore considered to yield T for this process of selecting candidate functions.to
Conversion functions that return "reference to X" return lvalues of type X and are therefore considered to yield X for this process of selecting candidate functions.In 13.3.1.5 [over.match.conv] paragraph 1, change
Conversion functions that return "reference to T" return lvalues of type T and are therefore considered to yield T for this process of selecting candidate functions.to
Conversion functions that return "reference to cv2 X" return lvalues of type "cv2 X" and are therefore considered to yield X for this process of selecting candidate functions.
In 13.3.3 [over.match.best] paragraph 1, bullet 4 of the second set of bullets, there is a cross-reference to 8.5 [dcl.init] and 13.3.1.5 [over.match.conv] . I believe it should also reference 13.3.1.6 [over.match.ref] . I think the phrase "initialization by user-defined conversion" was intended to refer to all initializations using user-defined conversions, and not just the case in 13.3.1.5 [over.match.conv] . Referring to only 13.3.1.5 [over.match.conv] suggests a narrower meaning of the phrase.
13.3.1.4 [over.match.copy] , although it does deal with initialization by user-defined conversion, does not need to be referenced because it deals with class —> class cases, and therefore there are no standard conversions involved that could be compared.
By the letter of the standard, the conversions required to make auto_ptr work should be accepted.
However, there's good reason to wonder if there isn't a bug in the standard here. Here's the issue: line 16 in the example below comes down to
copy-initialize an auto_ptr<Base> from an auto_ptr<Derived> rvalueTo do that, we first look to see whether we can convert an auto_ptr<Derived> to an auto_ptr<Base>, by enumerating the constructors of auto_ptr<Base> and the conversion functions of auto_ptr<Derived>. There's a single possible way to do the conversion, namely the conversion function
auto_ptr<Derived>::operator auto_ptr<Base>()(generated from the template). (The constructor auto_ptr<Base>(auto_ptr_ref<Base>) doesn't work because it requires a user-defined conversion on the argument.)
So far, so good. Now, we do the copy step:
direct-initialize an auto_ptr<Base> from an auto_ptr<Base> rvalueThis, as we've gone to great lengths to set up, is done by calling the conversion function
auto_ptr<Base>::operator auto_ptr_ref<Base>()(generated from the template), and then the constructor
auto_ptr<Base>(auto_ptr_ref<Base>)(generated from the template).
The problem with this interpretation is that it violates the long-standing common-law rule that only a single user-defined conversion will be called to do an implicit conversion. I find that pretty disturbing. (In fact, the full operation involves two conversion functions and two constructors, but "copy" constructors are generally considered not to be conversions.)
The direct-initialization second step of a copy-initialization was intended to be a simple copy — you've made a temporary, and now you use a copy constructor to copy it. Because it is defined in terms of direct initialization, however, it can exploit the loophole that auto_ptr is based on.
To switch to personal opinion for a second, I think it's bad enough that auto_ptr has to exploit a really arcane loophole of overload resolution, but in this case it seems like it's exploiting a loophole on a loophole.
struct Base { // 2 static void sink(auto_ptr<Base>); // 3 }; // 4 struct Derived : Base { // 5 static void sink(auto_ptr<Derived>); // 6 }; // 7 auto_ptr<Derived> source() { // 8 auto_ptr<Derived> p(source()); // 9 auto_ptr<Derived> pp(p); // 10 Derived::sink(source()); // 11 p = pp; // 12 p = source(); // 13 auto_ptr<Base> q(source()); // 14 auto_ptr<Base> qp(p); // 15 Base::sink(source()); // 16 q = pp; // 17 q = source(); // 18 return p; // 19 return source(); }Derek Inglis:
It seems clear to me that the result of this direct initilization must be the second standard conversion sequence in a user defined conversion sequence. Otherwise the resulting conversion sequence is not an implicit conversion sequence. By the letter of the standard, the sequence of conversions making up a copy-initialization must be an implicit conversion sequence.
Paragraph 3 of clause 4 [conv]:
An expression e can be implicitly converted to a type T if and only if the declaration "T t=e;" is well-formed, for some invented temporary variable t (8.5 [dcl.init]).
Paragraph 1 of 13.3.3.1 [over.best.ics]:
An implicit conversion sequence is a sequence of conversions used to convert an argument in a function call to the type of the corresponding parameter of the function being called. The sequence of conversions is an implicit conversion as defined in clause 4 [conv], which means it is governed by the rules for initialization of an object or reference by a single expression (8.5 [dcl.init], 8.5.3 [dcl.init.ref]).Sentence 1 of paragraph 12 of 8.5 [dcl.init]:
The initialization that occurs in argument passing ... is called copy-initialization and is equivalent to the formT x = a;
For me, these sentences imply that all sequences of conversions permitted on a function argument must be valid implicit conversion sequences.
The 'loophole' can be closed by adding a sentence (or note) to the section describing the 'direct initialization second step of a copy initialization' stating that the copy initialization is ill-formed if the conversion sequence resulting from the direct initialization is not a standard conversion sequence.
(See also issue 177 and paper J16/00-0009 = WG21 N1232.)
Proposed resolution (10/00):
Change 13.3.3.1 [over.best.ics] paragraphs 3 and 4 from
Except in the context of an initialization by user-defined conversion (13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv]), a well-formed implicit conversion sequence is one of the following forms:
- a standard conversion sequence (13.3.3.1.1 [over.ics.scs]),
- a user-defined conversion sequence (13.3.3.1.2 [over.ics.user]), or
- an ellipsis conversion sequence (13.3.3.1.3 [over.ics.ellipsis])
In the context of an initialization by user-defined conversion (i.e., when considering the argument of a user-defined conversion function; see 13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv]), only standard conversion sequences and ellipsis conversion sequences are allowed.
to
A well-formed implicit conversion sequence is one of the following forms:
- a standard conversion sequence (13.3.3.1.1 [over.ics.scs]),
- a user-defined conversion sequence (13.3.3.1.2 [over.ics.user]), or
- an ellipsis conversion sequence (13.3.3.1.3 [over.ics.ellipsis])
However, when considering the argument of a user-defined conversion function that is a candidate by 13.3.1.3 [over.match.ctor] when invoked for the copying of the temporary in the second step of a class copy-initialization, or by 13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv], or 13.3.1.6 [over.match.ref] in all cases, only standard conversion sequences and ellipsis conversion sequences are allowed.
In 13.3.3.2 [over.ics.rank] , we have
int f(const int *); int f(int *); int i; int j = f(&i); // Calls f(int *)—end example] or, if not that,
void f(char *); void f(const char *); f("abc");The two conversion sequences differ only in their qualification conversions, and the destination types are similar. The cv-qualification signature of "char *", is a proper subset of the cv-qualification signature of "const char *", so f(char *) is chosen, which is wrong. The rule should be like the one for conversion to bool — the deprecated conversion should be worse than another exact match that is not the deprecated conversion.
Proposed resolution (10/00):
Change 13.3.3.2 [over.ics.rank] paragraph 3 bullet 1 sub-bullet 3 from
S1 and S2 differ only in their qualification conversion and yield similar types T1 and T2 (4.4 [conv.qual] ), respectively, and the cv-qualification signature of type T1 is a proper subset of the cv-qualification signature of type T2.to
S1 and S2 differ only in their qualification conversion and yield similar types T1 and T2 (4.4 [conv.qual] ), respectively, and the cv-qualification signature of type T1 is a proper subset of the cv-qualification signature of type T2, and S1 is not the deprecated string literal array-to-pointer conversion (4.2 [conv.array] ).
13.3.3.2 [over.ics.rank] paragraph 3 bullet 1 sub-bullet 2 says,
the rank of S1 is better than the rank of S2 (by the rules defined below)...This wording is confusing. The word "below" refers to paragraph 4 (which may not be clear), and the bulk of paragraph 4 deals with comparing conversion sequences whose "rank" is the same.
Proposed resolution (10/00):
In 13.3.3.2 [over.ics.rank] paragraph 3, change
the rank of S1 is better than the rank of S2 (by the rules defined below)to
the rank of S1 is better than the rank of S2, or S1 and S2 have the same rank and are distinguishable by the rules in the paragraph below
13.4 [over.over] paragraph 1 contains a supposedly exhaustive list of contexts in which the name of an overloaded function can be used without an argument list ("...shall not be used without arguments in contexts other than those listed"). However, 14.3.2 [temp.arg.nontype] paragraph 5, bullet 4 gives another context: as a template nontype argument.
Suggested resolution: Add the missing case to 13.4 [over.over].
Proposed resolution (10/00):
Add as the final bullet in 13.4 [over.over] paragraph 1:
- a non-type template-parameter (14.3.2 [temp.arg.nontype]).
and adjust the "or" and final period on the preceding two bullets.
13.4 [over.over] paragraph 2 says,
If the name is a function template, template argument deduction is done (14.8.2.2 [temp.deduct.funcaddr]), and if the argument deduction succeeds, the deduced template arguments are used to generate a single template function, which is added to the set of overloaded functions considered.
It is not clear whether this formulation allows explicit specification of non-deduced template arguments. For instance,
template <int I> void f(double x[]); typedef void (*FPtr)(double x[]); FPtr fp = &f<3>;
If only deduced arguments can be used, this example is ill-formed.
Suggested resolution: Clarify 13.4 [over.over] paragraph 2 to allow both deduced and explicitly-specified template arguments to be used to determine the function template specialization to be added to the overload set.
(See also issues 115 and 214.)
Proposed resolution (10/00):
In 13.4 [over.over] paragraph 2, change
...if the argument deduction succeeds, the deduced template arguments are used to generate a single template function...
to
...if the argument deduction succeeds, the resulting template argument list is used to generate a single function template specialization...
Section 14 [temp] paragraph 8 says:
A non-exported template that is neither explicitly specialized nor explicitly instantiated must be defined in every translation unit in which it is implicitly instantiated (14.7.1 [temp.inst] ) or explicitly instantiated (14.7.2 [temp.explicit] ); no diagnostic is required.Shouldn't the first underlined phrase be omitted to avoid conflict with the second underlined phrase?
From John Spicer:
The first "explicitly instantiated" is intended to mean "explicitly instantiated in some other translation unit".
Proposed Resolution (04/99): Change the text in 14 [temp] paragraph 8 from:
A non-exported template that is neither explicitly specialized nor explicitly instantiated must be defined in every translation unit in which it is implicitly instantiated (14.7.1 [temp.inst] ) or explicitly instantiated (14.7.2 [temp.explicit] ); no diagnostic is required.to:
A non-exported template must be defined in every translation unit in which it is implicitly instantiated (14.7.1 [temp.inst] ), unless the corresponding specialization is explicitly instantiated (14.7.2 [temp.explicit] ) in some translation unit; no diagnostic is required. [Note: See also 14.7.2 [temp.explicit] ]
The phrase "template function" is sometimes used to refer to a template (e.g., in 14 [temp] paragraph 8) and sometimes to refer to a function generated from a template (e.g., 13.4 [over.over] paragraph 4).
Suggested Resolution:
The phrase should mean "a function generated from a template" (or might perhaps include explicit specializations).
Proposed resolution (10/00):
In 1.3 [intro.defs] “signature,” replace "template function specialization" by "function template specialization".
In 9.3 [class.mfct] paragraph 2, replace "template member functions" by "member functions of class templates and member function templates."
In 13.3.1 [over.match.funcs] paragraph 7 and footnote, replace all instances of "template functions" by "function template specializations."
In 13.3.3 [over.match.best] paragraph 1, fourth bullet (counting all bullets in that paragraph), replace "template function specialization" by "function template specialization". In the fifth bullet, replace "template functions" by "function template specializations."
In 13.4 [over.over] paragraph 2, replace "template function" by "function template specialization."
Change 13.4 [over.over] paragraph 4 from:
If more than one function is selected, any template functions in the set are eliminated if the set also contains a non-template function, and any given template function is eliminated if the set contains a second template function that is more specialized than the first according to the partial ordering rules of 14.5.6.2 [temp.func.order]. After such eliminations, if any, there shall remain exactly one selected function.to:
If more than one function is selected, any function template specializations in the set are eliminated if the set also contains a non-template function, and any given function template specialization F1 is eliminated if the set contains a second function template specialization whose function template is more specialized than the function template of F1 according to the partial ordering rules of 14.5.6.2 [temp.func.order]. After such eliminations, if any, there shall remain exactly one selected function.
Change text in section 14 [temp] paragraph 8 from:
A template function declared both exported and inline is just inline and not exported.to:
A function template declared both exported and inline is just inline and not exported.
In 14.5.4 [temp.friend] paragraph 1, third bullet, replace "template function" by "function template" and "function specialization" by "function template specialization."
In footnote 130 (14.5.6 [temp.fct] paragraph 2), replace "template functions" by "function template specializations."
In 14.5.6.2 [temp.func.order] paragraph 1, third bullet change "template function specialization" to "function template specialization".
In 14.8.2 [temp.deduct] paragraph 1, change "template function specialization" to "function template specialization".
In 17.3 [definitions] “component” change "non-member template functions that operate" to "non-member function templates that operate".
In 17.3 [definitions] “traits class” change "template classes and template functions" to "class templates and function templates".
In 20.2 [utility] paragraph 1 change:
This subclause contains some basic template functions and classes that are used throughout the rest of the library.to:
This subclause contains some basic function and class templates that are used throughout the rest of the library.
In 20.3 [pairs] paragrah 1 change "template function" to "function template".
In footnote 215 (_N3225_.20.8.11 [function.pointer.adaptors] paragraph 6) change "template functions" to "function templates".
In 22.3.1 [locale] paragraph 4 change "template function" to "function template".
In 24.2 [iterator.requirements] paragraph 2 change "template function" to "function template".
In 24.4.3 [std.iterator.tags] paragraph 1, change "template function" to "function template specialization."
In 24.4.4 [iterator.operations] paragraph 1 change "template function" to "function template", and "These functions use" to "These function templates use".
In the section heading of 27.7.3.6.4 [ostream.inserters.character] change "template functions" to "function templates".
In 17.5.1.3 [structure.requirements] paragraph 2 change "template class name char_traits" to "class template char_traits".
In the section heading of 18.3.2.3 [numeric.limits] change "Template class" to "Class template".
In 17.6.3.5 [allocator.requirements] paragraph 3 change "template class member rebind" to "member class template rebind" and change "template typedef" to "typedef template".
In the section heading of D.9.1 [depr.lib.binder.1st] change "Template class" to "Class template".
In the section heading of D.9.3 [depr.lib.binder.2nd] change "Template class" to "Class template".
In the section heading of D.10.1 [auto.ptr] change "Template class" to "Class template".
In the section heading of 21.4 [basic.string] change "Template class" to "Class template".
In 21.4 [basic.string] paragraphs 1 and 2 change "template class basic_string" to "class template basic_string".
In the section heading of 22.4.1.1 [locale.ctype] change "Template class" to "Class template".
In the section heading of 22.4.1.2 [locale.ctype.byname] change "Template class" to "Class template".
In the section heading of 22.4.1.4 [locale.codecvt] change "Template class" to "Class template".
In the section heading of 22.4.1.5 [locale.codecvt.byname] change "Template class" to "Class template".
In the section heading of 22.4.2.1 [locale.num.get] change "Template class" to "Class template".
In the section heading of 22.4.2.2 [locale.nm.put] change "Template class" to "Class template".
In the section heading of 22.4.3.1 [locale.numpunct] change "Template class" to "Class template".
In the section heading of 22.4.3.2 [locale.numpunct.byname] change "Template class" to "Class template".
In the section heading of 22.4.4.1 [locale.collate] change "Template class" to "Class template".
In the section heading of 22.4.4.2 [locale.collate.byname] change "Template class" to "Class template".
In the section heading of 22.4.5.1 [locale.time.get] change "Template class" to "Class template".
In the section heading of 22.4.5.2 [locale.time.get.byname] change "Template class" to "Class template".
In the section heading of 22.4.5.3 [locale.time.put] change "Template class" to "Class template".
In the section heading of 22.4.5.4 [locale.time.put.byname] change "Template class" to "Class template".
In the section heading of 22.4.6.1 [locale.money.get] change "Template class" to "Class template".
In the section heading of 22.4.6.2 [locale.money.put] change "Template class" to "Class template".
In the section heading of 22.4.6.3 [locale.moneypunct] change "Template class" to "Class template".
In the section heading of 22.4.6.4 [locale.moneypunct.byname] change "Template class" to "Class template".
In the section heading of 22.4.7.1 [locale.messages] change "Template class" to "Class template".
In the section heading of 22.4.7.2 [locale.messages.byname] change "Template class" to "Class template".
In the section heading of 23.3.3 [deque] change "Template class" to "Class template".
In the section heading of 23.3.5 [list] change "Template class" to "Class template".
In the section heading of 23.6.3 [queue] change "Template class" to "Class template".
In the section heading of 23.6.4 [priority.queue] change "Template class" to "Class template".
In the section heading of 23.6.5 [stack] change "Template class" to "Class template".
In the section heading of 23.3.6 [vector] change "Template class" to "Class template".
In the section heading of 23.4.4 [map] change "Template class" to "Class template".
In the section heading of 23.4.5 [multimap] change "Template class" to "Class template".
In the section heading of 23.4.6 [set] change "Template class" to "Class template".
In the section heading of 23.4.7 [multiset] change "Template class" to "Class template".
In the section heading of 20.6 [template.bitset] change "Template class" to "Class template".
In 20.6 [template.bitset] paragraph 1, change "template class" to "class template".
In the section heading of 24.5.1.1 [reverse.iterator] change "Template class" to "Class template".
In the section heading of 24.5.2.1 [back.insert.iterator] change "Template class" to "Class template".
In the section heading of 24.5.2.3 [front.insert.iterator] change "Template class" to "Class template".
In the section heading of 24.5.2.5 [insert.iterator] change "Template class" to "Class template".
In 24.6 [stream.iterators] paragraph 1, change "template classes" to "class templates".
In the section heading of 24.6.1 [istream.iterator] change "Template class" to "Class template".
In the section heading of 24.6.2 [ostream.iterator] [lib.ostream.iterator] change "Template class" to "Class template".
In the section heading of 24.6.3 [istreambuf.iterator] change "Template class" to "Class template".
In 24.6.3 [istreambuf.iterator] paragraph 1, change "template class" to "class template".
In the section heading of 24.6.3.1 [istreambuf.iterator::proxy] change "Template class" to "Class template".
In the section heading of 24.6.4 [ostreambuf.iterator] change "Template class" to "Class template".
In 24.6.4 [ostreambuf.iterator] paragraph 1, change "template class" to "class template".
In 26.4 [complex.numbers] paragraph 1, change "template class" to "class template".
In the section heading of 26.4.2 [complex] change "Template class" to "Class template".
In _N2798_.26.5.1 [valarray.synopsis] paragraph 1, change "template classes" to "class templates" and change "function signatures" to "function templates".
In the section heading of 26.6.2 [template.valarray] change "Template class" to "Class template".
In the section heading of 26.6.5 [template.slice.array] change "Template class" to "Class template".
In the section heading of 26.6.7 [template.gslice.array] change "Template class" to "Class template".
In the section heading of 26.6.8 [template.mask.array] change "Template class" to "Class template".
In the section heading of 26.6.9 [template.indirect.array] change "Template class" to "Class template".
In 27.3 [iostream.forward] [lib.iostream.forward] paragraphs 3 to 7, change "template classes" to "class templates". [Note: Some editorial changes were made in paragraphs 2 to 8 when these changes were applied in September 2001.]
In the section heading of 27.5.4 [fpos] change "Template class" to "Class template".
In the section heading of 27.5.5 [ios] change "Template class" to "Class template".
In the section heading of 27.6.3 [streambuf] change "Template class" to "Class template".
In 27.6.3 [streambuf] paragraphs 2 and 3, change "template class" to "class template".
In the section heading of 27.7.2.1 [istream] change "Template class" to "Class template".
In the section heading of 27.7.2.5 [iostreamclass] change "Template class" to "Class template".
In the section heading of 27.7.3.1 [ostream] change "Template class" to "Class template".
In 27.8 [string.streams] paragraph 1 change "template classes" to "class templates".
In the section heading of 27.8.2 [stringbuf] change "Template class" to "Class template".
In the section heading of 27.8.3 [istringstream] change "Template class" to "Class template".
In the section heading of 27.8.5 [stringstream] change "Template class" to "Class template".
In the section heading of 27.9.1.1 [filebuf] change "Template class" to "Class template".
In the section heading of 27.9.1.6 [ifstream] change "Template class" to "Class template".
In the section heading of 27.9.1.10 [ofstream] change "Template class" to "Class template".
In the section heading of 27.9.1.14 [fstream] change "Template class" to "Class template".
14 [temp] paragraph 2 says,
[Note: in a class template declaration, if the declarator-id is a template-id, the declaration declares a class template partial specialization (14.5.5 [temp.class.spec] ). ]There is no declarator-id in a class template declaration (cf paragraph 3).
Proposed resolution (10/00):
Replace the phrase "if the declarator-id is a template-id" with "if the class name is a template-id."
14.1 [temp.param] paragraph 10 says:
The set of default template-arguments available for use with a template declaration or definition is obtained by merging the default arguments from the definition (if in scope) and all declarations in scope in the same way as default function arguments are (8.3.6 [dcl.fct.default] )."Can a default argument for a template argument appear in a friend declaration? If so, when is this default argument considered for template instantiations?
For example,
template<class T1, class T2 = int> class A; class B { template<class T1 = int, class T2> friend class A; };Is this well-formed? If it is, should the IS say when the default argument for T1 is considered for instantiations of class A?
Proposed resolution (10/00): Add to the end of 14.1 [temp.param] paragraph 9,
A default template-argument shall not be specified in a friend template declaration.
(See also issue 136.)
The example in 14.1 [temp.param] paragraph 8 is:
template<int* a> struct R { /*...*/ }; int* p; R<p> w;There was a French comment was that this is an error, and there was general agreement with that.
I've been looking for the verbiage that specifies that this is an error and haven't found it. In particular, nothing in 14.1 [temp.param] ("Template parameters") nor 14.3.2 [temp.arg.nontype] ("Template non-type arguments") appears to rule out this case. (14.3.2 [temp.arg.nontype] paragraph 1 allows an argument to be "the name of an object or function with external linkage," with no limitation on the kinds of parameters such a name can match; "p" is, in fact, such a name.)
Should the resolution of the French comment include beefing up one or both of these sections to cover the applicable rules explicitly?
Proposed Resolution (04/99): Change the example in 14.1 [temp.param] paragraph 8 from:
template<int *a> struct R { /* ... */ }; template<int b[5]> struct S { /* ... */ }; int *p; R<p> w; // OK S<p> x; // OK due to parameter adjustment int v[5]; R<v> y; // OK due to implicit argument conversion S<v> z; // OK due to both adjustment and conversionto:
template<int *a> struct R { /* ... */ }; template<int b[5]> struct S { /* ... */ }; int p; R<&p> w; // OK S<&p> x; // OK due to parameter adjustment int v[5]; R<v> y; // OK due to implicit argument conversion S<v> z; // OK due to both adjustment and conversionFurthermore, in 14.3.2 [temp.arg.nontype] paragraph 1:
At the Dublin meeting (04/99), the Committee proposed to resolve issue 22 by simply changing the wording to make clear that a template parameter cannot be used in its own default argument. This creates a third treatment of this kind of situation, in addition to 3.3.2 [basic.scope.pdecl] paragraph 1, where declarators are in scope and can be used in their initializers, and paragraph 3, where an enumerator is not in scope until after its complete enumerator-definition. The Dublin resolution is for the template parameter to be in scope in its default argument but not usable. It would be more consistent to treat template parameters like enumerators: simply not in scope until the entire template-parameter declaration is seen.
On a related note, 14.1 [temp.param] paragraph 14 should be rewritten to connect the prohibition with visibility rules; otherwise, it sounds as if the following example is not permitted:
const int Z = 1; template <int X = Z, int Z> class A {};
Notes from 04/00 meeting:
The core working group did not reach consensus on the suggested approach to issue 22. However, it was agreed that the intent expressed in the earlier resolution would be better served by different wording.
Proposed resolution (10/00):
[Note: This resolution supersedes the resolution to issue 22.]
Replace 14.1 [temp.param] paragraph 14 as follows:
A template parameter shall not be used in its own default argument.
I have a request for clarification regarding a issue similar to John Wiegley's, but wrt. the ::template syntax. More precisely, where is
X::template Yallowed? (It is required for dependent X where Y is a template-id, I believe, but it doesn't seem to be disallowed elsewhere.)
The question also holds for '.template' and '->template'.
Proposed Resolution (04/99): Append to 14.2 [temp.names] paragraph 5:
Furthermore, names of member templates shall not be prefixed by the keyword template if the postfix-expression or qualified-id does not appear in the scope of a template. [Note: just as is the case with the typename prefix, the template prefix is allowed in cases where it is not strictly necessary; i.e., when the expression on the left of the -> or ., or the nested-name-specifier is not dependent on a template-parameter. ]
It appears from the grammar that explicit template arguments cannot be specified for overloaded operator names. Does this mean that template operators can never be friends?
But assuming that I read things wrong, then I should be able to specify a global template 'operator +' by writing:
friend A::B operator + <>(char&);John Spicer:
You should be able to have explicit template arguments on operator functions, but the grammar does seem to prohibit it (unless I'm reading it incorrectly). This is an error in the grammar, they should be permitted.
Proposed resolution (10/00):
Change the grammar specified in 13.5 [over.oper]
paragraph 1 from
The explanation in 14.3.2 [temp.arg.nontype] paragraph 2 of why a string literal cannot be used as a template argument leaves something to be desired:
...because a string literal is an object with internal linkage.I can't find anything that says that a string literal has internal linkage. In fact, I'd be pretty surprised if I did, since linkage is defined (in 3.5 [basic.link] ) strictly in terms of names, and a string literal doesn't have a name. Actually, I think that it's the namelessness of a string literal that prevents it from being a template argument; only the third and fourth bullets of 14.3.2 [temp.arg.nontype] paragraph 1 could conceivably apply, and both of those require that the entity have a name (i.e., that they be given as an id-expression).
Proposed Resolution (10/99): In 14.3.2 [temp.arg.nontype] paragraph 2, change
[Note: a string literal (2.14.5 [lex.string] ) is not an acceptable template-argument because a string literal is an object with internal linkage.to
[Note: a string literal (2.14.5 [lex.string] ) does not satisfy the requirements of any of these categories and thus is not an acceptable template-argument.
The phrase "member function template" is used in 3.2 [basic.def.odr] paragraph 5 in the list of entities whose definitions can appear more than once in a program, with a cross-reference to 14.5.1.1 [temp.mem.func]. The title of that section is "Member functions of class templates," and paragraph 1 of that section says,
A member function template may be defined outside of the class template in which it is declared.
The example in that paragraph shows a non-template member function of a class template being defined. This gives the impression that the phrase "member function template" is intended to refer to a member function of a class template.
If this usage were intended, much of the rest of the Standard would be unintelligible: objects of class template specializations could not be copied (12.8 [class.copy] paragraph 3), member functions of class templates could not be declared virtual (14.5.2 [temp.mem] paragraph 3), etc.
Suggested resolution:
Change "member function template" to "member function of a class template" in both 3.2 [basic.def.odr] paragraph 5 and 14.5.1.1 [temp.mem.func] paragraph 1.
(See also issue 205.)
Proposed resolution (10/00): As suggested.
14.5.6.1 [temp.over.link] , paragraphs 5 and 6, describes equivalence and functional equivalence for expressions involving template parameters. As a note in paragraph 5 points out, such expressions may involve type parameters as well as non-type parameters.
Paragraph 7, however, describes the equivalence of function templates only with respect to non-type template parameters. It appears to be unspecified how to determine the equivalence of template functions whose types involve expressions that use template type parameters.
template <int I> struct S { }; // The following two declarations are equivalent: template <int I> void f(S<I>); template <int J> void f(S<J>); // The IS doesn't say whether these are equivalent: template <class T> void f(S<sizeof(T)>); template <class T> void f(S<sizeof(T)>);
Proposed resolution (10/99): Remove the three uses of the words "non-type" in 14.5.6.1 [temp.over.link] paragraph 7.
In 14.6 [temp.res] , references to the nonexistent syntactic non-terminal qualified-name occur twice in paragraph 3, twice in paragraph 4, and once in paragraph 5. There is also a reference in 14.1 [temp.param] paragraph 2.
Proposed resolution (10/99): Change the reference in all these cases to qualified-id.
The wording in 14.6 [temp.res] paragraph 3:
A qualified-name that refers to a type and that depends on a template-parameter (14.6.2 [temp.dep] ) shall be prefixed by the keyword typename to indicate that the qualified-name denotes a type, forming an elaborated-type-specifier (7.1.6.3 [dcl.type.elab] ).was intended to say:
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 ...in much the same vein as 14.6.2.1 [temp.dep.type], second bullet, first half.
Proposed resolution (10/00): As suggested.
John Spicer: In 14.6 [temp.res] paragraph 5, the standard says
The keyword typename shall only be used in template declarations and definitions...My understanding of the intent of this restriction is to say that typename is only allowed in contexts in which template dependent names can be found, but the wording leaves open to interpretation whether typename is allowed in an explicit specialization, such as:
template <class T> struct A {}; template <class T> struct B { typedef int X; }; template <> struct A<int> { typename B<int>::X x; };My understanding is that such usage is not permitted. This should be clarified one way or the other.
Mike Miller: I agree with your understanding that you are not allowed to use typename in an explicit specialization. However, I think the standard already says that — an explicit specialization is not a template declaration. According to the grammar in 14 [temp] paragraph 1, a template-declaration must have a non-empty template-parameter-list.
Nathan Myers: Is there any actual reason for this restriction? Its only apparent effect is to make it harder to specialize templates, with no corresponding benefit.
Proposed resolution (10/00):
In 14.6 [temp.res] paragraph 5, replace
The keyword typename shall only be applied to qualified names, but those names need not be dependent.
with
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.
Paragraphs 3-4 of 14.6.2 [temp.dep] say, in part,
if a base class of [a class] template depends on a template-parameter, the base class scope is not examined during name lookup until the class template is instantiated... If a base class is a dependent type, a member of that class cannot hide a name declared within a template, or a name from the template's enclosing scope.
John Spicer: The wording in paragraph 4 seems particularly odd to me. It essentially changes the order in which scopes are considered. If a scope outside of the template declares a given name, that declaration hides entities of the same name from template dependent base classes (but not from nondependent base classes).
In the following example, the calls of f and g are handled differently because B::f cannot hide ::f, but B::g doesn't try to hide anything, so it can be called.
extern "C" int printf(char *, ...); template <class T> struct A : T { void h(T t) { f(t); // calls ::f(B) g(t); // calls B::g } }; struct B { void f(B){printf("%s", "in B::f\n");} void g(B){printf("%s", "in B::g\n");} }; void f(B){printf("%s", "in ::f\n");} int main() { A<B> ab; B b; ab.h(b); }
I don't think the current wording in the standard provides a useful facility. The author of class A can't be sure that a given call is going to call a base class function unless the base class is explicitly specified. Adding a new global function could cause the program to suddenly change meaning.
What I thought the rule was is, "If a base class is a dependent type a member of that class is not found by unqualified lookup".
Derek Inglis: My understanding is the same except that I'd remove the word "qualified" from your sentence.
Erwin Unruh: My interpretation is based on 14.6.4 [temp.dep.res] and especially 14.6.4.2 [temp.dep.candidate] (and largely on my memory of the discussions). For all unqualified names you do something like the following algorithm:
Regarding names from base classes you cannot find them in 2) because you don't know what base class you have. You cannot find them in 3) because members of classes are not found by Koenig lookup (only namespaces are considered). So you don't find them at all (for unqualified names).
For a qualified name, you start lookup for each 'part' of the qualification. Once you reach a dependent part, you stop and continue lookup at the instantiation point. For example:
namespace A { namepace B { template <class T> class C { template <class U> class D { typedef int E; // ... }; }; }; }; template <class T> class F : public T { typename A::B::C<int>::D<T>::E var1; typename A::B::C<T>::D<int>::E var2; typename F::T::X var3; }
For var1 you do lookup for A::B::C<int>::D at definition time, for var2 you only do lookup for A::B::C. The rest of the lookup is done at instantiation time since specialisations could change part of the lookup. Similarly the lookup for var3 stops after F::T at definition time.
My impression was that an unqualified name never refers to a name in a dependent base class.
(See also issue 197.)
Proposed resolution (10/00):
In 14.6.2 [temp.dep] paragraph 3, replace
In the definition of a class template or in the definition of a member of such a template that appears outside of the template definition, if a base class of this template depends on a template-parameter, the base class scope is not examined during name lookup until the class template is instantiated.
with
In the definition of a class template or a member of a class template, if a base class of the class template depends on a template-parameter, the base class scope is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member.
Remove from 14.6.2 [temp.dep] paragraph 4:
If a base class is a dependent type, a member of that class cannot hide a name declared within a template, or a name from the template's enclosing scopes.
Mark Mitchell (via John Spicer): Given:
template <class T> struct S { struct I1 { typedef int X; }; struct I2 : public I1 { X x; }; };
Is this legal? The question really boils down to asking whether or not I1 is a dependent type. On the one hand, it doesn't seem to fit any of the qualifications in 14.6.2.1 [temp.dep.type] . On the other, 14.7.3 [temp.expl.spec] allows explicit specialization of a member class of a class template, so something like:
template <> struct S<double>::I1 { int X; };
is apparently legal. But, then, `X' no longer refers to a type name. So, it seems like `I1' should be classified as dependent. What am I missing?
Erwin Unruh: I wrote that particular piece of text and I just missed the problem above. It is intended to be a dependent type. The reasoning is that I1 is just a shorthand for S<T>::I1 which clearly is dependent.
Suggested Resolution: (Erwin Unruh)
I think the list of what is a dependent type should be extended to cover "a type declared and used within the same template" modulo of phrasing.
(See also paper J16/00-0009 = WG21 N1231. This issue is also somewhat related to issue 205: classes nested inside template classes are, in some sense, "templates," just as non-template member functions of class templates and static data members of class templates are "templates.")
Proposed resolution (10/00):
Add after 14.6.1 [temp.local] paragraph 2:
Within the scope of a class template, when the unqualified name of a nested class of the class template is referred to, it is equivalent to the name of the nested class qualified by the name of the enclosing class template. [Example:template <class T> struct A { class B {}; // B is equivalent to A::B, which is equivalent to A<T>::B, // which is dependent. class C : B { }; };—end example]
At what point are semantic constraints applied to uses of non-dependent names in template definitions? According to 14.6.3 [temp.nondep] , such names are looked up and bound at the point at which they are used, i.e., the point of definition and not the point of instantiation. However, the text does not mention the checking of semantic constraints.
Contrast this omission with the treatment of names in default argument expressions given in 8.3.6 [dcl.fct.default] paragraph 5, where the treatment of semantic constraints is explicit:
The names in the expression are bound, and the semantic constraints are checked, at the point where the default argument expression appears.The following code is an example of where this distinction matters:
struct S; template <class T> struct Q { S s; // incomplete type if semantic constraints // are applied in the definition context }; struct S { }; // Point of instantiation of Q<int>; S is complete here Q<int> si;There is real-world code that depends on late checking of semantic constraints. The Standard should be explicit about whether this code is broken or not.
Proposed resolution (10/00):
In 14.6 [temp.res] paragraph 7, add the following immediately preceding the note:
If a type used in a non-dependent name is incomplete at the point at which a template is defined but is complete at the point at which an instantiation is done, and if the completeness of that type affects whether or not the program is well-formed or affects the semantics of the program, the program is ill-formed; no diagnostic is required.
14.1 [temp.param] paragraph 13 says:
The scope of a template-parameter extends from its point of declaration until the end of its template. In particular, a template-parameter can be used in the declaration of subsequent template-parameters and their default arguments.Is the following well-formed?
template<class U = U> class X { ... };
[Note: this issue is resolved by the resolution of issue 187.]
Problem Description: At least four of the examples in 14.7.3 [temp.expl.spec] have errors.
Proposed Resolution (10/99):
1. Change the example in paragraph 8 from:
[Example:to:// file #1 #include <vector> // Primary class template vector export template<class T> void f(t) { vector<T> vec; // should match the specialization /* ... */ } // file #2 #include <vector> class B { }; // Explicit specialization of vector for vector<B> template<class T> class vector<B> { /* ... */ } template<class T> void f(T); void g(B b) { f(b); // ill formed: // f<B> should refer to vector<B>, but the // specialization was not declared with the // definition of f in file #1 }—end example]
[Example:// file #1 #include <vector> // Primary class template vector export template<class T> void f(T) { std::vector<T> vec; // should match the specialization /* ... */ }; // file #2 #include <vector> class B { }; // Explicit specialization of vector for vector<B> namespace std { template<> class vector<B> { /* ... */ }; } template<class T> void f(T); void g(B b) { f(b); // ill formed: // f<B> should refer to vector<B>, but the // specialization was not declared with the // definition of f in file #1 }—end example]
2. The example in paragraph 16 as it appears in the IS:
[Example:The word 'partial' in the third comment in the example should be removed because this example does not illustrate partial specialization. Also, the two specializations of template<> template<> void A<int>::g(int, char); violate 14.7 [temp.spec] , paragraph 5, which reads:template<class T> struct A { void f(T); template<class X> void g(T, X); void h(T) { } }; // specialization template<> void A<int>::f(int); // out of class member template definition template<class T> template<class X> void A<T>::g(T,X) { } // member template partial specialization template<> template<class X> void A<int>::g(int, X); // member template specialization template<> template<> void A<int>::g(int, char); // X deduced as char template<> template<> void A<int>::g<char>(int, char); // X specified as char // member specialization even if defined in class definition template<> void A<int>::h(int) { }—end example]
No program shall explicitly instantiate any template more than once, both explicitly instantiate and explicitly specialize a template, or specialize a template more than once for a given set of template-arguments. An implementation is not required to diagnose a violation of this rule.Proposed resolution (10/99):
[Example:template<class T> struct A { void f(T); template<class X1> void g1(T, X1); template<class X2> void g2(T, X2); void h(T) { } }; // specialization template<> void A<int>::f(int); // out of class member template definition template<class T> template<class X1> void A<T>::g1(T,X1) { } // member template specialization template<> template<class X1> void A<int>::g1(int, X1); // member template specialization template<> template<> void A<int>::g1(int, char); // X1 deduced as char template<> template<> void A<int>::g2<char>(int, char); // X2 specified as char // member specialization even if defined in class definition template<> void A<int>::h(int) { }—end example]
3. Remove the spurious semicolon (or the curly brackets) from the end of the last line in the example in paragraph 17. This is the example as it appears in the IS:
[Example:Proposed resolution (10/99):template<class T1> class A { template<class T2> class B { void mf(); }; }; template<> template<> A<int>::B<double> { }; template<> template<> void A<char>::B<char>::mf() {};—end example]
[Example:template<class T1> class A { template<class T2> class B { void mf(); }; }; template<> template<> A<int>::B<double>; template<> template<> void A<char>::B<char>::mf();—end example]
Note (Steve Adamczyk, March 2002): that's still incorrect. The missing "class" was added editorially when TC1 was prepared.
4. Remove spurious semicolons (or curly brackets) from the specializations of mf1 and mf2 in the example in paragraph 18. This is the text of the example as it appears in the IS:
[Example:Proposed resolution (10/99):template<class T1> class A { template<class T2> class B { template<class T3> void mf1(T3); void mf2(); }; }; template<> template<class X> class A<int>::B { }; template<> template<> template<class T> void A<int>::B<double>::mf1(T t) { }; template<class Y> template<> void A<Y>::B<double>::mf2() { }; // ill-formed; B<double> is specialized but // its enclosing class template A is not—end example]
[Example:template<class T1> class A { template<class T2> class B { template<class T3> void mf1(T3); void mf2(); }; }; template<> template<class X> class A<int>::B { }; template<> template<> template<class T> void A<int>::B<double>::mf1(T t) { } template<class Y> template<> void A<Y>::B<double>::mf2() { } // ill-formed; B<double> is specialized but // its enclosing class template A is not—end example]
Note (Steve Adamczyk, March 2002): that's still incorrect. See issue 336.
Paragraph 12 should address partial ordering. It wasn't updated when that change was made and conflicts with 14.5.6.2 [temp.func.order] paragraph 1.
Proposed resolution (10/00):
Remove 14.7.3 [temp.expl.spec] paragraph 12 and the example that follows.
14.8.1 [temp.arg.explicit] paragraph 6 contains the following example:
namespace A { struct B { }; template<int X> void f(); } namespace C { template<class T> void f(T t); } void g(A::B b) { f<3>(b); // ill-formed: not a function call A::f<3>(b); // well-formed C::f<3>(b); // ill-formed; argument dependent lookup // only applies to unqualified names using C::f; f<3>(b); // well-formed because C::f is visible; then // A::f is found by argument dependent lookup }
A::f() should have a parameter of type A::B.
Proposed resolution (10/00):
In the example in 14.8.1 [temp.arg.explicit] paragraph 6, change the third line from
template <int X> void f();
to
template <int X> void f(B);
14.8.2.5 [temp.deduct.type] paragraph 18 uses incorrect syntax. Instead of
template <template X<class T> > struct A { }; template <template X<class T> > void f(A<X>) { }it should be
template <template <class T> class X> struct A { }; template <template <class T> class X> void f(A<X>) { }
Proposed resolution (10/00): As suggested.
[Note: this section was numbered 14.8.2.4 in ISO/IEC 14882:2003.]
At the top of clause 15, in paragraph 2, it says:
A goto, break, return, or continue statement can be used to transfer control out of a try block or handler, but not into one.What about switch statements?
switch ( f() ) { case 1: try { g(); case 2: h(); } catch (...) { // handler } break; }Daveed Vandevoorde:
Consider:
void f() { try { label: ; } catch(...) { goto label; } }Now the phrase "try block" (without a hyphen) is used in paragraph 1 in a way that causes me to think that it is not intended to include the corresponding handlers. On the other hand, the grammar entity "try-block" (with hyphen) does include the handlers. So is the intent to prohibit the above or not?
Proposed resolution (10/00:
Change text in 15 [except] paragraph 2 from:
A goto, break, return, or continue statement can be used to transfer control out of a try block or handler, but not into one.to:
A goto or switch statement shall not be used to transfer control into a try block or into a handler.
[ Example:void f() {—end example ]
goto l1; // Ill-formed
goto l2; // Ill-formed
try {
goto l1; // OK
goto l2; // Ill-formed
l1: ;
} catch (...) {
l2: ;
goto l1; // Ill-formed
goto l2; // OK
}
}
A goto, break, return, or continue statement can be used to transfer control out of a try block or handler.
(See also issue 246.)
15.3 [except.handle] paragraph 3 says,
A handler is a match for a throw-expression with an object of type E...
This wording leaves it unclear whether it is the dynamic type of the object being thrown or the static type of the expression that determines whether a handler is a match for a given exception. For instance,
struct B { B(); virtual ~B(); }; struct D : B { D(); }; void toss(const B* b) { throw *b; } void f() { const D d; toss(&d); }
In this code, presumably the type to be matched is B and not const D (15.1 [except.throw]).
Suggested resolution: Replace the cited wording as follows:
A handler is a match for a throw-expression which initialized a temporary (15.1 [except.throw]) of type E...
Proposed resolution (10/00):
Change 15.1 [except.throw] paragraph 3 from
A throw-expression initializes a temporary object, the type of which is determined...
to
A throw-expression initializes a temporary object, called the exception object, the type of which is determined...
Change 15.3 [except.handle] paragraph 3 from
A handler is a match for a throw-expression with an object of type E if...
to
A handler is a match for an exception object of type E if...
15.4 [except.spec] paragraph 3 should say what happens when two pointers to members with different exception specifications are assigned to each other, initialized with one another, etc.
Proposed Resolution (04/99): Change the text in 15.4 [except.spec] paragraph 3 from:
Similarly, any function or pointer to function assigned to, or initializing, a pointer to function shall only allow exceptions that are allowed by the pointer or function being assigned to or initialized.to:
A similar restriction applies to assignment to and initialization of pointers to functions, pointers to member functions, and references to functions: the target entity shall allow at least the exceptions allowed by the source value in the assignment or initialization.
The standard is inconsistent about constness inside exception specifications.
struct X {}; struct Y:X {}; const Y bar() {return Y();} void foo()throw(const X) { throw bar(); }It is unclear whether calling foo will result in a call to std::unexpected. According to 15.4 [except.spec] paragraph 7, only two cases are treated specially with regard to inheritance: If "class X" appears in the type-id-list, or if "class X*" appears in the type-id-list. Neither is the case here, so foo only allows exceptions of the same type (const X). As a result, std::unexpected should be called.
On the other hand, the intent of exception specification appears to allow an implementation of this example as
void foo() try{ throw bar(); }catch(const X){ throw; }catch(...){ std::unexpected(); }According to 15.3 [except.handle] , this replacement code would catch the exception, so std::unexpected would not be called.
Suggested resolution: Change 15.4 [except.spec] paragraph 7 to read
A function is said to allow all exception objects of all types E for which one of the types T in the type-id-list would be a handler, according to 15.3 [except.handle] .
Proposed resolution (10/00):
Replace 15.4 [except.spec] paragraph 7 with the following:
A function is said to allow an exception of type E if its exception-specification contains a type T for which a handler of type T would be a match (15.3 [except.handle]) for an exception of type E.
D.1 [depr.incr.bool] indicates that use of the postfix ++ with a bool operand is deprecated. Annex D [depr] says nothing about prefix ++. However, this use of prefix ++ is also deprecated, according to 5.3.2 [expr.pre.incr] paragraph 1. Presumably D.1 [depr.incr.bool] should be expanded to cover prefix ++, or another section should be added to Annex D [depr].
Proposed resolution (10/00):
Change the entire section D.1 [depr.incr.bool], including its heading, to read as follows:
D.1 Increment operator with bool operand [depr.incr.bool] The use of an operand of type bool with the ++ operator is deprecated (see 5.3.2 [expr.pre.incr] and 5.2.6 [expr.post.incr]).
[Voted into the WP at the November, 2010 meeting as paper N3146.]
The list of identifier characters specified in the C++ standard annex _N2691_.E [extendid] and the C99 standard annex D are different. The C99 standard includes more characters.
The C++ standard says that the characters are from "ISO/IEC PDTR 10176" while the C99 standard says "ISO/IEC TR 10176". I'm guessing that the PDTR is an earlier draft of the TR.
Should the list in the C++ standard be updated?
Tom Plum: In my opinion, the "identifier character" issue has not been resolved with certainty within SC22.
One critical difference in C99 was the decision to allow a compiler to accept more characters than are given in the annex. This allows for future expansion.
The broader issue concerns the venue in which the "identifier character" issue will receive ongoing resolution.
Notes from 10/00 meeting:
The core language working group expressed a strong preference (13/0/5 in favor/opposed/abstaining) that the list of identifier characters should be extensible, as is the case in C99. However, the fact that this topic is under active discussion by other bodies was deemed sufficient reason to defer any changes to the C++ specification until the situation is more stable.
Notes from October, 2005 meeting:
The working group expressed interest in the kind of approach taken by XML 1.1, in which the definition of an identifier character is done by excluding large ranges of the Unicode character set and accepting any character outside those ranges, rather than by affirmatively designating each identifier character in each language. As noted above, consideration of this issue was previously deferred pending other related standardization efforts. Clark Nelson will investigate whether these have reached a point at which progress on this issue in C++ is now possible.
Additional note (May, 2008):
Issue 663 also deals with this appendix, and the proposed resolution there is to update the table to reflect the newest available technical report, ISO/IEC TR 10176:2003. That resolution might be seen as sufficient for this issue, as well. However, that approach does not address several of the concerns mentioned in the discussion above: coordination with WG14, the extensibility of the list of identifiers, the alternative approach used in the XML specification, etc.
[Voted into the WP at the March, 2011 meeting as part of paper N3272.]
There are some kinds of declarations that can appear in a derived class and hide names from a base class, but for which the syntax does not permit a [[hiding]] attribute. For example:
struct B1 { int N; int M; }; struct B2 { int M; }; struct [[base_check]] D: B1, B2 { enum { N }; // hides B1::N but cannot take an attribute using B1::M; // hides B2::M but cannot take an attribute };
Additional note (October, 2010):
alias-declarations should also be considered in this regard.
Notes from the November, 2010 meeting:
Paper N3206 did not address these cases; in fact, it introduced additional member declarations that cannot be annotated as hiding a base class member (function-definitions and template-declarations), because the new virt-specifier applies to a member-declarator and none of these member-declarations uses a member-declarator.
Additional note (November, 2010):
The injected-class-name can also hide a name from a base class but cannot be annotated with new.
[Voted into the WP at the November, 2010 meeting as part of paper N3206.]
The meaning of the [[base_check]] and [[hiding]] attributes is defined in terms of hiding as described in 3.3.10 [basic.scope.hiding]. In that section, however, hiding is orthogonal to overriding: practically by definition, a function that overrides a base class virtual function also hides it. According to the current specification, the [[override]] and [[hiding]] attributes would always need to be specified together on every overriding function in a [[base_check]] class. This is presumably unintended, so the current wording should be amended so that [[override]] implies [[hiding]] or some such.
[Voted into the WP at the November, 2010 meeting in paper N3206.]
N3092 comment US 44The facility for checking hiding and overriding of base class members should not use the attribute syntax but should use keywords instead. Concerns about breaking code by changing current identifiers into keywords can be addressed by using contextual keywords, i.e., by putting the keywords into syntactic locations where identifiers cannot appear and thus continuing to allow their use as ordinary identifiers in other contexts.
Notes from the August, 2010 meeting:
CWG expressed a preference for non-contextual keywords for these features.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 56Access declarations were deprecated in the 1998 standard and have no benefits over using-declarations. They should be removed in C++0x.
Proposed resolution (August, 2010):
Delete _N3225_.11.3 [class.access.dcl].
Delete _N3225_.D.3 [depr.access.dcl].
Delete the following production from the grammar in 9.2 [class.mem] paragraph 1:
...Except when used to declare friends (11.4) or to introduce the name of a member of a base class into a derived class (7.3.3, 11.3), member-declarations declare members of the class...
Delete 7.3.3 [namespace.udecl] paragraph 19:
[Note: use of access-declarations (_N3225_.11.3 [class.access.dcl]) is deprecated; member using-declarations provide a better alternative. —end note]
[Voted into the WP at the March, 2011 meeting as document N3288.]
N2800 comment UK 6There should be a list of incompatibilities between the current and previous Standards, as in ISO/IEC TR 10176 4.1.1 paragraph 9.
(See document N2733 for an initial list of this information.)
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
1.8 [intro.object] paragraph 6 says,
Two distinct objects that are neither bit-fields nor base class subobjects of zero size shall have distinct addresses.
This formulation leaves open the possibility that two base class subobjects of the same type could have the same address (because one or both might be zero-length base class subobjects).
Proposed resolution (November, 2010):
Change 1.8 [intro.object] paragraph 6 as follows:
Unless an object is a bit-field or a base class subobject of zero size, the address of that object is the address of the first byte it occupies. Two distinct objects that are neither not bit-fields nor base class subobjects of zero size shall have distinct addresses, if both have the same type or if not both are base class subobjects of zero size...
[Voted into the WP at the November, 2010 meeting.]
N3092 comment GB 6There are core issues surrounding the undefined behavior of dereferencing a null pointer. It appears the intent is that dereferencing is well defined, but using the result of the dereference will yield undefined behavior. This topic is too confused to be the reference example of undefined behavior, or should be stated more precisely if it is to be retained.
(See also issue 232.)
Proposed resolution (September, 2010):
Change 1.9 [intro.execution] paragraph 4 as follows:
Certain other operations are described in this International Standard as undefined (for example, the effect of dereferencing the null pointer attempting to modify a const object). [Note:...
[Voted into the WP at the November, 2010 meeting.]
N3092 comment GB 7The current wording of 1.9 [intro.execution] paragraph 6 could be read as saying that any signal would leave the program in an unspecified state after completing.
Proposed resolution (August, 2010):
Change 1.9 [intro.execution] paragraph 6 as follows:
When the processing of the abstract machine is interrupted by receipt of a signal, the values of objects which are neither
of type volatile std::sig_atomic_t nor
lock-free atomic objects (29.4 [atomics.lockfree])
are unspecified during the execution of the signal handler, and the value of any object not in either of these two categories that is modified by the handler becomes undefined.
[Voted into the WP at the November, 2010 meeting as part of paper N3196.]
N3092 comment CA 12The current wording of the standard suggests that release sequences are maximal with respect to sequence inclusion, i.e. that if there are two release operations in the modification order,
mod mod rel1----->rel2----->w
then [rel1;rel2;w] is the only release sequence, as the other candidate [rel2;w] is included in it. This interpretation precludes synchronizing with releases which have other releases sequenced-before them. We believe that the intention is actually to define the maximal release sequence from a particular release operation, which would admit both [rel1;rel2;w] and [rel2;w].
Proposed resolution (August, 2010):
Change 1.10 [intro.multithread] paragraph 6 as follows:
A release sequence from a release operation A on an atomic object M is a maximal contiguous sub-sequence of side effects in the modification order of M, where the first operation is a release A, and every subsequent operation
is performed by the same thread that performed the release, or
is an atomic read-modify-write operation.
[Voted into the WP at the November, 2010 meeting as part of paper N3196.]
N3092 comment CA 15The current draft has release/acquire synchronize-with edges only between a release on one thread and an acquire on a different thread, whereas the definition of dependency-ordered-before permits the release and consume to be on the same thread; it seems odd to permit the latter. (At the moment function arguments can't race or sync with each other, but they can be dependency ordered before each other.)
We don't currently have an example in which this makes a real difference, but for symmetry could suggest changing the definition of dependency-ordered-before in 1.10 [intro.multithread].
Proposed resolution (August, 2010):
Change 1.10 [intro.multithread] paragraph 9 as follows:
An evaluation A is dependency-ordered before an evaluation B if
A performs a release operation on an atomic object M, and, on another thread, B performs a consume operation on M and reads a value written by any side effect in the release sequence headed by A, or
for some evaluation X, A is dependency-ordered before X and X carries a dependency to B.
[Note:...
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 13“Raw” strings are still only Pittsburgh-rare strings: the reversion in phase 3 only applies to an r-char-sequence. It should apply to the entire raw string literal.
Proposed resolution (August, 2010):
Change 2.2 [lex.phases] paragraph 1 phase 1 as follows:
...(An implementation may use any internal encoding, so long as an actual extended character encountered in the source file, and the same extended character expressed in the source file as a universal-character-name (i.e., using the \uXXXX notation), are handled equivalently except where this replacement is reverted in a raw string literal.).)
Change 2.2 [lex.phases] paragraph 1 phase 3 as follows:
...[Example: see the handling of < within a #include preprocessing directive. —end example] Within the r-char-sequence of a raw string literal, any transformations performed in phases 1 and 2 (trigraphs, universal-character-names, and line splicing) are reverted.
Change 2.2 [lex.phases] paragraph 1 phase 5 as follows:
Each source character set member and universal-character-name in a character literal or a string literal, as well as each escape sequence and universal-character-name in a character literal or a non-raw string literal, is converted to the corresponding member of the execution character set (2.14.3 [lex.ccon], 2.14.5 [lex.string]); if there is no corresponding member, it is converted to an implementation-defined member other than the null (wide) character.
Change 2.3 [lex.charset] paragraph 2 as follows:
...Additionally, if the hexadecimal value for a universal-character-name outside the c-char-sequence, s-char-sequence, or r-char-sequence of a character or string literal corresponds to a control character (in either of the ranges 0x000x1F or 0x7F0x9F, both inclusive) or to a character in the basic source character set, the program is ill-formed. [Footnote: A sequence of characters resembling a universal-character-name in an r-char-sequence (2.14.5 [lex.string]) does not form a universal-character-name. —end footnote]
Change 2.5 [lex.pptoken] paragraph 3 as follows:
If the input stream has been parsed into preprocessing tokens up to a given character:
if If the next character begins a sequence of characters that could be the prefix and initial double quote of a raw string literal, such as R", the next preprocessing token shall be a raw string literal;. Between the initial and final double quote characters of the raw string, any transformations performed in phases 1 and 2 (trigraphs, universal-character-names, and line splicing) are reverted; this reversion shall apply before any d-char, r-char, or delimiting parenthesis is identified. The raw string literal is defined as the shortest sequence of characters that matches the raw-string pattern
encoding-prefixopt R raw-string
otherwise Otherwise, the next preprocessing token is the longest sequence of characters that could constitute a preprocessing token, even if that would cause further lexical analysis to fail.
Delete footnote 24 in 2.14.5 [lex.string] paragraph 2:
Use of characters with trigraph equivalents in a d-char-sequence may produce unintended results.
Insert the following examples after 2.14.5 [lex.string] paragraph 4:
[Example: The raw string
R"a( )\ a" )a"
is equivalent to "\n)\\\na\"\n". The raw string
R"(??)"
is equivalent to "\?\?". The raw string
R"#( )??=" )#"
is equivalent to "\n)\?\?=\"\n". —end example]
2.12 [lex.key] paragraph 2 says,
Furthermore, the alternative representations shown in Table 4 for certain operators and punctuators (2.6 [lex.digraph]) are reserved and shall not be used otherwise:
Also, 2.6 [lex.digraph] paragraph 2 says,
In all respects of the language, each alternative token behaves the same, respectively, as its primary token, except for its spelling.
It is not clear whether the following example is well-formed:
#define STR2(x) #x #define STR(x) STR2(x) int main() { return sizeof STR('\0'bitor 0) - sizeof STR('\0'bitor 0); }
In this example, bitor is not the | operator but the identifier in a user-defined-character-literal. Does this violate the restrictions of 2.12 [lex.key] and 2.6 [lex.digraph]?
Proposed resolution (March, 2011):
This issue is resolved by the resolution of issue 1239 in document N3262, since literal suffix identifiers must begin with an underscore.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 15Passing a name qualified by the global scope operator :: as a template argument can inadvertently trigger recognition of the <: digraph, causing a syntax error. This should be handled by a lexical rule similar to the special treament given to >> so that <:: would be recognized as an open angle-bracket followed by the scope-resolution operator.
Proposed resolution (August, 2010):
Insert a bullet into the list in 2.5 [lex.pptoken] paragraph 3 as follows:
If the input stream has been parsed into preprocessing tokens up to a given character:
if the next character begins a sequence of characters that could be the prefix and initial double quote of a raw string literal, such as R", the next preprocessing token shall be a raw string literal;
otherwise, if the next three characters are <:: and the subsequent character is neither : nor >, the < is treated as a preprocessor token by itself (and not as the first character of the alternative token <:);
otherwise, the next preprocessing token is the longest sequence...
[Voted into the WP at the November, 2010 meeting as paper N3146.]
N3092 comment CA 24“Combining characters should not appear as the first character of an identifier.” [Reference: ISO/IEC TR 10176:2003 (Annex A)] This is not reflected in FCD.
Restrictions on the first character of an identifier are not observed as recommended in TR 10176:2003. The inclusion of digits (outside of those in the basic character set) under identifer-nondigit is implied by FCD.
It is implied that only the “main listing” from Annex A is included for C++. That is, the list ends with the Special Characters section. This is not made explicit in FCD. Existing practice in C++03 as well as WG 14 (C, as of N1425) and WG 4 (COBOL, as of N4315) is to include a list in a normative Annex.
Specify width sensitivity as implied by C++03: \uFF21 is not the same as A. Case sensitivity is already stated in 2.11 [lex.name].
Notes from the August, 2010 meeting:
CWG expressed interest in an approach by which all characters except for those in specifically-excluded categories would be acceptable identifier characters. This suggestion will be brought up with WG14 as a liaison matter in hopes of agreeing on a uniform approach between C and C++.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment DE 3It is not sufficiently clear that std::nullptr_t is a distinct type and neither a pointer type nor a pointer-to-member type. Add a note in 2.14.7 [lex.nullptr] stating that, preferably with cross-references to the normative statements in 3.9 [basic.types].
Proposed resolution (September, 2010):
Change 2.14.7 [lex.nullptr] paragraph 1 as follows:
The pointer literal is the keyword nullptr. It is a prvalue of type std::nullptr_t. [Note: std::nullptr_t is a distinct type that is neither a pointer type nor a pointer to member type; rather, a prvalue of this type is a null pointer constant and can be converted to a null pointer value or null member pointer value. See 4.10 [conv.ptr] and 4.11 [conv.mem]. —end note]
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 17In general, the parameter type of a literal operator must be the same as the argument passed to it. That is not the case for a user-defined-character-literal, where the argument could inadvertently match a literal operator intended for use with user-defined-integer-literals:
typedef unsigned long long ULL; int operator "" X(ULL); int i = 'c'X; // operator"" X(ULL('c'))
Suggested resolution: Add the following phrase to the description in paragraph 6:
S shall contain a literal operator whose parameter type is the same as the type of ch.
Proposed resolution (August, 2010):
Change 2.14.8 [lex.ext] paragraph 6 as follows:
If L is a user-defined-character-literal, let ch be the literal without its ud-suffix. The S shall contain a literal operator (13.5.8 [over.literal]) whose only parameter has the type of ch and the literal L is treated as a call of the formoperator "" X (ch )
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
A user-defined literal like 0x123DZ could be parsed either as a hexadecimal-literal of 0x123 and a ud-suffix of DZ or as a hexadecimal-literal of 0x123D and a ud-suffix of Z. There does not appear to be a rule that disambiguates the two possible parses.
Proposed resolution (November, 2010):
Change 2.14.8 [lex.ext] paragraph 1 as follows:
If a token matches both user-defined-literal and another literal kind, it is treated as the latter. [Example: 123_km, 1.2LL, "Hello"s are all user-defined-literals, but 12LL is an integer-literal. —end example] The syntactic nonterminal preceding the ud-suffix in a user-defined-literal is taken to be the longest sequence of characters that could match that nonterminal. [Example: The ud-suffix in 1.0e0X is X, not e0X; in 0x1DZ, the ud-suffix is Z, not DZ. —end example]
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
2.11 [lex.name] paragraph 3 says,
In addition, some identifiers are reserved for use by C++ implementations and standard libraries (17.6.4.3.2 [global.names]) and shall not be used otherwise; no diagnostic is required.
There is no corresponding mention in 2.14.8 [lex.ext] of the restrictions on user-defined literal suffixes in 17.6.4.3.5 [usrlit.suffix]. Furthermore, considering the likelihood of adding hexadecimal floating-point literals, whose syntax overlaps that of user-defined literals except for that restriction, it would be a good idea to require a diagnostic for a violation of that rule.
[Voted into WP at August, 21010 meeting.]
3.1 [basic.def] makes statements about declarations that do not appear to apply to static_assert-declarations. For example, paragraph 1 says,
A declaration (clause 7 [dcl.dcl]) introduces names into a translation unit or redeclares names introduced by previous declarations. A declaration specifies the interpretation and attributes of these names.
What name is being declared or described by a static_assert-declaration?
Also, paragraph 2 lists the kinds of declarations that are not definitions, and a static_assert-declaration is not among them. Is it intentional that static_assert-declarations are definitions?
Proposed resolution (March, 2010):
Change 3.1 [basic.def] paragraphs 1-2 as follows:
A declaration (Clause 7 [dcl.dcl]) may introduces one or more names into a translation unit or redeclares names introduced by previous declarations. A If so, the declaration specifies the interpretation and attributes of these names. A declaration may also have effects including
a static assertion (Clause 7 [dcl.dcl]),
controlling template instantiation (14.7.2 [temp.explicit],
use of attributes (Clause 7 [dcl.dcl], or
nothing (in the case of an empty-declaration).
A declaration is a definition unless it declares a function without specifying the function's body (8.4 [dcl.fct.def]), it contains the extern specifier (7.1.1 [dcl.stc]) or a linkage-specification25 (7.5 [dcl.link]) and neither an initializer nor a function-body, it declares a static data member in a class definition (9.4 [class.static]), it is a class name declaration (9.1 [class.name]), it is an opaque-enum-declaration (7.2 [dcl.enum]), or it is a typedef declaration (7.1.3 [dcl.typedef]), a using-declaration (7.3.3 [namespace.udecl]), a static_assert-declaration (Clause 7 [dcl.dcl]), an attribute-declaration (Clause 7 [dcl.dcl]), an empty-declaration (Clause 7 [dcl.dcl]), or a using-directive (7.3.4 [namespace.udir]). [Example:...
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
N2800 comment UK 23The list of declarations that are not definitions given in 3.1 [basic.def] paragraph 2 does not mention several plausible candidates: parameter declarations in non-defining function declarations, non-static data members, and template parameters. It might be argued that none of these are declarations (paragraph 1 does not use the syntactic non-terminal declaration but does explicitly refer to clause 7 [dcl.dcl], where that non-terminal is defined). However, the list in paragraph 2 does mention static member declarations, which also are not declarations, so the intent is not clear.
Proposed resolution (November, 2010):
Change 3.1 [basic.def] paragraph 2 as follows:
A declaration is a definition unless it declares a function without specifying the function's body (8.4 [dcl.fct.def]), it contains the extern specifier (7.1.1 [dcl.stc]) or a linkage-specification25 (7.5 [dcl.link]) and neither an initializer nor a function-body, it declares a static data member in a class definition (9.2 [class.mem], 9.4 [class.static]), it is a class name declaration (9.1 [class.name]), it is an opaque-enum-declaration (7.2 [dcl.enum]), it is a template-parameter (14.1 [temp.param]), it is a parameter-declaration (8.3.5 [dcl.fct]) in a function declaration that is not a definition, or it is a typedef declaration (7.1.3 [dcl.typedef]), an alias-declaration (7.1.3 [dcl.typedef]), a using-declaration (7.3.3 [namespace.udecl]), a static_assert-declaration (Clause 7 [dcl.dcl]), an attribute-declaration (Clause 7 [dcl.dcl]), an empty-declaration (Clause 7 [dcl.dcl]), or a using-directive (7.3.4 [namespace.udir]).
[Voted into the WP at the March, 2011 meeting.]
According to 3.2 [basic.def.odr] paragraph 2,
A declaration is a definition unless it declares a function without specifying the function's body (8.4 [dcl.fct.def]), it contains the extern specifier (7.1.1 [dcl.stc]) or a linkage-specification25 (7.5 [dcl.link]) and neither an initializer nor a function-body...
Because = delete and = default are not forms of function-body, this description does not cover defaulted and deleted functions, even though these declarations are elsewhere referred to as being definitions.
Proposed resolution (January, 2011):
Change the grammar in 8.4.1 [dcl.fct.def.general] paragraph 1 as follows:
[Voted into WP at August, 21010 meeting.]
I thought this case would result in undefined behavior according to 3.2 [basic.def.odr]:
// t.h: struct A { void (*p)(); }; // t1.cpp: #include "t.h" // A::p is a pointer to C++ func // t2.cpp: extern "C" { #include "t.h" // A::p is a pointer to C func }
...but I don't see how any of the bullets in the list in paragraph 5 apply.
Proposed resolution (March, 2010):
Add a new bullet following 3.2 [basic.def.odr] paragraph 5, second bullet:
...Given such an entity named D defined in more than one translation unit, then
each definition of D shall consist of the same sequence of tokens; and
in each definition of D, corresponding names, looked up according to 3.4 [basic.lookup], shall refer to an entity defined within the definition of D, or shall refer to the same entity, after overload resolution (13.3 [over.match]) and after matching of partial template specialization (14.8.3 [temp.over]), except that a name can refer to a const object with internal or no linkage if the object has the same literal type in all definitions of D, and the object is initialized with a constant expression (5.19 [expr.const]), and the value (but not the address) of the object is used, and the object has the same value in all definitions of D; and
in each definition of D, corresponding entities shall have the same language linkage; and
...
[Voted into the WP at the November, 2010 meeting as paper N3214.]
N3092 comment US 19It is not always clear when an occurrence of the word “use” is intended as an implicit reference to 3.2 [basic.def.odr] and when it is simply the ordinary English meaning. This could be addressed by:
Replacing all the non-technical appearances of the word “use” with synonyms such as “occurrence” or “appearance” or “reference to.”
Following each occurrence of the word “use” that is intended in the technical sense with a cross-reference to 3.2 [basic.def.odr].
Replacing all the technical occurrences of the word “use” with a different word or phrase, such as “odr-use,” that would unambiguously indicate the intent.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 3.2 [basic.def.odr] paragraph 2,
A variable or non-overloaded function whose name appears as a potentially-evaluated expression is used... A virtual member function is used if it is not pure.
However, that does not adequately address when a pure virtual function is used or not used. For example,
struct S { virtual void pure1() = 0; virtual void pure2() = 0; }; void f(S* p) { p->pure1(); p->S::pure2(); };
Both pure1 and pure2 satisfy the criterion that their name appears in a potentially-evaluated expression, but pure1 should not be considered “used” (which would require that it be defined); pure2 is “used” and must be defined.
Proposed resolution (November, 2010):
Change 3.2 [basic.def.odr] paragraph 2 as follows:
...A variable or non-overloaded function whose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19 [expr.const]) and the lvalue-to-rvalue conversion (4.1 [conv.lval]) is immediately applied... A virtual member function is odr-used if it is not pure. A non-overloaded function whose name appears as a potentially-evaluated expression or a member of a set of candidate functions is odr-used if it is selected by overload resolution when referred to from a potentially-evaluated expression, are odr-used, unless the function is pure and its name is not explicitly qualified. [Note:...
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
Issue 678 added a bullet to the list in 3.2 [basic.def.odr] paragraph 5, inadvertently removing the second bullet from the reach of the part of that paragraph that reads,
If D is a template and is defined in more than one translation unit, then the last four requirements from the list above shall apply to names from the template's enclosing scope used in the template definition (14.6.3 [temp.nondep]),
In fixing this error, the wording should be recast to be more robust in the face of possible further edits to the list (i.e., not just changing “four” to “five”).
Proposed resolution (November, 2010):
Change 3.2 [basic.def.odr] paragraph 5 as follows:
...If D is a template and is defined in more than one translation unit, then the last four preceding requirements from the list above shall apply both to names from the template's enclosing scope used in the template definition (14.6.3 [temp.nondep]), and also to dependent names at the point of instantiation (14.6.2 [temp.dep])...
[Voted into the WP at the March, 2011 meeting.]
The current wording of 3.3.2 [basic.scope.pdecl] does not specify the point of declaration for an alias-declaration (although it does do so in paragraph 3 for a template alias: “The point of declaration of a template alias immediately follows the identifier for the alias being declared”). One might assume that an alias-declaration would be the same, but it's not clear that that is the right resolution (for either declaration, but especially for the alias-declaration).
An alias-declaration is intended to be essentially a different syntactic form of a typedef declaration (7.1.3 [dcl.typedef] paragraph 2). Placing the point of declaration at the trailing semicolon instead of following the name of the alias would allow more compatibility with the capabilities of typedefs, for instance:
struct S { }; namespace N { using S = S; }
Notes from the November, 2010 meeting:
The CWG agreed that the point of declaration for both template and non-template cases should be at the semicolon.
Proposed resolution (January, 2011):
Change 3.3.2 [basic.scope.pdecl] paragraph 3 as follows:
...The point of declaration of a template an alias or alias template immediately follows the identifier for the alias being declared the type-id to which the alias refers.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 3.3.2 [basic.scope.pdecl] paragraph 6,
for an elaborated-type-specifier of the form
class-key identifier
if 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.
This should have been, but was not, updated when enumeration scope (3.3.8 [basic.scope.enum]) was added:
enum class E { e = sizeof((struct S*)0) };
Presumably the name S belongs to the same scope as E, not the enumeration scope of E.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
3.4.2 [basic.lookup.argdep] paragraph 2 excludes dependent parameter types and return types from consideration in determining the associated classes and namespaces of a function template. Presumably this means that an example like
namespace N { template<class T> struct A { }; void f(void (*)()); } template <class T> void g(T, N::A<T>); void g(); int main() { f(g); }
is ill-formed because the second parameter of the function template g does not add namespace N to the list of associated namespaces. This was probably unintentional.
See also issue 1015.
Notes from the November, 2010 meeting:
The CWG agreed that the rules should be changed to make this example well-formed.
Proposed resolution (November, 2010):
Change 3.4.2 [basic.lookup.argdep] paragraph 2 as follows:
...In addition, if the argument is the name or address of a set of overloaded functions and/or function templates, its associated classes and namespaces are the union of those associated with each of the members of the set, i.e., the classes and namespaces associated with its (non-dependent) parameter types and return type. Additionally, if the aforementioned set of overloaded functions is named with a template-id, its associated classes and namespaces are those of its type template-arguments and its template template-arguments.
This resolution also resolves issue 1015.
[Drafting note: It's not clear that we need the inserted text above, because for the example in issue 1015, the type N::S is already represented in the type of the function address, so there is no need to pull it from template arguments. For cases where template parameters are not represented in the function type, it's not clear that we want ADL to reach further.]
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
Currently, according to 3.4.2 [basic.lookup.argdep] paragraph 2, explicit template arguments in a function argument do not contribute to the associated namespaces in a function call, although they plausibly should in an example like the following:
namespace N {
struct S { };
void f(void (*)(S));
};
template<typename T> void g(T);
void h() {
f(g<N::S>); // Should find N::f
}
See also issue 997.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 997.
[Voted into the WP at the March, 2011 meeting.]
N3092 comment US 23According to 3.4.5 [basic.lookup.classref] paragraph 1,
In a class member access expression (5.2.5 [expr.ref]), if the . or -> token is immediately followed by an identifier followed by a <, the identifier must be looked up to determine whether the < is the beginning of a template argument list (14.2 [temp.names]) or a less-than operator. The identifier is first looked up in the class of the object expression. If the identifier is not found, it is then looked up in the context of the entire postfix-expression and shall name a class template. If the lookup in the class of the object expression finds a template, the name is also looked up in the context of the entire postfix-expression and
if the name is not found, the name found in the class of the object expression is used, otherwise
if the name is found in the context of the entire postfix-expression and does not name a class template, the name found in the class of the object expression is used, otherwise
if the name found is a class template, it shall refer to the same entity as the one found in the class of the object expression, otherwise the program is ill-formed.
This makes the following ill-formed:
#include <set> using std::set; struct X { template <typename T> void set(const T& value); }; void foo() { X x; x.set<double>(3.2); }
That's confusing and unnecessary. The compiler has already done the lookup in X's scope, and the obviously-correct resolution is that one, not the identifier from the postfix-expression's scope. Issue 305 fixed a similar issue for destructor names but missed member functions.
Suggested resolution: Delete the end of paragraph 1, starting with “If the lookup in the class...” and including all three bullets.
Proposed resolution (November, 2010):
Change 3.4.3.1 [class.qual] paragraph 1 bullet 2 as follows:
a conversion-type-id of an conversion-function-id is looked up both in the scope of the class and in the context in which the entire postfix-expression occurs and shall refer to the same type in both contexts in the same manner as a conversion-type-id in a class member access (see 3.4.5 [basic.lookup.classref]);
Change 3.4.5 [basic.lookup.classref] paragraph 1 as follows:
In a class member access expression (5.2.5 [expr.ref]), if the . or -> token is immediately followed by an identifier followed by a <, the identifier must be looked up to determine whether the < is the beginning of a template argument list (14.2 [temp.names]) or a less-than operator. The identifier is first looked up in the class of the object expression. If the identifier is not found, it is then looked up in the context of the entire postfix-expression and shall name a class template. If the lookup in the class of the object expression finds a template, the name is also looked up in the context of the entire postfix-expression and
if the name is not found, the name found in the class of the object expression is used, otherwise
if the name is found in the context of the entire postfix-expression and does not name a class template, the name found in the class of the object expression is used, otherwise
if the name found is a class template, it shall refer to the same entity as the one found in the class of the object expression, otherwise the program is ill-formed.
Change 3.4.5 [basic.lookup.classref] paragraph 4 as follows:
If the id-expression in a class member access is a qualified-id of the form
class-name-or-namespace-name::...the class-name-or-namespace-name following the . or -> operator is looked up both in the context of the entire postfix-expression and in the scope of the class of the object expression. If the name is found only in the scope of the class of the object expression, the name shall refer to a class-name. If the name is found only in the context of the entire postfix-expression, the name shall refer to a class-name or namespace-name. If the name is found in both contexts, the class-name-or-namespace-name shall refer to the same entity. first looked up in the class of the object expression and the name, if found, is used. Otherwise it is looked up in the context of the entire postfix-expression. [Note: See 3.4.3 [basic.lookup.qual], which describes the lookup of a name before ::, which will only find a type or namespace name. —end note]
Change 3.4.5 [basic.lookup.classref] paragraph 7 as follows:
If the id-expression is a conversion-function-id, its conversion-type-id shall denote the same type in both the context in which the entire postfix-expression occurs and in the context of the class of the object expression (or the class pointed to by the pointer expression). is first looked up in the class of the object expression and the name, if found and denotes a type, is used. Otherwise it is looked up in the context of the entire postfix-expression and the name shall denote a type. [Example:
struct A { }; namespace N { struct A { void g() { } template <class T> operator T(); }; } int main() { N::A a; a.operator A(); // calls N::A::operator N::A }
—end example]
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The resolution of issue 1111 changes 3.4.5 [basic.lookup.classref] paragraph 7 to read,
[A] conversion-type-id is first looked up in the class of the object expression and the name, if found and denotes a type, is used. Otherwise it is looked up in the context of the entire postfix-expression and the name shall denote a type.
The result of this specification is that a non-type member declaration in the class scope of the object expression will not be found (although it will hide a base class type member of the same name), but a non-type declaration in the context of the expression will be found (and make the program ill-formed).
This is inconsistent with the way other lookups are handled when they occur in a context that requires a type. For example, the lookup for a nested-name-specifier “considers only namespaces, types, and templates whose specializations are types” (3.4.3 [basic.lookup.qual] paragraph 1); the lookup for a name appearing in an elaborated-type-specifier is done “ignoring any non-type names that have been declared” (3.4.4 [basic.lookup.elab] paragraph 2); and in the lookup for a name in a base-type-specifier, “non-type names are ignored” (10 [class.derived] paragraph 2). The lookup for a conversion-type-id should be similar, and the wording in 3.4.5 [basic.lookup.classref] paragraph 7 adjusted accordingly.
[Voted into WP at August, 2010 meeting.]
Is this case valid? G++ compiles it.
namespace X { namespace Y { struct X { void f() { using namespace X::Y; namespace Z = X::Y; } }; } }
The relevant citation from the standard is 3.4.6 [basic.lookup.udir]: "When looking up a namespace-name in a using-directive or namespace-alias-definition, only namespace names are considered." This statement could reasonably be interpreted to apply only to the last element of a qualified name, and that's the way EDG and Microsoft seem to interpret it.
However, since a class can't contain a namespace, it seems to me that this interpretation is, shall we say, sub optimal. If the X qualifiers in the above example are interpreted as referring to the struct X, an error of some sort is inevitable, since there can be no namespace for the qualified name to refer to. G++ apparently interprets 3.4.6 [basic.lookup.udir] as applying to nested-name-specifiers in those contexts as well, which makes a valid interpretation of the test possible.
I'm thinking it might be worth tweaking the words in 3.4.6 [basic.lookup.udir] to basically mandate the more useful interpretation. Of course a person could argue that the difference would matter only to a perverse program. On the other hand, namespaces were invented specifically to enable the building of programs that would otherwise be considered perverse. Where name clashes are concerned, one man's perverse is another man's real world.
Proposed Resolution (November, 2006):
Change 3.4.6 [basic.lookup.udir] paragraph 1 as follows:
When looking up a namespace-name in a using-directive or namespace-alias-definition, In a using-directive or namespace-alias-definition, during the lookup for a namespace-name or for a name in a nested-name-specifier, only namespace names are considered.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 24One of the critieria for giving a name internal linkage is “a variable that is explicitly declared const and neither explicitly declared extern nor previously declared to have external linkage.” This should presumably apply to variables declared constexpr as well.
Proposed resolution (August, 2010):
Change 3.5 [basic.link] paragraph 3 bullet 2 as follows:
a variable that is explicitly declared const or constexpr and neither explicitly declared extern nor previously declared to have external linkage; or
[Voted into the WP at the November, 2010 meeting.]
N3092 comment DE 4It is odd that, in the following example, N has no linkage but g has external linkage:
namespace { namespace N // has no linkage { void g(); // has external linkage } }
Proposed resolution (August, 2010):
Change 3.5 [basic.link] paragraph 4 as follows:
An unnamed namespace or a namespace declared directly or indirectly within an unnamed namespace has internal linkage. All other namespaces have external linkage. A name having namespace scope that has not been given internal linkage above has external linkage the same linkage as the enclosing namespace if it is the name of
a variable, unless it has internal linkage; or
a function, unless it has internal linkage; or
a named class (Clause 9 [class]), or an unnamed class defined in a typedef declaration in which the class has the typedef name for linkage purposes (7.1.3 [dcl.typedef]); or
a named enumeration (7.2 [dcl.enum]), or an unnamed enumeration defined in a typedef declaration in which the enumeration has the typedef name for linkage purposes (7.1.3 [dcl.typedef]); or
an enumerator belonging to an enumeration with external linkage; or
a template, unless it is a function template that has internal linkage (Clause 14 [temp]); or.
a namespace (7.3 [basic.namespace]), unless it is declared within an unnamed namespace.
[Voted into the WP at the March, 2011 meeting.]
The note in 3.6.2 [basic.start.init] paragraph 3 contains the following example:
inline double fd() { return 1.0; } extern double d1; double d2 = d1; // unspecified: // may be statically initialized to 0.0 or // dynamically initialized to 1.0 double d1 = fd(); // may be initialized statically to 1.0
The comment for d2 overlooks the third possibility: if both d1 and d2 are dynamically initialized, d2 will be initialized to 0.
Proposed resolution (November, 2010):
Change the comments in the example in 3.6.2 [basic.start.init] paragraph 3 as follows:
inline double fd() { return 1.0; } extern double d1; double d2 = d1; // unspecified: // may be statically initialized to 0.0 or // dynamically initialized to 1.0 to 0.0 if d1 is dynamically initialized, or 1.0 otherwise double d1 = fd(); // may be initialized statically or dynamically to 1.0
(The note should also be in running text following the bulleted list instead of appearing as a bulleted item, as well.)
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
3.7.4.3 [basic.stc.dynamic.safety] paragraph 4 only prohibits the dereferencing and deallocation of non-safely-derived pointers. This is insufficient. Explicit deallocation of storage is described as rendering invalid all pointers to that storage, with the result that all operations on such a pointer value causes undefined behavior (3.7.4.2 [basic.stc.dynamic.deallocation] paragraph 4). The same should be true if the storage pointed to by a non-safely-derived pointer is garbage collected. In particular, the promise of objects having distinct addresses (1.8 [intro.object] paragraph 6) should not apply if one of those objects is designated by a non-safely-derived pointer.
Proposed resolution (November, 2010):
Change 3.7.4.3 [basic.stc.dynamic.safety] paragraph 4 as follows:
...Alternatively, an implementation may have strict pointer safety, in which case, if a pointer value that is not a safely-derived pointer value is dereferenced or deallocated, and an invalid pointer value, unless the referenced complete object is of dynamic storage duration and has not previously been declared reachable (20.8.2 [util.smartptr]), the behavior is undefined. [Note: this The effect of using an invalid pointer value (including passing it to a deallocation function) is undefined, see 3.7.4.2 [basic.stc.dynamic.deallocation]. This is true even if the unsafely-derived pointer value might compare equal to some safely-derived pointer value. —end note] It is implementation defined...
[Voted into the WP at the November, 2010 meeting.]
N3092 comment GB 18The example in 3.8 [basic.life] paragraph 9 reads,
struct B {
B();
~B();
};
const B b;
void h() {
b.~B();
new (&b) const B; // undefined behavior
}
Assuming that the placement new is intended to use the operator defined in the Standard library, the new-expression is ill-formed, because there is no implicit conversion from “pointer to const B” to void*.
Proposed resolution (August, 2010):
Change the example in 3.8 [basic.life] paragraph 9 as follows:
... new (const_cast<B *>(&b)) const B; // undefined behavior ...
[Voted into WP at August, 21010 meeting.]
Is the following example well-formed?
struct S { static char a[5]; }; char S::a[]; // Unspecified bound in definition
3.5 [basic.link] paragraph 10 certainly makes allowance for declarations to differ in the presence or absence of a major array bound. However, 3.1 [basic.def] paragraph 5 says that
A program is ill-formed if the definition of any object gives the object an incomplete type (3.9 [basic.types]).
3.9 [basic.types] paragraph 7 says,
The declared type of an array object might be an array of unknown size and therefore be incomplete at one point in a translation unit and complete later on; the array types at those two points (“array of unknown bound of T” and “array of N T”) are different types.
This wording appears to make no allowance for the C concept of “composite type;” instead, each declaration is said to have its own type. By this interpretation, the example is ill-formed, because the type declared by the definition of S::a is incomplete.
If the example is intended to be well-formed, the Standard needs explicit wording stating that an omitted array bound in a declaration is implicitly taken from that of a visible declaration of that object, if any.
Notes from the April, 2007 meeting:
The CWG agreed that this usage should be permitted.
Proposed resolution (June, 2008):
Change 8.3.4 [dcl.array] paragraph 1 as follows:
...If Except as noted below, if the constant expression is omitted, the type of the identifier of D is “derived-declarator-type-list array of unknown bound of T,” an incomplete object type...
Change 8.3.4 [dcl.array] paragraph 3 as follows:
When several “array of” specifications are adjacent, a multidimensional array is created; only the first of the constant expressions that specify the bounds of the arrays can may be omitted only for the first member of the sequence. [Note: this elision is useful for function parameters of array types, and when the array is external and the definition, which allocates storage, is given elsewhere. —end note] In addition to declarations in which an incomplete object type is allowed, an array bound may be omitted in the declaration of a function parameter (8.3.5 [dcl.fct]). The first constant-expression can An array bound may also be omitted when the declarator is followed by an initializer (8.5 [dcl.init]). In this case the bound is calculated from the number of initial elements (say, N) supplied (8.5.1 [dcl.init.aggr]), and the type of the identifier of D is “array of N T.” Furthermore, if there is a visible declaration of the name declared by the declarator-id (if any) in which the bound was specified, an omitted array bound is taken to be the same as in that earlier declaration.
Notes from the September, 2008 meeting:
The proposed resolution does not capture the result favored by the CWG: array bound information should be accumulated across declarations within the same scope, but a block extern declaration in a nested scope should not inherit array bound information from the outer declaration. (This is consistent with the treatment of default arguments in function declarations.) For example:
int a[5]; void f() { extern int a[]; sizeof(a); }
Although there was some confusion about the C99 wording dealing with this case, it is probably well-formed in C99. However, it should be ill-formed in C++, because we want to avoid the concept of “compatible types” as it exists in C.
Proposed resolution (March, 2010):
Change 8.3.4 [dcl.array] paragraph 1 as follows:
...If Except as noted below, if the constant expression is omitted, the type of the identifier of D is “derived-declarator-type-list array of unknown bound of T,” an incomplete object type...
Change 8.3.4 [dcl.array] paragraphs 3-4 as follows:
When several “array of” specifications are adjacent, a multidimensional array is created; only the first of the constant expressions that specify the bounds of the arrays can may be omitted only for the first member of the sequence. [Note: this elision is useful for function parameters of array types, and when the array is external and the definition, which allocates storage, is given elsewhere. —end note] In addition to declarations in which an incomplete object type is allowed, an array bound may be omitted in some cases in the declaration of a function parameter (8.3.5 [dcl.fct]). The first constant-expression can An array bound may also be omitted when the declarator is followed by an initializer (8.5 [dcl.init]). In this case the bound is calculated from the number of initial elements (say, N) supplied (8.5.1 [dcl.init.aggr]), and the type of the identifier of D is “array of N T.” Furthermore, if there is a preceding declaration of the entity in the same scope in which the bound was specified, an omitted array bound is taken to be the same as in that earlier declaration, and similarly for the definition of a static data member of a class.
[Example:...
...can reasonably appear in an expression. Finally,
extern int x[10]; struct S { static int y[10]; }; int x[]; //OK: bound is 10 int S::y[]; //OK: bound is 10 void f() { extern int x[]; int i = sizeof(x); //error: incomplete object type }
—end example]
[Voted into the WP at the March, 2011 meeting.]
3.9 [basic.types] paragraph 10 requires that a class have at least one constexpr constructor other than the copy constructor in order to be considered a literal type. However, a constexpr constructor template might be instantiated in such a way that the constexpr specifier is ignored (7.1.5 [dcl.constexpr] paragraph 5). It is therefore not known whether a class with a constexpr constructor template is a literal type or not until the constructor template is specialized, which could mean that an example like
struct IntValue { template<typename T> constexpr IntValue(T t) : val(t) { } constexpr intmax_t get_value() { return val; } private: intmax_t val; };
is ill-formed, because it is an error to declare a member function (like get_value()) of a non-literal class to be constexpr (7.1.5 [dcl.constexpr] paragraph 6).
3.9 [basic.types] paragraph 10 should be revised so that either a constexpr constructor or constexpr constructor template allows a class to be a literal type.
Proposed resolution (November, 2010):
Change 3.9 [basic.types] paragraph 10 as follows:
A type is a literal type if it is:
a scalar type; or
a class type (Clause 9 [class]) with that
a trivial copy constructor,
no non-trivial move constructor,
has a trivial destructor,
a trivial default constructor or is an aggregate type (8.5.1 [dcl.init.aggr]) or has at least one constexpr constructor other than the or constructor template that is not a copy or move constructor, and
has all non-static data members and base classes of literal types; or
an array of literal type.
This resolution also resolves issues 1071 and 1198.
[Voted into the WP at the March, 2011 meeting.]
According to 3.9 [basic.types] paragraph 10, one of the requirements for a literal class type is
a trivial default constructor or at least one constexpr constructor other than the copy or move constructor
This rule has unfortunate consequences. For example, in
struct A { int x; }; struct B: A { int y; };
B is a literal type, even though it is impossible to initialize a constant of that type. Conversely, in
struct C { int a, b; constexpr C(int x, int y): a(x), b(y) { } }; struct D { int x; C c; };
D is not a literal type, even though it could be initialized as an aggregate.
It would be an improvement to replace the requirement for a trivial default constructor with a requirement that the class be an aggregate.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 981.
[Voted into the WP at the March, 2011 meeting.]
The current draft uses the term “built-in type” several times, but it is not defined anywhere. The Index appears to make it synonymous with “fundamental type,” but the implication of 4 [conv] paragraph 1 is that compound types like pointers should also be considered as “built-in.”
Proposed resolution (January, 2011):
Change 1.8 [intro.object] paragraph 7 as follows:
[Note: C++ provides a variety of built-in fundamental types and several ways of composing new types from existing types (3.9 [basic.types]). —end note]
Change 2.5 [lex.pptoken] as follows:
[Example: The program fragment x+++++y is parsed as x ++ ++ + y, which, if x and y are of built-in have integral types, violates a constraint on increment operators, even though the parse x ++ + ++ y might yield a correct expression. —end example]
Change 18.3.2.4 [numeric.limits.members] paragraph 58 as follows:
True if the set of values representable by the type is finite.220 [Note: All built-in fundamental types (3.9.1 [basic.fundamental]) are bounded. This member would be false for arbitrary precision types. —end note]
Change 24.2.1 [iterator.requirements.general] paragraph 1 as follows:
...All input iterators i support the expression *i, resulting in a value of some class, enumeration, or built-in object type T, called the value type of the iterator...
Change 24.2.3 [input.iterators] paragraph 1 as follows:
A class or a built-in pointer type X satisfies the requirements of an input iterator for the value type T if X satisfies the Iterator (24.2.2 [iterator.iterators]) and EqualityComparable (Table 33) requirements and the expressions in Table 107 are valid and have the indicated semantics.
Change 24.2.4 [output.iterators] paragraph 1 as follows:
A class or a built-in pointer type X satisfies the requirements of an output iterator if X if X satisfies the Iterator requirements (24.2.2 [iterator.iterators]) and the expressions in Table 108 are valid and have the indicated semantics.
Change 24.2.5 [forward.iterators] paragraph 1 as follows:
A class or a built-in pointer type X satisfies the requirements of a forward iterator if...
Change 24.2.6 [bidirectional.iterators] paragraph 1 as follows:
A class or a built-in pointer type X satisfies the requirements of a bidirectional iterator if, in addition to satisfying the requirements for forward iterators, the following expressions are valid as shown in Table 110.
Change 24.2.7 [random.access.iterators] paragraph 1 as follows:
A class or a built-in pointer type X satisfies the requirements of a random access iterator if, in addition to satisfying the requirements for bidirectional iterators, the following expressions are valid as shown in Table 111.
Change C.1.2 [diff.basic] section 3.1 as follows:
Rationale: This avoids having different initialization rules for built-in fundamental types and user-defined types.
Change C.1.8 [diff.class] section 9.1 as follows:
...This new name space definition provides important notational conveniences to C++ programmers and helps making the use of the user-defined types as similar as possible to the use of built-in fundamental types...
Delete the index entry, “built-in type; see fundamental type
.”(Note: This resolution assumes that the resolution for issue 572 has been applied, removing “built-in type” from 4 [conv] paragraph 1.)
[Voted into the WP at the March, 2011 meeting.]
According to 3.9 [basic.types] paragraph 10, a literal class type has
a trivial copy constructor,
no non-trivial move constructor,
...
Is this intended to mean that
struct A { A(const A&) = default; A(A&); };
is a literal class because it does have a trivial copy constructor even though it also has a non-trivial one? That seems inconsistent with the prohibition of non-trivial move constructors.
My preference would be to resolve this inconsistency by dropping the restriction on non-trivial move constructors. It seems to me that having a trivial copy or move constructor is sufficient, we don't need to prohibit additional non-trivial ones. Actually, it's not clear to me that we need the first condition either; a literal type could be used for singleton variables even if it can't be copied.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 981.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The current treatment of constexpr constructors and constant expressions does not deal with the initializers for non-static data members, which should also be required to be constant expressions.
Proposed resolution (November, 2010):
Change 3.6.2 [basic.start.init] paragraph 2 as follows:
if an object with static or thread storage duration is initialized by a constructor call, if the constructor is a constexpr constructor, if all constructor arguments are constant expressions (including conversions), and if, after function invocation substitution (7.1.5 [dcl.constexpr]), every constructor call and full-expression in the mem-initializers and in the brace-or-equal-initializers for non-static data members is a constant expression
Change 3.9 [basic.types] paragraph 10 as follows (wording assumes the proposed resolution of issue 981)
A type is a literal type if it is:
a scalar type; or
a class type (clause 9 [class]) that has all of the following properties:
it has a trivial destructor,
every constructor call and full-expression in the brace-or-equal-initializers for non-static data members (if any) is a constant expression (5.19 [expr.const]),
it is an aggregate type (8.5.1 [dcl.init.aggr]) or has at least one constexpr constructor or constructor template that is not a copy or move constructor, and
it has all non-static data members and base classes of literal types; or
an array of literal type.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 3.9.1 [basic.fundamental] paragraph 9,
Any expression can be explicitly converted to type cv void (5.4 [expr.cast]). An expression of type void shall be used only as an expression statement (6.2 [stmt.expr]), as an operand of a comma expression (5.18 [expr.comma]), as a second or third operand of ?: (5.16 [expr.cond]), as the operand of typeid, or as the expression in a return statement (6.6.3 [stmt.return]) for a function with the return type void.
First, this is self-contradictory: if “any expression” can be converted to void, why is such a conversion not listed among the acceptable uses of an expression of type void?
Second, presumably an expression of type void can be used as an operand of decltype, but this use is not listed.
Finally, there are several places in the Standard that speak of expressions having a cv-qualified void type (5.16 [expr.cond] paragraph 2, 6.6.3 [stmt.return] paragraph 3). However, an expression of type void is a non-class prvalue, and there are no cv-qualified non-class prvalues (3.10 [basic.lval] paragraph 4).
Proposed resolution (February, 2011):
Change 3.9.1 [basic.fundamental] paragraph 9 as follows:
...Any expression can be explicitly converted to type cv void (5.4 [expr.cast]). An expression of type void shall be used only as an expression statement (6.2 [stmt.expr]), as an operand of a comma expression (5.18 [expr.comma]), as a second or third operand of ?: (5.16 [expr.cond]), as the operand of typeid or decltype, or as the expression in a return statement (6.6.3 [stmt.return]) for a function with the return type void, or as the operand of an explicit conversion to type cv void.
Change 5.16 [expr.cond] paragraph 2 as follows:
If either the second or the third operand has type (possibly cv-qualified) void, then...
Change 6.6.3 [stmt.return] paragraph 3 as follows:
A return statement with an expression of type “cv void” can be used only in functions with a return type of cv void; the expression is evaluated just before the function returns to its caller.
[Voted into the WP at the November, 2010 meeting.]
3.10 [basic.lval] paragraph 7 says,
Whenever an lvalue appears in a context where an rvalue is expected, the lvalue is converted to an rvalue
That is not correct in the context of an attempt to bind an rvalue reference to an lvalue (8.5.3 [dcl.init.ref]).
Proposed resolution (October, 2009):
Change 3.10 [basic.lval] paragraph 7 as follows:
Whenever an lvalue appears in a context where an rvalue is expected and an lvalue is not explicitly prohibited (as, for example, in 8.5.3 [dcl.init.ref]), the lvalue it is converted to an rvalue; see 4.1 [conv.lval], 4.2 [conv.array], and 4.3 [conv.func].
Notes from the March, 2010 meeting:
This resolution needs to be reconsidered in light of the new expression taxonomy.
Proposed resolution (September, 2010):
Change 3.10 [basic.lval] paragraph 2 as follows:
Whenever a glvalue appears in a context where a prvalue is expected, the glvalue is converted to a prvalue; see 4.1 [conv.lval], 4.2 [conv.array], and 4.3 [conv.func]. [Note: An attempt to bind an rvalue reference to an lvalue is not such a context; see 8.5.3 [dcl.init.ref]. —end note]
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The current wording of the Standard does not recognize the fact that the alignment of a complete object of a given type may be different from its alignment as a subobject. This arises in particular with virtual base classes. For example,
struct B { long double d; }; struct D: virtual B { char c; };
When D is a complete object, it will have a subobject of type B, which must be aligned appropriately for a long double. On the other hand, if D appears as a subobject of another object, the B subobject might be part of a different subobject, reducing the alignment requirement on the D subobject.
The Standard should make clear that it is the complete-object alignment that is being described, in parallel with the distinction between the size of a complete object and a subobject of the same type.
[Voted into the WP at the November, 2010 meeting in document N3190.]
N3092 comment US 25The C and C++ approaches to alignment are incompatible. See document PL22.16 10-0083 = WG21 N3093 for details.
Notes from the August, 2010 meeting:
CWG agreed that the alignment specifier should be a keyword instead of an attributes.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
Now that alignment can be applied directly to class types, the current wording of the note at the end of 3.11 [basic.align] paragraph 3 is no longer correct:
[Note: every over-aligned type is or contains a class type with a non-static data member to which an extended alignment has been applied. —end note]
Proposed resolution (November, 2010):
Change 3.11 [basic.align] paragraph 3 as follows:
[Note: every over-aligned type is or contains a class type with a non-static data member to which an extended alignment has been applied to which extended alignment applies (possibly through a non-static data member). —end note]
[Voted into the WP at the March, 2011 meeting.]
4 [conv] paragraph 1 says,
Standard conversions are implicit conversions defined for built-in types.
However, enumeration types (which take part in the integral promotions) and class types (which take part in the lvalue-to-rvalue conversion) are not “built-in” types, so the definition of “standard conversions” is wrong.
Proposed resolution (October, 2006):
Change 4 [conv] paragraph 1 as follows:
Standard conversions are implicit conversions defined for built-in types with built-in meaning...
[Voted into the WP at the November, 2010 meeting.]
N3092 comment JP 1One of the bullets in the note in 5 [expr] paragraph 6 says that an expression is an xvalue if it is:
a class member access expression designating a non-static data member in which the object expression is an xvalue
This is incorrect if the type of the non-static data member is a reference type (cf 5.2.5 [expr.ref] paragraph 4.)
Proposed resolution (September, 2010):
Change 5 [expr] paragraph 6 bullet 3 as follows:
a class member access expression designating a non-static data member of non-reference type in which the object expression is an xvalue, or
(Moved from issue 760.)
Although it was considered and rejected as part of issue 643, more recent developments may argue in favor of allowing the use of this in a late-specified return type. In particular, declaring the return type for a forwarding function in a derived class template that invokes a member function of a dependent base class is difficult without this facility. For example:
template <typename T> struct derived: base<T> { auto invoke() -> decltype(this->base_func()) { return this->base_func(); } };
(See also issue 1207 for another potential motivation for a change to this rule.)
Additional note (October, 2010):
The question should also be considered for parameter types; for example,
class comparable { public: bool is_equal(decltype(*this) other) { // should be X& return /*...*/; } };
Proposed resolution (March, 2011):
This issue is resolved by the resolution of issues 1017 and 1207 in document N3282.
[Voted into the WP at the November, 2010 meeting.]
5.1.2 [expr.prim.lambda] paragraph 4 bullet 1 says,
if the compound-statement if [sic] of the form
{ return attribute-specifieropt expression ; }
the type of the returned expression...
The problem (besides the typo “if”) is that the attribute-specifier for a return statement precedes, rather than following, the keyword (6 [stmt.stmt] paragraph 1).
Proposed resolution (September, 2010):
Change 5.1.2 [expr.prim.lambda] paragraph 4 bullet 1 as follows:
if the compound-statement is of the form
the type of the returned expression after lvalue-to-rvalue conversion (4.1 [conv.lval]), array-to-pointer conversion (4.2 [conv.array]), and function-to-pointer conversion (4.3 [conv.func]);
[Voted into the WP at the November, 2010 meeting.]
N3092 comment CH 4The adoption of paper N3067 at the March, 2010 meeting moved the position of the optional attribute-specifier in a function declarator from immediately following the parameter-declaration-clause to after the exception-specification. However, the grammar in 5.1.2 [expr.prim.lambda] paragraph 1 and the verbal description in paragraph 5 still have the attribute-specifier in a lambda-declarator at its old position. These should be updated to reflect the new function declarator syntax.
Proposed resolution (August, 2010):
Change the grammar in 5.1.2 [expr.prim.lambda] paragraph 1 as follows:
Change 5.1.2 [expr.prim.lambda] paragraph 5 as follows:
...Any attribute-specifiers appearing immediately after the lambda-expression's parameter-declaration-clause appertain An attribute-specifier in a lambda-declarator appertains to the type of the corresponding function call operator...
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
N2800 comment UK 535.2.1 [expr.sub] paragraph 2 deals with one particular aspect of the overloaded operator[], which seems out of place. Either 5.2.1 [expr.sub] should be augmented to discuss the overloaded operator[] in general or the information in paragraph 2 should be moved into 13.5.5 [over.sub].
[Voted into the WP at the November, 2010 meeting.]
5.2.2 [expr.call] paragraph 7 should mention a non-trivial move constructor as well, for consistency with “trivially copyable.”
Proposed resolution (September, 2010):
Change 5.2.2 [expr.call] paragraph 7 as follows:
...Passing a potentially-evaluated argument of class type (Clause 9 [class]) with having a non-trivial copy constructor, a non-trivial move constructor, or a non-trivial destructor, with no corresponding parameter is conditionally-supported, with implementation-defined semantics. If the argument...
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 32According to 5.2.5 [expr.ref] paragraph 5,
If E2 is a non-static data member or a non-static member function, the program is ill-formed if the class of which E2 is directly a member is an ambiguous base (10.2 [class.member.lookup]) of the naming class (11.2 [class.access.base]) of E2.
This does not cover the following case:
struct A { int i; }; struct B: A { }; struct C: A, B { }; void f(C* p) { p->A::i; // Should be ambiguous }
Notes (August, 2010):
The example in the FCD National Body comment is incorrect: it is missing the A:: in the next-to-last line.
The ambiguity actually is covered in the Standard but in an unexpected location: 11.2 [class.access.base] paragraph 6:
If a class member access operator, including an implicit “this->,” is used to access a non-static data member or non-static member function, the reference is ill-formed if the left operand (considered as a pointer in the “.” operator case) cannot be implicitly converted to a pointer to the naming class of the right operand.
An explanatory note, including a cross-reference to 11.2 [class.access.base], should be added to 5.2.5 [expr.ref] paragraph 6.
Proposed resolution (September, 2010):
Change 5.2.5 [expr.ref] paragraph 5 as follows:
If E2 is a non-static data member or a non-static member function, the program is ill-formed if the class of which E2 is directly a member is an ambiguous base (10.2 [class.member.lookup]) of the naming class (11.2 [class.access.base]) of E2. [Note: The program is also ill-formed if the naming class is an ambiguous base of the class type of the object expression; see 11.2 [class.access.base]. —end note]
[Voted into the WP at the November, 2010 meeting.]
According to 5.2.9 [expr.static.cast] paragraph 7, static_cast can be used to perform the inverse of any standard conversion sequence except the lvalue-to-rvalue, array-to-pointer, function-to-pointer, and boolean conversions. The null pointer and null pointer-to-member conversions should also be listed — it should not be permitted to cast a pointer or pointer-to-member to either integral type or std::nullptr_t.
Proposed resolution (October, 2010):
Change 4.10 [conv.ptr] paragraph 1 as follows:
...A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of pointer to object or pointer to function type. Such a conversion is called a null pointer conversion. Two null pointer values...
Change 4.11 [conv.mem] paragraph 1 as follows:
A null pointer constant (4.10 [conv.ptr]) can be converted to a pointer to member type; the result is the null member pointer value of that type and is distinguishable from any pointer to member not created from a null pointer constant. Such a conversion is called a null member pointer conversion. Two null member pointer values...
Change 5.2.9 [expr.static.cast] paragraph 7 as follows:
The inverse of any standard conversion sequence (Clause 4 [conv]), other than the not containing an lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), function-to-pointer (4.3 [conv.func]), and null pointer (4.10 [conv.ptr]), null member pointer (4.11 [conv.mem]), or boolean (4.12 [conv.bool]) conversions, can be performed explicitly using static_cast. A program is ill-formed...
[Voted into the WP at the March, 2011 meeting.]
According to 7.2 [dcl.enum] paragraph 10,
An expression of arithmetic or enumeration type can be converted to an enumeration type explicitly.
However, 5.2.9 [expr.static.cast] paragraph 10 says only,
A value of integral or enumeration type can be explicitly converted to an enumeration type.
This omits floating-point values. Presumably unscoped enumeration types are covered by paragraph 7,
The inverse of any standard conversion sequence (Clause 4 [conv]), other than the lvalue-to-rvalue (4.1 [conv.lval]), array-to- pointer (4.2 [conv.array]), function-to-pointer (4.3 [conv.func]), and boolean (4.12 [conv.bool]) conversions, can be performed explicitly using static_cast.
because 4.9 [conv.fpint] paragraph 2 allows an unscoped enumeration value to be implicitly converted to a floating point type. (Although that also covers the integral types, so it's not clear why they would be mentioned specifically in 5.2.9 [expr.static.cast] paragraph 10.) However, this should presumably say “arithmetic” instead of “integral” to match the statement in 7.2 [dcl.enum] paragraph 10.
Proposed resolution (November, 2010):
Change 5.2.9 [expr.static.cast] paragraph 10 as follows:
A value of integral or enumeration type can be explicitly converted to an enumeration type. The value is unchanged if the original value is within the range of the enumeration values (7.2 [dcl.enum]). Otherwise, the resulting enumeration value is unspecified (and might not be in that range). A value of floating-point type can also be converted to an enumeration type. The resulting value is the same as converting the original value to the underlying type of the enumeration (4.9 [conv.fpint]), and subsequently to the enumeration type.
Add the following footnote to the end of 7.2 [dcl.enum] paragraph 7:
...If the enumerator-list is empty, the values of the enumeration are as if the enumeration had a single enumerator with value 0. [Footnote: This set of values is used to define promotion and conversion semantics for the enumeration type; it does not exclude an expression of enumeration type from having a value that falls outside this range. —end footnote]
Delete 7.2 [dcl.enum] paragraph 10:
An expression of arithmetic or enumeration type can be converted to an enumeration type explicitly. The value is unchanged if it is in the range of enumeration values of the enumeration type; otherwise the resulting enumeration value is unspecified.
[Voted into the WP at the March, 2011 meeting.]
The resolution to issue 195 makes “converting a pointer to a function into a pointer to an object type or vice versa” conditionally-supported behavior. In doing so, however, it overlooked the fact that void is not an “object type” (3.9 [basic.types] paragraph 9). The wording should be amended to allow conversion to and from void* types.
Proposed resolution (November, 2010):
Change 3.7.4.3 [basic.stc.dynamic.safety] paragraphs 1-2 as follows:
A traceable pointer object is
an object of pointer-to-object an object pointer type (3.9.2 [basic.compound]), or
an object of an integral type that is at least as large as std::intptr_t, or
a sequence of elements in an array of character type, where the size and alignment of the sequence match that those of some pointer-to-object object pointer type.
A pointer value is a safely-derived pointer to a dynamic object only if it has pointer-to-object an object pointer type and it is...
Change 3.9.2 [basic.compound] paragraphs 3-4 as follows:
The type of a pointer to void or a pointer to an object type is called an object pointer type. [Note: A pointer to void does not have a pointer-to-object type, however, because void is not an object type. —end note] The type of a pointer that can designate a function is called a function pointer type. A pointer to objects of type T is referred to as a “pointer to T.” [Example:...
Objects of cv-qualified (3.9.3 [basic.type.qualifier]) or cv-unqualified type void* (pointer to void), A pointer to cv-qualified (3.9.3 [basic.type.qualifier]) or cv-unqualified void can be used to point to objects of unknown type. A void* Such a pointer shall be able to hold any object pointer. A cv-qualified or cv-unqualified (3.9.3 [basic.type.qualifier]) An object of type cv void* shall have the same representation and alignment requirements as a cv-qualified or cv-unqualified cv char*.
Change 4.10 [conv.ptr] paragraph 1 as follows:
...A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of pointer to object or pointer to function object pointer or function pointer type...
Change 4.11 [conv.mem] paragraph 2 footnote 58 as follows:
...Note that a pointer to member is not a pointer to object or a pointer to function an object pointer or a function pointer and...
Change 5.2.10 [expr.reinterpret.cast] paragraphs 6-8 as follows:
A pointer to a function pointer can be explicitly converted to a pointer to a function pointer of a different type...
A pointer to an An object pointer can be explicitly converted to a pointer to a different object type an object pointer of a different type...
Converting a pointer to a function into a pointer to an object function pointer to an object pointer type or vice versa is conditionally-supported...
Change the note in 8.3.5 [dcl.fct] paragraph 6 as follows:
[Note: function types are checked during the assignments and initializations of pointer-to-functions, reference-to-functions, and pointer-to-member-functions pointers to functions, references to functions, and pointers to member functions. —end note]
In the “Index of Implementation-defined Behavior,” change the following item as indicated:
converting pointer to function into pointer to object function pointer to object pointer and vice versa
[Drafting note: 5.3.5 [expr.delete] paragraph 1 was not changed, so the operand of delete still cannot be a void*. 13.6 [over.built] paragraph 14 was not changed, so void* pointers still do not get overloads for operator-. 14.1 [temp.param] paragraph 4 was not changed and thus continues to allow only pointers to objects, not object pointers, as non-type template parameters.]
(See also issue 1120.)
[Voted into the WP at the March, 2011 meeting.]
N3092 comment GB 22It is not permitted to use reinterpret_cast to convert between pointers to object type and pointers to void.
See also issue 573.
Proposed resolution (August, 2010):
Change 5.2.10 [expr.reinterpret.cast] paragraph 7 as follows:
A pointer to an object An object pointer can be explicitly converted to a pointer to a different object type an object pointer of a different type.70 When a prvalue v of type “pointer to T1” is converted to the type “pointer to cv T2”, the result is static_cast<cv T2*>(static_cast<cv void*>(v)) if both T1 and T2 are standard-layout types (3.9 [basic.types]) and the alignment requirements of T2 are no stricter than those of T1, or if either type is void. Converting a prvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value. The result of any other such pointer conversion is unspecified.
(Note: this resolution depends on that of issue 573.)
[Voted into the WP at the November, 2010 meeting.]
5.2.11 [expr.const.cast] paragraph 1 refers to casting to an rvalue reference to function type. This was an accidental edit in the application of the value categories and should be removed.
Proposed resolution (October, 2010):
Change 5.2.11 [expr.const.cast] paragraph 1 as follows:
If T is an lvalue reference to object type or an rvalue reference to function type, the result is an lvalue; if T is an rvalue reference to object type, the result is an xvalue; otherwise, the result is a prvalue and the lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), and function-to-pointer (4.3 [conv.func]) standard conversions are performed on the expression v...[Drafting note: with this change to the wording, an attempt to cast a function lvalue to either an lvalue or rvalue reference will be ill-formed because the function-to-pointer conversion will be applied to the operand and the resulting operand base type will be incompatible with the target base type.]
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 33The resolution of issue 983 restored an error, inadvertently removed by the resolution of issue 39, for the formation of a member of an ambiguous base class. For example:
struct B { int i; }; struct I1: B { }; struct I2: B { }; struct D: I1, I2 { }; int B::* pm = &D::i; // Originally and again ambiguous
This error is not necessary, because the result of taking the address of a member of an ambiguous base class is a pointer to a member of that class; an actual ambiguity would occur only if that pointer to a base class member is converted to a pointer to a member of the derived class. (See issue 203, which suggests changing the result of taking the address of a member to reflect the naming class of the member instead of the class of which it is directly a member; if that change were made, the ambiguity error would be needed.)
The resolution of issue 983 should be reverted.
Proposed resolution (August, 2010):
Change 5.3.1 [expr.unary.op] paragraph 3 as follows:
...If the operand is a qualified-id naming a non-static member m of some class C with type T, the result has type “pointer to member of class C of type T and is a prvalue designating C::m; the program is ill formed if C is an ambiguous base (10.2) of the class designated by the nested-name-specifier of the qualified-id. Otherwise...
Change 10.2 [class.member.lookup] paragraph 13 as follows:
[Note: Even if the result of name lookup is unambiguous, use of a name found in multiple subobjects might still be ambiguous (4.11 [conv.mem], 5.2.5 [expr.ref], 5.3.1 [expr.unary.op], 11.2 [class.access.base]). —end note]...
[Voted into the WP at the November, 2010 meeting.]
N3092 comment GB 24The return type of the sizeof operator is defined as being of type std::size_t, defined in library clause 18.2 [support.types]. This, in turn, says that size_t is defined in the C standard, which in turn says that size_t is defined as the type of the result of the sizeof operator!
The C definition of sizeof returns an implementation-defined unsigned integer type, recommended not to have “an integer conversion rank greater than signed long int, unless the implementation supports objects large enough to make this necessary.”
Proposed resolution (September, 2010):
Add the following three paragraphs after 18.2 [support.types] paragraph 4:
The type ptrdiff_t is an implementation-defined signed integer type that can hold the difference of two subscripts in an array object, as described in 5.7 [expr.add].
The type size_t is an implementation-defined unsigned integer type that is large enough to contain the size in bytes of any object.
[Note: It is recommended that implementations choose types for ptrdiff_t and size_t whose integer conversion ranks (4.13 [conv.rank]) are no greater than that of signed long int unless a larger size is necessary to contain all the possible values. —end note]
[Voted into the WP at the November, 2010 meeting.]
Recent changes have added the requirement (5.3.4 [expr.new] paragraph 7),
If the value of that expression is such that the size of the allocated object would exceed the implementation-defined limit, no storage is obtained and the new-expression terminates by throwing an exception of a type that would match a handler (15.3 [except.handle]) of type std::bad_array_new_length (18.6.2.2 [new.badlength]).
Given this checking, is there any current reason for the statement in the preceding paragraph,
If the value of the expression is negative, the behavior is undefined.
Presumably for most negative expressions on most platforms, a negative value would result in a too-large request anyway, and even if not the check could easily be expanded to look explicitly for a negative value in addition to a too-large request.
Proposed resolution (September, 2010):
Change 5.3.4 [expr.new] paragraphs 6 and 7 as follows:
...If the value of the expression is negative, the behavior is undefined. [Example: given the definition int n = 42, new float[n][5] is well-formed (because n is the expression of a noptr-new-declarator), but new float[5][n] is ill-formed (because n is not a constant expression). If n is negative, the effect of new float[n][5] is undefined. —end example]
When the value of the expression in a noptr-new-declarator is zero, the allocation function is called to allocate an array with no elements. If the value of that expression is less than zero or such that the size of the allocated object would exceed the implementation-defined limit, no storage is obtained and the new-expression terminates by throwing an exception of a type that would match a handler (15.3 [except.handle]) of type std::bad_array_new_length (18.6.2.2 [new.badlength]).
Change 18.6.2.2 [new.badlength] paragraph 1 as follows:
The class bad_array_new_length defines the type of objects thrown as exceptions by the implementation to report an attempt to allocate an array of size less than zero or greater than an implementation-defined limit (5.3.4 [expr.new]).
[Voted into the WP at the November, 2010 meeting.]
According to 5.3.5 [expr.delete] paragraph 2,
...in the 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 subobject (1.8 [intro.object]) representing a base class of such an object (Clause 10 [class.derived]). If not, the behavior is undefined. In the second alternative (delete array), the value of the operand of delete shall be the pointer value which resulted from a previous array new-expression.79 If not, the behavior is undefined.
The second part of this specification makes it clear that an array object being deleted must have been allocated via new. However, the first part, for the non-array object, completely omits this vital requirement, requiring only that it not be an array.
The corresponding requirement for an argument to a deallocation function is found in 3.7.4.2 [basic.stc.dynamic.deallocation] paragraph 3:
...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.
This correctly states the required provenance of the pointer, but it does so using “shall,” which is inappropriate for a runtime requirement. This wording should be recast in terms of undefined behavior if the requirement is not met.
Proposed resolution (October, 2010):
Change 5.3.5 [expr.delete] paragraph 2 as follows:
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, the value of the operand of delete may be a null pointer value. If it is not a null pointer value, in In the first alternative (delete object), the value of the operand of delete shall may be a null pointer value, a pointer to a non-array object created by a previous new-expression, or a pointer to a subobject (1.8 [intro.object]) representing a base class of such an object (Clause 10 [class.derived]). If not, the behavior is undefined. In the second alternative (delete array), the value of the operand of delete shall may be the a null pointer value or a pointer value which that resulted from a previous array new-expression.79 If not, the behavior is undefined. [Note: this means that the syntax of the delete-expression must match the type of the object allocated by new new, not the syntax of the new-expression. —end note]...
Change 3.7.4.2 [basic.stc.dynamic.deallocation] paragraph 3 as follows:
...Otherwise, the behavior is undefined if the value supplied to operator delete(void*) in the standard library shall be is not 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 behavior is undefined if the value supplied to operator delete[](void*) in the standard library shall be is not 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.
[Voted into the WP at the November, 2010 meeting as paper N3204.]
N3092 comment FI 17Destructors should by default be noexcept. Such a rule should be obeyed even for cases where a destructor is defaulted. Then a throwing destructor would need to be declared noexcept(false), and the resulting code breakage is acceptable.
Notes from the August, 2010 meeting:
CWG agreed with the suggested direction.
(Duplicate of issue 1147.)
[Voted into the WP at the March, 2011 meeting.]
The description of class member access expressions in 5.2.5 [expr.ref] paragraph 2 defines the terms “object expression” and “pointer expression:”
For the first option (dot) the type of the first expression (the object expression) shall be “class object” (of a complete type). For the second option (arrow) the type of the first expression (the pointer expression) shall be “pointer to class object” (of a complete type).
(Note in passing that the phrase “class object” seems very odd when describing a type.) The rest of that section is based on the equivalence of the expression E1->E2 to (*(E1)).E2 and thus is phrased only in terms of “object expression.” This terminology appears to have been misapplied in other parts of the Standard, using the term “object expression” to refer both to class types and pointers to class types. The most egregious of these is 5.5 [expr.mptr.oper] paragraph 4, describing the operands of a pointer-to-member expression:
The first operand is called the object expression. If the dynamic type of the object expression does not contain the member to which the pointer refers, the behavior is undefined.
The dynamic type of the first operand in the ->* case is a pointer type, not a class type, so it cannot “contain the member to which the pointer refers.” Another example is 3.4.5 [basic.lookup.classref], describing the lookup in a class member access expression. The first paragraph uses the term consistently with its use in 5.2.5 [expr.ref], but paragraph 2 reads:
If the id-expression in a class member access (5.2.5 [expr.ref]) is an unqualified-id, and the type of the object expression is of a class type C, the unqualified-id is looked up in the scope of class C. If the type of the object expression is of pointer to scalar type, the unqualified-id is looked up in the context of the complete postfix-expression.
Paragraph 7 gets it right:
...in the context of the class of the object expression (or the class pointed to by the pointer expression).
Another misapplication of the term occurs in 5.2.2 [expr.call] paragraph 1:
...the call is as a member of the object pointed to or referred to by the object expression (5.2.5 [expr.ref], 5.5 [expr.mptr.oper])... its final overrider (10.3 [class.virtual]) in the dynamic type of the object expression is called. [Note: the dynamic type is the type of the object pointed or referred to by the current value of the object expression...
Here again we have the idea that an object expression can “point to” an object.
Another minor complication is that 5 [expr] paragraph 7 has a separate definition for the (hyphenated) term “object-expression:”
An expression designating an object is called an object-expression.
This term is used several times in the Standard, apparently interchangeably with the non-hyphenated version defined in 5.2.5 [expr.ref]; for example, 5.1.1 [expr.prim.general] paragraph 10 bullet 1 mentions
a class member access (5.2.5 [expr.ref]) in which the object-expression refers to the member's class
using the term defined in 5 [expr] paragraph 7 but linking it with 5.2.5 [expr.ref].
These uses of “object expression” and “object-expression” need to be made consistent, especially the reference in 5.5 [expr.mptr.oper] that implies that the dynamic type of a pointer is that of the complete object to which it points.
Proposed resolution (February, 2011):
Change 3.4.5 [basic.lookup.classref] paragraph 2 as follows:
...If the type of the object expression is of pointer to scalar type For a pseudo-destructor call (5.2.4 [expr.pseudo]), the unqualified-id is looked up in the context of the complete postfix-expression.
Delete 5 [expr] paragraph 7
An expression designating an object is called an object-expression.
Change 5.1.1 [expr.prim.general] paragraph 10 as follows:
An id-expression that denotes a non-static data member or non-static member function of a class can only be used:
as part of a class member access (5.2.5 [expr.ref]) in which the object-expression object expression refers to the member's class or a class derived from that class, or
...
Change 5.2.2 [expr.call] paragraph 1 as follows:
...For a member function call, the postfix expression shall be an implicit (9.3.1 [class.mfct.non-static], 9.4 [class.static]) or explicit class member access (5.2.5 [expr.ref]) whose id-expression is a function member name, or a pointer-to-member expression (5.5 [expr.mptr.oper]) selecting a function member; the call is as a member of the class object pointed to or referred to by the object expression (5.2.5 [expr.ref], 5.5 [expr.mptr.oper])... [Note: the dynamic type is the type of the object pointed or referred to by the current value of the object expression. 12.7 [class.cdtor] describes the behavior of virtual function calls when the object-expression object expression refers to an object under construction or destruction. —end note]
Change 5.2.5 [expr.ref] paragraph 2 as follows:
For the first option (dot) the type of the first expression (the object expression) shall be “class object” (of a complete type) have complete class type. For the second option (arrow) the type of the first expression (the pointer expression) shall be “pointer to class object” (of a complete type) have pointer to complete class type. The expression E1->E2 is converted to the equivalent form (*(E1)).E2; the remainder of 5.2.5 [expr.ref] will address only the first option (dot) [Footnote: Note that (*(E1)) is an lvalue. —end footnote]. In these cases either case, the id-expression shall name a member of the class or of one of its base classes...
Change 5.2.5 [expr.ref] paragraph 3 as follows:
If E1 has the type “pointer to class X,” then the expression E1->E2 is converted to the equivalent form (*(E1)).E2; the remainder of 5.2.5 [expr.ref] will address only the first option (dot)66. Abbreviating object-expressionpostfix-expression.id-expression as E1.E2, then the E1 is called the object expression. The type and value category of this expression E1.E2 are determined as follows...
Change 5.5 [expr.mptr.oper] paragraph 3 as follows:
...The result is an object or a function of the type specified by the second operand. The expression E1->*E2 is converted into the equivalent form (*(E1)).*E2.
Change 5.5 [expr.mptr.oper] paragraph 4 as follows:
The first operand Abbreviating pm-expression.*cast-expression as E1.*E2, E1 is called the object expression. If the dynamic type of the object expression E1 does not contain the member to which the pointer E2 refers, the behavior is undefined.
Change 5.5 [expr.mptr.oper] paragraph 6 as follows:
...In a .* expression whose object expression is an rvalue, the program is ill-formed if the second operand is a pointer to member function with ref-qualifier &. In a ->* expression or in a .* expression whose object expression is an lvalue, the program is ill-formed if the second operand is a pointer to member function with ref-qualifier &&. The result of a .* expression whose second operand is a pointer to a data member is of the same value category (3.10 [basic.lval]) as its first operand. The result of a .* expression whose second operand is a pointer to a member function is a prvalue. The result of an ->* expression is an lvalue if its second operand is a pointer to data member and a prvalue otherwise. If the second operand is the null pointer to member value (4.11 [conv.mem]), the behavior is undefined.
Change 9.4 [class.static] paragraph 2 as follows:
...A static member may be referred to using the class member access syntax, in which case the object-expression object expression is evaluated...
Change 12.7 [class.cdtor] paragraph 4 as follows:
...If the virtual function call uses an explicit class member access (5.2.5) and the object-expression object expression refers to the object under construction...
Change 14.2 [temp.names] paragraph 4 as follows:
When the name of a member template specialization appears after . or -> in a postfix-expression or after a nested-name-specifier in a qualified-id, and the object or pointer expression of the postfix-expression or...
[Note: although the current text of 3.4.5 [basic.lookup.classref] paragraph 7 mentions the phrase “pointer expression,” that wording will be replaced by issue 1111 or issue 1220 and is thus not addressed here.]
[Voted into the WP at the March, 2011 meeting as part of paper N3260.]
According to 5.19 [expr.const] paragraph 3,
A constant expression is an integral constant expression if it is of integral or enumeration type. [Note: such expressions may be used as array bounds (8.3.4 [dcl.array], 5.3.4 [expr.new]), as case expressions (6.4.2 [stmt.switch]), as bit-field lengths (9.6 [class.bit]), as enumerator initializers (7.2 [dcl.enum]), and as integral or enumeration non-type template arguments (14.3 [temp.arg]). —end note]
Although there is conceptually a conversion from enumeration type to integral type involved in using an enumerator as an array bound or bit-field length, the normative wording for those uses does not explicitly mention it and simply requires an integral constant expression. Consequently, the current wording permits uses like the following:
enum class E { e = 10; }; struct S { int arr[E::e]; int i: E::e; };
This seems surprising.
Proposed resolution (February, 2011):
This issue is resolved by the resolution of issue 1197.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
One of the bullets in 5.19 [expr.const] paragraph 2 says,
a type conversion from a pointer or pointer-to-member type to a literal type
This appears to prohibit conversion from one pointer type to another; for example,
int x;
constexpr void* p = &x; // ill-formed
This seems excessive and probably unintentional.
Proposed resolution (November, 2010):
Change 5.19 [expr.const] paragraph 2 as follows:
a type conversion from a pointer or pointer-to-member type to a literal an integral type [Note: a user-defined conversion invokes a function —end note];
[Note: the proposed resolution of issue 1188 edits this bullet in an incompatible fashion.]
[Voted into the WP at the March, 2011 meeting.]
It is not clear what happens when a program violates the limits on constexpr function recursion in a context that does not require a constant expression. For example,
constexpr int f(int i) { return f(i); }
const int i = f(1); // error, undefined behavior, or dynamic initialization?
(Presumably the “within its resource limits” caveat of 1.4 [intro.compliance] paragraph 2 would effectively result in undefined behavior in a context that required a constant expression.)
Notes from the November, 2010 meeting:
The CWG was of mixed opinion as to whether an infinite recursion in a constexpr function should be ill-formed or simply render an expression non-constant.
Proposed resolution (January, 2011):
Add the following bullet in 5.19 [expr.const] paragraph 2:
an invocation of a constexpr function or a constexpr constructor that would exceed the implementation-defined recursion limit (see annex B [implimits]);
[Voted into the WP at the March, 2011 meeting as part of paper N3260.]
According to 14.3.2 [temp.arg.nontype] paragraph 1, one of the possibilities for a template-argument for a non-type, non-template template-parameter is
an integral constant expression (including a constant expression of literal class type that can be used as an integral constant expression as described in 5.19 [expr.const])
However, the requirement for such a literal class type is (5.19 [expr.const] paragraph 5):
...that class type shall have a single non-explicit conversion function to an integral or enumeration type and that conversion function shall be constexpr.
Note that this normative requirement for a single conversion function is contradicted by the example in that paragraph, which reads in significant part,
struct A {
constexpr A(int i) : val(i) { }
constexpr operator int() { return val; }
constexpr operator long() { return 43; }
private:
int val;
};
template<int> struct X { };
constexpr A a = 42;
X<a> x; // OK: unique conversion to int
Proposed resolution (February, 2011):
This issue is resolved by the resolution of issue 1197.
[Voted into the WP at the November, 2010 meeting as paper N3218.]
N3092 comment DE 8In the definition of “potential constant expression” in 5.19 [expr.const] paragraph 6, it is unclear how “arbitrary” the substitution of the function parameters is. Does it mean “there exists a value for which the result is a constant expression” or does it mean “for all possible values, the result needs to be a constant expression?” Example:
constexpr int f(int x){ return x + 1; }
is a constant expression under the first interpretation, but not under the second (because overflow occurs for x == INT_MAX, cf 5.19 [expr.const] paragraph 2 bullet 5). The answer also affects expressions such as:
constexpr int f2(bool v) { return v ? throw 0 : 0; } constexpr int f3(bool v) { return v && (throw 0, 0); }
See also issue 1129.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment GB 25It does not appear to be clearly enough stated that the example
constexpr int f() { return 42 + 84; } const int sz = f(); int a[sz];
is equivalent to
const int sz = 42 + 84; int a[sz];
Proposed resolution (August, 2010):
Change 5.19 [expr.const] paragraph 1 as follows:
Certain contexts require expressions that satisfy additional requirements as detailed in this sub-clause; other contexts have different semantics depending on whether or not an expression satisfies these requirements. Such expressions Expressions that satisfy these requirements are called constant expressions. [Note: Those Constant expressions can be evaluated during translation. —end note]
[Voted into the WP at the November, 2010 meeting as paper N3218.]
N3092 comment GB 26It is not clear how overload resolution is performed inside the body of a constexpr function. In particular, if the function is invoked with parameters such that an expression evaluates to an integral 0, does that make the expression eligible for the null pointer conversion and thus potentially select a different overloaded function than in invocations in which the expression has a non-zero value? (Suggested answer: no.)
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The status of the following example is not clear:
union U { float f; unsigned long u; }; constexpr U u1 = { 1.0 }; constexpr unsigned long u2 = u1.u;
This might be ill-formed because the aliasing causes undefined behavior, which should make the expression not a constant expression. However, a similar example using a permitted aliasing would presumably be acceptable:
union U { unsigned char c[sizeof(double)]; double d; }; constexpr U c1u = { 0x12, 0x34 /* etc. */ }; constexpr double c1 = c1u.d;
One suggestion was that unions should not be considered literal types, but some in the ensuing discussion deemed that excessive. That also would not address similar examples using reinterpret_cast, which is currently also permitted in constant expressions.
Proposed resolution (November, 2010):
Change 5.19 [expr.const] paragraph 2 as follows:
an lvalue-to-rvalue conversion...
an lvalue-to-rvalue conversion (4.1 [conv.lval]) that is applied to a glvalue that refers to a non-active member of a union or a subobject thereof;
...
a type conversion from a pointer or pointer-to-member type to a literal type [Note: a user-defined conversion invokes a function —end note] a reinterpret_cast (5.2.10 [expr.reinterpret.cast]);
[Note: the proposed resolution of issue 1098 edits this bullet in an incompatible fashion.]
[Voted into the WP at the March, 2011 meeting.]
It's not clear whether the current rules for constant expressions allow indirect calls of constexpr functions and constexpr member functions; for example,
constexpr bool is_negative(int x) { return x < 0; } constexpr bool check(int x, bool (*p)(int)) { return p(x); } static_assert(check(-2, is_negative), "Error");
If this is to be permitted, there does not seem to be a reason to prohibit equality comparison of pointers to functions or pointers to objects of static storage duration -- these can be tracked as is already done for non-type template parameters.
Proposed resolution (November, 2010):
Change 5.19 [expr.const] paragraph 2 bullet 19 as follows:
a relational (5.9 [expr.rel]) or equality (5.10 [expr.eq]) operator where at least one of the operands is a pointer the result is unspecified;
[Voted into the WP at the March, 2011 meeting as part of paper N3260.]
The requirement in 5.19 [expr.const] that a constant expression cannot contain
an array-to-pointer conversion (4.2 [conv.array]) that is applied to a glvalue that does not designate an object with static storage duration
effectively eliminates the use of automatic constexpr arrays such as
void f() { constexpr int ar[] = { 1, 2 }; constexpr int i = ar[1]; }
There does not seem to be a problem with this kind of usage.
Proposed resolution (February, 2011):
The proposed resolution will be submitted as a separate document.
[Voted into the WP at the March, 2011 meeting.]
C and C++ differ in the treatment of an expression statement, in particular with regard to whether a volatile lvalue is fetched. For example,
volatile int x; void f() { x; // Fetches x in C, not in C++ }
The reason C++ is different in this regard is principally due to the fact that an assignment expression is an lvalue in C++ but not in C. If the lvalue-to-rvalue conversion were applied to expression statements, a statement like
x = 5;
would write to x and then immediately read it.
It is not clear that the current approach to dealing with the difference in assignment expressions is the only or best approach; it might be possible to avoid the unwanted fetch on the result of an assignment statement without giving up the fetch for a variable appearing by itself in an expression statement.
Proposed resolution (January, 2011):
Add a new paragraph after 5 [expr] paragraph 10:
In some contexts, an expression only appears for its side-effects. Such an expression is called a discarded-value expression. The expression is evaluated and its value is discarded. The array-to-pointer (4.2 [conv.array]) and function-to-pointer (4.3 [conv.func]) standard conversions are not applied. The lvalue-to-rvalue conversion (4.1 [conv.lval]) is applied only if the expression is an lvalue of volatile-qualified type and it has one of the following forms:
id-expression (5.1.1 [expr.prim.general]),
subscripting (5.2.1 [expr.sub]),
class member access (5.2.5 [expr.ref]),
indirection (5.3.1 [expr.unary.op]),
pointer-to-member operation (5.5 [expr.mptr.oper]),
conditional expression (5.16 [expr.cond]) where both the second and the third operand are one of the above, or
comma expression (5.18 [expr.comma]) where the right operand is one of the above.
Change 5.2.9 [expr.static.cast] paragraph 6 as follows:
Any expression can be explicitly converted to type cv void, in which case it becomes a discarded-value expression (Clause 5 [expr]). The expression value is discarded. [Note: however, if the value is in a temporary object (12.2 [class.temporary]), the destructor for that object is not executed until the usual time, and the value of the object is preserved for the purpose of executing the destructor. —end note] The lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), and function-to-pointer (4.3 [conv.func]) standard conversions are not applied to the expression.
Change 5.18 [expr.comma] paragraph 1 as follows:
...A pair of expressions separated by a comma is evaluated left-to-right; and the value of the left expression is discarded a discarded-value expression (Clause 5 [expr]).83 The lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), and function-to-pointer (4.3 [conv.func]) standard conversions are not applied to the left expression. Every value computation...
Change 6.2 [stmt.expr] paragraph 1 as follows:
...The expression is evaluated and its value is discarded a discarded-value expression (clause 5 [expr]). The lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), and function-to-pointer (4.3 [conv.func]) standard conversions are not applied to the expression. All side effects...
[Voted into WP at August, 21010 meeting.]
The grammar for condition in 6.4 [stmt.select] paragraph 1 does not allow for the constexpr specifier. This was not intended by the original proposal.
Proposed resolution (March, 2010):
Change the definition of condition in 6.4 [stmt.select] paragraph 1 as follows:
Insert the following text as a new paragraph at the end of 6.4 [stmt.select]:
In the decl-specifier-seq of a condition, each decl-specifier shall be either a type-specifier or constexpr.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
It seems unfortunate that the beginning of a C-style for loop can look like
whereas the beginning of a range-based for loop looks like
So that we don't know what constraints we are trying to apply to the specifiers until we see, or don't see, a :. The inconsistency between decl-specifier-seq and type-specifier-seq seems gratuitous and inconvenient.
Proposed resolution (November, 2010):
Change the grammar 6.5 [stmt.iter] paragraph 1 as follows:
Add the following as a new paragraph at the end of 6.5.4 [stmt.ranged]:
The the decl-specifier-seq of a for-range-declaration, each decl-specifier shall be either a type-specifier or constexpr.
[Voted into WP at August, 21010 meeting.]
The intent is that the range-based for statement should be able to be used with a braced-init-list as the range over which to iterate. However, this does not work grammatically: a braced-init-list is not an expression, as required by the syntax in 6.5.4 [stmt.ranged] paragraph 1:
Even if this were resolved, the “equivalent to” code is not correct. It contains the declaration,
This has a similar problem, in that 7.1.6.4 [dcl.spec.auto] paragraph 3 requires that the initializer have one of the forms
which does not allow for a braced-initializer-list. In addition, although not allowed by the grammar, 7.1.6.4 [dcl.spec.auto] paragraph 6 treats the braced-init-list specially, in order for the type deduction to work correctly:
Obtain P from T by replacing the occurrences of auto with either a new invented type template parameter U or, if the initializer is a braced-init-list (8.5.4 [dcl.init.list]), with std::initializer_list<U>.
The problem here is that a parenthesized initializer, as in the code expansion of the range-based for statement, is not a braced-init-list.
Proposed resolution (June, 2010):
Change 6.5 [stmt.iter] paragraph 1 as follows:
Iteration statements specify looping.
iteration-statement:
while ( condition ) statement
do statement while ( expression ) ;
for ( for-init-statement conditionopt ; expressionopt ) statement
for ( for-range-declaration : expression for-range-initializer ) statement
for-init-statement:
expression-statement
simple-declaration
for-range-declaration:
type-specifier-seq attribute-specifieropt declarator
for-range-initializer:
expression
braced-init-list
[Note: a for-init-statement ends with a semicolon. —end note]
Change 6.5.4 [stmt.ranged] paragraph 1 as follows:
The For a range-based for statement of the form
for ( for-range-declaration : expression ) statement
let range-init be equivalent to the expression surrounded by parentheses:
( expression )
[Footnote: this ensures that a top-level comma operator cannot be reinterpreted as a delimiter between init-declarators in the declaration of __range. —end footnote] and for a range-based for statement of the form
for ( for-range-declaration : braced-init-list ) statement
let range-init be equivalent to the braced-init-list. In each case, a range-based for statement is equivalent to
{ auto && __range = ( expression ) range-init; for ( auto __begin = begin-expr, ...
Note to editor:
The formatting in the preceding change for range-init follows that of the existing text for begin-expr and end-expr. However, CWG is concerned that this style makes all of these elements look too much like grammar nonterminals and asks that the editor consider some other formatting convention.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The grammar for declarations includes the following two nonterminals:
An attribute-specifier followed by a semicolon could thus be parsed as either an attribute-declaration or as a simple-declaration that omits the optional decl-specifier-seq and init-declarator-list, and the current wording does not disambiguate the two. (There doesn't seem to be a compelling need for attribute-declaration as a separate nonterminal, given that simple-declaration can accommodate that form.)
Proposed resolution (February, 2011):
Change 7 [dcl.dcl] paragraph 1 as follows:
...The optional attribute-specifier-seq in a simple-declaration appertains to each of the entities declared by the declarators; it shall not appear if the optional of the init-declarator-list is omitted...
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The grammar for an alias-declaration does not have a place for an attribute-specifier, although a typedef declaration does. Since an alias-declaration is essentially a different syntactic form of a typedef declaration (7.1.3 [dcl.typedef] paragraph 2), this could be surprising.
Proposed resolution (February, 2011):
Change the grammar in 7 [dcl.dcl] paragraph 1 as follows:
Change 7.1.3 [dcl.typedef] paragraph 2 as follows:
A typedef-name can also be introduced by an alias-declaration. The identifier following the using keyword becomes a typedef-name and the optional attribute-specifier-seq following the identifier appertains to that typedef-name. It has the same semantics...
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 39The current wording of 7.1 [dcl.spec] paragraph 1 says,
The optional attribute-specifier in a decl-specifier-seq appertains to the type determined by the decl-specifier-seq (8.3 [dcl.meaning]).
However, decl-specifier-seq is a recursive production. The intent is that the attribute-specifier appertains to the type determined by the top-level decl-specifier-seq, but this makes it sound as if it appertains to the one that directly contains the attribute-specifier.
Proposed resolution (August, 2010):
Change 7.1 [dcl.spec] paragraph 1 as follows:
The optional attribute-specifier in a decl-specifier-seq appertains to the type determined by the decl-specifier-seq preceding decl-specifiers (8.3 [dcl.meaning]).
[Voted into the WP at the March, 2011 meeting.]
Here's an example:
typedef struct S { ... } S; void fs(S *x) { ... }
The big question is, to what declaration does the reference to identifier S actually refer? Is it the S that's declared as a typedef name, or the S that's declared as a class name (or in C terms, as a struct tag)? (In either case, there's clearly only one type to which it could refer, since a typedef declaration does not introduce a new type. But the debugger apparently cares about more than just the identity of the type.)
Here's a classical, closely related example:
struct stat { ... }; int stat(); ... stat( ... ) ...
Does the identifier stat refer to the class or the function? Obviously, in C, you can't refer to the struct tag without using the struct keyword, because it is in a different name space, so the reference must be to the function. In C++, the reference is also to the function, but for a completely different reason.
Now in C, typedef names and function names are in the same name space, so the natural extrapolation would be that, in the first example, S refers to the typedef declaration, as it would in C. But C++ is not C. For the purposes of this discussion, there are two important differences between C and C++
The first difference is that, in C++, typedef names and class names are not in separate name spaces. On the other hand, according to section 3.3.10 [basic.scope.hiding] (Name hiding), paragraph 2:
A class name (9.1) or enumeration name (7.2) can be hidden by the name of an object, function, or enumerator declared in the same scope. If a class or enumeration name and an object, function, or enumerator are declared in the same scope (in any order) with the same name, the class or enumeration name is hidden wherever the object, function, or enumerator name is visible.
Please consider carefully the phrase I have highlighted, and the fact that a typedef name is not the name of an object, function or enumerator. As a result, this example:
struct stat { ... }; typedef int stat;
Which would be perfectly legal in C, is disallowed in C++, both implicitly (see the above quote) and explicitly (see section 7.1.3 [dcl.typedef] (The typedef specifier), paragraph 3):
In a given scope, a typedef specifier shall not be used to redefine the name of any type declared in that scope to refer to a different type. Similarly, in a given scope, a class or enumeration shall not be declared with the same name as a typedef-name that is declared in that scope and refers to a type other than the class or enumeration itself.
From which we can conclude that in C++ typedef names do not hide class names declared in the same scope. If they did, the above example would be legal.
The second difference is that, in C++, a typedef name that refers to a class is a class-name; see 7.1.3 [dcl.typedef] paragraph 4:
A typedef-name that names a class is a class-name(9.1). If a typedef-name is used following the class-key in an elaborated-type-specifier (7.1.5.3) or in the class-head of a class declaration (9), or is used as the identifier in the declarator for a constructor or destructor declaration (12.1, 12.4), the program is ill-formed.
This implies, for instance, that a typedef-name referring to a class can be used in a nested-name-specifier (i.e. before :: in a qualified name) or following ~ to refer to a destructor. Note that using a typedef-name as a class-name in an elaborated-type-specifier is not allowed. For example:
struct X { }; typedef struct X X2; X x; // legal X2 x2; // legal struct X sx; // legal struct X2 sx2; // illegal
The final relevant piece of the standard is 7.1.3 [dcl.typedef] paragraph 2:
In a given scope, a typedef specifier can be used to redefine the name of any type declared in that scope to refer to the type to which it already refers.
This of course is what allows the original example, to which let us now return:
typedef struct S { ... } S; void fs(S *x) { ... }
The question, again is, to which declaration of S does the reference actually refer? In C, it would clearly be to the second, since the first would be accessible only by using the struct keyword. In C++, if typedef names hid class names declared in the same scope, the answer would be the same. But we've already seen that typedef names do not hide class names declared in the same scope.
So to which declaration does the reference to S refer? The answer is that it doesn't matter. The second declaration of S, which appears to be a declaration of a typedef name, is actually a declaration of a class name (7.1.3 [dcl.typedef] paragraph 4), and as such is simply a redeclaration. Consider the following example:
typedef int I, I; extern int x, x; void f(), f();
To which declaration would a reference to I, x or f refer? It doesn't matter, because the second declaration of each is really just a redeclaration of the thing declared in the first declaration. So to save time, effort and complexity, the second declaration of each doesn't add any entry to the compiler's symbol table.
Note (March, 2005):
Matt Austern: Is this legal?
struct A { }; typedef struct A A; struct A* p;
Am I right in reading the standard [to say that this is ill-formed]? On the one hand it's a nice uniform rule. On the other hand, it seems likely to confuse users. Most people are probably used to thinking that 'typedef struct A A' is a null operation, and, if this code really is illegal, it would seem to be a gratuitous C/C++ incompatibility.
Mike Miller: I think you're right. 7.1.3 [dcl.typedef] paragraph 1:
A name declared with the typedef specifier becomes a typedef-name.
7.1.3 [dcl.typedef] paragraph 2:
In a given non-class scope, a typedef specifier can be used to redefine the name of any type declared in that scope to refer to the type to which it already refers.
After the typedef declaration in the example, the name X has been “redefined” — it is no longer just a class-name, it has been “redefined” to be a typedef-name (that, by virtue of the fact that it refers to a class type, is also a class-name).
John Spicer: In C, and originally in C++, an elaborated-type-specifier did not consider typedef names, so “struct X* x” would find the class and not the typedef.
When C++ was changed to make typedefs visible to elaborated-type-specifier lookups, I believe this issue was overlooked and inadvertantly made ill-formed.
I suspect we need add text saying that if a given scope contains both a class/enum and a typedef, that an elaborated type specifier lookup finds the class/enum.
Mike Miller: I'm a little uncomfortable with this approach. The model we have for declaring a typedef in the same scope as a class/enum is redefinition, not hiding (like the “struct stat” hack). This approach seems to assume that the typedef hides the class/enum, which can then be found by an elaborated-type-specifier, just as if it were hidden by a variable, function, or enumerator.
Also, this approach reduces but doesn't eliminate the incompatibility with C. For example:
struct S { }; { typedef struct S S; struct S* p; // still ill-formed }
My preference would be for something following the basic principle that declaring a typedef-name T in a scope where T already names the type designated by the typedef should have no effect on whether an elaborated-type-specifier in that or a nested scope is well-formed or not. Another way of saying that is that a typedef-name that designates a same-named class or enumeration in the same or a containing scope is transparent with respect to elaborated-type-specifiers.
John Spicer: This strikes me as being a rather complicated solution. When we made the change to make typedefs visible to elaborated-type-specifiers we did so knowing it would make some C cases ill-formed, so this does not bother me. We've lived with the C incompatibility for many years now, so I don't personally feel a need to undo it. I also don't like the fact that you have to essentially do the old-style elaborated-type-specifier lookup to check the result of the lookup that found the typedef.
I continue to prefer the direction I described earlier where if a given scope contains both a class/enum and a typedef, that an elaborated-type-specifier lookup finds the class/enum.
Notes from the April, 2005 meeting:
The CWG agreed with John Spicer's approach, i.e., permitting a typedef-name to be used in an elaborated-type-specifier only if it is declared in the same scope as the class or enumeration it names.
Proposed resolution (January, 2011):
Add the following new paragraph after 7.1.3 [dcl.typedef] paragraph 4:
If a typedef specifier is used to redefine in a given scope an entity that can be referenced using an elaborated-type-specifier, the entity can continue to be referenced by an elaborated-type-specifier or as an enumeration or class name in an enumeration or class definition respectively. [Example:
struct S; typedef struct S S; int main() { struct S* p; // OK } struct S {}; // OK—end example]
[Voted into WP at August, 21010 meeting.]
7.1.5 [dcl.constexpr] paragraph 5 applies only to “the instantiated template specialization of a constexpr function template;” it should presumably apply to non-template member functions of a class template, as well.
Notes from the September, 2008 meeting:
This question is more involved than it might appear. For example, a constexpr member function is implicitly const; if the constexpr specifier is ignored, does that make the member function non-const? Also, should this provision apply only to dependent expressions in the function? Should it be an error if no constexpr function can be instantiated from the template, along the lines of the permission given in 14.6 [temp.res] paragraph 8 for an implementation to diagnose a template definition from which no valid specialization can be instantiated?
Notes from the July, 2009 meeting:
The consensus of the CWG was that an “ignored” constexpr specifier in this case simply means that the specialization is not constexpr, not that it is not const. The CWG also decided not to address the question of non-dependent expressions that render a function template specialization non-constexpr, leaving it to quality of implementation whether a (warning) diagnostic is issued in such cases.
Proposed resolution (February, 2010):
Change 7.1.5 [dcl.constexpr] paragraph 5 as follows:
If the instantiated template specialization of a constexpr function template or member function of a class template would fail to satisfy the requirements for a constexpr function or constexpr constructor, the constexpr specifier is ignored that specialization is not a constexpr function or constexpr constructor. [Note: if the function is a member function it will still be const as described below. Implementations are encouraged to issue a warning if a function was rendered not constexpr by a non-dependent construct. —end note]
[Voted into the WP at the March, 2011 meeting as part of paper N3268.]
The body of a constexpr function is required by 7.1.5 [dcl.constexpr] paragraph 3 to be of the form
However, there does not seem to be any good reason for prohibiting the alternate return syntax involving a braced-init-list. The restriction should be removed.
Proposed resolution (March, 2010):
Change 6.6.3 [stmt.return] paragraph 2 as follows:
A return statement without an expression with neither an expression nor a braced-init-list can be used only in functions that do not return a value...
Change 7.1.5 [dcl.constexpr] paragraph 3 bullets 4 and 5 as follows:
its function-body shall be a compound-statement of the form
where expression is a potential constant expression (5.19), or
where every assignment-expression that is an initializer-clause appearing directly or indirectly within the braced-init-list is a potential constant expression
every constructor call and implicit conversion used in converting expression to the function return type initializing the return value (6.6.3 [stmt.return], 8.5 [dcl.init]) shall be one of those allowed in a constant expression (5.19 [expr.const]).
Notes from the March, 2010 meeting:
The new wording added in 5.19 [expr.const] in support of reference parameters for constexpr functions should also be considered to see whether additional changes are needed.
[Voted into WP at August, 21010 meeting.]
7.1.5 [dcl.constexpr] paragraph 6 says,
A constexpr specifier for a non-static member function that is not a constructor declares that member function to be const (9.3.1 [class.mfct.non-static]).
Is a const qualifier on such a member function redundant or ill-formed?
Notes from the July, 2009 meeting:
The CWG agreed that a const qualifier on a constexpr member function is simply redundant and not an error.
Proposed resolution (February, 2010):
Change 7.1.5 [dcl.constexpr] paragraph 6 as follows:
A constexpr specifier for a non-static member function that is not a constructor declares that member function to be const (9.3.1 [class.mfct.non-static]). [Note: the constexpr specifier has no other effect on the function type. —end note] The keyword const is ignored if it appears in the cv-qualifier-seq of the function declarator of the declaration of such a member function. The class of which that function is a member shall be a literal type (3.9 [basic.types]). [Example:...
[Voted into WP at August, 21010 meeting.]
The rules for constexpr constructors are missing some necessary requirements. In particular, there is no requirement that a brace-or-equal-initializer for a non-static data member be a constant expression, and the requirement for constexpr constructors for initializing non-static data members applies only to members named in a mem-initializer, allowing a non-constexpr default constructor to be invoked.
Proposed resolution (February, 2010):
Change 7.1.5 [dcl.constexpr] paragraph 4 as follows:
The definition of a constexpr constructor shall satisfy the following constraints:
each of its parameter types shall be a literal type or a reference to a literal type
its function-body shall not be a function-try-block
the compound-statement of its function-body shall be empty
every non-static data member and base class sub-object shall be initialized (12.6.2 [class.base.init])
every constructor involved in initializing non-static data members and base class sub-objects invoked by a mem-initializer shall be a constexpr constructor.
every constructor argument and full-expression in a mem-initializer shall be a potential constant expression
every assignment-expression that is an initializer-clause appearing directly or indirectly within a brace-or-equal-initializer for a non-static data member that is not named by a mem-initializer-id shall be a constant expression
every implicit conversion used in converting a constructor argument to the corresponding parameter type and converting a full-expression to the corresponding member type shall be one of those allowed in a constant expression.
[Voted into the WP at the March, 2011 meeting as part of paper N3268.]
According to 7.1.5 [dcl.constexpr] paragraph 3, no declarations are permitted in the body of a constexpr function. This seems overly restrictive. At least three kinds of declarations would seem to be helpful in writing such functions: static_assert, typedef and alias declarations, and local automatic variables initialized with constant expressions. (Similar considerations apply to lambdas in which the lambda-return-type-clause is omitted.)
Rationale (July, 2009):
This suggestion needs a proposal and analysis by EWG before it can be considered by CWG.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment GB 29A constexpr function is not permitted to return via an exception. This should be recognised, and a function declared constexpr without an explicit exception specification should be treated as if declared noexcept(true) rather than the usual noexcept(false). For a function template declared constexpr without an explicit exception specification, it should be considered noexcept(true) if and only if the constexpr keyword is respected on a given instantiation.
See also issue 1125.
Notes from the August, 2010 meeting:
The premise is not correct: an exception is forbidden only when a constexpr function is invoked in a context that requires a constant expression. Used as an ordinary function, it can throw.
Proposed resolution (August, 2010):
Change 5.3.7 [expr.unary.noexcept] paragraph 3 bullet 1 as follows:
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
7.1.5 [dcl.constexpr] paragraph 5 says,
If the instantiated template specialization of a constexpr function template or member function of a class template would fail to satisfy the requirements for a constexpr function or constexpr constructor, that specialization is not a constexpr function or constexpr constructor. [Note: if the function is a member function it will still be const as described below. Implementations are encouraged to issue a warning if a function is rendered not constexpr by a non-dependent construct. —end note]
A non-dependent error in a function template renders it ill-formed with no diagnostic required (14.6 [temp.res] paragraph 8). A similar approach is being taken in the proposed resolution of issue 1125 with respect to constexpr functions. It would be more consistent to treat constexpr function templates in the same way, along the lines of
If no specialization of the template would be constexpr, the program is ill-formed, no diagnostic required.
Proposed resolution (November, 2010):
Change 7.1.5 [dcl.constexpr] paragraph 6 as follows:
If the instantiated template specialization of a constexpr function template or member function of a class template would fail to satisfy the requirements for a constexpr function or constexpr constructor, that specialization is not a constexpr function or constexpr constructor. [Note: if the function is a member function it will still be const as described below. Implementations are encouraged to issue a warning if a function is rendered not constexpr by a non-dependent construct. —end note] If no specialization of the template would yield a constexpr function or constexpr constructor, the program is ill-formed; no diagnostic required.
[Voted into the WP at the March, 2011 meeting as part of paper N3277.]
7.1.5 [dcl.constexpr] restricts the constexpr specifier to object and function declarations. Especially given the support for reference types in constexpr functions, and considering that constexpr pointer declarations are permitted, there does not seem to be a good reason for excluding constexpr references.
(See also issue 1195.)
Proposed resolution (November. 2010):
Change 7.1.5 [dcl.constexpr] paragraph 1 as follows:
The constexpr specifier shall be applied only to the definition of an object a variable, the declaration of a function...
Change 7.1.5 [dcl.constexpr] paragraph 8 as follows:
A constexpr specifier used in an object declaration declares the object as const. Such an object shall be initialized. If it is initialized by a constructor call, the constructor shall be a constexpr constructor and every argument to the constructor shall be a constant expression. Otherwise, or if a constexpr specifier is used in a reference declaration, every full-expression that appears in its initializer shall be a constant expression...
[Voted into the WP at the March, 2011 meeting as part of paper N3277.]
7.1.5 [dcl.constexpr] paragraph 3 is overly restrictive in requiring that reference parameter and return types of a constexpr function or constructor must refer to a literal type. 5.19 [expr.const] paragraph 2 already prevents any problematic uses of lvalues of non-literal types, and it permits use of pointers to non-literal types as address constants. The same should be permitted via reference parameters and return types of constexpr functions.
(See also issue 1194.)
Proposed resolution (November, 2010):
Change 3.9 [basic.types] paragraph 10 as follows:
A type is a literal type if it is:
a scalar type; or
a reference type; or
...
Change 7.1.5 [dcl.constexpr] paragraph 3 as follows:
The definition of a constexpr function shall satisfy the following constraints:
it shall not be virtual (10.3 [class.virtual])
its return type shall be a literal type or a reference to literal type
each of its parameter types shall be a literal type or a reference to literal type
...
Change 7.1.5 [dcl.constexpr] paragraph 4 as follows:
The definition of a constexpr constructor shall satisfy the following constraints:
each of its parameter types shall be a literal type or a reference to literal type;
...
[Voted into the WP at the March, 2011 meeting as part of paper N3277.]
The current requirements for constexpr functions do not permit a deleted constexpr function because the definition does not consist of a compound-statement containing just a return statement. However, it could be useful to allow this form in a case where a single piece of code is used in multiple configurations, in some of which the function is constexpr and others deleted; having to update all declarations of the function to remove the constexpr specifier is unnecessarily onerous.
Proposed resolution (January, 2011):
Change 7.1.5 [dcl.constexpr] paragraph 3 as follows:
...
its function-body shall be = delete or a compound-statement of the form
{ return expression ; }...
Change 7.1.5 [dcl.constexpr] paragraph 4 as follows:
The definition of a constexpr constructor In the definition of a constexpr constructor, each of the parameter types shall be a literal type or a reference to a literal type. In addition, either its function-body shall be = delete or it shall satisfy the following constraints:
each of its parameter types shall be a literal type or a reference to literal type;
...
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
A class with a virtual base should not be allowed to have a constexpr constructor.
Proposed resolution (November, 2010):
Add the following bullet to the list in 7.1.5 [dcl.constexpr] paragraph 4:
the class shall not have virtual base classes;
[Voted into the WP at the November, 2010 meeting.]
According to 14.2 [temp.names] paragraph 7,
A template-id that names a template alias specialization is a type-name.
However, the grammar for type-name in 7.1.6.2 [dcl.type.simple] does not include a production for simple-template-id.
Proposed resolution (September, 2010):
Change the definition of type-name in 7.1.6.2 [dcl.type.simple] paragraph 1 as follows:
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 40The description of decltype does not specify whether the type of a parameter is the declared type or the type as adjusted in 8.3.5 [dcl.fct] paragraph 5:
auto f(int a[])->decltype(a); // ill-formed or int*? auto g(const int i)->decltype(i); // int or const int?
Suggested resolution: Clarify the wording to indicate that the type of a parameter is after the array- and function-to-pointer decay but before the removal of cv-qualification.
Proposed resolution (August, 2010):
Change 8.3.5 [dcl.fct] paragraph 5 as follows:
...After producing the list of parameter types, several transformations take place upon these types to determine the function type. Any any top-level cv-qualifiers modifying a parameter type is are deleted when forming the function type. [Example: the type void(*)(const int) becomes void(*)(int) —end example] Such cv-qualifiers affect only the definition of the parameter within the body of the function; they do not affect the function type. If a storage-class-specifier modifies a parameter type, the specifier is deleted. [Example: register char* becomes char* —end example] Such storage-class-specifiers affect only the definition of the parameter within the body of the function; they do not affect the function type. The resulting list of transformed parameter types and the presence or absence of the ellipsis or a function parameter pack is the function's parameter-type-list. [Note: This transformation does not affect the types of the parameters. For example, int(*)(const int p, decltype(p)*) and int(*)(int, const int*) are identical types. —end note]
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
Given
int&& f(); int i;
it is surprising that decltype(f()) and decltype(static_cast<int&&>(i)) are not the same type.
Proposed resolution (November, 2010):
Change 7.1.6.2 [dcl.type.simple] paragraph 4 as follows:
The type denoted by decltype(e) is defined as follows:
if e is an unparenthesized id-expression or a class member access (5.2.5 [expr.ref]), decltype(e) is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;
otherwise, if e is a function call (5.2.2 [expr.call]) or an invocation of an overloaded operator (parentheses around e are ignored), decltype(e) is the return type of the statically chosen function an xvalue, decltype(e) is T&&, where T is the type of e;
otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;
otherwise, decltype(e) is the type of e.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 41The current wording disallows use of typedef-names in elaborated-type-specifiers. This prohibition should also apply to template aliases:
struct A { }; template<typename T> using X = A; struct X<int>* p2; // ill-formed
Proposed resolution (August, 2010):
Change 7.1.6.3 [dcl.type.elab] paragraph 2 as follows:
...If the identifier resolves to a typedef-name or the simple-template-id resolves to an alias template specialization, the elaborated-type-specifier is ill-formed. [Note:...
[Note: this wording assumes the change from “template alias” to “alias template” requested by comment FI 11 on FCD N3092.]
[Voted into the WP at the March, 2011 meeting.]
N3092 comment US 31According to 7.2 [dcl.enum] paragraph 10,
An expression of arithmetic or enumeration type can be converted to an enumeration type explicitly. The value is unchanged if it is in the range of enumeration values of the enumeration type; otherwise the resulting enumeration value is unspecified.
(There is similar wording in 5.2.9 [expr.static.cast].) Does the phrase “resulting enumeration value” mean that the result, although unspecified, must lie within the range of enumeration values of the enumeration type? Existing practice seems to allow out-of-range values to be preserved if the underlying type is large enough to represent the value. This freedom is important both for efficiency (to avoid having to mask values while storing and/or fetching) and to prevent optimizers from removing code that tests for out-of-range values.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 1094.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment FI 6Although 7.3.1.1 [namespace.unnamed] states that the use of the static keyword for declaring variables in namespace scope is deprecated because the unnamed namespace provides a superior alternative, it is unlikely that the feature will be removed at any point in the foreseeable future, especially in light of C compatibility concerns. The Committee should consider removing the deprecation.
Proposed resolution (August, 2010):
Delete 7.3.1.1 [namespace.unnamed] paragraph 2:
The use of the static keyword is deprecated when declaring variables in a namespace scope (see annex D [depr]); the unnamed-namespace provides a superior alternative.
Delete _N3225_.D.2 [depr.static]:
D.2 static keyword [depr.static] The use of the static keyword is deprecated when declaring objects in namespace scope (see 3.3.6 [basic.scope.namespace]).
[Voted into the WP at the November, 2010 meeting.]
Here's an interesting case:
int f; namespace N { extern "C" void f () {} }As far as I can tell, this is not precluded by the ODR section (3.2 [basic.def.odr]) or the extern "C" section (7.5 [dcl.link]). However, I believe many compilers do not do name mangling on variables and (more-or-less by definition) on extern "C" functions. That means the variable and the function in the above end up having the same name at link time. EDG's front end, g++, and the Sun compiler all get essentially the same error, which is a compile-time assembler-level error because of the duplicate symbols (in other words, they fail to check for this, and the assembler complains). MSVC++ 7 links the program without error, though I'm not sure how it is interpreted.
Do we intend for this case to be valid? If not, is it a compile time error (required), or some sort of ODR violation (no diagnostic required)? If we do intend for it to be valid, are we forcing many implementations to break binary compatibility by requiring them to mangle variable names?
Personally, I favor a compile-time error, and an ODR prohibition on such things in separate translation units.
Notes from the 4/02 meeting:
The working group agreed with the proposal. We feel a diagnostic should be required for declarations within one translation unit. We also noted that if the variable in global scope in the above example were declared static we would still expect an error.
Relevant sections in the standard are 7.5 [dcl.link] paragraph 6 and 3.5 [basic.link] paragraph 9. We feel that the definition should be written such that the entities in conflict are not "the same entity" but merely not allowed together.
Additional note (September, 2004)
This problem need not involve a conflict between a function and a variable; it can also arise with two variable declarations:
int x; namespace N { extern "C" int x; }
Proposed resolution (March, 2008):
Change 7.5 [dcl.link] paragraph 6 as follows:
At most one function with a particular name can have C language linkage. Two declarations for a function with C language linkage with the same function name (ignoring the namespace names that qualify it) that appear in different namespace scopes refer to the same function. Two declarations for an object with C language linkage with the same name (ignoring the namespace names that qualify it) that appear in different namespace scopes refer to the same object. A function or object with C linkage shall not be declared with the same name (clause 3 [basic]) as an object or reference declared in global scope, unless both declarations denote the same object; no diagnostic is required if the declarations appear in different translation units. [Note: because of the one definition rule (3.2 [basic.def.odr]), only Only one definition for a function or object with C linkage may appear in the program (see 3.2 [basic.def.odr]); that is, implies that such a function or object must not be defined in more than one namespace scope. For example,
int x; namespace A { extern "C" int f(); extern "C" int g() { return 1; } extern "C" int h(); extern "C" int x(); // ill-formed: same name as global-scope object x } namespace B { extern "C" int f(); // A::f and B::f refer // to the same function extern "C" int g() { return 1; } // ill-formed, the function g // with C language linkage // has two definitions } int A::f() { return 98; } // definition for the function f // with C language linkage extern "C" int h() { return 97; } // definition for the function h // with C language linkage // A::h and ::h refer to the same function—end note]
Notes from the September, 2008 meeting:
It should also be possible to declare references with C name linkage (although the meaning the first sentence of 7.5 [dcl.link] paragraph 1 with respect to the meaning of such a declaration is not clear), which would mean that the changed wording should refer to declaring “the same entity” instead of “the same object.” The formulation here would probably benefit from the approach currently envisioned for issues 570 and 633, in which “variable” is defined as being either an object or a reference.
Proposed resolution (February, 2010):
Change 7.5 [dcl.link] paragraph 6 as follows:
At most one function with a particular name can have C language linkage. Two declarations for a function with C language linkage with the same function name (ignoring the namespace names that qualify it) that appear in different namespace scopes refer to the same function. Two declarations for an object or reference with C language linkage with the same name (ignoring the namespace names that qualify it) that appear in different namespace scopes refer to the same object or reference. An entity with C language linkage shall not be declared with the same name as an entity in global scope, unless both declarations denote the same object or reference; no diagnostic is required if the declarations appear in different translation units. [Note: because of the one definition rule (3.2 [basic.def.odr]), only Only one definition for a function or object an entity with C linkage may appear in the program (see 3.2 [basic.def.odr]); that is, implies that such a function or object an entity must not be defined in more than one namespace scope. —end note] For example, [Example:
int x; namespace A { extern "C" int f(); extern "C" int g() { return 1; } extern "C" int h(); extern "C" int x(); // ill-formed: same name as global-scope object x } namespace B { extern "C" int f(); // A::f and B::f refer // to the same function extern "C" int g() { return 1; } // ill-formed, the function g // with C language linkage // has two definitions } int A::f() { return 98; } // definition for the function f // with C language linkage extern "C" int h() { return 97; } // definition for the function h // with C language linkage // A::h and ::h refer to the same function—end note example]
Additional note (February, 2010):
The proposed wording above does not cover the case where two different entities with C linkage are declared in different namespaces, only the case where one of the entities is in global scope.
Proposed resolution (August, 2010):
Change 7.5 [dcl.link] paragraph 6 as follows:
At most one function with a particular name can have C language linkage. Two declarations for a function with C language linkage with the same function name (ignoring the namespace names that qualify it) that appear in different namespace scopes refer to the same function. Two declarations for an object a variable with C language linkage with the same name (ignoring the namespace names that qualify it) that appear in different namespace scopes refer to the same object variable. An entity with C language linkage shall not be declared with the same name as an entity in global scope, unless both declarations denote the same entity; no diagnostic is required if the declarations appear in different translation units. A variable with C language linkage shall not be declared with the same name as a function with C language linkage (ignoring the namespace names that qualify the respective names); no diagnostic is required if the declarations appear in different translation units. [Note: because of the one definition rule (3.2 [basic.def.odr]), only Only one definition for a function or object an entity with a given name with C language linkage may appear in the program (see 3.2 [basic.def.odr]); that is, implies such a function or object an entity must not be defined in more than one namespace scope. —end note] For example, [Example:
int x; namespace A { extern "C" int f(); extern "C" int g() { return 1; } extern "C" int h(); extern "C" int x(); // ill-formed: same name as global-scope object x } namespace B { extern "C" int f(); // A::f and B::f refer // to the same function extern "C" int g() { return 1; } // ill-formed, the function g // with C language linkage // has two definitions } int A::f() { return 98; } // definition for the function f // with C language linkage extern "C" int h() { return 97; } // definition for the function h // with C language linkage // A::h and ::h refer to the same function—end note example]
[Note to editor: please consider reformatting the comments in the example.]
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The current wording of 7.5 [dcl.link] paragraph 4 is:
...A C language linkage is ignored for the names of class members and the member function type of class member functions...
This has two problems. First, it sounds as if a class member function has a “member function type,” while in fact the type of a class member function is an ordinary function type (cf 9.2 [class.mem] paragraph 11).
Second, even if that problem is fixed, it is not accurate to say that a C language linkage is “ignored” for the type of a member function. It does not affect the language linkage of the type of the member function, but it does affect the language linkage of any function declarators appearing in the parameter and return types of the function and thus the type of the function.
Proposed resolution (November, 2010):
Change 7.5 [dcl.link] paragraph 4 as follows:
...A C language linkage is ignored for in determining the language linkage of the names of class members and the member function type of class member functions...
[Voted into the WP at the November, 2010 meeting as part of paper N3190.
The current syntax requires that multiple attributes that appertain to the same entity be grouped into a single attribute-specifier. The migration from existing vendor-specific attributes would be easier if the syntax allowed multiple attribute-specifiers at each location where an attribute-specifier currently appears.
Proposed resolution (October, 2010):
Replace every occurrence of attribute-specifier with attribute-specifier-seq except in 7.6.1 [dcl.attr.grammar] paragraphs 1, 3, and 6 (but including paragraph 4).
Insert the following production at the beginning of the grammar of 7.6.1 [dcl.attr.grammar] paragraph 1:
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
An attribute-argument-clause should be allowed to consist solely of (), i.e., with no balanced-token-seq. Furthermore, the grammar for balanced-token should make the balanced-token-seq optional. Both of these goals could be accomplished by making the balanced-token optional in the first production of the rule for balanced-token-seq.
Proposed resolution (February, 2011):
Change the grammar of 7.6.1 [dcl.attr.grammar] paragraph 1 as follows:
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 7.6.2 [dcl.align] paragraph 5,
The combined effect of all alignment attributes in a declaration shall not specify an alignment that is less strict than the alignment that would otherwise be required for the entity being declared.
“...would otherwise be required” could be read as referring to the alignment set by another declaration of the entity. However, it was intended to prevent specifying an alignment smaller than the natural alignment the entity would have in the absence of an align attribute. The wording should be changed to make that clearer.
Proposed resolution (February, 2011):
Change 7.6.2 [dcl.align] paragraph 5 as follows:
The combined effect of all alignment-specifiers in a declaration shall not specify an alignment that is less strict than the alignment that would otherwise be required for the entity being declared if all alignment-specifiers were ignored (including those in other declarations).
[Voted into the WP at the November, 2010 meeting.]
The Standard explicitly bans alignment attributes for function parameters (7.6.2 [dcl.align] paragraph 1), but it is silent regarding the “parameter” of an exception handler. This should be clarified one way or the other.
Proposed resolution (October, 2010):
Change 7.6.2 [dcl.align] paragraph 1 as follows:
...The attribute may be followed by an ellipsis. The attribute may be applied to a variable that is neither a function parameter nor declared with the register storage class specifier and to a class data member that is not a bit-field or to a class data member, but it shall not be applied to a bit-field, a function parameter, the formal parameter of a catch clause (15.3 [except.handle]), or a variable declared with the register storage class specifier. The attribute may also be applied to the declaration of a class or enumeration type.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The footnote for 8 [dcl.decl] paragraph 3 reads,
A declaration with several declarators is usually equivalent to the corresponding sequence of declarations each with a single declarator... The exception occurs when a name introduced by one of the declarators hides a type name used by the decl-specifiers, so that when the same decl-specifiers are used in a subsequent declaration, they do not have the same meaning...
A more important exception to the rule has been added in C++0x, specifically with the auto specifier when the deduced type is not the same for all declarators, which renders the declaration ill-formed. The footnote should be updated accordingly.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The ellipsis for a parameter pack enters the normal declarator grammar as part of the declarator-id nonterminal. In contrast, however, the abstract-declarator grammar has no counterpart to declarator-id; instead, the ellipsis is one of the productions for the abstract-declarator nonterminal itself. It is thus impossible to declare a parameter pack for a pointer or reference using an abstract declarator, e.g.,
template<typename... T> void f(T& ...t); // t is a parameter pack template<typename... T> void f(T& ...); // equivalent to void f(T&, ...)
[Voted into the WP at the March, 2011 meeting.]
Issue 1199 proposes to add the capability of defining a constexpr special function as deleted. It would be similarly useful to be able to mark a defaulted constructor as constexpr. (It should be noted that the existing text of 12.1 [class.ctor] and the proposed resolution of issue 1224 already allow for implicitly-defined constructors to be implicitly constexpr; this issue simply proposes allowing the explicit use of the constexpr specifier.)
Proposed resolution (February, 2011):
Change 7.1.5 [dcl.constexpr] paragraph 3 as follows:
...
its function-body shall be = delete, = default, or a compound-statement of the form
{ return expression ; }...
Change 7.1.5 [dcl.constexpr] paragraph 4 as follows:
In the definition of a constexpr constructor, each of the parameter types shall be a literal type or a reference to a literal type. In addition, either its function-body shall be = delete or = default or it shall satisfy the following constraints:
...
A trivial copy/move constructor is also a constexpr constructor.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The following example appears to be well-formed, with the partial specialization matching the type of Y::f(), even though it is rejected by many compilers:
template<class T> struct X; template<class R> struct X< R() > { }; template<class F, class T> void test(F T::* pmf) { X<F> x; } struct Y { void f() { } }; int main() { test( &Y::f ); }
However, 8.3.5 [dcl.fct] paragraph 4 says,
A cv-qualifier-seq shall only be part of the function type for a non-static member function, the function type to which a pointer to member refers, or the top-level function type of a function typedef declaration. The effect of a cv-qualifier-seq in a function declarator is not the same as adding cv-qualification on top of the function type. In the latter case, the cv-qualifiers are ignored.
This specification makes it impossible to write a partial specialization for a const member function:
template<class R> struct X<R() const> { };
A template argument is not one of the permitted contexts for cv-qualification of a function type. This restriction should be removed.
Notes from the April, 2006 meeting:
During the meeting the CWG was of the opinion that the “R() const” specialization would not match the const member function even if it were allowed and so classified the issue as NAD. Questions have been raised since the meeting, however, suggesting that the template argument in the partial specialization would, in fact, match the type of a const member function (see, for example, the very similar usage via typedefs in 9.3 [class.mfct] paragraph 9). The issue is thus being left open for renewed discussion at the next meeting.
Proposed resolution (June, 2008):
Change 8.3.5 [dcl.fct] paragraph 7 as follows:
A cv-qualifier-seq shall only be part of the function type for a non-static member function, the function type to which a pointer to member refers, or the top-level function type of a function typedef declaration, or the top-level function type of a type-id that is a template-argument for a type template-parameter. The effect... A ref-qualifier shall only be part of the function type for a non-static member function, the function type to which a pointer to member refers, or the top-level function type of a function typedef declaration, or the top-level function type of a type-id that is a template-argument for a type template-parameter. The return type...
[Voted into the WP at the November, 2010 meeting.]
It seems strange that it is possible to call a function with an explict argument of {} but that it is not possible to specify that same argument as a default in a function declaration.
Rationale (August, 2010):
This was previously considered and rejected by EWG.
Note (October, 2010):
Additional discussion has indicated a potential willingness to revisit this question.
Proposed resolution (November, 2010):
See paper N3217.
[Voted into the WP at the November, 2010 meeting.]
In 8.3.5 [dcl.fct] paragraph 2, the type of a function declarator with a trailing-return-type is said to be
“function of (parameter-declaration-clause) cv-qualifier-seqopt ref-qualifieropt returning type-id”.
This formulation incorrectly omits the derived-declarator-type-list modifier for the type, and it should refer to “the trailing-type-specifier-seq of the trailing-return-type” as the return type instead of type-id (which is left over from before the introduction of trailing-return-type).
Proposed resolution (September, 2010):
Change 8.3.5 [dcl.fct] paragraph 2 as follows:
...T shall be the single type-specifier auto. The type of the declarator-id in D is “derived-declarator-type-list function of (parameter-declaration-clause) cv-qualifier-seqopt ref-qualifieropt returning type-id trailing-return-type”. The optional...
[Voted into the WP at the March, 2011 meeting as part of paper N3270.]
8.3.5 [dcl.fct] paragraph 13 says,
The type T of the declarator-id of the function parameter pack shall contain a template parameter pack; each template parameter pack in T is expanded by the function parameter pack.
I think that's incorrect. For example, I think
template<class... P> void f(int (* ...p)[sizeof...(P)]);
should be an error, and that the function parameter pack p does not expand the template parameter pack P in this case.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 778.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 478.4.2 [dcl.fct.def.default] paragraph 4 says,
A special member function is user-provided if it is user-declared and not explicitly defaulted on its first declaration. A user-provided explicitly-defaulted function is defined at the point where it is explicitly defaulted...
The second sentence should say “user-declared” instead of “user-provided.”
Proposed resolution (September, 2010):
Change 8.4.2 [dcl.fct.def.default] paragraph 4 as follows:
...A special member function is user-provided if it is user-declared and not explicitly defaulted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed. [Note:...[Drafting note: the suggestion in the NB comment is incorrect. The proposed resolution clarifies the intent.]
[Voted into the WP at the March, 2011 meeting.]
N3092 comment FI 1It should be allowed to explicitly default a non-public special member function on its first declaration. It is very likely that users will want to default protected/private constructors and copy constructors without having to write such defaulting outside the class.
Proposed resolution (November, 2010):
Change 8.4.2 [dcl.fct.def.default] paragraphs 1-5 as follows:
A function definition of the form:
attribute-specifieropt decl-specifier-seqopt declarator = default ;
is called an explicitly-defaulted definition. A function that is explicitly defaulted shall
be a special member function,
have the same declared function type (except for possibly differing ref-qualifiers and except that in the case of a copy constructor or copy assignment operator, the parameter type may be “reference to non-const T”, where T is the name of the member function's class) as if it had been implicitly declared, and
not have default arguments, and.
not have an exception-specification.
[Note: This implies that parameter types, return type, and cv-qualifiers must match the hypothetical implicit declaration. —end note]
An explicitly-defaulted function may be declared constexpr only if it would have been implicitly declared as constexpr, and may have an explicit exception-specification only if it is compatible (15.4 [except.spec]) with the exception-specification on the implicit declaration. If it a function is explicitly defaulted on its first declaration,
it shall be public,
it shall not be explicit,
it shall not be virtual,
it is implicitly considered to be constexpr if the implicit declaration would be,
it is implicitly considered to have the same exception-specification as if it had been implicitly declared (15.4 [except.spec]), and
in the case of a copy constructor, move constructor, copy assignment operator, or move assignment operator, it shall have the same parameter type as if it had been implicitly declared.
[Note: Such a special member function may be trivial, and thus its accessibility and explicitness should match the hypothetical implicit definition; see below. —end note] [Example:
struct S { constexpr S() = default; //ill-formed: implicit S() is not constexpr S(int a = 0) = default; // ill-formed: default argument void operator=(const S&) = default; // ill-formed: non-matching return type ~S() throw(int) = default; // ill-formed: exception specification doesn't match private: int i; S(S&); // OK: private copy constructor }; S::S(S&) = default; // OK: defines copy constructor—end example]
Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall provide implicit definitions for them (12.1 [class.ctor] 12.4 [class.dtor], 12.8 [class.copy]), which might mean defining them as deleted. A special member function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed. [Note: while an implicitly-declared special member function is inline (Clause 12 [special]), an explicitly-defaulted definition may be non-inline. Non-inline definitions are user-provided, and hence non-trivial (12.1 [class.ctor], 12.4 [class.dtor], 12.8 [class.copy]). This rule enables Declaring a function as defaulted after its first declaration can provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base. —end note]
[Example:
struct trivial { trivial() = default; trivial(const trivial&) = default; trivial(trivial&&) = default; trivial& operator=(const trivial&) = default; trivial& operator=(trivial&&) = default; ~trivial() = default; }; struct nontrivial1 { nontrivial1(); }; nontrivial1::nontrivial1() = default; // not inline first declaration struct nontrivial2 { nontrivial2(); }; inline nontrivial2::nontrivial2() = default; // not first declaration struct nontrivial3 { virtual ~nontrivial3() = 0; // virtual }; inline nontrivial3::~nontrivial3() = default; // not first declaration—end example]
Change 12.1 [class.ctor] paragraph 5 as follows:
Change 12.4 [class.dtor] paragraph 3 as follows:
...A destructor is trivial if it is neither not user-provided nor deleted and...
Change 12.8 [class.copy] paragraph 13 as follows:
A copy/move constructor for class X is trivial if it is neither not user-provided nor deleted and...
Change 12.8 [class.copy] paragraph 27 as follows:
A copy/move assignment operator for class X is trivial trivial if it is neither not user-provided nor deleted and...
This resolution also resolves issues 1136, 1137, 1140, 1145, 1149, and 1208.
[Voted into the WP at the March, 2011 meeting.]
N3092 comment FI 2It should be allowed to explicitly default an explicit special member function on its first declaration. It is very likely that users will want to default explicit copy constructors without having to write such defaulting outside of the class.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 1135.
[Voted into the WP at the March, 2011 meeting.]
N3092 comment FI 3It should be allowed to explicitly default a virtual special member function on its first declaration. It is very likely that users will want to default virtual copy assignment operators and destructors without having to write such defaulting outside of the class.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 1135.
[Voted into WP at August, 2010 meeting.]
According to the definition of value initialization (8.5 [dcl.init] paragraph 5), non-union class types without user-declared constructors are value-initialized by value-initializing each of their members rather than by executing the (generated) default constructor. However, a number of other items in the Standard are described in relationship to the execution of the constructor:
12.4 [class.dtor] paragraph 6: “Bases and members are destroyed in the reverse order of the completion of their constructor.” If a given base or member is value-initialized without running its constructor, is it destroyed? (For that matter, paragraph 10 refers to “constructed” objects; is an object that is value-initialized without invoking a constructor “constructed?”)
15.2 [except.ctor] paragraph 2: “An object that is partially constructed or partially destroyed will have destructors executed for all of its fully constructed subobjects, that is, for subobjects for which the constructor has completed execution...”
3.8 [basic.life] paragraph 1: The lifetime of an object begins when “the constructor call has completed.” (In the TC1 wording — “if T is a class type with a non-trivial constructor (12.1 [class.ctor]), the constructor call has completed” — the lifetime of some value-initialized objects never began; in the current wording — “the constructor invoked to create the object is non-trivial” — the lifetime begins before any of the members are initialized.)
Proposed resolution (October, 2005):
Add the indicated words to 8.5 [dcl.init] paragraph 6:
A program that calls for default-initialization or value-initialization of an entity of reference type is ill-formed. If T is a cv-qualified type, the cv-unqualified version of T is used for these definitions of zero-initialization, default-initialization, and value-initialization. Even when value-initialization of an object does not call that object's constructor, the object is deemed to have been fully constructed once its initialization is complete and thus subject to provisions of this International Standard applying to “constructed” objects, objects “for which the constructor has completed execution,” etc.
Notes from April, 2006 meeting:
There was some concern about whether this wording covered (or needed to cover) cases where an object is “partially constructed.” Another approach might be simply to define value initialization to be “construction.” Returned to “drafting” status for further investigation.
Proposed resolution (February, 2010):
Change 8.5 [dcl.init] paragraph 7 as follows:
To value-initialize an object of type T means:
...
An object that is value-initialized is deemed to be constructed and thus subject to provisions of this International Standard applying to “constructed” objects, objects “for which the constructor has completed,” etc., even if no constructor is invoked for the object's initialization.
[Voted into WP at August, 21010 meeting.]
8.5 [dcl.init] paragraph 2 reads,
Automatic, register, static, and external variables of namespace scope can be initialized by arbitrary expressions involving literals and previously declared variables and functions.
Both “automatic” and “static” are used to describe storage durations, “register” is a storage class specifier which indicates the object has automatic storage duration, “external” describes linkage, and “namespace scope” is a kind of scope. Automatic, register, static and external, together with namespace scope, are used to restrict the “variables.”
Register objects are only a sub-set of automatic objects and thus the word “register” is redundant and should be elided. If register objects are to be emphasized, they should be mentioned like “Automatic (including register)...”
Variables having namespace scope can never be automatic; they can only be static, with either external or internal linkage. Therefore, there are in fact no “automatic variables of namespace scope,” and the “static” in “static variables of namespace scope” is useless.
In fact, automatic and static variables already compose all variables with either external linkage or not, and thus the “external” becomes redundant, too, and the quoted sentence seems to mean that all variables of namespace scope can be initialized by arbitrary expressions. But this is not true because not all internal variables of namespace scope can. Therefore, the restrictive “external” is really necessary, not redundant.
As a result, the erroneous restrictive “automatic, register, static” should be removed and the quoted sentence may be changed to:
External variables of namespace scope can be initialized by arbitrary expressions involving literals and previously declared variables and functions.
Notes from the April, 2007 meeting:
This sentence is poorly worded, but the analysis given in the issue description is incorrect. The intent is simply that the storage class of a variable places no restrictions on the kind of expression that can be used to initialize it (in contrast to C, where variables of static storage duration can only be initialized by constant expressions).
Proposed resolution (June, 2008):
Change 8.5 [dcl.init] paragraph 2 as follows:
Automatic, register, static, and external variables of namespace scope Variables of automatic, thread, and static storage duration can be initialized by arbitrary expressions involving literals and previously declared variables and functions...
Notes from the September, 2008 meeting:
The existing wording is intended to exclude block-scope extern declarations but to allow initializers in all other forms of variable declarations. The best way to phrase that is probably to say that all variable definitions (except for function parameters, where the initializer syntax is used for default arguments) can have arbitrary expressions as initializers, regardless of storage duration.
Proposed resolution (February, 2010):
Change 8.5 [dcl.init] paragraph 2 as follows:
Automatic, register, thread_local, static, and namespace-scoped external variables can be initialized by Except for objects declared with the constexpr specifier, for which see 7.1.5 [dcl.constexpr], an initializer in the definition of a variable can consist of arbitrary expressions involving literals and previously declared variables and functions, regardless of the variable's storage duration. [Example:...
[Voted into the WP at the November, 2010 meeting.]
The C committee is considering changing the definition of zero-initialization of unions to guarantee that the bytes of the entire union are set to zero before assigning 0, converted to the appropriate type, to the first member. The argument (summarized here) is for backward compatibility. The C++ Committee may want to consider the same change.
Proposed resolution (August, 2008):
Change bullet 3 of 8.5 [dcl.init] paragraph 5 (in the first list, dealing with zero-initialization) as follows:
[Drafting notes: Ask a C liaison about the progress of WG14 paper N1311, which deals with this issue. Since the adoption of WG21 paper N2544, unions may have static data members, hence the change to refer to the first non-static data member and the deletion of the footnote.]
Notes from the September, 2008 meeting:
It was observed that padding bytes in structs are zero-initialized in C, so if we are changing the treatment of unions in this way we should consider adding the C behavior for padding bytes at the same time. In particular, using memcmp to compare structs only works reliably if the padding bytes are zero-initialized.
Additional notes (February, 2010):
The C Committee has changed its approach to this question and is no longer using a two-phase process; in C, zero-initialization is specific to program start-up and thus is not appropriate for use with thread-local storage. See C paper N1387.
Proposed resolution (October, 2010):
Change 8.5 [dcl.init] paragraph 5 as follows:
To zero-initialize an object or reference of type T means:
if T is a scalar type (3.9 [basic.types]), the object is set to the value 0 (zero), taken as an integral constant expression, converted to T;103
if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized, and padding is initialized to zero bits;
if T is a (possibly cv-qualified) union type, the object's first non-static named data member is zero-initialized, and padding is initialized to zero bits;
if T is an array type, each element is zero-initialized;
if T is a reference type, no initialization is performed.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
8.5 [dcl.init] paragraph 16 describes three kinds of initializers: a single expression, a braced-init-list, and a parenthesized list of expressions. It is not clear which, if any, of these categories is the appropriate description for an initializer like
T t( { 1, 2 } )
and thus not clear which of the bullets in the list applies.
[Voted into the WP at the March, 2011 meeting.]
8.5.1 [dcl.init.aggr] paragraph 4 says,
An initializer-list is ill-formed if the number of initializer-clauses exceeds the number of members or elements to initialize.
However, in a new-expression, the number of elements to be initialized is potentially unknown at compile time. How should an overly-long initializer-list in a new-expression be treated?
Notes from the August, 2010 meeting:
The consensus of the CWG was that this case should throw an exception at runtime.
Proposed resolution (January, 2011):
Change 5.3.4 [expr.new] paragraph 7 as follows:
When the value of the expression in a noptr-new-declarator is zero, the allocation function is called to allocate an array with no elements. If the value of that expression is less than zero or such that the size of the allocated object would exceed the implementation-defined limit, or if the new-initializer is a braced-init-list for which the number of initializer-clauses exceeds the number of elements to initialize, no storage is obtained and the new-expression terminates by throwing an exception of a type that would match a handler (15.3 [except.handle]) of type std::bad_array_new_length (18.6.2.2 [new.badlength]).
[Voted into the WP at the March, 2011 meeting.]
The ordering imposed by 8.5.1 [dcl.init.aggr] paragraph 17 applies only to “the full-expressions in an initializer-clause” (i.e., what follows an = in an aggregate initializer); this leaves unspecified the order in which the expressions in an initializer-list (the term used by the braced-init-list form of initializer, with no =) are evaluated.
Notes from the November, 2010 meeting:
The CWG favored guaranteeing the order of evaluation of initializer-clauses appearing in a braced-init-list, regardless of whether the braced-init-list is an aggregate initialization or constructor call.
Proposed resolution (January, 2011):
Delete 8.5.1 [dcl.init.aggr] paragraph 17:
The full-expressions in an initializer-clause are evaluated in the order in which they appear.
Insert the following as a new paragraph between paragraphs 3 and 4 of 8.5.4 [dcl.init.list]
Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions (14.5.3 [temp.variadic]), are evaluated in the order in which they appear. That is, every value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clause that follows it in the comma-separated list of the initializer-list. [Note: This evaluation ordering holds regardless of the semantics of the initialization; for example, it applies when the elements of the initializer-list are interpreted as arguments of a constructor call, even though ordinarily there are no sequencing constraints on the arguments of a call. —end note]
[Voted into the WP at the November, 2010 meeting.]
Issue 990 added the following text to 8.5.4 [dcl.init.list] paragraph 3:Otherwise, if the initializer list has no elements and T is an aggregate, each of the members of T is initialized from an empty initializer list. [Example:...
A better way to handle this would be to delete that bullet and change 8.5.1 [dcl.init.aggr] paragraph 7 as follows:
If there are fewer initializer-clauses in the list than there are members in the aggregate, then each member not explicitly initialized shall be value-initialized (8.5 [dcl.init]) initialized from an empty initializer list (8.5.4 [dcl.init.list]).
This makes { } less of a special case and makes the following example work:
struct A { A(std::initializer_list<int>); }; struct B { int i; A a; }; B b = { 1 };
Proposed resolution (August, 2010):
Delete 8.5.4 [dcl.init.list] paragraph 3 bullet 2, includeing the example:
Change 8.5.1 [dcl.init.aggr] paragraph 7 as follows:
If there are fewer initializer-clauses in the list than there are members in the aggregate, then each member not explicitly initialized shall be value-initialized (8.5 [dcl.init]) initialized from an empty initializer list (8.5.4 [dcl.init.list]).
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 48The requirement that an rvalue reference must be bound to an rvalue is found in 8.5.3 [dcl.init.ref] paragraph 5 bullet 2:
Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference shall be an rvalue reference and the initializer expression shall be an rvalue or have a function type.
This is not quite correct, as it is phrased in terms of the value category of the initializer expression itself rather than that of the result of any conversions applied to the initializer. It should be permitted to bind an rvalue reference to a temporary created from an lvalue, for instance, or to the rvalue result of a conversion function for an lvalue object of class type. Also, it should not be permitted to bind an rvalue reference to the lvalue result of a conversion function for a class rvalue.
Proposed resolution (August, 2010):
Change 8.5.3 [dcl.init.ref] paragraph 5 as follows:
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
If the reference is an lvalue reference and the initializer expression
is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2,” or
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an lvalue of type “cv3 T3,” where “cv1 T1” is reference-compatible with “cv3 T3”105 (this conversion is selected by enumerating the applicable conversion functions (13.3.1.6 [over.match.ref]) and choosing the best one through overload resolution (13.3 [over.match])),
then the reference is bound to the initializer expression lvalue in the first case and to the lvalue result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object). [Note: the usual lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), and function-to-pointer (4.3 [conv.func]) standard conversions are not needed, and therefore are suppressed, when such direct bindings to lvalues are done. —end note]
[Example:
double d = 2.0; double& rd = d; // rd refers to d const double& rcd = d; // rcd refers to d struct A { }; struct B : A { operator int&(); } b; A& ra = b; // ra refers to A subobject in b const A& rca = b; // rca refers to A subobject in b int& ir = B(); // ir refers to the result of B::operator int&—end example]
Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference shall be an rvalue reference and the initializer expression shall be an rvalue or have a function type. [Example:
double& rd2 = 2.0; // error: not an lvalue and reference not const int i = 2; double& rd3 = i; // error: type mismatch and reference not const double&& rd4 = i; // error: rvalue reference cannot bind to lvalue—end example]
If the initializer expression
is an xvalue, class prvalue, array prvalue or function lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an xvalue, class prvalue, or function lvalue of type “cv3 T3,” where “cv1 T1” is reference-compatible with “cv3 T3”,
then the reference is bound to the value of the initializer expression in the first case and to the result of the conversion in the second case (or, in either case, to an appropriate base class subobject). In the second case, if the reference is an rvalue reference and the second standard conversion sequence of the user-defined conversion sequence includes an lvalue-to-rvalue conversion, the program is ill-formed.
If T1 is a function type, then
if T2 is the same type as T1, the reference is bound to the initializer expression lvalue;
if T2 is a class type and the initializer expression can be implicitly converted to an lvalue of type T1 (this conversion is selected by enumerating the applicable conversion functions (13.3.1.6 [over.match.ref]) and choosing the best one through overload resolution (13.3 [over.match])), the reference is bound to the function lvalue that is the result of the conversion;
otherwise, the program is ill-formed.
Otherwise, if T2 is a class type and
the initializer expression is an rvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or
T1 is not reference-related to T2 and the initializer expression can be implicitly converted to an rvalue of type “cv3 T3” (this conversion is selected by enumerating the applicable conversion functions (13.3.1.6 [over.match.ref]) and choosing the best one through overload resolution (13.3 [over.match])),
then the reference is bound to the initializer expression rvalue in the first case and to the object that is the result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object).
[Example:
struct A { }; struct B : A { } b; extern B f(); const A& rca = f(); // bound to the A subobject of the B rvalue. A&& rcb rra = f(); // same as above struct X { operator B(); operator int&(); } x; const A& r = x; // bound to the A subobject of the result of the conversion int&& rri = static_cast<int&&>(i); // bound directly to i B&& rrb = x; // bound directly to the result of operator B int&& rri2 = X(); // error: lvalue-to-rvalue conversion applied to result of operator int&—end example]
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]).
Otherwise, a temporary of type “cv1 T1” is created and initialized from the initializer expression using the rules for a non-reference copy-initialization (8.5 [dcl.init]). The reference is then bound to the temporary. If T1 is reference-related to T2, cv1 must shall be the same cv-qualification as, or greater cv-qualification than, cv2; otherwise, the program is ill-formed. If T1 is reference-related to T2 and the reference is an rvalue reference, the initializer expression shall not be an lvalue. [Example:
const double& rcd2 = 2; // rcd2 refers to temporary with value 2.0 double&& rcd3 rrd = 2; // rcd3 rrd refers to temporary with value 2.0 const volatile int cvi = 1; const int& r = cvi; // error: type qualifiers dropped double&& rrd2 = d; // error: copying lvalue of related type double&& rrd3 = i; // rrd3 refers to temporary with value 2.0—end example]
In all cases except the last (i.e., creating and initializing a temporary from the initializer expression), the reference is said to bind directly to the initializer expression.
This resolution also resolves issue 1139.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 49The current wording of 8.5.3 [dcl.init.ref] paragraph 5 does not specify direct binding of an rvalue reference to a scalar xvalue; instead, it requires creation of a temporary and binding the rvalue reference to the temporary.
Proposed resolution (August, 2010):
This issue is resolved by the resolution of 1138.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The examples in 8.5.3 [dcl.init.ref] paragraph 5 are not consistent as to whether earlier code fragments are assumed to be part of the example or not. For instance, the variables i and d are used as initializers without declaration in later fragments, presumably intended to refer to the declarations introduced in the first couple. However, the third fragment declares rca, which is an incompatible redeclaration of a name declared in the first fragment.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The current wording of the WP appears not to allow for list-initialization of a reference like the following:
int i; int& ir{i};
First, 8.5 [dcl.init] paragraph 16 bullet 1 reads,
If the initializer is a braced-init-list, the object is list-initialized (8.5.4).
A reference is not an object, so this does not appear to apply; however, the second bullet sends reference initialization off to 8.5.3 [dcl.init.ref], which does not cover braced-init-lists: paragraph 5 of that section deals only with initilizer expressions, and a braced-init-list is not an expression.
Assuming that the use of “object” in the first bullet is just an oversight, 8.5.4 [dcl.init.list] also does not cover the case of a reference to a scalar type whose initalizer is a braced-init-list with a single element. Bullet 7 of paragraph 3 reads,
Otherwise, if the initializer list has a single element, the object is initialized from that element
and would cover this case except that, again, a reference is not an object. As a result, such an initialization would end up in the last bullet and consequently be ill-formed.
Presumably all that is needed is to add “or reference” to the appropriate bullets of 8.5 [dcl.init] paragraph 16 and 8.5.4 [dcl.init.list] paragraph 3.
Proposed resolution (November, 2010):
Change 8.5 [dcl.init] paragraph 16 bullet 1 as follows:
If the initializer is a braced-init-list, the object or reference is list-initialized (8.5.4 [dcl.init.list]).
Change 8.5.4 [dcl.init.list] paragraph 3 bullet 7 as follows:
Otherwise, if the initializer list has a single element, the object or reference is initialized from that element; if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
By analogy with the variable definition
int arr[3] = {1, 2, 3};
it should be possible to write something like
void f(const int(&)[3]); void g() { f({1, 2, 3}); }
There are currently at least two problems with the latter usage. First, the variable initializer relies on brace elision, which appears to be defined only for variable declarations (8.5.1 [dcl.init.aggr] paragraph 11), and possibly only for certain forms of variable declarations.
Second, the call would require creation of an array temporary to which the parameter reference would be bound, and the current Standard does not describe array temporaries (although rvalue arrays can occur as members of class rvalues). This is also contrary to the direction established by CWG in considering 1058.
[Voted into the WP at the March, 2011 meeting as paper N3259.]
In looking at a large handful of core issues related to elaborated-type-specifiers and the naming of classes in general, I discovered an odd fact. It turns out that there is exactly one place in the grammar where nested-name-specifier is not immediately preceded by "::opt": in class-head, which is used only for class definitions. So technically, this example is ill-formed, and should evoke a syntax error:
struct A; struct ::A { };
However, all of EDG, GCC and Microsoft's compiler accept it without a qualm. In fact, I couldn't get any of them to even warn about it.
Suggested resolution:
It would simplify the grammar, and apparently better reflect existing practice, to factor the global-scope operator into the rule for nested-name-specifier.
Proposed resolution (February, 2011):
The proposed resolution will be submitted as a separate document.
[Voted into the WP at the March, 2011 meeting.]
N3092 comment US 50The class
struct A { const int i; };
was a POD in C++98, but is not a POD under the FCD rules because it does not have a trivial default constructor. C++0x POD was intended to be a superset of C++98 POD.
Suggested resolution: Change POD to be standard layout and trivially copyable.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 1135.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The definition of a POD struct in 9 [class] paragraph 9 is not (but should be) restricted to non-union class types.
Proposed resolution (November, 2010):
Change 9 [class] paragraph 9 as follows:
A POD struct109 is a non-union class that is both a trivial class and a standard-layout class...
[Voted into WP at August, 21010 meeting.]
The grammar for member-declaration in 9.2 [class.mem] does not include a production for the alias-declaration form of typedef declarations, meaning that something like
struct S { using UINT = unsigned int; };
is ill-formed. This seems like an oversight.
Proposed resolution (February, 2010):
In the grammar in 9.2 [class.mem], add the indicated production to the definition of member-declaration:
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 9.2 [class.mem] paragraph 6,
The decl-specifier-seq is omitted in constructor, destructor, and conversion function declarations only.
This is incorrect, as some decl-specifiers (explicit, virtual, inline, constexpr) are permitted in these declarations. Conversely, C.1.6 [diff.dcl], “Banning implicit int,” says,
In C++ a decl-specifier-seq must contain a type-specifier.
This is also incorrect for these declarations.
Proposed resolution (February, 2011):
Change 9.2 [class.mem] paragraph 7 as follows:
The decl-specifier-seq is may be omitted in constructor, destructor, and conversion function declarations only; when declaring another kind of member the decl-specifier-seq shall contain a type-specifier that is not a cv-qualifier. The member-declarator-list can be omitted...
Change C.1.6 [diff.dcl], “Banning implicit int,” as follows:
In C++ a decl-specifier-seq must contain a type-specifier, unless it is followed by a declarator for a constructor, a destructor, or a conversion function. In the following example...
[Voted into the WP at the November, 2010 meeting.]
9.2 [class.mem] paragraph 13 requires that all enumerators of a member enumeration type have names different from that of the containing class. This is only necessary for an unscoped enumeration; scoped enumerators are not in the scope of the containing class.
Proposed resolution (September, 2010):
Change 9.2 [class.mem] paragraph 14 bullet 4 as follows:
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 52The current wording of 9.3 [class.mfct] paragraph 7 allows friend declarations to name member functions “after their class has been defined.” This appears to prohibit a friend declaration in a nested class defined inside its containing class that names a member function of the containing class, because the containing class is still considered to be incomplete at that point.
Proposed resolution (September, 2010):
Change 9.3 [class.mfct] paragraph 7 as follows:
Member Previously-declared member functions may be mentioned in friend declarations after their class has been defined.
[Voted into the WP at the March, 2011 meeting as part of paper N3282.]
The following innocuous-appearing code is currently ill-formed:
struct A { int a; }; struct B { void f() { decltype(A::a) i; // ill-formed } };
The reason is that, according to 9.3.1 [class.mfct.non-static] paragraph 3, the reference to A::a is transformed into (*this).A::a, and there is no A subobject of B. It would seem reasonable to suppress this transformation in unevaluated operands, where a reference to a non-static member is permitted without an object expression.
(See also issue 1005.)
Notes from the November, 2010 meeting:
The CWG agreed that the resolution of issue 515 was ill-advised and should be reversed.[Voted into the WP at the March, 2011 meeting as part of paper N3282.]
Consider the following example:
struct vector { struct iterator { }; struct const_iterator { }; iterator begin(); const_iterator begin() const; }; class block { vector v; auto end() const -> decltype(v.begin()) { return v.begin(); } };
Because the transformation of a member name into a class member access expression (9.3.1 [class.mfct.non-static] paragraph 3) only occurs inside the body of a non-static member function, the type of v in the trailing-return-type is non-const but is const in the return expression, resulting in a type mismatch between the return expression and the return type of the function.
One possibility would be to include the trailing-return-type as being subject to the transformation in 9.3.1 [class.mfct.non-static]. Note, however, that this is currently not in scope at that point (see issue 945).
Notes from the November, 2010 meeting:
The CWG felt that, because this is effectively an implicit parameter, the best approach would be to model its usability on the visibility of parameters: it could be named wherever a parameter of the function is in scope.
Proposed resolution (February, 2011):
Change 5.1.1 [expr.prim.general] paragraph 2 as follows, adding three new paragraphs:
The keyword this names a pointer to the object for which a non-static member function (9.3.2 [class.this]) is invoked or a non-static data member's initializer (9.2 [class.mem]) is evaluated. The keyword this shall be used only inside the body of a non-static member function (9.3 [class.mfct]) of the nearest enclosing class or in a brace-or-equal-initializer for a non-static data member (9.2 [class.mem]). The type of the expression is a pointer to the class of the function or non-static data member, possibly with cv-qualifiers on the class type. The expression is a prvalue.
If a function-definition or member-declarator declares a member function of a class X, the expression this is a prvalue of type “pointer to cv-qualifier-seq X” between the optional cv-qualifier-seq and the end of the function-definition or member-declarator. It shall not appear before the optional cv-qualifier-seq and it shall not appear within the declaration of a static member function (although its type and value category is defined within a static member function as it is within a non-static member function). [Note: the type and value category is defined even for the case of a static member function because declaration matching does not occur until the complete declarator is known, and this may be used in the trailing-return-type of the declarator. —end note]
Otherwise, if a member-declarator declares a non-static data member (9.2 [class.mem]) of a class X, the expression this is a prvalue of type “pointer to X” within the optional brace-or-equal-initializer. It shall not appear elsewhere in the member-declarator.
The expression this shall not appear in any other context.
[Example:...
Change 5.1.1 [expr.prim.general] paragraph 10 as follows:
An id-expression that denotes a non-static data member or non-static member function of a class can only be used:
...
- in the body of beyond the optional cv-qualifier-seq in the member-declarator or function-definition that declares a non-static member function of that class or of a class derived from that class (9.3.1 [class.mfct.non-static]), or
...
Change 9.3.1 [class.mfct.non-static] paragraph 3 as follows:
When an id-expression (5.1 [expr.prim]) that is not part of a class member access syntax (5.2.5 [expr.ref]) and not used to form a pointer to member (5.3.1 [expr.unary.op]) is used in the body declaration of a non-static member function of class X, if name lookup (3.4 [basic.lookup]) resolves the name...
[Voted into the WP at the March, 2011 meeting.]
It is currently not permitted to specify an exception-specification in a defaulted definition. It would be nice to be able to do so (providing the explicit specification matches the one that would be implicitly supplied) for documentation purposes.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 1135.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 9.4.2 [class.static.data] paragraph 3,
If a static data member is of const literal type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. [Note: In both these cases, the member may appear in constant expressions. —end note]
However, 5.19 [expr.const] paragraph 2 bullet 7 allows only integral and enumeration types in constant expressions for the const case; the other types require constexpr to be considered constant expressions.
Proposed resolution (November, 2010):
Change 9.4.2 [class.static.data] paragraph 3 as follows:
If a non-volatile const static data member is of const literal integral or enumeration type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression (5.19 [expr.const]). A static data member of literal type can be declared in the class definition with the constexpr specifier...
[Voted into WP at August, 21010 meeting.]
The type long long is missing from the list of bit-field types in 9.6 [class.bit] paragraph 3 for which the implementation can choose the signedness. This was presumably an oversight. (If that is the case, we may want to reconsider the handling of 4.5 [conv.prom] paragraph 3: a long long bit-field that the implementation treats as unsigned will — pending the outcome of issue 739 — still promote to signed long long, which can lead to unexpected results for bit-fields with the same number of bits as long long.)
Proposed resolution (February, 2010):
Change 9.6 [class.bit] paragraph 3 as follows:
...It is implementation-defined whether a plain (neither explicitly signed nor unsigned) char, short, int or, long, or long long bit-field is signed or unsigned...
[Voted into the WP at the March, 2011 meeting.]
According to 9.8 [class.local] paragraph 1,
Declarations in a local class can use only type names, static variables, extern variables and functions, and enumerators from the enclosing scope.
This would presumably make both of the members of S2 below ill-formed:
void test () { const int local_const = 7; struct S2 { int member:local_const; void f() { int j = local_const; } }; }
Should there be an exception to this rule for constant values? Current implementations seem to accept the reference to local_const in the bit-field declaration but not in the member function definition. Should they be the same or different?
Notes from the September, 2008 meeting:
The CWG agreed that both uses of local_const in the example above should be accepted. The intent of the restriction was to avoid the need to pass a frame pointer into local class member functions, so uses of local const variables as values should be permitted.
Notes from the October, 2009 meeting:
There was interest in an approach that would allow explicitly-captured constants to appear in constant expressions but also to be “used.” Another suggestion was to have variables captured if they appear in either “use” or “non-use” contexts.
Proposed resolution (February, 2011):
Change 5.1.2 [expr.prim.lambda] paragraph 17 as follows:
Every id-expression that is an odr-use (3.2 [basic.def.odr]) of an entity captured by copy is transformed into an access to the corresponding unnamed data member of the closure type. [Note: an id-expression that is not an odr-use refers to the original entity, never to a member of the closure type. Furthermore, such an id-expression does not cause the implicit capture of the entity. —end note] If this is captured, each odr-use of this is transformed into an access to the corresponding unnamed data member of the closure type, cast (5.4 [expr.cast]) to the type of this. [Note: the cast ensures that the transformed expression is a prvalue. —end note] [Example:
void f(const int*); void g() { const int N = 10; [=] { int arr[N]; // OK: not an odr-use, refers to automatic variable f(&N); // OK: causes N to be captured; &N points to the // corresponding member of the closure type } }
—end example]
...Declarations in a local class can use only type names, static variables, extern variables and functions, and enumerators from the shall not odr-use (3.2 [basic.def.odr]) a variable with automatic storage duration from an enclosing scope. [Example:
int x; void f() { static int s ; int x; const int N = 5; extern int g q(); struct local { int g() { return x; } // error: odr-use of automatic variable x has automatic storage duration int h() { return s; } // OK int k() { return ::x; } // OK int l() { return g q(); } // OK int m() { return N; } // OK: not an odr-use int* n() { return &N; } // error: odr-use of automatic variable N }; } local* p = 0; // error: local not in scope—end example]
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The resolution of issue 372 leaves unclear whether the following are well-formed or not:
class C { typedef int I; // private template <int> struct X; template <int> friend struct Y; } template <C::I> struct C::X { }; // C::I accessible to member? template <C::I> struct Y { }; // C::I accessible to friend?
Presumably the answer to both questions is “yes,” but the new wording does not address template-parameters.
Proposed resolution (June, 2008):
Change 11 [class.access] paragraph 6 as follows:
...For purposes of access control, the base-specifiers of a class, the template-parameters of a template-declaration, and the definitions of class members that appear outside of the class definition are considered to be within the scope of that class...
Notes from the September, 2008 meeting:
The proposed resolution preserves the word “scope” as a holdover from the original specification prior to issue 372, which intended to change access determination from a scope-based model to an entity-based model. The resolution should eliminate all references to scope and simply use the entity-based model.
(See also issue 718.)
Proposed resolution (February, 2010):
Change 11 [class.access] paragraphs 6-7 as follows:
All access controls in Clause 11 [class.access] affect the ability to access a class member name from a declaration of a particular scope entity, including references appearing in those parts of the declaration that precede the name of the entity being declared and implicit references to constructors, conversion functions, and destructors involved in the creation and destruction of a static data member. For purposes of access control, the base-specifiers of a class and the definitions of class members that appear outside of the class definition are considered to be within the scope of that class. In particular, access controls apply as usual to member names accessed as part of a function return type, even though it is not possible to determine the access privileges of that use without first parsing the rest of the function declarator. Similarly, access control for implicit calls to the constructors, the conversion functions, or the destructor called to create and destroy a static data member is performed as if these calls appeared in the scope of the member's class. [Example:
class A { typedef int I; // private member I f(); friend I g(I); static I x; template<int> struct X; template<int> friend struct Y; protected: struct B { }; }; A::I A::f() { return 0; } A::I g(A::I p = A::x); A::I g(A::I p) { return 0; } A::I A::x = 0; template<A::I> struct A::X { }; template<A::I> struct Y { }; struct D: A::B, A { };Here, all the uses of A::I are well-formed because A::f and, A::x, and A::X are members of class A and g is a friend and Y are friends of class A. This implies, for example, that access checking on the first use of A::I must be deferred until it is determined that this use of A::I is as the return type of a member of class A. Similarly, the use of A::B as a base-specifier is well-formed because D is derived from A, so checking of base-specifiers must be deferred until the entire base-specifier-list has been seen. —end example]
[Voted into WP at August, 21010 meeting.]
According to 12.1 [class.ctor] paragraph 1, only function-specifiers are permitted in the declaration of a constructor, and constexpr is not a function-specifier. (See also issue 263, in which the resolution of a similar concern regarding the friend specifier did not change 12.1 [class.ctor] paragraph 1 but perhaps should have done so.)
Proposed resolution (February, 2010):
Change 12.1 [class.ctor] paragraph 1 as follows:
Constructors do not have names. A special declarator syntax using an optional sequence of function-specifiers (7.1.2 [dcl.fct.spec]) followed by the constructor's class name followed by a parameter list is used to declare or define the constructor. The syntax uses
an optional decl-specifier-seq in which each decl-specifier is either a function-specifier or constexpr,
the constructor's class name, and
a parameter list
in that order. In such a declaration, optional parentheses around the constructor class name are ignored. [Example:...
[Voted into the WP at the March, 2011 meeting.]
N3092 comment FI 4What effect does defaulting have on triviality? Related to issue 1135, non-public special members defaulted on their first declaration should retain triviality, because they shouldn't be considered user-provided. Related to issue 1137, defaulted member functions that are virtual should not be considered trivial, but there's no reason why non-virtuals could not be.
Furthermore, a class with a non-public explicitly-defaulted constructor isn't ever trivially constructible under the current rules. If such a class is used as a subobject, the constructor of the aggregating class should be trivial if it can access the non-public explicitly defaulted constructor of a subobject.
Suggested resolution: Change the triviality rules so that a class can have a trivial default constructor if the class has access to the default constructors of its subobjects and the default constructors of the subobjects are explicitly defaulted on first declaration, even if said defaulted constructors are non-public.
See also issue 1149.
Rationale (August, 2010):
The consensus of the CWG was that this change should not be made at this point in the standardization process, but that it might be considered at a later date.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 1135.
[Voted into the WP at the March, 2011 meeting.]
Consider the following example:
struct A { A(); ~A() = delete; }; struct B: A { }; B* b = new B;
Under the current rules, B() is not deleted, but is ill-formed because it calls the deleted ~A::A() if it exits via an exception after the completion of the construction of A. A deleted subobject destructor should be added to the list of reasons for implicit deletion in 12.1 [class.ctor] and 12.8 [class.copy].
Notes from the November, 2010 meeting:
The CWG agreed that a change was needed, but only if one or more base and/or member constructors are non-trivial.
Proposed resolution (January, 2011):
Add a new bullet to 12.1 [class.ctor] paragraph 5 as follows:
...A defaulted default constructor for class X is defined as deleted if:
...
X is a non-union class and all members of any anonymous union member are of const-qualified type (or array thereof), or
any direct or virtual base class, or non-static data member with no brace-or-equal-initializer, has class type M (or array thereof) and either M has no default constructor or overload resolution (13.3 [over.match]) as applied to M's default constructor results in an ambiguity or in a function that is deleted or inaccessible from the defaulted default constructor., or
any direct or virtual base class or non-static data member has a type with a destructor that is deleted or inaccessible from the defaulted default constructor.
Add a new bullet to 12.8 [class.copy] paragraph 12 as follows:
...A defaulted copy/move constructor for a class X is defined as deleted (8.4.3 [dcl.fct.def.delete]) if X has:
a variant member with a non-trivial corresponding constructor and X is a union-like class,
a non-static data member of class type M (or array thereof) that cannot be copied/moved because overload resolution (13.3 [over.match]), as applied to M's corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor, or
a direct or virtual base class B that cannot be copied/moved because overload resolution (13.3 [over.match]), as applied to B's corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor, or
any direct or virtual base class or non-static data member of a type with a destructor that is deleted or inaccessible from the defaulted constructor,
for the copy constructor, a non-static data member of rvalue reference type, or
for the move constructor, a non-static data member or direct or virtual base class with a type that does not have a move constructor and is not trivially copyable.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
An implicit declaration of a copy assignment operator is deprecated if the class has a user-declared copy constructor or a user-declared destructor. However, the example in 12.2 [class.temporary] relies on such an implicit declaration; an explicit declaration for the copy assignment operator for class X should be provided:
class X {
public:
X(int);
X(const X&);
~X();
};
class Y {
public:
Y(int);
Y(Y&&);
~Y();
};
X f(X);
Y g(Y);
void h() {
X a(1);
X b = f(X(2));
Y c = g(Y(3));
a = f(a); // relies on implicitly-declared X::operator=(const X&)
}
[Voted into the WP at the November, 2010 meeting.]
The Standard does not define the type of a destructor call. Although that is not of any practical importance, it should do so as a matter of completeness. (5.2.4 [expr.pseudo] paragraph 1 defines the type of a pseudo-destructor call as void.)
Proposed resolution (September, 2010):
Change 5.2.2 [expr.call] paragraph 3 as follows:
The If the postfix-expression designates a destructor (12.4 [class.dtor]), the type of the function call expression is void; otherwise, the type of the function call expression is the return type of the statically chosen function (i.e., ignoring the virtual keyword), even if the type of the function actually called is different. This type shall be a complete object type, a reference type or the type void.
[Voted into the WP at the March, 2011 meeting.]
A defaulted destructor should be implicitly defined as deleted if operator delete is deleted or inaccessible.
Proposed resolution (November, 2010):
Change 12.4 [class.dtor] paragraph 3 as follows:
...A defaulted destructor for a class X is defined as deleted if:
X is a union-like class that has a variant member with a non-trivial destructor,
any of the non-static data members has class type M (or array thereof) and M has a deleted destructor or a destructor that is inaccessible from the defaulted destructor, or
any direct or virtual base class has a deleted destructor or a destructor that is inaccessible from the defaulted destructor.,
or, for a virtual destructor, lookup of the non-array deallocation function results in an ambiguity or in a function that is deleted or inaccessible from the defaulted destructor.
A destructor is trivial if...
[Voted into the WP at the November, 2010 meeting.]
N3092 comment GB 39The note in 12.4 [class.dtor] paragraph 4 says,
An explictly defaulted definition has no implicit exception-specification.
There are similar notes in 12.8 [class.copy] paragraphs 15 and 29.
However, 8.4.2 [dcl.fct.def.default] paragraph 2 bullet 4 says that a special member function that is explicitly defaulted on its first declaration
is implicitly considered to have the same exception-specification as if it had been implicitly declared (15.4 [except.spec])
The notes are incorrect.
Proposed resolution (August, 2010):
Change 12.4 [class.dtor] paragraph 4 as follows:
[Note: an implicitly- declared destructor has an exception-specification (15.4). An explictly defaulted definition has no implicit exception-specification. —end note]
Change 12.8 [class.copy] paragraph 15 as follows:
[Note: an implicitly-declared copy/move constructor has an exception-specification (15.4). An explicitly-defaulted definition (8.4.2) has no implicit exception-specification. —end note]
Change 12.8 [class.copy] paragraph 29 as follows:
[Note: An implicitly-declared copy/move assignment operator has an exception- specification (15.4). An explicitly-defaulted definition has no implicit exception-specification. —end note]
[Voted into the WP at the November, 2010 meeting as paper N3204.]
N3092 comment GB 40A user-declared destructor that does not supply an exception specification should be considered as if declared noexcept(true) rather than noexcept(false).
(Duplicate of issue 1123.)
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The current wording of 12.4 [class.dtor] paragraph 7 says,
After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X's direct non-variant members...
This is incorrect; it is only the non-static members that are destroyed.
[Voted into WP at August, 21010 meeting.]
The changes for delegating constructors overlooked the need to change 12.6.2 [class.base.init] paragraph 3:
The expression-list in a mem-initializer is used to initialize the base class or non-static data member subobject denoted by the mem-initializer-id. The semantics of a mem-initializer are as follows:
if the expression-list of the mem-initializer is omitted, the base class or member subobject is value-initialized (see 8.5 [dcl.init]);
otherwise, the subobject indicated by mem-initializer-id is direct-initialized using expression-list as the initializer (see 8.5 [dcl.init]).
The initialization of each base and member constitutes a full-expression. Any expression in a mem-initializer is evaluated as part of the full-expression that performs the initialization.
This paragraph deals only with subobjects; it needs to be made more general to apply to the complete object as well when the mem-initializer-id designates the constructor's class.
Proposed resolution (June, 2008):
Change 12.6.2 [class.base.init] paragraph 3 as follows:
The expression-list in a mem-initializer is used to initialize the base class or non-static data member subobject denoted by the mem-initializer-id. The semantics of a mem-initializer are A mem-initializer in which the mem-initializer-id names the constructor's class initializes the object by invoking the selected target constructor with the mem-initializer's expression-list. A mem-initializer in which the mem-initializer-id names a base class or non-static data member initializes the designated subobject as follows:
if the expression-list of the mem-initializer is omitted, the base class or member subobject is value-initialized (see 8.5 [dcl.init]);
otherwise, the subobject indicated by mem-initializer-id is direct-initialized using expression-list as the initializer (see 8.5 [dcl.init]).
...
The initialization of each base and member performed by each mem-initializer constitutes a full-expression. Any expression...
Notes from the September, 2008 meeting:
This text was significantly modified by N2756 (nonstatic data member initializers) and needs to be reworked in light of those changes.
Proposed resolution (February, 2010):
Change 12.6.2 [class.base.init] paragraph 7 as follows:
The expression-list or braced-init-list in a mem-initializer is used to initialize the base class or non-static data member subobject denoted by the mem-initializer-id designated subobject (or, in the case of a delegating constructor, the complete class object) according to the initialization rules of 8.5 [dcl.init] for direct-initialization.
[Example: ...
—end example] The initialization of each base and member performed by each mem-initializer constitutes a full-expression...
Change 12.6.2 [class.base.init] paragraph 8 as follows:
If In a non-delegating constructor, if a given non-static data member or base class is not named by a mem-initializer-id...
Change 12.6.2 [class.base.init] paragraph 10 as follows:
Initialization In a non-delegating constructor, initialization proceeds in the following order:
Change 12.6.2 [class.base.init] paragraph 12 as follows (this is an unrelated change correcting an error noticed while preparing the resolution of this issue):
Names in the expression-list or braced-init-list of a mem-initializer are evaluated in the scope of the constructor...
Change the next-to-last bullet of the note in 8.5.4 [dcl.init.list] paragraph 1 as follows:
as a base-or-member initializer in a mem-initializer (12.6.2 [class.base.init])
References to non-static data members inside the body of a non-static member function (which includes the mem-initializers of a constructor definition) are implicitly transformed to member access expressions using (*this) (9.3.1 [class.mfct.non-static] paragraph 3). Although 5.1.1 [expr.prim.general] paragraph 3 permits use of this in a brace-or-equal-initializer for a non-static data member, 12.6.2 [class.base.init] does not give details about the value of this in that context, and there is no parallel to the transformation of member references into class member access expressions. This leaves use of non-static data members in this context underspecified.
Proposed resolution (March, 2011):
This issue is resolved by the resolution of issues 1017 and 1207 in document N3282.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The current wording of 12.6.2 [class.base.init] paragraph 5 says,
A ctor-initializer may initialize the member of an anonymous union that is a member of the constructor's class.
The wording “the member” is strange; furthermore, this should be restricted to non-static data members. That could be accomplished by using the existing term “variant members,” which is defined in 9.5 [class.union] paragraph 8 to be “the non-static data members of all anonymous unions that are members of” the class (which by definition must be non-static data members, since a storage class specifier is not allowed on an anonymous union in class scope).
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 12.7 [class.cdtor] paragraph 4,
Member functions, including virtual functions (10.3 [class.virtual]), can be called during construction or destruction (12.6.2 [class.base.init]). When a virtual function is called directly or indirectly from a constructor (including the mem-initializer or brace-or-equal-initializer for a non-static data member) or from a destructor, and the object to which the call applies is the object under construction or destruction, the function called is the one defined in the constructor or destructor's own class or in one of its bases, but not a function overriding it in a class derived from the constructor or destructor's class, or overriding it in one of the other base classes of the most derived object (1.8 [intro.object]).
This is clear regarding virtual functions called during the initialization of a class's members, but it does not specifically address the polymorphic behavior of the class during the destruction of the members. Presumably the behavior during destruction should be the exact inverse of that of the constructor, i.e., the class's virtual functions should still be called during member destruction.
In addition, the wording
If the virtual function call uses an explicit class member access (5.2.5 [expr.ref]) and the object-expression refers to the object under construction or destruction but its type is neither the constructor or destructor's own class or one of its bases, the result of the call is undefined.
should be clarified that “refers to the object under construction” does not include referring to member subobjects but only to base or more-derived classes of the class under construction or destruction.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment FI 19It is not clear whether the current specification allows a defaulted copy constructor to call an explicit constructor to copy a base or member subobject, and if so, whether that is desirable or not. See also issues 535 and 1118.
Proposed resolution (August, 2010):
This issue is resolved by the resolution of issue 1051.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 62The new wording describing generated copy constructors (12.8 [class.copy] paragraph 16) does not describe the initialization of members with reference type.
See also issue 992.
Proposed resolution (October, 2010):
Change 12.8 [class.copy] paragraph 12 as follows:
An implicitly-declared copy/move constructor is an inline public member of its class. A defaulted copy/move constructor for a class X is defined as deleted (8.4.3 [dcl.fct.def.delete]) if X has:
a variant member with a non-trivial corresponding constructor and X is a union-like class,
a non-static data member of class type M (or array thereof) that cannot be copied/moved because overload resolution (13.3 [over.match]), as applied to M's corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor, or
a direct or virtual base class B that cannot be copied/moved because overload resolution (13.3 [over.match]), as applied to B's corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor, or
for the copy constructor, a non-static data member of rvalue reference type, or
for the move constructor, a non-static data member or direct or virtual base class with a type that does not have a move constructor and is not trivially copyable.
Change 12.8 [class.copy] paragraph 16 as follows:
The implicitly-defined copy/move constructor for a non-union class X performs a memberwise copy/move of its subobjects bases and members. [Note: brace-or-equal-initializers of non-static data members are ignored. See also the example in 12.6.2 [class.base.init]. —end note] The order of copying initialization is the same as the order of initialization of bases and members in a user-defined constructor (see 12.6.2 [class.base.init]). Let x be either the parameter of the constructor or, for the move constructor, an xvalue referring to the parameter. Each subobject base or non-static data member is copied/moved in the manner appropriate to its type:
if the subobject is of class type, the copy constructor for the class is used;
if the subobject member is an array, each element is copied, in the manner appropriate to the element type direct-initialized with the corresponding subobject of x;
if a member m has rvalue reference type T&&, it is direct-initialized with static_cast<T&&>(x.m);
otherwise, the base or member is direct-initialized with the corresponding base or member of x.
if the subobject is of scalar type, the built-in assignment operator is used.
Virtual base class subobjects shall be copied initialized only once by the implicitly-defined copy/move constructor (see 12.6.2 [class.base.init]).
Delete 12.8 [class.copy] paragraph 17:
The implicitly-defined move constructor for a non-union class X performs a memberwise move of its subobjects. [Note: brace-or-equal-initializers of non-static data members are ignored. See also the example in 12.6.2 [class.base.init]. —end note] The order of moving is the same as the order of initialization of bases and members in a user-defined constructor (see 12.6.2 [class.base.init]). Given a parameter named x, each base or non-static data member is moved in the manner appropriate to its type:
a named member m of reference or class type T is direct-initialized with the expression static_cast<T&&>(x.m);
a base class B is direct-initialized with the expression static_cast<B&&>(x);
an array is initialized by moving each element in the manner appropriate to the element type;
a scalar type is initialized with the built-in assignment operator.
Virtual base class subobjects shall be moved only once by the implicitly-defined move constructor (see 12.6.2 [class.base.init]).
Change 12.8 [class.copy] paragraph 18 as follows:
The implicitly-defined copy/move constructor for a union X copies the object representation (3.9 [basic.types]) of X.
Change 12.8 [class.copy] paragraph 28 as follows:
A copy/move assignment operator that is defaulted and not defined as deleted is implicitly defined when is assigned a value of its class type or a value of a class type derived from its class type it is used (3.2 [basic.def.odr]) (e.g., when it is selected by overload resolution to assign to an object of its class type) or when it is explicitly defaulted after its first declaration.
Change 12.8 [class.copy] paragraph 30 as follows:
The implicitly-defined copy/move assignment operator for a non-union class X performs memberwise copy/move assignment of its subobjects. The direct base classes of X are assigned first, in the order of their declaration in the base-specifier-list, and then the immediate non-static data members of X are assigned, in the order in which they were declared in the class definition. Let x be either the parameter of the function or, for the move assignment operator, an xvalue referring to the parameter. Each subobject is assigned in the manner appropriate to its type:
if the subobject is of class type, the copy assignment operator for the class is used as if by a call to operator= with the subobject as the object expression and the corresponding subobject of x as a single function argument (as if by explicit qualification; that is, ignoring any possible virtual overriding functions in more derived classes);
if the subobject is an array, each element is assigned, in the manner appropriate to the element type;
if the subobject is of scalar type, the built-in assignment operator is used.
It is unspecified whether subobjects representing virtual base classes are assigned more than once by the implicitly-defined copy assignment operator. [Example:
struct V { }; struct A : virtual V { }; struct B : virtual V { }; struct C : B, A { };It is unspecified whether the virtual base class subobject V is assigned twice by the implicitly-defined copy assignment operator for C. —end example] [Note: This does not apply to move assignment, as a defaulted move assignment operator is deleted if the class has virtual bases. —end note]
Delete 12.8 [class.copy] paragraph 31:
The implicitly-defined move assignment operator for a non-union class X performs memberwise assignment of its subobjects. The direct base classes of X are assigned first, in the order of their declaration in the base-specifier-list, and then the immediate non-static data members of X are assigned, in the order in which they were declared in the class definition. Given a parameter named x, each subobject is assigned in the manner appropriate to its type:
if the subobject is a named member c of class type C, as if by the expression this->c = static_cast<C&&>(x.c);
if the subobject is a direct base class B, as if by the expression this->B::operator=(static_cast<B&&>(x));
if the subobject is an array, each element is moved, in the manner appropriate to the element type;
if the subobject is of scalar type, the built-in assignment operator is used.
This resolution also resolves issues 1020, 1064 and 1066.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 63The new wording in 12.8 [class.copy] specifies the behavior of an implicitly-defined copy constructor for a non-union class (paragraph 16), an implicitly-defined move constructor for a non-union class (paragraph 17), and an implicitly-defined copy constructor for a union (paragraph 18), but not an implicitly-defined move constructor for a union.
Proposed resolution (August, 2010):
This issue is resolved by the resolution of issue 1051.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 64According to 12.8 [class.copy] paragraph 28,
A copy/move assignment operator that is defaulted and not defined as deleted is implicitly defined when an object of its class type is assigned a value of its class type or a value of a class type derived from its class type or when it is explicitly defaulted after its first declaration.
This sounds as if any assignment to a class object, regardless of whether it is a copy or a move assignment, defines both the copy and move operators. Presumably an assignment should only define the assignment operator chosen by overload resolution for the operation. (Compare the corresponding wording in paragraph 14 for the copy/move constructors: “...implicitly defined if it is used to initialize an object of its class type...”)
Proposed resolution (August, 2010):
This issue is resolved by the resolution of issue 1051.
[Voted into the WP at the March, 2011 meeting.]
12.8 [class.copy] paragraphs 6-7 currently read,
A declaration of a constructor for a class X is ill-formed if its first parameter is of type (optionally cv-qualified) X and either there are no other parameters or else all other parameters have default arguments.
A member function template is never instantiated to perform the copy of a class object to an object of its class type. [Example:
struct S { template<typename T> S(T); template<typename T> S(T&&); S(); }; S f(); const S g; void h() { S a( f() ); // does not instantiate member template; // uses the implicitly generated move constructor S a(g); // does not instantiate the member template; // uses the implicitly generated copy constructor }
These paragraphs were previously a single paragraph, and the second sentence was intended to mean that
template <class T> A(T):
will never be instantiated to produce A(A). It should not have been split and the example should not have been amended to include move construction.
Lawrence Crowl: I suggest something along the lines of
A member function template is never instantiated to match the signature of an ill-formed constructor.
Proposed resolution (November, 2010):
Merge 12.8 [class.copy] paragraphs 6 and 7 and change the text as follows:
A declaration of a constructor for a class X is ill-formed if its first parameter is of type (optionally cv-qualified) X and either there are no other parameters or else all other parameters have default arguments. A member function template is never instantiated to perform the copy of a class object to an object of its class type produce such a constructor signature. [Example:
struct S { template<typename T> S(T); template<typename T> S(T&&); S(); }; S f(); const S g; void h() { S a( f() ); // does not instantiate member template; // uses the implicitly generated move constructor S a(g); // does not instantiate the member template to produce S::S<S>(S); // uses the implicitly generated declared copy constructor }
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
It seems odd to have an implicitly declared copy constructor (and the same for the copy assignment operator) if one of the subobjects does not have one. For example,
struct A { A(); A(A&&); }; struct B: A { }; B b; B b2(b); // error when implicitly defining B(B&), should not be declared
If we don't declare it in that case, we need to decide what happens if one base has only a move constructor and another has only a copy constructor.
Notes from the November, 2010 meeting:
The consensus of the CWG was to change the behavior so that all classes have a declaration of a copy constructor, but that it is defined as deleted in the cases where the declaration is omitted by the current rules.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment DE 11It is unclear whether copy elision is permitted when returning a parameter of class type. If not, it should still be possible to move, rather than copy, the return value.
Suggested resolution: Amend paragraph 34 to explicitly exclude function parameters from copy elision. Amend paragraph 35 to include function parameters as eligible for move-construction.
Proposed resolution (September, 2010):
Change 12.8 [class.copy] paragraph 34 bullets 1 and 2 as follows:
in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function's return value
in a throw-expression, when the operand is the name of a non-volatile automatic object (other than a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one), the copy/move operation from the operand to the exception object (15.1 [except.throw]) can be omitted by constructing the automatic object directly into the exception object
Change 12.8 [class.copy] paragraph 35 as follows:
When the criteria for elision of a copy operation are met, or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution...
[Voted into the WP at the March, 2011 meeting.]
N3092 comment FI 5A class with a non-public explicitly-defaulted copy constructor isn't ever trivially copyable under the current rules. If such a class is used as a subobject, the copy constructor of the aggregating class should be trivial if it can access the non-public explicitly defaulted copy constructor of a subobject.
See also issue 1145.
Rationale (August, 2010):
The consensus of the CWG was that this change should not be made at this point in the standardization process, but that it might be considered at a later date.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 1135.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
12.1 [class.ctor] allows for a defaulted default constructor to be constexpr, but 12.8 [class.copy] does not do the same for a defaulted copy constructor. This seems wrong.
Proposed resolution (November, 2010):
Change 12.8 [class.copy] paragraph 14 as follows:
A copy/move constructor that is defaulted and not defined as deleted is implicitly defined if it is odr-used (3.2 [basic.def.odr]) to initialize an object of its class type from a copy of an object of its class type or of a class type derived from its class type123 or when it is explicitly defaulted after its first declaration. [Note: the copy/move constructor is implicitly defined even if the implementation elided its odr-use (3.2 [basic.def.odr], 12.2 [class.temporary]). —end note] If the implicitly-defined constructor would satisfy the requirements of a constexpr constructor (7.1.5 [dcl.constexpr]), the implicitly-defined constructor is constexpr.
[Voted into the WP at the November, 2010 meeting.]
According to 13 [over] paragraph 1,
Only function declarations can be overloaded; object and type declarations cannot be overloaded.
There are two problems with this statement. First, it does not allow for overloading function templates. (There may be other places in the Standard that refer to “functions” but should include function templates, as well.)
Second, the restriction on “object” declarations should presumably be on “variable” declarations instead, since one can also not overload reference declarations.
Proposed resolution (September, 2010):
Change 13 [over] paragraph 1 as follows:
...Only function and function template declarations can be overloaded; object variable and type declarations cannot be overloaded.
[Voted into the WP at the November, 2010 meeting.]
The resolution of issue 899 needs to be extended to cover move constructors and template constructors as well.
Proposed resolution (August, 2010):
When the type of the initializer expression is a class type “cv S”, the non-explicit conversion functions of S and its base classes are considered. When initializing a temporary to be bound to the first parameter of a copy constructor (12.8 [class.copy]) that takes a reference to possibly cv-qualified T as its first argument, called with a single argument in the context of direct-initialization, explicit conversion functions are also considered. Those that are not hidden within S and yield a type whose cv-unqualified version is the same type as T or is a derived class thereof are candidate functions. Conversion functions that return “reference to X” return lvalues or xvalues, depending on the type of reference, of type X and are therefore considered to yield X for this process of selecting candidate functions.
For example
struct C {
template <class T = int> C(C&, T = 0);
};
struct A {
explicit operator C&() const;
};
int main() {
A a;
C c (a); // should use template constructor
}
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
N3092 comment US 66Overload resolution should first look for a viable list constructor, then look for a non-list constructor if no list constructor is viable.
Proposed resolution (August, 2010):
Change 8.5.4 [dcl.init.list] paragraph 3 bullet 5 as follows:
Otherwise, if T is a class type, constructors are considered. If T has an initializer-list constructor, the argument list consists of the initializer list as a single argument; otherwise, the argument list consists of the elements of the initializer list. The applicable constructors are enumerated (13.3.1.7 [over.match.list]) and the best one is chosen through overload resolution (13.3.1.7 [over.match.list], 13.3 [over.match]). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed. [Example:...
Change 13.3.1.7 [over.match.list] as follows:
When objects of non-aggregate class type T are list-initialized (8.5.4 [dcl.init.list]), overload resolution selects the constructor in two phases as follows, where T is the cv-unqualified class type of the object being initialized:
If T has an initializer-list constructor (8.5.4 [dcl.init.list]), Initially, the candidate functions are the initializer-list constructors (8.5.4 [dcl.init.list]) of the class T and the argument list consists of the initializer list as a single argument.
; otherwise, If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.
For direct-list-initialization, the candidate functions are all the constructors of the class T.
For In copy-list-initialization, the candidate functions are all the constructors of T. However, if an explicit constructor is chosen, the initialization is ill-formed. [Note: This differs from other situations (13.3.1.3 [over.match.ctor], 13.3.1.4 [over.match.copy]), where only converting constructors are considered for copy-initialization. This restriction only applies if this initialization is part of the final result of overload resolution. —end note]
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The changes for issue 990 did not address the description of overload resolution when an argument is an empty braced-init-list. For example:
struct A { A(); A(std::initializer_list<int>); A(std::initializer_list<double>); }; A a{}; // OK void f(A); void g() { f({}); // ambiguous }
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 67To determine whether there is an implicit conversion sequence that converts the argument to the corresponding parameter, 13.3.2 [over.match.viable] paragraph 3 uses 13.3.3.1 [over.best.ics] instead of just saying “there is an ICS if-and-only-if a copy initialization would be well-formed.” Apparently this is intended, but to a casual reader or an implementor reading these rules for the first time for a new implementation, it's not clear why that's desirable. A note should be added to explain the rationale.
Proposed resolution (August, 2010):
Change 13.3.3.1.4 [over.ics.ref] paragraph 3 as follows:
Except for an implicit object parameter, for which see 13.3.1 [over.match.funcs], a standard conversion sequence cannot be formed if it requires binding an lvalue reference to non-const other than a reference to a non-volatile const type to an rvalue or binding an rvalue reference to an lvalue. [Note:...
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The current wording makes some calls involving aggregate initialization ambiguous that should not be. For example, the calls below to f and g should each prefer the second overload:
struct A { int i; }; void f (const A &); void f (A &&); void g (A, double); void g (A, int); int main() { f ( { 1 } ); g ( { 1 }, 1 ); }
Proposed resolution (August, 2010):
Change 13.3.3.2 [over.ics.rank] paragraph 3 bullet 2 as follows:
User-defined conversion sequence U1 is a better conversion sequence than another user-defined conversion sequence U2 if they contain the same user-defined conversion function or constructor or aggregate initialization and if the second standard conversion sequence of U1 is better than the second standard conversion sequence of U2.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
Currently overload resolution does not distinguish between binding an lvalue reference to a function lvalue and an rvalue reference to a function lvalue. The former should be preferred.
In a related point, the current wording of 13.3.3.1.4 [over.ics.ref] paragraph 3 forbids binding an rvalue reference to an lvalue; this should be changed to allow binding an rvalue reference to a function lvalue.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 68Overload resolution within the operand of a unary & operator is done by selecting the function “whose type matches the target type required in the context.” The criterion for determining whether the types match, however, is not defined. At least three possibilities suggest themselves:
The types are identical.
The source type can be implicitly converted to the target type.
The expression would be well-formed if the function under consideration were not overloaded.
This question arises for pointer-to-member types, where there is an implicit conversion from a pointer-to-base-member to a pointer-to-derived-member, as well as when the context is an explicit type conversion (which allows, for static_cast, a conversion from pointer-to-derived-member to a pointer-to-base-member and, in the reinterpret_cast interpretation of functional and old-style casts, essentially any conversion).
Notes from the August, 2010 meeting:
CWG observed that the only case in which the types might not match exactly was for pointers to member functions. In this case, the approach should be to ignore the class of which the functions are members and just match (exactly) on the function type.
Proposed resolution (September, 2010):
Change 13.4 [over.over] paragraph 1 as follows:
...The function selected is the one whose type matches is identical to the function type of the target type required in the context. [Note: That is, the class of which the function is a member is ignored when matching a pointer-to-member-function type. —end note] The target can be...
Non-member functions and static member functions match targets of type “pointer-to-function” or “reference- to-function.” Nonstatic member functions match targets of type “pointer-to-member-function;.” the function type of the pointer to member is used to select the member function from the set of overloaded member functions. If a non-static member function is selected, the reference to the overloaded function name is required to have the form of a pointer to member as described in 5.3.1 [expr.unary.op].
[Voted into the WP at the November, 2010 meeting.]
According to 14 [temp] paragraph 2,
In a function template declaration, the last component of the declarator-id shall be a template-name or operator-function-id (i.e., not a template-id).
This is too restrictive; it should also allow conversion-function-ids and literal-operator-ids.
Proposed resolution (September, 2010):
Change 14 [temp] paragraph 2 as follows:
A template-declaration can appear only as a namespace scope or class scope declaration. In a function template declaration, the last component of the declarator-id shall not be a template-id template-name or operator-function-id (i.e., not a template-id). [Note: in That last component may be an identifier, an operator-function-id, a conversion-function-id, or a literal-operator-id. In a class template declaration, if the class name is a simple-template-id, the declaration declares a class template partial specialization (14.5.5 [temp.class.spec]). —end note]
[Voted into the WP at the March, 2011 meeting.]
The removal of the export keyword inadvertently deleted the text (previously found in 14 [temp] paragraph 8 of the 2003 Standard),
A non-exported template must be defined in every translation unit in which it is implicitly instantiated (14.7.1 [temp.inst]), unless the corresponding specialization is explicitly instantiated (14.7.2 [temp.explicit]) in some translation unit; no diagnostic is required.
This requirement must be reinstated.
Proposed resolution (January, 2011):
Add the following as a new paragraph following 14 [temp] paragraph 5:
A function template, member function of a class template, or static data member of a class template shall be defined in every translation unit in which it is implicitly instantiated (14.7.1 [temp.inst]), unless the corresponding specialization is explicitly instantiated (14.7.2 [temp.explicit]) in some translation unit; no diagnostic is required.
[Voted into WP at August, 21010 meeting.]
14.1 [temp.param] paragraph 11 currently says,
If a template-parameter of a class template is a template parameter pack, it shall be the last template-parameter. [Note: These are not requirements for function templates because template arguments might be deduced (14.8.2 [temp.deduct])...
This restriction was only meant to apply to primary class templates, not partial specializations.
Suggested resolution:
If a template-parameter of a primary class template is a template parameter pack, it shall be the last template-parameter. [Note: These are not requirements for function templates or class template partial specializations because template arguments might be deduced (14.8.2 [temp.deduct])...
Proposed resolution (February, 2010):
Change 14.1 [temp.param] paragraph 11 as follows:
If a template-parameter of a class template has a default template-argument, each subsequent template-parameter shall either have a default template-argument supplied or be a template parameter pack. If a template-parameter of a primary class template is a template parameter pack, it shall be the last template-parameter. [Note: These are not requirements for function templates or class template partial specializations because template arguments might can be deduced (14.8.2 [temp.deduct]). [Example:...
[Voted into the WP at the March, 2011 meeting as part of paper N3270.]
Consider an example like:
template <typename T, T Value> struct bar { }; template <typename... T, T ...Value> void foo(bar<T, Value>);
The current wording in 14.1 [temp.param] is unclear as to whether this is permitted or not. For comparison, 8.3.5 [dcl.fct] paragraph 13 says,
A declarator-id or abstract-declarator containing an ellipsis shall only be used in a parameter-declaration. Such a parameter-declaration is a parameter pack (14.5.3 [temp.variadic]). When it is part of a parameter-declaration-clause, the parameter pack is a function parameter pack (14.5.3 [temp.variadic]). [Note: Otherwise, the parameter-declaration is part of a template-parameter-list and the parameter pack is a template parameter pack; see 14.1 [temp.param]. —end note] A function parameter pack, if present, shall occur at the end of the parameter-declaration-list. The type T of the declarator-id of the function parameter pack shall contain a template parameter pack; each template parameter pack in T is expanded by the function parameter pack.
The requirement here that the type of a function parameter pack must contain a template parameter pack is not repeated for template non-type parameters in 14.1 [temp.param], nor is the statement that it expands the template parameter pack.
A related issue is that neither function nor template parameter packs are listed in 14.5.3 [temp.variadic] paragraph 4 among the contexts in which a pack expansion can appear.
Proposed resolution (November, 2010):
Change 5.3.3 [expr.sizeof] paragraph 5 as follows:
The identifier in a sizeof... expression shall name a parameter pack. The sizeof... operator yields the number of arguments provided for the parameter pack identifier. The parameter pack is expanded (14.5.3 [temp.variadic]) by the sizeof... operator A sizeof... expression is a pack expansion (14.5.3 [temp.variadic]). [Example:...
Change 8.3.5 [dcl.fct] paragraph 13 as follows:
A declarator-id or abstract-declarator containing an ellipsis shall only be used in a parameter-declaration. Such a parameter-declaration is a parameter pack (14.5.3 [temp.variadic]). When it is part of a parameter-declaration-clause, the parameter pack is a function parameter pack (14.5.3 [temp.variadic]). [Note: Otherwise, the parameter-declaration is part of a template-parameter-list and the parameter pack is a template parameter pack; see 14.1 [temp.param]. —end note] The type T of the declarator-id of the function parameter pack shall contain a template parameter pack; each template parameter pack in T is expanded by the function parameter pack A function parameter pack is a pack expansion (14.5.3 [temp.variadic]). [Example:...
Change 14.1 [temp.param] paragraph 15 as follows:
If a template-parameter is a type-parameter with an ellipsis prior to its optional identifier or is a parameter-declaration that declares a parameter pack (8.3.5 [dcl.fct]), then the template-parameter is a template parameter pack (14.5.3 [temp.variadic]). A template parameter pack that is a parameter-declaration whose type contains one or more unexpanded parameter packs is a pack expansion. Similarly, a template parameter pack that is a type-parameter with a template-parameter-list containing one or more unexpanded parameter packs is a pack expansion. [Example:
template <class... Types> class Tuple; // Types is a template type parameter pack and a pack expansion template <class T, int... Dims> struct multi_array; // Dims is a non-type template parameter pack but not a pack expansion template <class T, T... Values> struct static_array; // Values is a non-type template parameter pack and a pack expansion
Change 14.5.3 [temp.variadic] paragraphs 4-6 and add a new paragraph 7 as follows:
A pack expansion is a sequence of tokens that names one or more parameter packs, followed by an ellipsis. The sequence of tokens is called the pattern of the expansion; its syntax consists of a pattern and an ellipsis, the instantiation of which produces zero or more instantiations of the pattern in a list (described below). The form of the pattern depends on the context in which the expansion occurs. Pack expansions can occur in the following contexts:
In a function parameter pack (8.3.5 [dcl.fct]); the pattern is the parameter-declaration without the ellipsis.
In a template parameter pack that is a pack expansion (14.1 [temp.param]):
if the template parameter pack is a parameter-declaration; the pattern is the parameter-declaration without the ellipsis,
if the template parameter pack is a type-parameter with a template-parameter-list; the pattern is the corresponding type-parameter without the ellipsis.
...
In a sizeof... expression (5.3.3 [expr.sizeof]), the pattern is an identifier.
[Example:...
A parameter pack whose name appears within the pattern of a pack expansion is expanded by that pack expansion. An appearance of the name of a parameter pack is only expanded by the innermost enclosing pack expansion. The pattern of a pack expansion shall name one or more parameter packs that are not expanded by a nested pack expansion; such parameter packs are called unexpanded parameter packs in the pattern. All of the parameter packs expanded...
... void g(Args ... args) { // OK: “Args” is expanded by the function parameter pack “args” ...
The instantiation of an a pack expansion that is not a sizeof... expression produces a list...
The instantiation of a sizeof... expression (5.3.3 [expr.sizeof]) produces an integral constant containing the number of elements in the parameter pack it expands.
This resolution also resolves issues 1182 and 1183.
Additional note (February, 2011):
A problematic case is a function like
template<typename... T, T... t> void f(T...) { }
where each element of the nontype pack actually has a different type. This causes problems for template argument deduction, since T and t are supposed to be deduced independently, but they're linked through their sizes. There doesn't appear to be any use case for this kind of example, so it should be ill-formed.
The rule should probably be to consider a non-type template parameter pack that expands any template parameter packs from the same template-parameter-list as ill-formed.
[Voted into the WP at the November, 2010 meeting.]
std::nullptr_t is not currently allowed by 14.1 [temp.param] paragraph 4 to be used as the type of a non-type template parameter. However, this could arise for a template with a non-type template parameter with a dependent type in a template intended for use with pointers, e.g.,
template<typename T, T t> void f(); ... f<std::nullptr_t, nullptr>();
or in a case of delegation.
Proposed resolution (September, 2010):
Change 14.1 [temp.param] paragraph 4 as follows:
A non-type template-parameter shall have one of the following (optionally cv-qualified) types:
integral or enumeration type,
pointer to object or pointer to function,
lvalue reference to object or lvalue reference to function,
pointer to member.,
std::nullptr_t.
[Voted into the WP at the March, 2011 meeting.]
Since there appear to be no restrictions against it, it would appear that default arguments and template parameter packs can be used with template aliases just as with other templates. If that is the case, then, the current wording in 14.1 [temp.param] paragraph 11 requires adjustment:
If a template-parameter of a class template has a default template-argument, each subsequent template-parameter shall either have a default template-argument supplied or be a template parameter pack. If a template-parameter of a class template is a template parameter pack, it shall be the last template-parameter.
Presumably these restrictions should also apply to template aliases, but as written, they only apply to class templates.
Proposed resolution (January, 2011):
Change 14.1 [temp.param] paragraph 11 as follows:
If a template-parameter of a class template or alias template has a default template-argument, each subsequent template-parameter shall either have a default template-argument supplied or be a template parameter pack. If a template-parameter of a primary class template or alias template is a template parameter pack, it shall be the last template-parameter. [Note:...
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 14.1 [temp.param] paragraph 11,
If a template-parameter of a class template has a default template-argument, each subsequent template-parameter shall either have a default template-argument supplied or be a template parameter pack. If a template-parameter of a primary class template is a template parameter pack, it shall be the last template-parameter. [Note: These are not requirements for function templates or class template partial specializations because template arguments can be deduced (14.8.2 [temp.deduct])...
Should the Standard forbid non-final parameter packs in cases where the declaration does not allow the template arguments to be deduced? For example,
template<typename... T, typename... U> void f() { } template<typename... T, typename U> void g() { }
(See also issue 549.)
[Voted into WP at August, 2010 meeting.]
The following is the wording from 14.2 [temp.names] paragraphs 4 and 5 that discusses the use of the "template" keyword following . or -> and in qualified names.
class X { public: template<std::size_t> X* alloc(); template<std::size_t> static X* adjust(); }; template<class T> void f(T* p) { T* p1 = p->alloc<200>(); // ill-formed: < means less than T* p2 = p->template alloc<200>(); // OK: < starts template argument list T::adjust<100>(); // ill-formed: < means less than T::template adjust<100>(); // OK: < starts explicit qualification }—end example]
If a name prefixed by the keyword template is not the name of a member template, the program is ill-formed. [Note: the keyword template may not be applied to non-template members of class templates. ]
The whole point of this feature is to say that the "template" keyword is needed to indicate that a "<" begins a template parameter list in certain contexts. The constraints in paragraph 5 leave open to debate certain cases.First, I think it should be made more clear that the template name must be followed by a template argument list when the "template" keyword is used in these contexts. If we don't make this clear, we would have to add several semantic clarifications instead. For example, if you say "p->template f()", and "f" is an overload set containing both templates and nontemplates: a) is this valid? b) are the nontemplates in the overload set ignored? If the user is forced to write "p->template f<>()" it is clear that this is valid, and it is equally clear that nontemplates in the overload set are ignored. As this feature was added purely to provide syntactic guidance, I think it is important that it otherwise have no semantic implications.
I propose that paragraph 5 be modified to:
(See also issue 30 and document J16/00-0008 = WG21 N1231.)
Notes from 04/00 meeting:
The discussion of this issue revived interest in issues 11 and 109.
Notes from the October 2003 meeting:
We reviewed John Spicer's paper N1528 and agreed with his recommendations therein.
Proposed resolution (February, 2010):
Change 14.2 [temp.names] paragraph 5 as follows:
If a A name prefixed by the keyword template is not the name of a template, shall be a template-id or the name shall refer to a class template the program is ill-formed. [Note: the keyword template may not be applied to non-template members of class templates. —end note] [Note: as is the case with the typename prefix, the template prefix is allowed in cases where it is not strictly necessary; i.e., when the nested-name-specifier or the expression on the left of the -> or . is not dependent on a template-parameter, or the use does not appear in the scope of a template. —end note] [Example:
template <class T> struct A { void f(int); template <class U> void f(U); }; template <class T> void f(T t) { A<T> a; a.template f<>(t); // OK: calls template a.template f(t); // error: not a template-id } template <class T> struct B {template <class T2> struct C {}; }; // OK: T::template C names a class template: template <class T, template <class X> class TT = T::template C> struct D {}; D<B<int>> db;
—end example]
[Voted into WP at August, 2010 meeting.]
Consider this example:
class Foo { public: template< typename T > T *get(); }; template< typename U > U *testFoo( Foo &foo ) { return foo.get< U >(); //#1 }
I am under the impression that this should compile without requiring the insertion of the template keyword before get in the expression at //#1. This notion is supported by this note excerpted from 14.2 [temp.names]/5:
[Note: just as is the case with the typename prefix, the template prefix is allowed in cases where it is not strictly necessary; i.e., when the expression on the left of the -> or ., or the nested-name-specifier is not dependent on a template parameter.]
But 14.2 [temp.names]/4 contains this text:
When the name of a member template specialization appears after . or -> in a postfix-expression, or after nested-name-specifier in a qualified-id, and the postfix-expression or qualified-id explicitly depends on a template-parameter (14.6.2), the member template name must be prefixed by the keyword template. Otherwise the name is assumed to name a non-template.
The only way that I can read this to support my assumption above is if I assume that the phrase postfix-expression is used twice above with different meaning. That is I read the first use as referring to the full expression while the second use refers to the subexpression preceding the operator. Is this the correct determination of intent? I find this text confusing. Would it be an improvement if the second occurrence of "postfix-expression" should be replaced by "the subexpression preceding the operator". Of course that begs the question "where is subexpression actually defined in the standard?"
John Spicer: I agree that the code should work, and that we should tweak the wording.
Proposed resolution (March, 2010):
Change 14.2 [temp.names] paragraph 4 as follows:
When the name of a member template specialization appears after . or -> in a postfix-expression, or after a nested-name-specifier in a qualified-id, and the object or pointer expression of the postfix-expression or the nested-name-specifier in the qualified-id explicitly depends on a template-parameter template parameter (14.6.2 [temp.dep]) but does not refer to a member of the current instantiation (14.6.2.1 [temp.dep.type]), the member template name must be prefixed by the keyword template. Otherwise the name is assumed to name a non-template. [Example:...
[Voted into WP at August, 21010 meeting.]
According to 14.3.2 [temp.arg.nontype] paragraph 1, bullet 3, one of the acceptable forms of a non-type, non-template template argument is:
the address of an object or function... expressed as & id-expression where the & is optional if the name refers to a function or array, or if the corresponding template-parameter is a reference
It is not clear from this whether a template argument like (&i) satisfies the requirement or not.
Notes from the March, 2009 meeting:
The consensus of the CWG was that the parentheses should be allowed.
Proposed resolution (February, 2010):
Change 14.3.2 [temp.arg.nontype] paragraph 1 bullet 3 as follows:
the address of an object or function with external linkage, including function templates and function template-ids but excluding non-static class members, expressed (ignoring parentheses) as & id-expression, except that where the & is optional may be omitted if the name refers to a function or array, or and shall be omitted if the corresponding template-parameter is a reference; or
[Drafting note: The change requiring the omission of the & in the reference case fixes an existing problem that is not related to this issue.]
[Voted into the WP at the November, 2010 meeting.]
The current wording of 14.3.2 [temp.arg.nontype] paragraph 1 does not prevent the use a reference as a non-type template argument. It simply requires
the address of an object or function with external linkage... expressed as & id-expression...
This would presumably (but unintentionally?) allow an example like the following:
struct S { }; template<S*> struct X { }; S s; S& ref = s; X<&ref> xr; // well-formed?
The expression &ref is not a constant expression, but the current wording of 14.3.2 [temp.arg.nontype] does not require a constant expression.
Proposed resolution (September, 2010):
Change 14.3.2 [temp.arg.nontype] paragraph 1 bullet 3 as follows:
a constant expression (5.19 [expr.const]) that designates the address of an object or function with external linkage, including function templates and function template-ids but excluding non-static class members, expressed (ignoring parentheses) as & id-expression, except that the & may be omitted if the name refers to a function or array and shall be omitted if the corresponding template-parameter is a reference; or
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 69The standard permits the address of a thread_local object as a non-type template argument. The addresses of these objects are not constant, however. Such template arguments should require objects of static storage duration.
Proposed resolution (August, 2010):
This issue is resolved by the resolution of issue 1155.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment DE 12Now that local classes can be used as template arguments, it seems odd that there are “external linkage” restrictions on non-type template parameters. The addresses of objects and functions with internal linkage should be permitted as well.
Proposed resolution (August, 2010):
Change 14.3.2 [temp.arg.nontype] paragraph 1 bullet 3 as follows:
the address of an object with static storage duration and external or internal linkage or a function with external or internal linkage, including function templates...
This resolution also resolves issue 1154.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The example in 14.4 [temp.type] paragraph 1 reads in significant part,
template<template<class> class TT> struct X { }; template<class> struct Y { }; template<class T> using Z = Y<T>; X<Y> y; X<Z> z;
and says that y and z have the same type.
This would only be true if alias template Z were considered to be equivalent to class template Y. However, 14.5.7 [temp.alias] describes equivalence only for specializations of alias templates, not for the alias templates themselves. Either such rules should be specified, which could be tricky, or the example should be deleted.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
Presumably an out-of-class definition for an opaque enumeration member of a class template is intended to be allowed; however, the current wording of 14.5.1 [temp.class] provides only for out-of-class definitions of member functions, member classes, static data members, and member templates, not for opaque enumerations.
Proposed resolution (November, 2010):
Change 14 [temp] paragraph 1 as follows:
...The declaration in a template-declaration shall
declare or define a function or class, or
define a member function, a member class, a member enumeration, or a static data member of a class template or of a class nested within a class template, or
...
Change 14.5.1 [temp.class] paragraph 3 as follows:
When a member function, a member class, a member enumeration, a static data member or a member template of a class template is defined outside of the class template definition...
Add a new section following 14.5.1.3 [temp.static]:
14.5.1.4 Enumeration members of class templates [temp.mem.enum]
An enumeration member of a class template may be defined outside the class template definition. [Example:
template<class T> struct A { enum E: T; }; A<int> a; template<class T> enum A<T>::E: T { e1, e2 }; A<int>::E e = A<int>::e1;
—end example]
Change 14.7 [temp.spec] paragraph 2 as follows:
A function instantiated from a function template is called an instantiated function. A class instantiated from a class template is called an instantiated class. A member function, a member class, a member enumeration, or a static data member of a class template instantiated from the member definition of the class template is called, respectively, an instantiated member function, member class, member enumeration, or static data member. A member function...
Change 14.7.1 [temp.inst] paragraph 1 as follows:
...The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions or default arguments, of the class member functions, member classes, scoped member enumerations, static data members and member templates; and it causes the implicit instantiation of the definitions of unscoped member enumerations and member anonymous unions. Unless a member...
Change 14.7.3 [temp.expl.spec] paragraph 1 as follows:
An explicit specialization of any of the following:
function template
class template
member function of a class template
static data member of a class template
member class of a class template
member enumeration of a class template
member class template of a class or class template
member function template of a class or class template
can be declared by a declaration introduced by template<>...
Change 14.7.3 [temp.expl.spec] paragraph 4 as follows:
A member function, a member class, a member enumeration, or a static data member of a class template may be explicitly specialized for a class specialization that is implicitly instantiated...
Add the indicated text to the example in 14.7 [temp.spec] paragraph 6:
template<> void sort<>(Array(<char*>& v); // OK: sort<char*> not yet used template<class T> struct A { enum E: T; enum class S: T; }; template<> enum A<int>::E: int { eint }; // OK template<> enum class A<int>::S: int { sint }; // OK template<class T> enum A<T>::E: T { eT }; template<class T> enum class A<T>::S: T { sT }; template<> enum A<char>::E: int { echar }; // ill-formed, A<char>::E was instantiated when A<char> was instantiated template<> enum class A<char>::S: int { schar }; // OK
Change 14.7.3 [temp.expl.spec] paragraph 7 as follows:
The placement of explicit specialization declarations for function templates, class templates, member functions of class templates, static data members of class templates, member classes of class templates, member enumerations of class templates, member class templates of class templates, member function templates...
Type matching rules aren't well-specified in the current Standard, but it seems reasonable to say that if a declaration uses decltype, its definition must do so as well. For example, the following should be ill-formed:
template<class T, T* u> struct S { decltype(u) foo(T); }; template<class T, T *u> T* S<T, u>::foo(T) { return nullptr; }
Proposed resolution (March, 2011):
This issue is resolved by the resolution of issue 1057 in document N3262.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
It was intended for empty pack expansions to be useful in contexts like base-specifiers, e.g.,
template<class... T> struct A : T... {}; A<> x; // ok?
However, the current wording provides no description of how that might work. (More generally, the problem arises in any context where the pack expansion follows a token that should only be present when the pack expansion is non-empty: following another argument in a function call, etc.)
[Voted into the WP at the March, 2011 meeting as part of paper N3270.]
According to 14.5.3 [temp.variadic] paragraph 4,
A pack expansion is a sequence of tokens that names one or more parameter packs, followed by an ellipsis.
This is contradicted by 5.3.3 [expr.sizeof] paragraph 5, which describes sizeof...(Types) as an expansion, as well as the case where the expansion appears in a declarator like the example given in 8.3.5 [dcl.fct] paragraph 13:
template<typename... T> void f(T (* ...t)(int, int));
This is also described as a pack expansion, although it does not fit the syntactic summary.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 778.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
Should the Standard allow declarations of variadic templates or member functions of class templates where only an empty expansion would be well-formed? For example,
template<typename ... T> struct A { void operator++(int, T... t); }; template<typename ... T> union X: T... { }; template<typename ... T> struct A: T..., T... { };
[Voted into WP at August, 21010 meeting.]
14.5.4 [temp.friend] paragraph 1 bullet 3 says:
if the name of the friend is a qualified-id and a matching specialization of a function template is found in the specified class or namespace, the friend declaration refers to that function template specialization, otherwise,
I'm not sure this says what it's supposed to say. For example:
namespace N { template<class T> int f(T); } class A { friend int N::f(int); int m; A(); }; namespace N { template< class T > int f(T) { A a; // ok for T=int? return a.m; // ok for T=int? } } int m = N::f(42); // ok? char c = N::f('a'); // Clearly ill-formed.
The key is that the wording talks about a “matching specialization,” which to me means that N::f<int> is befriended only if that specialization existed in N before the friend declaration. So it's ill-formed as written, but if we move the call to N::f<int> up to a point before the definition of A, it's well-formed.
That seems surprising, especially given that the first bullet does not require a pre-existing specialization. So I suggest replacing bullet 3 with something like:
if the name of the friend is a qualified-id and a matching function template is found in the specified class or namespace, the friend declaration refers to the deduced specialization of that function template, otherwise,
Proposed resolution (June, 2010):
Change 14.5.4 [temp.friend] paragraph 1 bullet 3 as follows:
...For a friend function declaration that is not a template declaration:
...
if the name of the friend is a qualified-id and a matching specialization of a function template is found in the specified class or namespace, the friend declaration refers to that function template specialization the deduced specialization of that function template (14.8.2.6 [temp.deduct.decl]), otherwise,
...
(This resolution depends on that of issue 873; in particular, the cross-reference to 14.8.2.6 [temp.deduct.decl] refers to a new section added by the resolution of that issue.)
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
Given an example like
template<typename T, typename U> struct Outer { template<typename X, typename Y> struct Inner; template<typename Y> struct Inner<T, Y> {}; template<typename Y> struct Inner<U, Y> {}; }; Outer<int, int> outer; // #1 Outer<int, int>::Inner<int, float> inner; // #2
Is #1 ill-formed because of the identical partial specializations? If not, presumably #2 is ill-formed because of the resulting ambiguity (14.5.5.1 [temp.class.spec.match] paragraph 1).
Notes from the November, 2010 meeting:
The instantiation of Outer<int,int> results in duplicate declarations of the partial specialization, which are ill-formed by 9.2 [class.mem] paragraph 1. No normative change is required, but it might be helpful to add an example like this somewhere.
[Voted into WP at August, 2010 meeting.]
The Standard does not specify how member and nonmember function templates are to be ordered. This question arises with an example like the following:
struct A { template<class T> void operator<<(T&); }; template<class T> struct B { }; template<class T> void operator<<(A&, B<T>&); int main() { A a; B<A> b; a << b; }
The two candidates for “a << b” are:
How should we treat the implicit this parameter of #1 and the explicit first parameter of #2?
Option 0: Make them unordered.
Option 1: If either function is a non-static member function, ignore any this parameter and ignore the first parameter of any non-member function. This option will select #2, as “B<T>&” is more specialized than “T&”.
Option 2: Treat the this parameter as if it were of reference to object type, and then perform comparison to the first parameter of the other function. The other function's first parameter will either be another this parameter, or it will be a by-value or by-reference object parameter. In the example above, this option will also select #2.
The difference between option 1 and option 2 can be seen in the following example:
struct A { }; template<class T> struct B { template<typename R> int operator*(R&); // #1 }; template <typename T> int operator*(T&, A&); // #2 int main() { A a; B<A> b; b * a; }
Should this select #1, select #2, or be ambiguous? Option 1 will select #2, because “A&” is more specialized than “T&”. Option 2 will make this example ambiguous, because “B<A>&” is more specialized than “T&”.
If one were considering two non-member templates,
template <typename T> int operator*(T&, A&); // #2 template <typename T, typename R> int operator*(B<A>&, R&); // #3
the current rules would make these unordered. Option 2 thus seems more consistent with this existing behavior.
Notes from the April, 2006 meeting:
The group favored option 2.
Proposed resolution (February, 2010):
Change 14.5.6.2 [temp.func.order] paragraph 3 as follows:
...and substitute it for each occurrence of that parameter in the function type of the template. If only one of the function templates is a non-static member, that function template is considered to have a new first parameter inserted in its function parameter list. The new parameter is of type “reference to cv A,” where cv are the cv-qualifiers of the function template (if any) and A is the class of which the function template is a member. [Note: This allows a non-static member to be ordered with respect to a nonmember function and for the results to be equivalent to the ordering of two equivalent nonmembers. —end note] [Example:
struct A { }; template<class T> struct B { template<typename R> int operator*(R&); // #1 }; template<typename T, typename R> int operator*(T&, R&); // #2 // The declaration of B::operator* is transformed into the equivalent of // template<typename R> int operator*(B<A>&, R&); // #1a int main() { A a; B<A> b; b * a; // calls #1a }
—end example]
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 7014.8.2.4 [temp.deduct.partial] paragraph 3 specifies that the deduction used in partial ordering in a non-call context is based on the complete function type of the function templates. The wording in 14.5.6.2 [temp.func.order] paragraph 2 (and echoed in paragraph 4) reflects an earlier specification, however, saying that the deduction uses only “the function parameter types, or in the case of a conversion function the return type.” This is a contradiction. The wording in 14.5.6.2 [temp.func.order] should be changed.
Proposed resolution (September, 2010):
Change 14.5.6.2 [temp.func.order] paragraph 2 as follows:
Partial ordering selects which of two function templates is more specialized than the other by transforming each template in turn (see next paragraph) and performing template argument deduction using the function parameter types, or in the case of a conversion function the return type. The deduction process determines whether one of the templates is more specialized than the other. If so, the more specialized template is the one chosen by the partial ordering process.
Change 14.5.6.2 [temp.func.order] paragraph 4 as follows:
Using the transformed function template's function parameter list, or in the case of a conversion function its transformed return type, perform type deduction against the function parameter list (or return type) type of the other function template. The mechanism for performing these deductions is given in 14.8.2.4 [temp.deduct.partial].
The specification for how to handle default arguments and ellipsis in partial ordering of function templates is confusing. 14.5.6.2 [temp.func.order] paragraph 5 currently reads,
The presence of unused ellipsis and default arguments has no effect on the partial ordering of function templates.
It is not clear what “unused” means in this context. According to the original issue resolution that resulted in this wording (N1053, item 6.55), the intent was that “When partial ordering of function templates containing a different number of parameters is done, only the common parameters are considered.” Presumably this would include parameters with default arguments if each function had such parameters in corresponding positions.
The wording needs to be revised to make this intent clear.
Proposed resolution (March, 2011):
This issue is resolved by the resolution of issue 692 in document N3281.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 14.6.2.1 [temp.dep.type] paragraph 1, in a primary class template a name refers to the current instantiation if it is the injected-class-name or the name of the class template followed by the template argument list of the template. Although a template-id referring to a specialization of a template alias is described as “equivalent to” the associated type, a specialization of a template alias is neither of the things that qualifies as naming the current instantiation, so presumably the typename keyword in the following example is required:
template <class T> struct A; template <class T> using B = A<T>; template <class T> struct A { struct C {}; typename B<T>::C bc; // typename needed };
(However, the list in 14.6.2.1 [temp.dep.type] may not be exactly what we want; it doesn't allow use of a typedef denoting the type of the current instantiation, either, but that presumably should be accepted.)
For analogous reasons, it should not be permitted to use a template alias as a nested-name-specifier when defining the members of a class template:
template <class T> struct A { void g(); }; template <class T> using B = A<T>; template <class T> void B<T>::g() {} // error
Notes from the November, 2010 meeting:
The CWG disagreed with the suggested direction, feeling that aliases should work like typedefs and that the examples should be accepted.
Proposed resolution (November, 2010):
Change 14.6.2.1 [temp.dep.type] paragraph 1 as follows:
In the definition of a class template, a nested class of a class template, a member of a class template, or a member of a nested class of a class template, a name refers to the current instantiation if it is
the injected-class-name (Clause
9 [class] ) of the class template or nested class,in the definition of a primary class template, the name of the class template followed by the template argument list of the primary template (as described below) enclosed in <> (or a template alias specialization equivalent to same),
in the definition of a nested class of a class template, the name of the nested class referenced as a member of the current instantiation, or
in the definition of a partial specialization, the name of the class template followed by the template argument list of the partial specialization enclosed in <> (or a template alias specialization equivalent to same). If the nth template parameter is a parameter pack, the nth template argument is a pack expansion (14.5.3 [temp.variadic]) whose pattern is the name of the parameter pack.
Change 14.6.2.1 [temp.dep.type] paragraph 3 as follows:
A template argument that is equivalent to a template parameter (i.e., has the same constant value or the same type as the template parameter) can be used in place of that template parameter in a reference to the current instantiation, except that a decltype-specifier that denotes a dependent type is always considered non-equivalent. In the case of a non-type template argument, the argument must have been given the value of the template parameter and not an expression in which the template parameter appears as a subexpression. [Example:...This resolution also resolves issue 1057.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 73The current wording of 7.1.3 [dcl.typedef] paragraph 2 requires that the identifier in an alias-declaration “...shall not appear in the type-id.” With template aliases, however, the name of the alias can be used indirectly:
template<typename T> struct A; template<typename T> using B=typename A<T>::U; template<typename T> struct A { typedef B<T> U; }; B<short> b;
Here the instantiation of B<short> causes the instantiation of A<short>, and the typedef in A<short> ends up attempting to use B<short>, which is still in the process of being instantiated.
There should be an explicit prohibition of such uses.
Proposed resolution (August, 2010):
Add the following as a new paragraph at the end of 14.5.7 [temp.alias]:
[Note: this wording assumes the change from “template alias” to “alias template” requested by comment FI 11 on FCD N3092.]The type-id in an alias template declaration shall not refer to the alias template being declared. The type produced by an alias template specialization shall not directly or indirectly make use of that specialization. [Example:
template <class T> struct A; template <class T> using B = typename A<T>::U; template <class T> struct A { typedef B<T> U; }; B<short> b; // Error: instantiation of B<short> uses own type via A<short>::U.
—end example]
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 74An alias-declaration allows a class or enumeration type to be defined in its type-id (7.1.6 [dcl.type] paragraph 3). However, it's not clear that this is desirable when the alias-declaration is part of a template alias:
template<typename T> using A = struct { void f(T) { } };
Proposed resolution (August, 2010):
Change 7.1.6 [dcl.type] paragraph 3 as follows:
...A type-specifier-seq shall not define a class or enumeration unless it appears in the type-id of an alias-declaration (7.1.3 [dcl.typedef]) that is not the declaration of a template-declaration.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment GB 44It is not clear from the current wording of 14.6 [temp.res] whether the following is well-formed or not:
template<typename T> struct id { typedef T type; }; template<typename T> void f() { int id<T>::type::*p = 0; } struct A { }; int main() { f<A>(); }
If typename is required before the use of id<T>::type, it is not permitted by the current syntax.
Proposed resolution (August, 2010):
Change 14.6 [temp.res] paragraph 5 as follows:
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. In a nested-name-specifier that immediately contains a nested-name-specifier that depends on a template parameter, the identifier or simple-template-id 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 August, 2010 meeting.]
Is this program valid?
template <typename T> int g(int); class h{}; template <typename T> int l(){h j; return g<T>(j);} template <typename T> int g(const h&); class j{}; int jj(){return l<j>();}
The key issue is when "g" is looked up, i.e., whether both overloaded template "g" functions are available at the call site or only the first. Clearly, the entire postfix-expression "g<T>(j)" is dependent, but when is the set of available template functions determined?
For consistency with the rules about when the set of available overloads is determined when calling a function given by an unqualified-id, I would think that we should postpone determining the set of template functions if (and only if) any of the explicit template arguments are dependent.
John Spicer: I agree that there should be a core issue for this. The definition of "dependent name" (14.6.2 [temp.dep] paragraph 1) should probably be modified to cover this case. It currently only handles cases where the function name is a simple identifier.
Notes from the March 2004 meeting:
A related issue is a call with a qualified name and dependent arguments, e.g., x::y(depa, depb).
Proposed resolution (June, 2010):
Change 14.6.2 [temp.dep] paragraph 1 as follows:
...In an expression of the form:
postfix-expression ( expression-listopt )
where the postfix-expression is an unqualified-id id-expression, the unqualified-id id-expression denotes a dependent name if and only if any of the expressions in the expression-list is a type-dependent expression (14.6.2.2 [temp.dep.expr]) or if the unqualified-id of the id-expression is a template-id in which any of the template arguments depends on a template parameter. If an operand of an operator is a type-dependent expression, the operator also denotes a dependent name. Such names are unbound and are looked up at the point of the template instantiation (14.6.4.1 [temp.point]) in both the context of the template definition and the context of the point of instantiation.
Change 14.6.4.2 [temp.dep.candidate] paragraph 1 as follows:
For a function call that depends on a template parameter, if the function name is an unqualified-id or if the function is called using operator notation, the candidate functions are found using the usual lookup rules (3.4.1 [basic.lookup.unqual], 3.4.2 [basic.lookup.argdep], 3.4.3 [basic.lookup.qual]) except that:
For the part of the lookup using unqualified name lookup (3.4.1 [basic.lookup.unqual]) or qualified name lookup (3.4.3 [basic.lookup.qual]), only function declarations from the template definition context are found.
For the part of the lookup using associated namespaces (3.4.2 [basic.lookup.argdep]), only function declarations found in either the template definition context or the template instantiation context are found.
If the function name is an unqualified-id and the call would be ill-formed or would find a better match had the lookup within the associated namespaces considered all the function declarations with external linkage introduced in those namespaces in all translation units, not just considering those declarations found in the template definition and template instantiation contexts, then the program has undefined behavior.
[Voted into WP at August, 2010 meeting.]
The list of cases in 14.6.1 [temp.local] about when a template parameter is hidden seems to be incomplete.
Consider
// example-1 struct S { int C; template<class> void f(); }; template<class C> void S::f() { C c; // #1 }
Someone asked whether line #1 is well-formed and I responded "no" based on my understanding of the rules in 14.6.1. After a second looking, I've realized that the above case is currently missing from the list.
The list in 14.6.1 covers cases like
// example-2 template<class T> struct S { int C; void f(); }; template<class C> void S<C>::f() { C c; // ERROR: 'C' is 'S::C' not the template parameter }or
// example-3 struct A { int C; } template<class C> struct S : A { C c; // ERROR: 'C' is 'A::C', not the template parameter };But the case of a 'member template' is missing. I believe it should follow the same rule as above. The reason is this.
In the case listed in 14.6.1 (having to do with members of classes), the "algorithm" seems to be this:
I believe that any rule, coherent with 14.6.1/5 and 14.6.1/7, for covering the cases of member templates (example-1) will be described by the above "algorithm".
Am I missing something?
[1] of course, the standard text does not formally speak of "template parameter scope", but we all know that the template parameters "live" somewhere. I'm using that terminology to designate the declarative region of the template parameters.
Mike Miller: I have a somewhat different perspective on this question. I think your example-1 is fundamentally different from your example-2 and example-3. Looking, for instance, at your example-2, I see four nested scopes:
namespace scope template scope (where the parameter is) class S scope S::f() block scope
Naturally, S::C hides the template parameter C. The same is true of your example-3, with three scopes:
namespace scope template scope class S scope (includes 10.2 base class lookup)
Again, it's clear that the C inherited from A hides the template parameter in the containing scope.
The scopes I see in your example-1, however, are different:
namespace scope struct S scope template scope (where the parameter is) S::f() block scope
Here it seems clear to me that the template parameter hides the class member.
It might help to look at the case where the function template is defined inline in the class:
struct S { int C; template<class C> int f() { C c; // #1 } };
It would be pretty strange, I think, if the #1 C were the member and not the template parameter. It would also be odd if the name lookup were different between an inline definition and an out-of-line definition.
See also issue 459.
Notes from the March 2004 meeting:
Basically, the standard is okay. We think Gaby's desired cases like #1 should be ill-formed.
There is a wording problem in 14.6.1 [temp.local] paragraph 7. It says:
In the definition of a member of a class template that appears outside of the class template definition, the name of a member of this template hides the name of a template-parameter.
It should say "hides the name of a template-parameter of the class template (but not a template-parameter of the member, if the member is itself a template)" or words to that effect.
Proposed resolution (February, 2010):
Change 14.6.1 [temp.local] paragraph 8 as follows:
In the definition of a member of a class template that appears outside of the class template definition, the name of a member of this the class template hides the name of a template-parameter of any enclosing class templates (but not a template-parameter of the member, if the member is a class or function template). [Example:
template<class T> struct A { struct B { /* ... */ }; typedef void C; void f(); template<class U> void g(U); }; template<class B> void A<B>::f() { B b; // A's B, not the template parameter } template<class B> template<class C> void A<B>::g(C) { B b; // A's B, not the template parameter C c; // the template parameter C, not A's C }
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
Consider the following example:
template<class T> struct A { template<class U> friend struct A; // Which A? };
Presumably the lookup for A in the friend declaration finds the injected-class-name of the template. However, according to 14.6.1 [temp.local] paragraph 1,
The injected-class-name can be used with or without a template-argument-list. When it is used without a template-argument-list, it is equivalent to the injected-class-name followed by the template-parameters of the class template enclosed in <>. When it is used with a template-argument-list, it refers to the specified class template specialization, which could be the current specialization or another specialization.
If that rule applies, then this example is ill-formed (because you can't have a template-argument-list in a class template declaration that is not a partial specialization).
Mike Miller: The injected-class-name has a dual nature, as described in 14.6.1 [temp.local], acting as either a template name or a class name, depending on the context; a template argument list forces the name to be interpreted as a template. It seems reasonable that in this example the injected-class-name has to be understood as referring to the class template; a template header is at least as strong a contextual indicator as a template argument list. However, the current wording doesn't say that.
(See also issue 1004.)
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 1004.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The injected-class-name of a class template can be used either by itself, in which case it is a type denoting the current instantiation, or followed by a template argument list, in which case it is a template-name. It would be helpful to extend this treatment so that the injected-class-name could be used as an argument for a template template parameter:
template <class T> struct A { }; template <template <class> class TTP> struct B { }; struct C: A<int> { B<A> b; };
(This is accepted by g++.)
James Widman:
It would not be so helpful when used with overloaded function templates, for example:
template <template <class> class TTP> void f(); // #1 template < class T > void f(); // #2 template <class T> struct A { }; struct C: A<int> { void h( ) { f<A>(); // #1? #2? Substitution failure? } };
(See also issue 602.)
Proposed resolution (November, 2010):
Change 14.6.1 [temp.local] paragraphs 1-5 as follows:
Like normal (non-template) classes, class templates have an injected-class-name (Clause 9 [class]). The injected-class-name can be used with or without a template-argument-list as a template-name or a type-name. When it is used without a template-argument-list, it is equivalent to the injected-class-name followed by the template-parameters of the class template enclosed in <>. When it is used with a template-argument-list, as a template-argument for a template template-parameter, or as the final identifier in the elaborated-type-specifier of a friend class template declaration it refers to the specified class template specialization, which could be the current specialization or another specialization. class template itself. Otherwise, it is equivalent to the template-name followed by the template-parameters of the class template enclosed in <>.
Within the scope of a class template specialization or partial specialization, when the injected-class-name is not followed by a < used as a type-name, it is equivalent to the injected-class-name template-name followed by the template-arguments of the class template specialization or partial specialization enclosed in <>. [Example:
template<template<class> class T> class A { }; template<class T> class Y; template<> class Y<int> { Y* p; // meaning Y<int> Y<char>* q; // meaning Y<char> A<Y>* a; // meaning A<::Y> class B { template<class> friend class Y; // meaning ::Y }; };—end example]
The injected-class-name of a class template or class template specialization can be used either with or without a template-argument-list as a template-name or a type-name wherever it is in scope. [Example:
template <class T> struct Base { Base* p; }; template <class T> struct Derived: public Base<T> { typename Derived::Base* p; // meaning Derived::Base<T> }; template<class T, template<class> class U = T::template Base> struct Third { }; Third<Base<int>> t; // OK, default argument uses injected-class-name as a template—end example]
A lookup that finds an injected-class-name (10.2 [class.member.lookup]) can result in an ambiguity in certain cases (for example, if it is found in more than one base class). If all of the injected-class-names that are found refer to specializations of the same class template, and if the name is followed by a template-argument-list used as a template-name, the reference refers to the class template itself and not a specialization thereof, and is not ambiguous. [Example:
template <class T> struct Base { }; template <class T> struct Derived: Base<int>, Base<char> { typename Derived::Base b; // error: ambiguous typename Derived::Base<double> d; // OK };—end example]
When the normal name of the template (i.e., the name from the enclosing scope, not the injected-class-name) is used without a template-argument-list, it always refers to the class template itself and not a specialization of the template. [Example:...
This resolution also resolves issue 602.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
In an example like
void f(int, int, int); template<int ...N> void g() { f((N+N)...); } void h() { g<1, 2, 3>(); }
the call to f needs to be dependent; however, the arguments are not type-dependent, so the criteria of 14.6.2 [temp.dep] paragraph 1 are not met. Presumably the specification needs to be updated so that an argument list containing a type-level pack expansion is dependent.
[Voted into WP at August, 2010 meeting.]
The Standard is currently silent on the dependency status of enumerations and enumerators that are members of class templates. There are three questions that must be answered in this regard:
Are enumeration members of class templates dependent types?
It seems clear that nested enumerations must be dependent. For example:
void f(int); template<typename T> struct S { enum E { e0 }; void g() { f(e0); } }; void f(S<int>::E); void x() { S<int> si; si->g(); // Should call f(S<int>::E) }
Is sizeof applied to a nested enumeration a value-dependent expression (14.6.2.3 [temp.dep.constexpr])?
There are three distinct cases that might have different answers to this question:
template<typename T> struct S { enum E { e0 }; };
Here, the size of E is, in principle, known at the time the template is defined.
template<short I> struct S { enum E { e0 = I }; };
In this case, the minimum size required for E cannot be determined until instantiation, but it is clear that the underlying type need be no larger than short.
template<typename T> struct S { enum E { e0 = T::e0; }; }
Here, nothing can be known about the size of E at the time the template is defined.
14.6.2.3 [temp.dep.constexpr] paragraph 2 says that a sizeof expression is value-dependent if the type of the operand is type-dependent. Unless enumerations are given special treatment, all three of these examples will have value-dependent sizes. This could be surprising for the first case, at least, if not the second as well.
Are nested enumerators value-dependent expressions?
Again the question of dependent initializers comes into play. As an example, consider:
template<short I> struct S { enum E { e0, e1 = I, e2 }; };
There seem to be three possible approaches as to whether the enumerators of E are value-dependent:
The enumerators of a nested enumeration are all value-dependent, regardless of whether they have a value-dependent initializer or not. This is the current position of 14.6.2.3 [temp.dep.constexpr] paragraph 2, which says that an identifier is value-dependent if it is a name declared with a dependent type.
The enumerators of a nested enumeration are all value-dependent if any of the enumeration's enumerators has a value-dependent initializer. In this approach, e0 would be value-dependent, even though it is clear that it has the value 0.
An enumerator of a nested enumeration is value-dependent only if it has a value-dependent initializer (explict or implicit). This approach would make e1 and e2 value-dependent, but not e0.
An example that bears on the third approach is the following:
template<typename T> struct S { enum E { N = UINT_MAX, O = T::O }; int a[N + 2]; };
With the normal treatment of enumerations, the type of a might be either int[UINT_MAX+2] or int[1], depending on whether the value of T::O was such that the underlying type of E is unsigned int or long.
One possibility for addressing this problem under the third
approach would be to treat a given enumerator as having the type of
its initializer in such cases, rather than the enumeration type. This
would be similar to the way enumerators are treated within the
enumerator list, before the enumeration declaration is complete
(
Notes from the April, 2005 meeting:
The CWG agreed on the following positions:
Nested enumerations are dependent types.
The result of the sizeof operator applied to a nested enumeration is value-dependent unless there are no dependent initializers in its definition; the first case above is not dependent, while the second and third are dependent.
The approach described in 3.C above is correct. This is similar to the treatment of static const integral data members, which are dependent only if their initializer is dependent.
Notes from the October, 2005 meeting:
There was no consensus among the CWG regarding question #3 (which enumerators should be considered value-dependent). The argument in favor of 3.C is principally that the values of enumerators with non-dependent initializers are known at definition time, so there is no need to treat them as dependent.
One objection to 3.C is that, according to the consensus of the CWG, the enumeration type is dependent and thus even the known values of the enumeration would have a dependent type, which could affect the results when such enumerations are used in expressions. A possible response to this concern would be to treat non-dependent initializers as having the type of the initializer rather than the enumeration type, similar to the treatment of enumerators within the enumerator-list (7.2 [dcl.enum] paragraph 5). However, this approach would be inconsistent with the treatment of other enumeration types. It would also interfere with overload resolution (e.g., the call in the example under question #1 above would resolve to f(int) with this approach rather than f(S<int>::E)).
Those in favor of option 3.A also suggested that it would be simpler and require less drafting: if all the enumerators have the (dependent) type of the enumeration, 14.6.2.3 [temp.dep.constexpr] paragraph 2 already says that a name with a dependent type is value-dependent, so nothing further would need to be said. Option 3.C would require additional caveats to exempt some enumerators.
The proponents of 3.A also pointed out that there are many other cases where a known value with a dependent type is treated as dependent:
static const T t = 0; ... A<t> ...
or
template <int I> void f() { g(I-I); }
With regard to current practice, g++ and MSVC++ implement 3.A, while EDG implements 3.C.
Notes from the July, 2009 meeting:
The consensus of the CWG was that all the types and values are dependent.
Proposed resolution (June, 2010):
Change 14.6.2.1 [temp.dep.type] paragraph 6 as follows:
A type is dependent if it is
...
a nested class or enumeration that is a member of the current instantiation,
...
In 14.6.2.1 [temp.dep.type] paragraph 5 we have:
A name is a member of an unknown specialization if the name is a qualified-id in which the nested-name-specifier names a dependent type that is not the current instantiation.
So given:
template<class T> struct A { struct B { struct C { A<T>::B::C f(); }; }; };
it appears that the name A<T>::B::C should be taken as a member of an unknown specialization, because the WP refers to “the” current instantiation, implying that there can be at most one at any given time. At the declaration of f(), the current instantiation is C, so A<T>::B is not the current instantiation.
Would it be better to refer to “a known instantiation” instead of “the current instantiation?”
Mike Miller:
I agree that there is a problem here, but I don't think the “current instantiation” terminology needs to be replaced. By way of background, paragraph 1 makes it clear that A<T>::B “refers to” the current instantiation:
In the definition of a class template, a nested class of a class template, a member of a class template, or a member of a nested class of a class template, a name refers to the current instantiation if it is
the injected-class-name (9 [class]) of the class template or nested class,
in the definition of a primary class template, the name of the class template followed by the template argument list of the primary template (as described below) enclosed in <>,
in the definition of a nested class of a class template, the name of the nested class referenced as a member of the current instantiation...
A<T>::B satisfies bullet 3. Paragraph 4 says,
A name is a member of the current instantiation if it is
An unqualified name that, when looked up, refers to a member of a class template. [Note: this can only occur when looking up a name in a scope enclosed by the definition of a class template. —end note]
A qualified-id in which the nested-name-specifier refers to the current instantiation.
So clearly by paragraphs 1 and 4, A<T>::B::C is a member of the current instantiation. The problem is in the phrasing of paragraph 5, which incorrectly requires that the nested-name-specifier “be” the current instantiation rather than simply “referring to” the current instantiation, which would be the correct complement to paragraph 4. Perhaps paragraph 5 could simply be rephrased as, “...a dependent type and it is not a member of the current instantiation.”
(Paragraph 1 may require a bit more wordsmithing to make it truly recursive across multiple levels of nested classes; as it stands, it's not clear whether the name of a nested class of a nested class of a class template is covered or not.)
Additional note (April, 2011):
It appears that these concerns are addressed by the resolution of issue 1043 in document N3283.
Proposed resolution (December, 2011):
This issue is resolved by the resolution of issue 1043.
[Voted into the WP at the March, 2011 meeting as paper N3283.]
14.6.2.1 [temp.dep.type] paragraph 4 treats unqualified-ids and qualified-ids in which the nested-name-specifier refers to the current instantiation as equivalent. However, the lookups associated with these two id-expressions are different in the presence of dependent base classes (14.6.2 [temp.dep] paragraph 3): with an unqualified-id, a dependent base class scope is never examined, while with a qualified-id it is. The current wording does not specify how an example like the following is to be handled:
template<typename T> struct B {}; struct C { typedef int type; }; template<typename T> struct A : B<T>, C { template<typename U> type a(); // #1 template<typename U> typename A<T>::type a(); // #2: different from #1? }; template<typename T> template<typename U> typename A<T>::type A<T>::a() { ... } // defines #1 or #2?
There seem to be two possible strategies for the handling of typename A<T>::type:
It is handled like the unqualified-id case, looking only in non-dependent base classes and not being a dependent type.
Since the current instantiation has dependent base classes, it is handled as a dependent type.
EDG seems to be doing the former, g++ the latter.
Notes from the November, 2010 meeting:
The CWG agreed that if a name is found in a non-dependent base, the type should be treated as non-dependent also.
Additional note (November, 2010):
The overall treatment of dependent base classes in handling a qualified-id in which the nested-name-specifier names the current instantiation or in a member access expression where the object expression is *this is not very clear. It would be helpful if the resolution of this issue could clarify the overall treatment while dealing with the mixed dependent/non-dependent case given in the example.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 14.6.2.1 [temp.dep.type] paragraph 3,
A template argument that is equivalent to a template parameter (i.e., has the same constant value or the same type as the template parameter) can be used in place of that template parameter in a reference to the current instantiation.
This would presumably include something like
template<typename T> struct A { struct B { }; A<decltype(T())>::B b; // no typename };
However, this example is rejected by current implementations. Does this need to be clarified in the existing wording?
Notes from the November, 2010 meeting:
The example is not well-formed; if T is an rvalue reference type, for example, decltype(T()) is not equivalent to T.
Proposed resolution (November, 2010):
This issue is resolved by the resolution of issue 1056.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment GB 43The current rules for determining whether a name refers to the current instantiation, given in 14.6.2.1 [temp.dep.type] paragraph 1, do not cover the case when a template-id matching a primary template or partial specialization appears in the definition of a member of the template.
Proposed resolution (August, 2010):
Change 14.6.2.1 [temp.dep.type] paragraph 1 as follows:
In the definition of a class template, a nested class of a class template, a member of a class template, or a member of a nested class of a class template, a A name refers to the current instantiation if it is
in the definition of a class template, a nested class of a class template, a member of a class template, or a member of a nested class of a class template, the injected-class-name (9 [class]) of the class template or nested class,
in the definition of a primary class template or a member of a primary class template, the name of the class template followed by the template argument list of the primary template (as described below) enclosed in <>,
in the definition of a nested class of a class template, the name of the nested class referenced as a member of the current instantiation, or
in the definition of a partial specialization or a member of a partial specialization, the name of the class template followed by the template argument list of the partial specialization enclosed in <>. If the nth template parameter is a parameter pack, the nth template argument is a pack expansion (14.5.3 [temp.variadic]) whose pattern is the name of the parameter pack.
[Voted into the WP at the November, 2010 meeting.]
The Standard should, but does not currently, say that typeid is value-dependent if its expression or type is type-dependent.
Proposed resolution (September, 2010):
Change 14.6.2.3 [temp.dep.constexpr] paragraph 2 as follows:
...Expressions of the following form are value-dependent if the unary-expression or expression is type-dependent or the type-id is dependent:
sizeof unary-expression
sizeof ( type-id )
typeid ( expression )
typeid ( type-id )
alignof ( type-id )
noexcept ( expression )
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
It is not clear why a noexcept-expression is value-dependent if its operand is value-dependent. It would seem that the value of the expression depends only on the type of the operand, not its value.
Proposed resolution (November, 2010):
Delete “noexcept( expression )” from the list in 14.6.2.3 [temp.dep.constexpr] paragraph 3.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
Given
template <const char *N> struct A { static const char *p; }; template <class T> struct B { static const char c[1]; typedef A<B<T>::c> C; }; template <class T> struct D { static const char c[1]; typedef A<c> C; };
14.6.2.4 [temp.dep.temp] says that B<T>::c is dependent because it is used as a non-integral non-type template argument and it is a qualified-id with a nested-name-specifier that names a dependent type.
There doesn't seem to be anything to say that c in the definition of D<T>::C is dependent, which suggests that D<T>::C is the same type for all T, which is clearly false.
Instead of this special rule in 14.6.2.4 [temp.dep.temp], 14.6.2.3 [temp.dep.constexpr] should say that the address of a member of a dependent type is value-dependent, regardless of whether the address is written with a qualified-id.
Proposed resolution (November, 2010):
Add a new paragraph at the end of 14.6.2.3 [temp.dep.constexpr]:
An id-expression is value-dependent if it names a member of an unknown specialization.
Change 14.6.2.4 [temp.dep.temp] paragraphs 2-3 as follows:
An integral A non-type template-argument is dependent if its type is dependent or the constant expression it specifies is value-dependent.
A non-integral Furthermore, a non-type template-argument is dependent if its type is dependent or it has either of the following forms
qualified-id
& qualified-id
and contains a nested-name-specifier which specifies a class-name that names a dependent type the corresponding non-type template-parameter is of reference or pointer type and the template-argument designates or points to a member of the current instantiation or a member of a dependent type.
[Voted into the WP at the March, 2011 meeting.]
The intent is that it is a permissible implementation technique to do template instantiation at the end of a translation unit rather than at an actual point of instantiation. This idea is not reflected in the current rules, however.
Proposed resolution (January, 2011):
Change 14.6.4.1 [temp.point] paragraph 7 as follows:
A specialization for a function template, a member function template, or of a member function or static data member of a class template may have multiple points of instantiations within a translation unit, and in addition to the points of instantiation described above, for any such specialization that has a point of instantiation within the translation unit, the end of the translation unit is also considered a point of instantiation. A specialization for a class template...
[Voted into WP at August, 2010 meeting.]
Issue 470 specified the explicit instantiation of members of explicitly-instantiated class templates. In restricting the affected members to those “whose definition is visible at the point of instantiation,” however, this resolution introduced an incompatibility between explicitly instantiating a member function or static data member and explicitly instantiating the class template of which it is a member (14.7.2 [temp.explicit] paragraph 3 requires only that the class template definition, not that of the member function or static data member, be visible at the point of the explicit instantiation). It would be better to treat the member instantiations the same, regardless of whether they are directly or indirectly explicitly instantiated.
Notes from the April, 2006 meeting:
In forwarding document J16/06-0057 = WG21 N1987 to be approved by the full Committee, the CWG reaffirmed its position that explicitly instantiating a class template only explicitly instantiates those of its members that have been defined before the point of the explicit instantiation. The effect of the position advocated above would be to require all non-exported member functions to be defined in the translation unit in which the class template is explicitly instantiated (cf paragraph 4), and we did not want to require that. We did agree that the “visible” terminology should be replaced by wording along the lines of “has been defined.”
Proposed resolution (February, 2010):
Change 14.7.2 [temp.explicit] paragraph 8 as follows:
An explicit instantiation definition that names a class template specialization explicitly instantiates the class template specialization and is only an explicit instantiation definition of members whose definition is visible that have been defined at the point of instantiation.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 14.7.2 [temp.explicit] paragraph 4,
For a given set of template parameters, if an explicit instantiation of a template appears after a declaration of an explicit specialization for that template, the explicit instantiation has no effect.
However, that does not appear to negate the requirements of paragraph 3 that a definition of the entity being instantiated must be in scope. Consequently, the following would appear to be ill-formed, even though there is no real need for the definition of C:
template<typename T> class C; template<> class C<int> { }; template class C<int>;
Proposed resolution (November, 2010):
Change 14.7.2 [temp.explicit] paragraphs 3-4 as follows:
A declaration of a function template shall be in scope at the point of the explicit instantiation of the function template. A definition of the class or class template containing a member function template shall be in scope at the point of the explicit instantiation of the member function template. A definition of a class template or class member template shall be in scope at the point of the explicit instantiation of the class template or class member template. A definition of a class template shall be in scope at the point of an explicit instantiation of a member function or a static data member of the class template. A definition of a member class of a class template shall be in scope at the point of an explicit instantiation of the member class. A declaration of a function template, a member function or static data member of a class template, or a member function template of a class or class template shall precede an explicit instantiation of that entity. A definition of a class template, a member class of a class template, or a member class template of a class or class template shall precede an explicit instantiation of that entity, unless the explicit instantiation is preceded by an explicit specialization of the entity with the same template arguments. If the declaration of the explicit instantiation names an implicitly-declared special member function (Clause 12 [special]), the program is ill-formed.
For a given set of template parameters arguments, if an explicit instantiation of a template appears after a declaration of an explicit specialization for that template, the explicit instantiation has no effect. Otherwise...
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The Standard does not fully describe the syntax to be used when a member of an explicitly-specialized member class or member class template is defined in namespace scope. 14.7.3 [temp.expl.spec] paragraph 4 says that the “explicit specialization syntax” (presumably referring to “template<>”) is not used in defining a member of an explicit specialization when a class template is explicitly specialized as a class. However, nothing is said anywhere about how to define a member of a specialization when:
the entity being specialized is a class (member of a template class) rather than a class template.
the result of the specialization is a class template rather than a class (cf 14.7.3 [temp.expl.spec] paragraph 18, which describes this case as a “member template that... remain[s] unspecialized”).
(See paper J16/05-0148 = WG21 N1888 for further details, including a survey of existing implementation practice.)
Notes from the October, 2005 meeting:
The CWG felt that the best approach, balancing consistency with implementation issues and existing practice, would be to require that template<> be used when defining members of all explicit specializations, including those currently covered by 14.7.3 [temp.expl.spec] paragraph 4.
Proposed resolution (February, 2010):
Change 14.7.3 [temp.expl.spec] paragraph 5 as follows:
...The definition of an explicitly specialized class is unrelated to the definition of a generated specialization. That is, its members need not have the same names, types, etc. as the members of a generated specialization. Definitions of members of an explicitly specialized class are defined in the same manner as members of normal classes, and not using the syntax for explicit specialization using the same template<> prefix(es) as the explicitly specialized class. [Example:
template<class T> struct A { void f(T) { /* ... */ } struct B { /* ... */ }; template<class U> struct C { /* ... */ }; }; template<> struct A<int> { void f(int); struct B; template<class U> struct C; }; void h() { A<int> a; a.f(16); // A<int>::f must be defined somewhere } // explicit specialization syntax not used for a member of // explicitly specialized class template specialization // members of explicitly specialized classes are defined using //the same syntax as the explicitly specialized class: template<> void A<int>::f(int) { /* ... */ } template<> struct A<int>::B { /* ... */ }; template<> template<class T> struct A<int>::C { /* ... */ };—end example]
Note (June, 2010):
Because the survey of implementations on which the CWG relied in reaching this resolution is quite old, a new survey of current practice is needed.
[Voted into WP at August, 2010 meeting.]
Given
template <class T> static T f(T t) { ... } template <> int f(int t) { ... }
what is the linkage of f(int)?
Section 14 [temp] paragraph 4 says,
Entities generated from a template with internal linkage are distinct from all entities generated in other translation units.
But is the explicit specialization “generated from” the primary template? Does it inherit the local linkage? If so, where do I find a reference saying so explicitly?
James Widman: Data points: EDG 3.8 inherits, GCC 4.0 does not.
Mike Miller: There's a pretty strong presumption that the linkage of an explicit specialization cannot be different from that of its primary template, given that storage class specifiers cannot appear in an explicit specialization (7.1.1 [dcl.stc] paragraph 1).
Notes from the April, 2007 meeting:
The CWG agreed that the linkage of an explicit specialization must be that of the template. Gabriel Dos Reis will investigate the reason for the different behavior of g++.
Proposed resolution (March, 2010):
Change 14 [temp] paragraph 4 as follows:
...Entities generated from Specializations (explicit or implicit) of a template with that has internal linkage are distinct from all entities generated specializations in other translation units...
[Voted into WP at August, 21010 meeting.]
It does not appear that the following example is well-formed, although most compilers accept it:
template <typename T> T foo(); template <> int foo();
The reason is that 14.7.3 [temp.expl.spec] paragraph 11 only allows trailing template-arguments to be omitted if they “can be deduced from the function argument type,” and there are no function arguments in this example.
14.7.3 [temp.expl.spec] should probably say “function type” instead of “function argument type.” Also, a subsection should probably be added to 14.8.2 [temp.deduct] to cover “Deducing template arguments from declarative contexts” or some such. It would be essentially the same as 14.8.2.2 [temp.deduct.funcaddr] except that the function type from the declaration would be used as the type of P.
Proposed resolution (March, 2008):
Insert the following as a new subsection after 14.8.2.5 [temp.deduct.type]:
14.9.2.6 Deducing template arguments in a declaration that names a specialization of a function template [temp.deduct.funcdecl] Template arguments can be deduced from the function type specified when declaring a specialization of a function template. [Note: this can occur in the context of an explicit specialization, an explicit instantiation, or a friend declaration. —end note] The function template's function type and the declared type are used as the types of P and A, and the deduction is done as described in 14.8.2.5 [temp.deduct.type].
Change 14.7.3 [temp.expl.spec] paragraph 11 as follows:
A trailing template-argument can be left unspecified in the template-id naming an explicit function template specialization provided it can be deduced from the function argument type (14.9.2.6 [temp.deduct.funcdecl])...
Notes from the September, 2008 meeting:
The proposed resolution is probably more than is needed. Instead of a complete new section, the material could become a paragraph in 14.5.6 [temp.fct].
Proposed resolution (February, 2010):
Add the following paragraph at the end of 14.5.6 [temp.fct]:
In a declaration that names a specialization of a function template, template arguments can be deduced from the function type. [Note: this can occur in the context of an explicit specialization, an explicit instantiation, or a friend declaration. —end note] The function template's function type and the declared type are used as the types of P and A and the deduction is done as described in 14.8.2.5 [temp.deduct.type].
Proposed resolution (March, 2010):
This issue is resolved by the resolution of issue 873.
[Voted into WP at August, 21010 meeting.]
According to 14.7.3 [temp.expl.spec] paragraph 1, only non-deleted function templates may be explicitly specialized. There doesn't appear to be a compelling need for this restriction, however, and it could be useful to forbid use of implicitly-instantiated specializations while still allowing use of explicitly-specialized versions.
Proposed resolution (February, 2010):
Change 14.7.3 [temp.expl.spec] paragraph 1 as follows:
An explicit specialization of any of the following:
non-deleted function template
class template
non-deleted member function of a class template
static data member of a class template
member class of a class template
member class template of a class or class template
non-deleted member function template of a class or class template
can be declared...
[Voted into WP at August, 2010 meeting.]
The last two sentences of 14.8.2 [temp.deduct] paragraph 5 read:
When all template arguments have been deduced or obtained from default template arguments, all uses of template parameters in non-deduced contexts are replaced with the corresponding deduced or default argument values. If the substitution results in an invalid type, as described above, type deduction fails.
Shouldn't the substitution occur for all uses of the parameters, so that any of them could result in deduction failure?
Proposed resolution (October, 2006):
Change 14.8.2 [temp.deduct] paragraph 5 as follows:
...When all template arguments have been deduced or obtained from default template arguments, all uses of template parameters in non-deduced contexts the function type are replaced with the corresponding deduced or default argument values. If the substitution results in an invalid type, as described above, type deduction fails.
Notes from the September, 2008 meeting:
This issue was returned to "drafting" status in order to coordinate the wording with the concepts proposal.
Proposed resolution (March, 2010):
Change 14.8.2 [temp.deduct] paragraph 5 as follows:
When all template arguments have been deduced or obtained from default template arguments, all uses of template parameters in non-deduced contexts the template parameter list of the template and the function type are replaced with the corresponding deduced or default argument values. If the substitution results in an invalid type, as described above, type deduction fails.
[Voted into WP at August, 21010 meeting.]
The current rules in 14.8.2 [temp.deduct] say that type deduction fails as a result of attempting to use a type that is not a class type in a qualified name. However, it is now possible to use enumeration names as nested-name-specifiers, so this rule needs to be updated accordingly.
Proposed resolution (February, 2010):
Change the third bullet of the note in 14.8.2 [temp.deduct] paragraph 8 as follows:
[Note: Type deduction may fail for the following reasons:
...
Attempting to use a type that is not a class or enumeration type in a qualified name. [Example:...
...
[Voted into the WP at the March, 2011 meeting.]
According to 14.8.2 [temp.deduct] paragraph 8,
Access checking is not done as part of the substitution process. Consequently, when deduction succeeds, an access error could still result when the function is instantiated.
This mimics the way access checking is done in overload resolution. However, experience has shown that this exemption of access errors from deduction failure significantly complicates the Standard library, so this rule should be changed.
Proposed resolution (January, 2011):
Change 14.8.2 [temp.deduct] paragraph 8 as follows:
If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed if written using the substituted arguments. [Note: Access checking is not done as part of the substitution process. —end note] Consequently, when deduction succeeds, an access error could still result when the function is instantiated. Only invalid types...
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 77The following example is ambiguous:
template<typename T> int f(T&); template<typename T> int f(T&&); int i; int j = f(i);
Because of the special deduction rule for lvalues passed to rvalue-reference parameters, deduction produces f(int&) for both templates, and they are indistinguishable.
Because f(T&) accepts a strict subset of the things that f(T&&) does, it should be considered more specialized by the partial ordering rules.
Proposed resolution (August, 2010):
Change 14.8.2.4 [temp.deduct.partial] paragraph 9 as follows:
If, for a given type, deduction succeeds in both directions (i.e., the types are identical after the transformations above) and both P and A were reference types (before being replaced with the type referred to above):
If the type from the argument template was an lvalue reference and the type from the parameter template was not, the argument type is considered to be more specialized than the other; otherwise,
and if the type from the argument template is more cv-qualified than the type from the parameter template (as described above), the argument that type is considered to be more specialized than the other.; otherwise
If neither type is more cv-qualified than the other then neither type is more specialized than the other.
[Editing note: this change transforms the running text at the end of the paragraph into a bulleted list.]
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
It is not clear whether the following example is well-formed or not:
template<class T> struct identity { typedef T type; }; template<class T, class C> void f(T C::*, typename identity<T>::type*){} struct X { void f() {}; }; int main() { f(&X::f, 0); }
The null pointer conversion required for the second parameter of f is not one of the ones permitted by 14.8.2.1 [temp.deduct.call] paragraph 4, but it's unclear whether that list should apply to parameters with nondeduced types or not. 14.8.1 [temp.arg.explicit] paragraph 6 is explicit that
Implicit conversions (Clause 4 [conv]) will be performed on a function argument to convert it to the type of the corresponding function parameter if the parameter type contains no template-parameters that participate in template argument deduction.
However, this statement appears in a section dealing with explicitly-specified template arguments, so its applicability to nondeduced contexts in general is not clear.
Implementations disagree on the handling of this example.
[Voted into the WP at the March, 2011 meeting as paper N3281.]
14.8.2.5 [temp.deduct.type] paragraph 22 describes how we cope with partial ordering between two function templates that differ because one has a function parameter pack while the other has a normal function parameter. However, this paragraph was meant to apply to template parameter packs as well, e.g., to help with partial ordering of class template partial specializations:
template <class T1, class ...Z> class S; // #1 template <class T1, class ...Z> class S<T1, const Z&...> {}; // #2 template <class T1, class T2> class S<T1, const T2&> {};; // #3 S<int, const int&> s; // both #2 and #3 match; #3 is more specialized
(See also issue 818.)
Proposed resolution (March, 2009):
Change 14.8.2.5 [temp.deduct.type] paragraphs 9-10 as follows (and add the example above to paragraph 9):
If P has a form that contains <T> or <i>, then each argument Pi of the respective template argument list of P is compared with the corresponding argument Ai of the corresponding template argument list of A. If the template argument list of P contains a pack expansion that is not the last template argument, the entire template argument list is a non-deduced context. If Pi is a pack expansion, then the pattern of Pi is compared with each remaining argument in the template argument list of A. Each comparison deduces template arguments for subsequent positions in the template parameter packs expanded by Pi. During partial ordering (14.8.2.4 [temp.deduct.partial]), if Ai was originally a pack expansion and Pi is not a pack expansion, or if P does not contain a template argument corresponding to Ai, argument deduction fails.
Similarly, if P has a form that contains (T), then each parameter type Pi of the respective parameter-type-list of P is compared with the corresponding parameter type Ai of the corresponding parameter-type-list of A. If the parameter-declaration corresponding to Pi is a function parameter pack, then the type of its declarator-id is compared with each remaining parameter type in the parameter-type-list of A. Each comparison deduces template arguments for subsequent positions in the template parameter packs expanded by the function parameter pack. During partial ordering (14.8.2.4 [temp.deduct.partial]), if Ai was originally a function parameter pack and Pi is not a function parameter pack, or if P does not contain a function parameter type corresponding to Ai, argument deduction fails. [Note: A function parameter pack can only occur at the end of a parameter-declaration-list (8.3.5 [dcl.fct]). —end note]
[Voted into WP at August, 21010 meeting.]
14.8.2.1 [temp.deduct.call] paragraph 3 gives the deduction of rvalue references special treatment in the context of a function call:
If P is of the form T&&, where T is a template parameter, and the argument is an lvalue, the type A& is used in place of A for type deduction.
A similar provision is needed, but is not present, in declarative contexts. For example:
template<typename T> void f(T&&); template<> void f(int&) { } // #1 template<> void f(int&&) { } // #2 void g(int i) { f(i); // calls f<int&>(int&), i.e., #1 f(0); // calls f<int>(int&&), i.e., #2 }
There need to be rules that deduce the template arguments for the specializations in the same way that the arguments are deduced in the calls.
Proposed resolution (February, 2010):
Change 14.8.2.5 [temp.deduct.type] paragraph 10 as follows:
Similarly, if P has a form that contains (T), then each parameter type Pi of the respective parameter-type-list of P is compared with the corresponding parameter type Ai of the corresponding parameter-type-list of A. If P and A are function types that originated from deduction when taking the address of a function template (14.8.2.2 [temp.deduct.funcaddr]) or when deducing template arguments from a function declaration ([temp.deduct.decl]) and Pi and Ai are parameters of the top-level parameter-type-list of P and A, respectively, Pi is adjusted if it is an rvalue reference to a cv-unqualified template parameter and Ai is an lvalue reference, in which case the type of Pi is changed to be the template parameter type (i.e., T&& is changed to simply T). [Note: As a result, when Pi is T&& and Ai is X&, the adjusted Pi will be T, causing T to be deduced as X&. —end note] [Example:
template<typename T> void f(T&&); template<> void f(int&) { } // #1 template<> void f(int&&) { } // #2 void g(int i) { f(i); // calls f<int&>(int&), i.e., #1 f(0); // calls f<int>(int&&), i.e., #2 }
—end example]
If the parameter-declaration corresponding to Pi is a function parameter pack...
Add a new section under 14.8.2 [temp.deduct], either before or after 14.8.2.5 [temp.deduct.type], as follows:
14.8.2.x Deducing template arguments from a function declaration [temp.deduct.decl]
In a declaration whose declarator-id refers to a specialization of a function template, template argument deduction is performed to identify the specialization to which the declaration refers. Specifically, this is done for explicit instantiations (14.7.2 [temp.explicit]), explicit specializations (14.7.3 [temp.expl.spec]), and certain friend declarations (14.5.4 [temp.friend]). This is also done to determine whether a function template specialization matches a placement operator new (3.7.4.2 [basic.stc.dynamic.deallocation], 5.3.4 [expr.new]). In all these cases, P is the type of the function template being considered as a potential match and A is the function type from the declaration. The deduction is done as described in 14.8.2.5 [temp.deduct.type].
If, for the set of function templates so considered, there is either no match or more than one match after partial ordering has been considered (14.5.6.2 [temp.func.order]), deduction fails and the declaration is ill-formed.
(Note that the resolution of issue 674 depends on this resolution.)
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
The new wording added by issue 873 says,
...This is also done to determine whether a function template specialization matches a placement operator new (3.7.4.2 [basic.stc.dynamic.deallocation], 5.3.4 [expr.new])... If, for the set of function templates so considered, there is either no match or more than one match after partial ordering has been considered (14.5.6.2 [temp.func.order]), deduction fails and the declaration is ill-formed.
The statement describing the consequence of deduction failure (“the declaration is ill-formed”) does not apply to the case when deduction is being performed for placement operator delete, as there is no declaration involved. It may not be necessary to describe what happens when deduction fails in that case, but at least the wording should be tweaked to limit the conclusion to declarative contexts.
Proposed resolution (November, 2010):
Change 14.8.2.6 [temp.deduct.decl] paragraphs 1-2 as follows:
In a declaration whose declarator-id refers to a specialization of a function template, template argument deduction is performed to identify the specialization to which the declaration refers. Specifically, this is done for explicit instantiations (14.7.2 [temp.explicit]), explicit specializations (14.7.3 [temp.expl.spec]), and certain friend declarations (14.5.4 [temp.friend]). This is also done to determine whether a deallocation function template specialization matches a placement operator new (3.7.4.2 [basic.stc.dynamic.deallocation], 5.3.4 [expr.new]). In all these cases, P is the type of the function template being considered as a potential match and A is either the function type from the declaration or the type of the deallocation function that would match the placement operator new as described in 5.3.4 [expr.new]. The deduction is done as described in 14.8.2.5 [temp.deduct.type].
If, for the set of function templates so considered, there is either no match or more than one match after partial ordering has been considered (14.5.6.2 [temp.func.order]), deduction fails and, in the declaration cases, the declaration program is ill-formed.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment US 78According to 15.2 [except.ctor] paragraph 2,
An object that is partially constructed or partially destroyed will have destructors executed for all of its fully constructed base classes and non-variant members, that is, for subobjects for which the principal constructor (12.6.2 [class.base.init]) has completed execution and the destructor has not yet begun execution.
This wording leaves unclear whether the remaining elements of an array will be destroyed if the destructor for one of the elements exits via an exception: an array element is a subobject (1.8 [intro.object] paragraph 2), but it is not a base class or non-variant member.
Proposed resolution (September, 2010):
Change 15.2 [except.ctor] paragraph 2 as follows:
An object that is partially constructed or partially destroyed of any storage duration whose initialization or destruction is terminated by an exception will have destructors executed for all of its fully constructed base classes and non-variant members subobjects (excluding the variant members of a union-like class), that is, for subobjects for which the principal constructor (12.6.2 [class.base.init]) has completed execution and the destructor has not yet begun execution. Similarly, if the non-delegating constructor...
The current wording of 15.3 [except.handle] paragraph 16 is:
The object declared in an exception-declaration or, if the exception-declaration does not specify a name, a temporary (12.2 [class.temporary]) is copy-initialized (8.5 [dcl.init]) from the exception object. The object shall not have an abstract class type. The object is destroyed when the handler exits, after the destruction of any automatic objects initialized within the handler.
There are two problems with this. First, it's not clear what it means for the handler's “parameter” to be a temporary. This possibility is briefly mentioned in 12.2 [class.temporary], but the lifetime of such a temporary is not defined there; the discussion of lifetime is restricted to those temporaries that arise during the evaluation of an expression, and this is not such a case.
Second, this wording assumes that there will be an object to be destroyed and thus ignores the possibility that the exception-declaration declares a reference.
Proposed resolution (March, 2011):
This issue is resolved by the resolution of issue 1166 in paper N3262.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
N3092 comment GB 45According to 15.3 [except.handle] paragraph 16,
The object declared in an exception-declaration or, if the exception-declaration does not specify a name, a temporary (12.2 [class.temporary]) is copy-initialized (8.5 [dcl.init]) from the exception object. The object shall not have an abstract class type. The object is destroyed when the handler exits, after the destruction of any automatic objects initialized within the handler.
This wording leaves unspecified how an exception-declaration that is a reference should be treated. For example, presumably a reference to an abstract class type should be permitted, but that is not specified. The treatment of ellipsis is also not clearly addressed.
[Voted into the WP at the March, 2011 meeting.]
N3092 comment CA 515.3 [except.handle] paragraph 8 defines the “currently handled exception” as
The exception with the most recently activated handler that is still active
This definition ignores the possibility that an exception might be thrown and caught in another thread during the execution of a handler. Since throw; rethrows the “currently handled exception,” one might conclude that it would be the other thread's exception that would be rethrown instead of the one that activated that handler.
Proposed resolution (January, 2011):
Change 15 [except] paragraph 1 as follows:
Exception handling provides a way of transferring control and information from a point in the execution of a program thread to an exception handler associated with a point previously passed by the execution...
Change 15.1 [except.throw] paragraph 4 as follows:
...The implementation may then deallocate the memory for the exception object; any such deallocation is done in an unspecified way. [Note: An exception thrown by a throw-expression does not propagate to other threads unless caught, stored, and rethrown using appropriate library functions; see 18.8.5 [propagation] and 30.6 [futures]. —end note]
Change 15.3 [except.handle] paragraph 6 as follows:
[Voted into the WP at the March, 2011 meeting.]
It is not clear how to handle compatible dynamic-exception-specifications and noexcept-specifications. For example, given
void f() throw(); void f() noexcept { throw 1; }
should we call terminate() or unexpected()? And for
void g() throw (int); void g() noexcept (false) { throw 1.0; }
should this call unexpected or propagate the exception? Does the order of the declarations (and which is the definition) matter?
Alisdair Meredith:
And what about something like
struct A { ~A() throw() { } }; struct B { ~B() noexcept { } }; struct C: A, B { };
What is the exception specification for C's destructor?
Proposed resolution (November, 2010):
Change 15.4 [except.spec] paragraph 3 as follows:
Two exception-specifications are compatible if:
both are non-throwing (see below), regardless of their form,
both have the form noexcept(constant-expression) and the constant-expressions are equivalent, or
one exception-specification is a noexcept-specification allowing all exceptions and the other is of the form throw(type-id-list), or
both are dynamic-exception-specifications that have the same set of adjusted types.
Add the following note to the end of 15.4 [except.spec] paragraph 9:
Whenever an exception is thrown and the search...
—end example]
[Note: A function can have multiple declarations with different non-throwing exception-specifications; for this purpose, the one on the function definition is used. —end note]
[Voted into the WP at the March, 2011 meeting.]
N3092 comment GB 46It is not entirely clear that a function-try-block on a destructor will catch exceptions from a base or member destructor; whether such exceptions might be swallowed with a simple return statement rather than being rethrown; and whether such a clause might be entered multiple times if multiple bases/members throw, or if that is an automatic terminate call.
Proposed resolution (August, 2010):
Change 15 [except] paragraph 4 as follows:
...An exception thrown during the execution of the initializer expressions in the ctor-initializer or during the execution of the compound-statement, or — in the case of a destructor — during the destruction of a subobject, transfers control to a handler in a function-try-block in the same way as an exception thrown during the execution of a try-block transfers control to other handlers. [Example:...
Additional note (October, 2010):
There is a related problem with this wording: it covers only “the execution of the initializer expressions in the ctor-initializer,” when it should also cover execution of base and member constructors, regardless of whether they have initializer expressions in the ctor-initializer or not.
The issue has been moved back to "review" status to allow consideration of amending the proposed resolution to something like
...during the execution of the compound-statement or, if the function is a constructor or destructor, during the initialization or destruction of the class's subobjects, transfers control...
Proposed resolution (November, 2010):
Change 15 [except] paragraph 4 as follows:
A function-try-block associates a handler-seq with the ctor-initializer, if present, and the compound-statement. An exception thrown during the execution of the initializer expressions in the ctor-initializer or during the execution of the compound-statement or, for constructors and destructors, during the initialization or destruction, respectively, of the class's subobjects, transfers control to a handler in a function-try-block in the same way as an exception thrown during the execution of a try-block transfers control to other handlers.
[Voted into the WP at the March, 2011 meeting as part of paper N3262.]
According to 15.4 [except.spec] paragraph2 8 and 9,
A function is said to allow an exception of type E if its dynamic-exception-specification contains a type T for which a handler of type T would be a match (15.3 [except.handle]) for an exception of type E.
Whenever an exception is thrown and the search for a handler (15.3 [except.handle]) encounters the outermost block of a function with an exception-specification that does not allow the exception, then,
if the exception-specification is a dynamic-exception-specification, the function std::unexpected() is called (15.5.2 [except.unexpected]),
otherwise, the function std::terminate() is called (15.5.1 [except.terminate]).
This does not define what it means for a noexcept-specification to allow an exception.
Proposed resolution (November, 2010):
Change 15.4 [except.spec] paragraph 8 as follows:
A function is said to allow an exception of type E if the constant-expression in its noexcept-specification evaluates to false or its dynamic-exception-specification contains a type T for which a handler of type T would be a match (15.3 [except.handle]) for an exception of type E.
[Voted into the WP at the November, 2010 meeting.]
N3092 comment GB 47The list of reasons for which std::terminate is called needs to be extended to cover several additional cases in C++0x:
when function std::nested_exception::rethrow_nested is called for an object that stores a null exception pointer.
when execution of a function registered with std::at_quick_exit exits using an exception.
when the destructor or a copy constructor of class std::thread is called for the object that is joinable.
Proposed resolution (August, 2010):
Change 15.5.1 [except.terminate] paragraph 1 as follows:
In the following some situations exception handling must be abandoned for less subtle error handling techniques:. [Note: These situations are:
when the exception handling mechanism, after completing evaluation of the expression to be thrown but before the exception is caught (15.1 [except.throw]), calls a function that exits via an uncaught exception, [Footnote: For example, if the object being thrown is of a class with a copy constructor, std::terminate() will be called if that copy constructor exits with an exception during a throw. —end footnote] or
when the exception handling mechanism cannot find a handler for a thrown exception (15.3 [except.handle]), or
when the search for a handler (15.3 [except.handle]) encounters the outermost block of a function with a noexcept-specification that does not allow the exception (15.4 [except.spec]), or
when the destruction of an object during stack unwinding (15.2) terminates by throwing an exception, or
when initialization of a non-local variable with static or thread storage duration (3.6.2 [basic.start.init], 3.6.3 [basic.start.term]) terminates by throwing exits via an exception, or
when destruction of an object with static or thread storage duration exits using via an exception (3.6.3 [basic.start.term]), or
when execution of a function registered with std::atexit or std::at_quick_exit exits using via an exception (18.5 [support.start.term]), or
when a throw-expression with no operand attempts to rethrow an exception and no exception is being handled (15.1 [except.throw]), or
when std::unexpected throws an exception which is not allowed by the previously violated dynamic-exception-specification, and std::bad_exception is not included in that dynamic-exception-specification (15.5.2 [except.unexpected]), or
when the implementation's default unexpected exception handler is called (D.11.1 [unexpected.handler])., or
when the function std::nested_exception::rethrow_nested is called for an object that has captured no exception (18.8.6 [except.nested]), or
when execution of the initial function of a thread exits via an exception (30.3.1.2 [thread.thread.constr]), or
when the destructor or the copy assignment operator is invoked on a std::thread object that refers to a joinable thread (30.3.1.3 [thread.thread.destr], 30.3.1.4 [thread.thread.assign]).
—end note]
Insert the following as a new paragraph following 15.1 [except.throw] paragraph 6:
An exception is considered caught...
If the exception handling mechanism, after completing evaluation of the expression to be thrown but before the exception is caught, calls a function that exits via an exception, std::terminate is called (15.5.1 [except.terminate]). [Example:
struct C { C() { } C(const C&) { throw 0; } }; int main() { try { throw C(); // calls std::terminate() } catch(C) { } }
—end example]
Change 15.2 [except.ctor] paragraph 3 as follows:
The process of calling destructors for automatic objects constructed on the path from a try block to a throw-expression is called “stack unwinding.” [Note: If a destructor called during stack unwinding exits with an exception, std::terminate is called (15.5.1 [except.terminate]). [Note: So destructors should generally catch exceptions and not let them propagate out of the destructor. —end note]
Change 3.6.2 [basic.start.init] paragraph 6 as follows:
[Note: If the initialization of a non-local variable with static or thread storage duration terminates by throwing exits via an exception, std::terminate is called (see 15.5.1 [except.terminate]). —end note]
Change 3.6.3 [basic.start.term] paragraph 1 as follows:
...[Note: If the destruction of a non-local an object with static or thread storage duration terminates by throwing exits via an exception, std::terminate is called (see 15.5.1 [except.terminate]). —end note]
Change 18.5 [support.start.term] paragraph 8 bullet 1 as follows:
First, objects with thread storage duration...
If control leaves a registered function called by exit because the function does not provide a handler for a thrown exception, terminate() shall be called (15.5.1 [except.terminate]).
[Voted into the WP at the November, 2010 meeting.]
The current wording of 15.5.1 [except.terminate] paragraph 2 makes it sound as if stack unwinding in the case of a noexcept violation is an all-or-nothing proposition. It would be useful to be able to partially unwind the stack, in particular, not to call destructors for the function containing the noexcept-specification.
Proposed resolution (August, 2010):
Change 15.5.1 [except.terminate] paragraph 2 as follows:
...In the situation where the search for a handler (15.3 [except.handle]) encounters the outermost block of a function with a noexcept-specification that does not allow the exception (15.4 [except.spec]), it is implementation-defined whether the stack is unwound, unwound partially, or not unwound at all before std::terminate() is called. In all other situations, the stack shall not be unwound...
[Voted into WP at August, 2010 meeting.]
See also issue 37.
Given this piece of code and S having a user-defined ctor, at precisely which point must std::uncaught_exception() return true and where false?
try { S s0; throw s0; } catch (S s2) { }
My understanding of the semantics of the code is as follows:
Is my understanding correct?
15.1 [except.throw] paragraph 3 talks about “the exception object” when describing the semantics of the throw-expression:
a throw-expression initializes a temporary object, called the exception object...
However, 15.5.1 [except.terminate] paragraph 1 talks about “the expression to be thrown” when enumerating the conditions under which terminate() is called:
when the exception handling mechanism, after completing evaluation of the expression to be thrown but before the exception is caught (15.1 [except.throw]), calls a user function that exits via an uncaught exception...
And, 15.5.3 [except.uncaught] paragraph 1 refers to “the object to be thrown” in the description of uncaught_exception():
The function std::uncaught_exception() returns true after completing evaluation of the object to be thrown...
Are all these objects one and the same? I believe the answer is important in case the construction of the temporary exception object throws another exception.
Suppose they are the same. Then uncaught_exception() invoked from the copy ctor for s1 (from the example [above]) must return false and a new exception (e.g., bad_alloc) may be thrown and caught by a matching handler (i.e., without calling terminate()).
But if they are not the same, then uncaught_exception() invoked from the copy ctor for s1 must return true and throwing another exception would end up calling terminate(). This would, IMO, have pretty severe consequences on writing exception safe exception classes.
As in the first case, different compilers behave differently, with most compilers not calling terminate() when the ctor for the temporary exception object throws. Unfortunately, the two compilers that I trust the most do call terminate().
FWIW, my feeling is that it should be possible for the copy ctor invoked to initialize the temporary exception object to safely exit by throwing another exception, and that the new exception should be allowed to be caught without calling terminate.
Mike Miller: The way I see this, a throw-expression has an assignment-expression as an operand. This expression is “the expression to be thrown.” Evaluation of this expression yields an object; this object is “the object to be thrown.” This object is then copied to the exception object.
Martin Sebor: Here's a survey of the return value from uncaught_exception() in the various stages of exception handling, implemented by current compilers:
expr | temp | unwind | handlr | 2nd ex | |
---|---|---|---|---|---|
HP aCC 6 | 0 | 0 | 1 | 0 | OK |
Compaq C++ 6.5 | 0 | 0 | 1 | 1 | ABRT |
EDG eccp 3.4 | 0 | 1 | 1 | 1 | ABRT |
g++ 3.4.2 | 0 | 0 | 1 | 0 | OK |
Intel C++ 7.0 | 0 | 0 | 1 | 0 | OK |
MIPSpro 7.4.1 | 0 | 0 | 1 | 1 | ABRT |
MSVC 7.0 | 0 | 0 | 1 | 0 | OK |
SunPro 5.5 | 1 | 1 | 1 | 0 | OK |
VisualAge 6.0 | 0 | 1 | 1 | 1 | OK |
In the table above:
expr | is the evaluation of the assignment-expression in the throw-expression |
temp | is the invocation of the copy ctor for the unnamed temporary exception object created by the runtime. |
unwind | is stack unwinding. |
handlr | is the invocation of the copy ctor in the exception-declaration in the catch handler. |
2nd ex | describes the behavior of the implementation when the invocation of the copy ctor for the unnamed temporary exception object [temp] throws another exception. |
Proposed resolution (October, 2004):
Change 15.1 [except.throw] paragraph 3 as follows:
A throw-expression initializes a temporary object, called the exception object, the by copying the thrown object (i.e., the result of evaluating its assignment-expression operand) to it. The type of which the exception object is determined by removing any top-level cv-qualifiers from the static type of the operand of throw and adjusting the type from “array of T” or “function returning T” to “pointer to T” or “pointer to function returning T,” respectively. [Note: the temporary object created for by a throw-expression that whose operand is a string literal is never of type char* or wchar_t*; that is, the special conversions for string literals from the types “array of const char” and “array of const wchar_t” to the types “pointer to char” and “pointer to wchar_t,” respectively (4.2 [conv.array]), are never applied to the operand of a throw-expression. —end note] The temporary is an lvalue and is used to initialize the variable named in the matching handler (15.3 [except.handle]). The type of the operand of a throw-expression shall not be an incomplete type, or a pointer to an incomplete type other than (possibly cv-qualified) void. [...]
Change the note in 15.3 [except.handle] paragraph 3 as follows:
[Note: a throw-expression operand that which is an integral constant expression of integer type that evaluates to zero does not match a handler of pointer type; that is, the null pointer constant conversions (4.10 [conv.ptr], 4.11 [conv.mem]) do not apply. —end note]
Change 15.5.1 [except.terminate] paragraph 1 bullet 1 as follows:
when the exception handling mechanism, after completing evaluation of the expression to be thrown operand of throw but before the exception is caught (15.1 [except.throw]), calls a user function that exits via an uncaught exception,
Change 15.5.3 [except.uncaught] paragraph 1 as follows:
The function std::uncaught_exception() returns true after completing evaluation of the object to be thrown operand of throw until completing the initialization of the exception-declaration in the matching handler (18.8.4 [uncaught]).
Change 18.8.4 [uncaught] paragraph 1 by adding the indicated words:
Returns: true after completing evaluation of the operand of a throw-expression until either completing initialization of the exception-declaration in the matching handler or entering unexpected() due to the throw; or after entering terminate() for any reason other than an explicit call to terminate(). [Note: This includes stack unwinding (15.2 [except.ctor]). —end note]
Notes from the April, 2005 meeting:
The CWG discussed this resolution both within the group and with other interested parties. Among the points that were made:
Martin Sebor pointed to a posting in which he argues that writing copy constructors is more difficult if an exception during the copy to the exception object will result in a call to std::terminate().
In response to a question about why the copy to the exception object is different from the copy from the exception object to the object in the exception-declaration, it was observed that the writer of the handler can avoid the second copy (by using a reference declaration), but the first copy is unavoidable.
John Spicer observed that not exiting via exception should be a design constraint for copy constructors in exception objects, regardless of whether std::terminate() is called or not.
Adopting the position that uncaught_exception() returns false during the copy to the exception object would reduce the differences between the case where that copy is elided and the case where it is performed.
Jason Merrill observed that making uncaught_exception() return false during the copy to the exception object would simplify the code generated by g++; as it currently stands, the compiler must generate code to catch exceptions during that copy so std::terminate() can be called.
Bjarne Stroustrup worried that allowing the copy constructor to throw an exception during the copy to the exception object could result in a serious and specific exception being silently transformed into a more trivial and generic one (although the CWG later noted that this risk already exists if something in the expression being thrown throws an exception before the expression completes).
The CWG felt that more input from a wider audience was necessary before a decision could be made on the appropriate resolution.
Notes from the April, 2006 meeting:
The CWG agreed with the position that std::uncaught_exception() should return false during the copy to the exception object and that std::terminate() should not be called if that constructor exits with an exception. The issue was returned to “drafting” status for rewording to reflect this position.
Additional notes (September, 2007):
Although this issue deals primarily with when std::uncaught_exception() begins to return true, the specification of when it begins to return false is also problematic. There are two parallel sections that define the meaning of std::uncaught_exception() and each has a different problem. 15.5.3 [except.uncaught] reads,
The function std::uncaught_exception() returns true after completing evaluation of the object to be thrown until completing the initialization of the exception-declaration in the matching handler (18.8.4 [uncaught]).
The problem here is that whether an exception is considered caught (the underlying condition tested by the function) is here presented in terms of having initialized the exception-declaration, while in other places it is specified by having an active handler for the exception, e.g., 15.1 [except.throw] paragraph 6:
An exception is considered caught when a handler for that exception becomes active (15.3 [except.handle]).
This distinction is important because of 15.3 [except.handle] paragraph 3:
A handler is considered active when initialization is complete for the formal parameter (if any) of the catch clause. [Note: the stack will have been unwound at that point. —end note] Also, an implicit handler is considered active when std::terminate() or std::unexpected() is entered due to a throw.
Note that there is no exception-declaration to be initialized for the std::terminate() and std::unexpected() cases; nevertheless, according to 18.8.4 [uncaught], std::uncaught_exception() is supposed to return false when one of those two functions is entered.
The specification in 18.8.4 [uncaught] is not well phrased, however, and is open to misinterpretation. It reads,
Returns: true after completing evaluation of a throw-expression until either completing initialization of the exception-declaration in the matching handler or entering unexpected() due to the throw; or after entering terminate() for any reason other than an explicit call to terminate().
The problem here is lack of parallelism: does “after entering terminate” refer to the condition for returning true or false? This would be better phrased along the lines of
Returns: true after completing evaluation of a throw-expression until a handler for the exception becomes active (15.3 [except.handle]).
Proposed resolution (March, 2010):
Change 15.5.1 [except.terminate] paragraph 1 bullet 1 as follows:
In the following situations exception handling must be abandoned for less subtle error handling techniques:
when the exception handling mechanism, after completing evaluation of the expression to be thrown the initialization of the exception object but before the exception is caught activation of a handler for the exception (15.1 [except.throw]), calls a function that exits via an uncaught exception, [Footnote: For example, if the object being thrown is of a class with a copy constructor, std::terminate() will be called if that copy constructor exits with an exception during a throw the initialization of the formal parameter of a catch clause. —end footnote]
...
Change 15.5.3 [except.uncaught] paragraph 1 as follows:
The function std::uncaught_exception() returns true after completing evaluation of the object to be thrown the initialization of the exception object (15.1 [except.throw]) until completing the initialization of the exception-declaration in the matching handler activation of a handler for the exception (15.3 [except.handle], 18.8.4 [uncaught])...
Change 18.8.4 [uncaught] paragraph 1 as follows:
Returns: true after completing evaluation of a throw-expression initializing an exception object 15.1 [except.throw] until either completing initialization of the exception-declaration in the matching handler or entering unexpected() due to the throw; or after entering terminate() for any reason other than an explicit call to terminate() a handler for the exception (including unexpected() or terminate()) is activated (15.3 [except.handle]). [Note: This includes stack unwinding (15.2 [except.ctor]). —end note]
[Voted into the WP at the November, 2010 meeting.]
N3092 comment DE 13The recommendations of document N2693 included a feature macro to enable a program to determine whether the implementation enforces strict pointer safety or not, but this macro is not specified in 16.8 [cpp.predefined].
Proposed resolution (August, 2010):
Add the following to the end of 16.8 [cpp.predefined] paragraph 2: