Date: | 2025-04-13 |
Project: | Programming Language C++ |
Reference: | ISO/IEC 14882:2024 |
Reply to: | Jens Maurer |
jens.maurer@gmx.net |
This document contains the C++ core language issues for which the Committee (J16 + WG21) has decided that no action is required, that is, issues with status "NAD" ("Not A Defect"), "dup" (duplicate), "concepts," and "extension."
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 seethe Active Issues List.
Section references in this document reflect the section numbering of documentWG21 N5008.
The intent appears to be that the following example is well-formed,even thoughD::f(int) hidesB2::f():
struct B1 { void f(); }; struct B2 { void f(); }; struct[[base_check]] D: B1, B2 { using B1::f; void f(int); };
However, this is not reflected in the current wording.
Rationale (November, 2010):
The consensus of the CWG was that theusing-declarationdoes, indeed, hideB2::f() and thusD should beill-formed.
_N3225_.D.2 [depr.static] says that declaringnamespace-scopeobjects asstatic is deprecated.Declaring namespace-scopefunctions asstatic shouldalso be deprecated.
Proposed resolution (10/99): In both9.9.2.2 [namespace.unnamed] paragraph 2and_N3225_.D.2 [depr.static] paragraph 1, replace
when declaring objects in a namespace scopewith
when declaring entities in a namespace scopeIn addition, there are a number of locations in the Standard where useof or reference tostatic should be reconsidered. Theseinclude:
Rationale (04/00):
This issue, along withissue 174, hasbeen subsumed byissue 223. Until thecommittee determines the meaning of deprecation, it does not makesense either to extend or reduce the number of features to which it isapplied.
The decision to deprecate global static should be reversed.
Rationale (04/00):
This issue, along withissue 167, hasbeen subsumed byissue 223. Until thecommittee determines the meaning of deprecation, it does not makesense either to extend or reduce the number of features to which it isapplied.
Inheriting constructors should not be part of C++0x unless theyhave implementation experience.
Rationale (March, 2011):
The full Committee voted not to remove this feature.
[Addressed with a different approach by paper P0846R0, adopted at the November, 2017 meeting.]
Consider the following:
namespace N { struct A { }; template<typename T> T func(const A&) { return T(); } } void f() { N::A a; func<int>(a); // error }
Although argument-dependent lookup would allowN::functo be found in this call, the< is taken as aless-than operator rather than as the beginning of a templateargument list. If the use of thetemplate keyword forsyntactic disambiguation were permitted forunqualified-ids,this problem could be solved by prefixing the function name withtemplate, allowing thetemplate-id to be parsedand argument-dependent lookup to be performed.
Rationale (July, 2009):
This suggestion would need a full proposal and discussion bythe EWG before the CWG could consider it.
Bullet 13.3 of _N4567_.5.1.1 [expr.prim.general] permits onlynon-static data members to appear without an object expressionin an unevaluated operand. There does not appear to be agood reason to exclude non-static member functions from thispermission.
Rationale (October, 2015):
Without knowing the type ofthis, overloadresolution cannot be performed, and it seems not worth thetrouble to allow member functions only in the case where thereis no overloading.
When a function throws an exception that is not in itsexception-specification,std::unexpected() is called.According to _N4606_.15.5.2 [except.unexpected] paragraph 2,
If [std::unexpected()] throws or rethrows an exception thattheexception-specification does not allow then the followinghappens: If theexception-specification does not include the classstd::bad_exception (17.9.4 [bad.exception]) thenthe functionstd::terminate() is called, otherwise the thrownexception is replaced by an implementation-defined object of the typestd::bad_exception, and the search for another handler willcontinue at the call of the functionwhoseexception-specification was violated.
The “replaced by” wording is imprecise and undefined.For example, does this mean that the destructor is called for theexisting exception object, or is it simply abandoned? Is thereplacementin situ, so that a pointer to the existingexception object will now point to thestd::bad_exceptionobject?
Mike Miller: The call tostd::unexpected() isnot described as analogous to invoking a handler, but if it were,that would resolve this question; it is clearly specified whathappens to the previous exception object when a new exception isthrown from a handler (14.2 [except.throw] paragraph 4).
This approach would also clarify other questions that have beenraised regarding the requirements for stack unwinding. Forexample, 14.6.2 [except.terminate] paragraph 2 says that
In the situation where no matching handler is found, it isimplementation-defined whether or not the stack is unwound beforestd::terminate() is called.
This requirement could be viewed as in conflict with the statementin _N4606_.15.5.2 [except.unexpected] paragraph 1 that
If a function with anexception-specification throws an exception thatis not listed in theexception-specification, the functionstd::unexpected() is called (_N4606_.D.6 [exception.unexpected])immediately after completing the stack unwinding for the formerfunction.
If it is implementation-defined whether stack unwinding occurs beforecallingstd::terminate() andstd::unexpected() iscalled only after doing stack unwinding, does that mean that it isimplementation-defined whetherstd::unexpected() is calledif there is ultimately no handler found?
Again, if invokingstd::unexpected() were viewed asessentially invoking a handler, the answer to this would be clear,because unwinding occurs before invoking a handler.
Rationale (February, 2017):
The issue is moot after the adoption of document P0003.
With the changes forissue 2256,extending destruction to apply to objects of scalar type, shouldinvoking a pseudo-destructor end the lifetime of that object?
Rationale (February, 2019):
This question is resolved by paper P0593R4.
Paragraph 7 of_N4868_.6.5.6 [basic.lookup.classref] says,
If theid-expression is aconversion-function-id, itsconversion-type-id shall denote the sametype in both the context in which theentirepostfix-expression occursand in the context of the class of the objectexpression (or the class pointed to by the pointer expression).Does this mean that the following example is ill-formed?
struct A { operator int(); } a; void foo() { typedef int T; a.operator T(); // 1) error T is not found in the context // of the class of the object expression? }The second bullet in paragraph 1 of6.5.5.2 [class.qual] says,
aconversion-type-id of anoperator-function-id is looked up bothin the scope of the class and in thecontext in which the entirepostfix-expressionoccurs and shall refer to thesame type in both contextsHow about:
struct A { typedef int T; operator T(); }; struct B : A { operator T(); } b; void foo() { b.A::operator T(); // 2) error T is not found in the context // of the postfix-expression? }Is this interpretation correct? Or was the intent forthis to be an error only ifT was found in both scopes and referred to different entities?
If the intent was for these to be errors,how do these rules apply to templatearguments?
template <class T1> struct A { operator T1(); } template <class T2> struct B : A<T2> { operator T2(); void foo() {T2 a = A<T2>::operator T2(); // 3) error? when instantiated T2 is not // found in the scope of the classT2 b = ((A<T2>*)this)->operator T2(); // 4) error when instantiated? } }
(Note bullets 2 and 3 in paragraph 1 of6.5.5.2 [class.qual] refer topostfix-expression. It would be better to usequalified-id in both cases.)
Erwin Unruh:The intent was that you look in both contexts. If you find it only once,that's the symbol. If you find it in both, both symbols must be "the same"in some respect. (If you don't find it, its an error).
Mike Miller:What's not clear to me in these examples is whether what isbeing looked up isT orint.Clearly theT has to belooked up somehow, but the "name" of a conversion functionclearly involves the base (non-typedefed) type, not typedefsthat might be used in a definition or reference (cf6.1 [basic.pre] paragraph 7and11.4.8 [class.conv] paragraph 5).(This is true even for types that must be writtenusing typedefs because of the limited syntax inconversion-type-ids — e.g., the "name" of the conversionfunction in the following example
typedef void (*pf)(); struct S {operator pf(); };isS::operator void(*)(), even though you can't write its namedirectly.)
My guess is that this means that in each scope you look upthe type named in the reference and form the canonicaloperator name; if the name used in the reference isn't foundin one or the other scope, the canonical name constructedfrom the other scope is used. These names must be identical,and theconversion-type-id in the canonical operator name mustnot denote different types in the two scopes (i.e., the typemight not be found in one or the other scope, but if it's foundin both, they must be the same type).
I think this is all very vague in the current wording.
Rationale (February, 2021):
This issue was resolved by the resolution ofissue 1111.
A change was introduced into the language that made names first declaredin friend declarations "invisible" to normal lookups until such time thatthe identifier was declared using a non-friend declaration. This is describedin _N4868_.9.8.2.3 [namespace.memdef] paragraph 3and11.8.4 [class.friend] paragraph 9(and perhaps other places).
The standard gives examples of how this all works with friend declarations,but there are some cases with nonfriend elaborated type specifiers forwhich there are no examples, and which might yield surprising results.
The problem is that an elaborated type specifier is sometimes a declarationand sometimes a reference. The meaning of the following code changes dependingon whether or not friend class names are injected (visibly) into the enclosingnamespace scope.
struct A; struct B; namespace N { class X { friend struct A; friend struct B; }; struct A *p; // N::A with friend injection, ::A without struct B; // always N::B }Is this the desired behavior, or shouldall elaborated type specifiers (and not just those of the form"class-key identifier;") have the effect of findingpreviously declared "invisible"names and making them visible?
Mike Miller: That's not how I would categorize the effect of"struct B;". That declaration introduces the name"B" into namespaceNin exactly the same fashion as if the friend declaration did not exist.The preceding friend declaration simply stated that, if a classN::B wereever defined, it would have friendly access to the members ofN::X. Inother words, the lookups in both "struct A*..." and"struct B;" ignorethe friend declarations.
(The standard is schizophrenic on the issue of whether such friend declarationsintroduce names into the enclosing namespace. 6.4 [basic.scope] paragraph 4says,
John Spicer: The previous declaration of B is not completelyignored though, because certainly changing "friend struct B;" to "friendunion B;" would result in an error when B was later redeclared as a struct,wouldn't it?
Bill Gibbons: Right. I think the intent was to model this afterthe existing rule for local declarations of functions (which dates backto C), where the declaration is introduced into the enclosing scope butthe name is not. Getting this right requires being somewhat more rigorousabout things like the ODR because there may be declaration clashes evenwhen there are no name clashes. I suspect that the standard gets this rightin most places but I would expect there to be a few that are still wrong,in addition to the one Mike pointed out.
Mike Miller: Regardingwould result in an error whenBwas later redeclared
I don't see any reason why it should. The restriction that the class-keymust agree is found in 9.2.9.5 [dcl.type.elab]and is predicated on having found a matchingdeclaration in a lookup according to 6.5.6 [basic.lookup.elab].Since a lookup of a name declaredonly (up to that point) in a friend declaration does not find that name(regardless of whether you subscribe to the "does-not-introduce" or "introduces-invisibly"school of thought), there can't possibly be a mismatch.
I don'tthink that the Standard's necessarily broken here. There is no requirementthat a class declared in a friend declaration ever be defined. Explicitlyputting an incompatible declaration into the namespace where that friendclass would have been defined is, to me, just making it impossible to define— which is no problem, since it didn't have to be defined anyway. Theonly error would occur if the same-named but unbefriended class attemptedto use the nonexisting grant of friendship, which would result in an accessviolation.
(BTW, I couldn'tfind anything in the Standard that forbidsdefining a class with a mismatchedclass-key, only using one in anelaborated-type-specifier. Is this a holethat needs to be filled?)
John Spicer: This is what 9.2.9.5 [dcl.type.elab] paragraph 3says:
class B; union B {};and
union B {}; class B;are both invalid. I think this paragraph isintended to say that. I'mnot so sure it actually does say that, though.
Mike Miller: RegardingI think the intent was to model thisafter the existing rule for local declarations of functions (which datesback to C)
Actually, that's not the C (1989) rule. To quote the Rationale fromX3.159-1989:
RegardingGetting this right requires being somewhat more rigorous
Yes, I think if this is to be made illegal, it would have to be donewith the ODR; the name-lookup-based current rules clearly (IMHO) don'tapply. (Although to be fair, the [non-normative] note in 6.4 [basic.scope] paragraph 4sounds asif it expects friend invisible injection to trigger the multiple-declarationprovisions of that paragraph; it's just that there's no normative textimplementing that expectation.)
Bill Gibbons: Nor does the ODR currently disallow:
translation unit #1 struct A; translation unit #2 union A;since it only refers to class definitions, not declarations.
But the obvious form of the missing rule (all declarations of a classwithin a program must have compatible struct/class/union keys) would alsoanswer the original question.
The declarations need not be visible. For example:
translation unit #1 int f() { return 0; } translation unit #2: void g() { extern long f(); }is ill-formed even though the second "f" is not a visible declaration.
Rationale (10/99): The main issue (differing behavior ofstandalone and embeddedelaborated-type-specifiers) is asthe Committee intended. The remaining questions mentioned in thediscussion may be addressed in dealing with related issues.
(See also issues136,138,139,143,165, and166.)
_N4868_.9.8.2.3 [namespace.memdef] paragraph 2says,
Members of a named namespace can also be defined outside thatnamespace by explicit qualification(6.5.5.3 [namespace.qual]) of the name beingdefined, provided that the entity being defined was already declaredin the namespace...It is not clear whether block-scopeextern declarations andfriend declarations are sufficient to permit the namedentities to be defined outside their namespace. For example,
namespace NS { struct A { friend struct B; }; void foo() { extern void bar(); } } struct NS::B { }; // 1) legal? void NS::bar() { } // 2) legal?
Rationale (10/99): Entities whose names are "invisiblyinjected" into a namespace as a result offriend declarationsare not "declared" in that namespace until an explicit declaration ofthe entity appears at namespace scope. Consequently, the definitionsin the example are ill-formed.
(See also issues95,136,138,139,143, and166.)
Consider the following example:
class C { public: enum E {}; friend void* operator new(size_t, E); friend void operator delete(void*, E); }; void foo() { C::E e; C* ptr = new(e) C(); }
This code, which is valid in global scope, becomes ill-formed whenthe class definition is moved into a namespace, and there is no wayto make it valid:
namespace N { class C { public: enum E {}; friend void* operator new(size_t, E); friend void operator delete(void*, E); }; } void foo() { N::C::E e; N::C* ptr = new(e) N::C(); }
The reason for this is that non-member allocation and deallocationfunctions are required to be members of the global scope (6.7.6.5.2 [basic.stc.dynamic.allocation] paragraph 1, 6.7.6.5.3 [basic.stc.dynamic.deallocation] paragraph 1), unqualified friend declarations declare names in theinnermost enclosing namespace (_N4868_.9.8.2.3 [namespace.memdef] paragraph 3), and these functions cannot be declared in global scopeat a point where the friend declarations could refer to them usingqualified-ids because their second parameter is a member of theclass and thus can't be named before the class containing the frienddeclarations is defined.
Possible solutions for this conundrum include invention of somemechanism to allow a friend declaration to designate a namespace scopeother than the innermost enclosing namespace in which the friend classor function is to be declared or to relax the innermost enclosingnamespace lookup restriction in _N4868_.9.8.2.3 [namespace.memdef] paragraph 3for friend declarations that nominate allocation anddeallocation functions.
Rationale (April, 2006):
The CWG acknowledged that it is not always possible to movecode from the global scope into a namespace but felt that thisproblem was not severe enough to warrant changing the language toaccommodate it. Possible solutions include moving the enumerationoutside the class or defining member allocation and deallocationfunctions.
_N4868_.9.8.2.3 [namespace.memdef] paragraph 3 is intended to preventinjection of names fromfriend declarations into the containingnamespace scope:
If afriend declaration in a non-local class first declares aclass or function the friend class or function is a member of theinnermost enclosing namespace. The name of the friend is not found byunqualified lookup (6.5.3 [basic.lookup.unqual]) or by qualified lookup(6.5.5 [basic.lookup.qual]) until a matching declaration is providedin that namespace scope (either before or after the class definitiongranting friendship).
However, this does not address names declared byelaborated-type-specifiers that are part of thefrienddeclaration. Are these names intended to be visibly injected? Forexample, is the following well-formed?
class A { friend class B* f(); }; B* bp; // IsB visible here?
Implementations differ in their treatment of this example: EDGand MSVC++ 8.0 accept it, while g++ 4.1.1 rejects it.
Rationale (July, 2009):
The current specification does not restrict injection of namesinelaborated-type-specifiers, and the consensus of the CWGwas that no change is needed on this point.
The current wording of _N4868_.9.8.2.3 [namespace.memdef] and13.9.4 [temp.expl.spec] requires that an explicit specializationbe declared either in the same namespace as the template or in anenclosing namespace. It would be convenient to relax that requirementand allow the specialization to be declared in a non-enclosingnamespace to which one or more if the template arguments belongs.
Additional note, April, 2015:
See EWG issue 48.
EWG 2022-11-11
This is a feature request, not a defect.
The term "throw exception" seems to sometimes refer to an expressionof the form "throwexpr" andsometimes just to the "expr"portion thereof.
As a result it is not quite clear to mewhether when "uncaught_exception()"becomes true: before or after the temporarycopy of the value of "expr".
Is there a definite consensus about that?
Rationale:The standard is sufficiently clear; the phrase "to be thrown" indicatesthat the throw itself (which includes the copy to the temporary object)has not yet begun. The footnote in14.6.2 [except.terminate] paragraph 1reinforcesthis ordering.
See alsoissue 475.
With the adoption of paper N4259 specifying thestd::uncaught_exceptions() function, thestd::uncaught_exception() function should bedeprecated.
Rationale (May, 2015):
This has already been done; see _N4140_.D.9 [depr.uncaught].
The standard is inconsistent in its use of a hyphen on thefollowing:nontype vs. non-type,non-dependent vs. nondependent,non-deduced vs. nondeduced, andnon-template vs. nontemplate.We should pick a preferred form.
Notes from the March 2004 meeting:
If this isn't a purely editorial issue, nothing is. We're referring thisto the editor. We prefer the hyphenated forms.
(From item JP 03 of the Japanese National Body comments on the C++14DIS ballot.)
A digit separator is allowed immediately following theprefix for an octal literal but not for a binary orhexadecimal literal. For example,0'01 ispermitted but0b'01 and0x'01 are not.This asymmetry makes tools such as automatic code generatorsmore complicated than necessary. The digit separator shouldbe consistently allowed or disallowed immediately followingthe prefix in all non-decimal integer literals.
Rationale (November, 2014):
CWG felt that the reported asymmetry is not a major difficultyand that it is more natural to think of the leading0 inan octal literal as part of the numeric value rather than as aseparate prefix, as it is with0b and0x.Consequently there was no consensus for a change to the existingspecification.
5.13.9 [lex.ext] paragraphs 3-4 state in notes that thearguments to a literal operator template “can only containcharacters from the basic source character set.” Thisrestriction does not appear to occur anywhere in normative text,however.
Rationale (July, 2009):
The characters in the template arguments are the characterscomprisingn, the integer literal, orf, the floatingliteral. As such, they are constrained by the grammar to be membersof the basic character set, and no further normative restriction isneeded.
User-defined literals should not be part of C++0x unless they haveimplementation experience.
Rationale (March, 2011):
The feature has been implemented.
The format macros that are part of<inttypes.h>(incorporated into C++11 as<cinttypes>) areconventionally written with no whitespace separating them from therest of the format string, e.g.,
printf("foo = "PRIu32", bar = "PRIi8"\n", foo, bar); printf("baz = "PRIu32"\n", baz);
This usage conflicts with user-defined literals.
Rationale (October, 2012):
CWG felt that whether this form of these macros needed to besupported in C++ should be examined by EWG.
Rationale (February, 2014):
EWG determined that no action should be taken on this issue.
Aud-suffix is defined in 5.13.9 [lex.ext] asanidentifier. This prevents plausible user-defined literalsfor currency symbols, which are not categorized as identifiercharacters.
Rationale (June, 2014):
CWG felt that a decision on whether to allow this capability ornot should be considered by EWG.
EWG 2022-11-11
This is a request for a new feature, which should be proposed in apaper to EWG. SG16 recommended not adding the feature.
According to the grammar in 5.13.9 [lex.ext], aud-suffix is anidentifier. However, implementationsseem to agree that"x"or"y" is equivalent to"xy"orand not totrue. Should the Standard permit identifier-likealternative tokens asud-suffixes?
Rationale (October, 2015):
Theidentifier in aud-suffix is required tobegin with an underscore, and the identifier-like alternativetokens do not satisfy this requirement.
According to 6.1 [basic.pre] paragraph 4,
A name is a use of anidentifier(5.11 [lex.name]),operator-function-id(12.4 [over.oper]),literal-operator-id(12.6 [over.literal]),conversion-function-id(11.4.8.3 [class.conv.fct]), ortemplate-id(13.3 [temp.names]) that denotes an entity or label(8.7.6 [stmt.goto], 8.2 [stmt.label]).
Since typedefs are neither entities nor labels, it appearsthat atypedef-name is not a name.
There is an additional discrepancy regarding alias templates.According to 6.1 [basic.pre] paragraph 3, templates(including, presumably, alias templates) and their specializationsare entities. However, the note in 13.3 [temp.names] paragraph 6says,
[Note: Asimple-template-id that names a classtemplate specialization is aclass-name(11.3 [class.name]). Anyothersimple-template-id that names a type isatypedef-name. —end note]
Thus an alias template specialization both is and is not anentity.
In 6.3 [basic.def.odr] paragraph 4bullet 4, it's presumably the case that a conversion toT*requires thatT be complete only if the conversion is from a differenttype. One could argue that there is no conversion (and thereforethe text is accurate as it stands) if a cast does not change the type ofthe expression, but it's probably better to be more explicit here.
On the other hand, this text is non-normative (it's in a note).
Rationale (04/99):The relevant normative text makes this clear. Implicit conversionandstatic_cast are defined (in7.3 [conv] and7.6.1.9 [expr.static.cast],respectively) as equivalent to declaration with initialization, whichpermits pointers to incomplete types, anddynamic_cast(7.6.1.7 [expr.dynamic.cast]) explicitlyprohibits pointers to incomplete types.
decltype applied to a function call expressionrequires a complete type (7.6.1.3 [expr.call] paragraph 3and 6.3 [basic.def.odr] paragraph 4), eventhoughdecltype's result might be used in a waythat does not actually require a complete type. This mightcause undesired and excessive template instantiations.Immediately applyingdecltype should not require acomplete type, for example, for the return type of afunction call.
Additional note (October, 2010):
Another potential consideration in this question is the useof the return type in template argument deduction. If the returntype is a specialization of a class template, one would want anerror occurring in the instantiation of that specialization tocause a deduction failure, which would argue in favor of requiringthe type to be complete. (However, that might also be covered by“when the completeness of the class type affects the semanticsof the program” in 13.9.2 [temp.inst] paragraph 1.)
Rationale (November, 2010):
The CWG was persuaded by the SFINAE consideration.
Note:
This issue was raised again at the March, 2011 meeting and paper N3276,implementing this recommendation, was adopted for the FDIS.
The relationship between when an expression is potentially evaluated,especially with respect to contexts requiring constant expressions,and non-type template arguments is not clear and should be clarified.In particular, it seems that these contexts should bepotentially-evaluated.
See alsoissue 1378.
Additional note, January, 2012:
Further discussion indicates that this is not a defect and should beclosed as such.
Notes from the February, 2012 meeting:
CWG determined that the current wording is clear enough that aninstantiation is required whenever it affects the semantics of theprogram.
According to 6.3 [basic.def.odr] paragraph 3,
this is odr-used if it appears as a potentially-evaluatedexpression (including as the result of the implicit transformation in thebody of a non-static member function (11.4.3 [class.mfct.non.static])).
This wording does not distinguish between constant and non-constantexpressions in determining whetherthis is odr-used or not.
Notes from the April, 2018 teleconference:
Specification of the odr-use ofthis was done to allowdetermination of whetherthis should be captured by a lambda.Recent changes to determine capture syntactically, rather than by odr-use,have rendered this issue almost moot. However, 6.3 [basic.def.odr]still describes whenthis is odr-used; this specification is nolonger necessary and should be removed.
Rationale (February, 2019):
This specification is now used by contracts.
According to 6.3 [basic.def.odr] bullet 6.5,
in each definition ofD, a default argument used by an (implicit orexplicit) function call is treated as if its token sequence were present inthe definition ofD; that is, the default argument is subject to therequirements described in this paragraph (and, if the default argument hassubexpressions with default arguments, this requirement appliesrecursively)
However, this rule is insufficient to handle a case like:
struct A { template<typename T> A(T); }; void f(A a = []{}); inline void g() { f(); }
This should be an ODR violation, because the call to f() will invoke adifferent specialization of the constructor template in each translationunit, but it is not, because the rule says this example is equivalentto:
inline void g() { f([]{}); }
which is not an ODR violation, since the type of the closure objectwill be the same in every translation unit (9.2.8 [dcl.inline] paragraph 6)..
Notes from the October, 2018 teleconference:
This will be addressed by work already underway to rework therelationship between lambdas and the ODR.Rationale (February, 2021):
The resolution ofissue 2300makes clear that this example is an ODR violation.
This seems like it should be well-formed:
template <class T> T list(T x); template <class H, class ...T> auto list(H h, T ...args) -> decltype(list(args...)); auto list3 = list(1, 2, 3);
but it isn't, because the second list isn't in scope in itsowntrailing-return-type; the point of declaration is after thedeclarator, which includes thetrailing-return-type. And sinceint has no associated namespaces, the call in the return typeonly sees the first list. G++, EDG and Clang all reject thetestcase on this basis.
But this seems like a natural pattern for writing variadicfunction templates, and we could support it by moving the pointof declaration to the->. This would mean having todeal with a function that only has a placeholder for a returntype, but I think we can handle that.
Rationale (February, 2012):
This is a request for an extension to the language and is thus moreappropriately addressed by EWG.
EWG 2022-11-11
This is a breaking change whose benefits and trade-offs need to becarefully analyzed.
Consider this code:
struct Base { enum { a, b, c, next }; }; struct Derived : public Base { enum { d = Base::next, e, f, next }; };The idea is that the enumerator "next" in each class is the next availablevalue for enumerators in further derived classes.
If we had written
enum { d = next, e, f, next };I think we would run afoul of 6.4.7 [basic.scope.class]:
A nameN used in a classS shall refer tothe same declaration in its context and when re-evaluated in the completedscope ofS. No diagnostic is required for a violation of thisrule.But in the original code, we don't have an unqualified "next" that refersto anything but the current scope. I think the intent was to allow thecode, but I don't find the wording clear on on that point.
Is there another section that makes it clear whether the original codeis valid? Or am I being obtuse? Or should the quoted section say "An unqualifiedname N used in a class ..."?
Rationale (04/99): It is sufficiently clear that "name"includes qualified names and hence the usual lookup rules make thislegal.
Consider:
struct A { struct B { typedef int X; }; }; template<class B> struct C : A { B::X q; // Ok: A::B. struct U { typedef int X; }; template<class U> struct D; }; template<class B> template<class U> struct C<B>::D { typename U::X r; // which U? }; C<int>::D<double> y;
In the definition ofD,U definitely needsto be in scope as soon as it's declared because it might havebeen used in subsequent template parameter declarations, or itmight have been used in theid-expression that names thedeclared entity — just asB is used inC<B>::D. (So 6.4.9 [basic.scope.temp] does the rightthing for that purpose.)
But it would be nice if the result of lookup did not depend onwhetherD's body appears lexically insideC's body;currently, we don't seem to have the wording that makes itso.
Rationale (October, 2012):
This example is covered by the wording in 13.8.2 [temp.local]paragraphs 7-8: the template parameter is found.
The name lookup in abase-specifier and amem-initializer differ in that the former ignores non-typenames but the latter does not. When themem-initializer-idis qualified, this can lead to surprising results:
struct file_stat : ::stat { // the class file_stat() : ::stat{} {} // the function };
Rationale (May, 2015):
The use of aqualified-id as amem-initializer-idis sufficiently unusual that it is not worth changing the lookuprules to accommodate it.
Consider:
struct B1 { bool operator==(B1 const&) const; }; struct B2 { bool operator==(B2 const&) const; }; struct D: B1, B2 {} d; bool operator==(D const&, D const&); auto r = d == d; // ambiguous?
There is implementation divergence in handling this example; someimplementations select the non-member operator, others diagnose anambiguous lookup.
Member name lookup foroperator== is ambiguous, making theprogram ill-formed per 6.5.2 [class.member.lookup] paragraph 6:
The result of the search is the declaration set of S(N, T). If it isan invalid set, the program is ill-formed.
There is no provision for simply failing if the lookup is invokedas part of some larger lookup, as in the case of a lookup for anoverloaded operator (12.2.2.3 [over.match.oper] paragraph 3):
For a unary operator @ with an operand of type cv1 T1, and for abinary operator @ with a left operand of type cv1 T1 and a rightoperand of type cv2 T2, four sets of candidate functions, designatedmember candidates,non-member candidates,built-incandidates, andrewritten candidates, are constructed asfollows:
- If T1 is a complete class type or a class currently being defined,the set of member candidates is the result of a searchforoperator@ in the scope of T1; otherwise, ....
- For the operators =, [], or ->, the set of non-membercandidates is empty; otherwise, it includes the result of unqualifiedlookup foroperator@ in the rewritten function call(6.5.3 [basic.lookup.unqual], 6.5.4 [basic.lookup.argdep]), ignoring allmember functions. ...
- For the operator,, the unary operator&, orthe operator->, the built-in candidates set is empty.For all other operators, the built-in candidates include all of thecandidate operator functions defined in 12.5 [over.built] that...
- The rewritten candidate set is determined as follows: ...
It is unclear whether that is intended or desirable.
Suggested resolution:
Change in 6.5.2 [class.member.lookup] paragraph 6 as follows:
The result of the search isIf the declaration set of S(N, T). If itisaninvalidset,the program is ill-formedthe result of thesearch is an empty set; otherwise, the result is that set.
Rationale (CWG 2023-06-17)
Changing the lookup rules to yield an empty set has undesirableeffects on non-operator lookup, where fall-back to non-member lookupis actually desired. The intended outcome for the example is asspecified (i.e. the program is ill-formed). The example can beaddressed by makingoperator== a member ofD.
The description of name lookup in theparameter-declaration-clause of member functions in6.5.3 [basic.lookup.unqual]paragraphs 7-8 isflawed in at least two regards.
First, both paragraphs 7 and 8 apply to theparameter-declaration-clause of a member function definitionand give different rules for the lookup. Paragraph 7 applies to names"used in the definition of a classX outside of a memberfunction body...," which includes theparameter-declaration-clause of a member function definition,while paragraph 8 applies to names following the function'sdeclarator-id (see the proposed resolution ofissue 41), including theparameter-declaration-clause.
Second, paragraph 8 appears to apply to the type names used in theparameter-declaration-clause of a member function definedinside the class definition. That is, it appears to allow thefollowing code, which was not the intent of the Committee:
struct S { void f(I i) { } typedef int I; };
Additional note, January, 2012:
brace-or-equal-initializers for non-static data members areintended effectively as syntactic sugar formem-initializers inconstructor definitions; the lookup should be the same.
Rationale (February, 2021):
This issue was resolved by the resolution ofissue 1352.
The wording of 6.5.3 [basic.lookup.unqual] paragraph 2 ismisleading. It says:
The declarations from the namespace nominated by ausing-directive become visible in a namespace enclosing theusing-directive; see 9.9.4 [namespace.udir].
According to 9.9.4 [namespace.udir] paragraph 1, thatnamespace is
the nearest enclosing namespace which contains both theusing-directive and the nominated namespace.
That would seem to imply the following:
namespace outer { namespace inner { int i; } void f() { using namespace inner; } int j = i; // inner::i is "visible" in namespace outer }
Suggested resolution: Change the first sentence of6.5.3 [basic.lookup.unqual] paragraph 2 to read:
The declarations from the namespace nominated by ausing-directive become visible in the scope in which theusing-directive appears after theusing-directive.
Notes from the 4/02 meeting:
After a lot of discussion of possible wording changes, we decidedthe wording should be left alone. 6.5.3 [basic.lookup.unqual] paragraph 2is not intended to be a full specification; that's in9.9.4 [namespace.udir] paragraph 1. See also6.4.6 [basic.scope.namespace] paragraph 1.
According to 6.5.3 [basic.lookup.unqual] paragraph 10,
In afriend declaration naming a member function, a name used in thefunction declarator and not part of atemplate-argument inthedeclarator-id is first looked up in the scope of the memberfunction's class (6.5.2 [class.member.lookup]). If it is not found, or if thename is part of atemplate-argument in thedeclarator-id, thelook up is as described for unqualified names in the definition of theclass granting friendship.
The corresponding specification for non-friend declarationsin paragraph 8 applies the class-scope lookup only to names thatfollow thedeclarator-id. The same should be true infriend declarations.
Proposed resolution (February, 2018):
Change 6.5.3 [basic.lookup.unqual] paragraph 8 as follows:
For the members of a classX, a name used in a member functionbody, in a default argument, in anoexcept-specifier, inthebrace-or-equal-initializer of a non-static data member(11.4 [class.mem]), or in the
definitiondeclaration of a class member outside of the definitionofX,following themember'sdeclarator-id32, shall be declared in one of thefollowing ways:
before its use in the block in which it is used or in anenclosing block (8.4 [stmt.block])within the body of themember function, or
shall beas a member of classXorbeas a member of a base class ofX(6.5.2 [class.member.lookup]), orifX is a nested class of classY(11.4.12 [class.nest]), shall be a member ofY, or shall bea member of a base class ofY (this lookup applies in turntoY's enclosing classes, starting with the innermost enclosingclass),33 or
ifX is a local class (11.6 [class.local]) or is anested class of a local class, before the definition of classX ina block enclosing the definition of classX, or
ifX is a member of namespaceN, or is a nestedclass of a class that is a member ofN, or is a local class or anested class within a local class of a function that is a memberofN, before the use of the name, in namespaceN or inone ofN's enclosing namespaces
., orfor a friend declaration in a classY, in a scope thatwould be searched for a name appearing withinY.
Delete 6.5.3 [basic.lookup.unqual] paragraph 10 and combine itsexample with that of paragraph 8:
In afriend declaration naming a member function, a nameused in the function declarator and not part of atemplate-argumentin thedeclarator-id is first looked up in the scope of the memberfunction's class (6.5.2 [class.member.lookup]). If it is not found, or if thename is part of atemplate-argument in thedeclarator-id, thelook up is as described for unqualified names in the definition of theclass granting friendship. [Example:struct A { typedef int AT; void f1(AT); void f2(float); template <class 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]
Notes from the February, 2018 teleconference:
There was some concern as to whether the added lookup for friendfunction declarations placed the additional lookups in the correct sequencerelative to the existing lookups and whether the new specification reflectsany existing practice.
Rationale (March, 2018):
After further discussion, CWG determined that the semantics described inthe existing wording were the most appropriate out of the alternativesconsidered.
Consider an example like the following:
template <typename T> void doit(const T& t, const T& t2) { } template <typename T> struct Container { auto doit(Container<T> &rhs) noexcept(noexcept(doit(T{}, T{}))) -> decltype(doit(T{}, T{})); }; Container<int> c;
This would appear to be ill-formed because the exception specificationis a delayed-parse region, where the lookup is in the context of thecompleted class, while the lookup in thedecltype in the returntype is done immediately. The latter should find the two-parameterversion ofdoit, as expected, while the former finds themember, one-parameter version. Current implementations accept the code,however, and it seems unfortunate that the meaning would be different inthe two contexts.
Rationale, June, 2018:
The example is ill-formed: the reference todoit in thereturn type would refer to the member function in the completed class,which is ill-formed, no diagnostic required, per 6.4.7 [basic.scope.class] paragraph 2.
When a union is used in argument-dependent lookup, the union's typeis not an associated class type. Consequently, code like this will failto work.
union U { friend void f(U); }; int main() { U u; f(u); // error: no matching f — U is not an associated class }Is this an error in the description of unions in argument-dependent lookup?
Also, this section is written as if unions were distinct from classes.So adding unions to the "associated classes" requires either rewritingthe section so that "associated classes" can include unions, or changingthe term to be more inclusive, e.g. "associated classes and unions" or"associated types".
Jason Merrill: Perhaps in both cases, the standard text was intendedto only apply to anonymous unions.
Liam Fitzpatrick: One cannot create expressions of ananonymous union type.
Rationale (04/99): Unions are class types, so the example iswell-formed. Although the wording here could be improved, it does not riseto the level of a defect in the Standard.
In discussingissue 197, the questionarose as to whether the handling of fundamental types inargument-dependent lookup is actually what is desired. This questionneeds further discussion.
Rationale (March, 2011):
There does not seem to be sufficient motivation at this point,with an additional eleven years' experience, to make a change.
I believe the following code example should unambiguously call themember operator+. Am I right?
//--- some library header --- // namespace N1 { template<class T> struct Base { }; template<class T> struct X { struct Y : public Base<T> { // here's a member operator+ Y operator+( int _Off ) const { return Y(); } }; Y f( unsigned i ) { return Y() + i; } // the "+" in question }; } //--- some user code --- // namespace N2 { struct Z { }; template<typename T> // here's another operator+ int* operator+( T , unsigned ) { static int i ; return &i ; } } int main() { N1::X< N2::Z > v; v.f( 0 ); }
My expectation is that 6.5.4 [basic.lookup.argdep] would govern, specifically:
If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated namespaces and classes are not considered.So I think the member should hide the otherwise-better-matching one inthe associated namespace. Here's what compilers do:
Agree with me and call the member operator+: Borland 5.5, Comeau 4.3.0.1, EDG 3.0.1, Metrowerks 8.0, MSVC 6.0
Disagree with me and try to call N2::operator+: gcc 2.95.3, 3.1.1, and 3.2; MSVC 7.0
Simple so far, but someone tells me that 12.2.2.3 [over.match.oper]muddies the waters.There, paragraph 10 summarizes that subclause:
[Note: the lookup rules for operators in expressions are different than the lookup rules for operator function names in a function call, ...In particular, consider the above call to "Y() + unsigned" and pleasehelp me step through 12.2.2.3 [over.match.oper] paragraph 3:
... 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,OK so far, here @ is +, and T1 is N1::X::Y.
three sets of candidate functions, designated member candidates, non-member candidates and built-in candidates, are constructed as follows:[and later are union'd together to get the candidate list]
If T1 is a class type, the set of member candidates is the result of the qualified lookup of T1::operator@ (over.call.func); otherwise, the set of member candidates is empty.So there is one member candidate, N1::X::Y::operator+.
The set of non-member candidates is the result of the unqualified lookup of operator@ in the context of the expression according to the usual rules for name lookup in unqualified function calls (basic.lookup.argdep) except that all member functions are ignored.
*** This is the question: What does that last phrase mean? Does it mean:
a) first apply the usual ADL rules to generate a candidate list, thenignore any member functions in that list (this is what I believe andhope it means, and in particular it means that the presence of a memberwill suppress names that ADL would otherwise find in the associatednamespaces); or
b) something else?
In short, does N2::operator+ make it into the candidate list? I think itshouldn't. Am I right?
John Spicer:I believe that the answer is sort-of "a" above. More specifically, theunqualified lookup consists of a "normal" unqualified lookup and ADL.ADL always deals with only namespace members, so the "ignore membersfunctions" part must affect the normal lookup, which should ignore classmembers when searching for an operator.
I suspect that the difference between compilers may have to do with detailsof argument-dependent lookup. In the example given, the argument typesare "N1::X<N2::Z>::Y" and "unsigned int". In order for N2::operator+ tobe a candidate, N2 must be an associated namespace.
N1::X<N2::Z>::Y is a class type, so6.5.4 [basic.lookup.argdep] says that its associated classes areits direct and indirect base classes, and its namespaces are the namespacesof those classes. So, its associated namespace is just N1.
6.5.4 [basic.lookup.argdep] also 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. ]First of all, there is a problem with the term "is a template-id". template-idis a syntactic constuct and you can't really talk about a type being atemplate-id. Presumably, this is intended to mean "If T is the type of aclass template specialization ...".But does this apply to N1::X<N2::Z>::Y?Y is a class nested within a class template specialization. In addition,its base class is a class template specialization.
I think this raises two issues:
Notes from the April 2003 meeting:
The ADL rules in the standard sort of look at if they are fullyrecursive, but in fact they are not; in some cases, enclosing classesand base classes are considered, and in others they are not.Microsoft and g++ did fully-recursive implementations, andEDG and IBM did it the other way. Jon Caves reports that Microsoft sawno noticeable difference (e.g., no complaints from customersinternal or external) when they made this change, so we believethat even if the rules are imperfect the way they are in thestandard, they are clear and the imperfections are small enoughthat programmers will not notice them. Given that, it seemedprudent to make no changes and just close this issue.
The template-id issue is spun off asissue 403.
Argument-dependent lookup does not consider the elements ofan initializer list used as an argument. This seems inconsistent:
namespace NS { struct X { } ; void f( std::initializer_list<X> ) { } } int main() { NS::X x ; // ADL fails to findNS::f f( {x,x,x} ) ; // OK. ADL findsNS::f auto i = {x,x,x} ; f( i ) ; // Also OK f( std::initializer_list<NS::X>{x,x,x} ) ; }
Rationale (October, 2015):
Argument-dependent lookup makes sense when the argumentscorrespond to actual parameters of the function. In thecase of an initializer list, however, the elements of theinitializer list need not bear any relationship to theactual parameter type of the function; instead, theyprovide values for aggregate initialization or constructionof the object being initialized, and there is no reasonto expect that that type will have the same associatednamespace as the types of the elements of the initializerlist.
One would expect to find a definition of the terms“associated class” and “associatednamespace” in 6.5.4 [basic.lookup.argdep], but thereis none. Note also that “associated class” isused in a different sense in 7.6.10 [expr.eq]bullet 3.6, and that drafting being proposed for otherissues also uses the term differently.
Rationale (October, 2015):
CWG felt that the current usage is plain English, not atechnical term, and is clear enough.
There is a discrepancy between the syntaxes allowed for defining aconstructor and a destructor of a class template. For example:
template <class> struct S { S(); ~S (); }; template <class T> S<T>::S<T>() { } // error template <class T> S<T>::~S<T>() { } // okay
The reason for this is that 6.5.5.2 [class.qual] paragraph 2says thatS::S is “considered to namethe constructor,” which is not a template and thus cannotaccept a template argument list. On the other hand, thesecondS inS::~S finds theinjected-class-name, which “can be used with or without atemplate-argument-list” (13.8.2 [temp.local] paragraph 1) and thus satisfies the requirement to name thedestructor's class (11.4.7 [class.dtor] paragraph 1).
Would it make sense to allow thetemplate-argument-listin the constructor declaration and thus make the language just alittle easier to use?
Rationale (July, 2007):
The CWG noted that the suggested change would be confusing in thecase where the class template had both template and non-templateconstructors.
6.6 [basic.link] paragraph 8says,
A name with no linkage (notably, the name of a class or enumerationdeclared in a local scope(6.4.3 [basic.scope.block])) shall not beused to declare an entity with linkage.This wording does not, but should, prohibit use of an unnamed localtype in the declaration of an entity with linkage. For example,
void f() { extern struct { } x; // currently allowed }
Proposed resolution: Change the text in6.6 [basic.link] paragraph 8 from:
A name with no linkage (notably, the name of a class or enumerationdeclared in a local scope (6.4.3 [basic.scope.block])) shall not be used to declare an entitywith linkage.to:
A name with no linkage (notably, the name of a class or enumerationdeclared in a local scope (6.4.3 [basic.scope.block])) or an unnamed type shall not be usedto declare an entity with linkage.In section 6.6 [basic.link] paragraph 8, add to the example, before theclosing brace of functionf:
extern struct {} x; //ill-formed
Rationale (10/00): The proposed change would have introducedan incompatibility with the C language. For example, the globaldeclaration
static enum { A, B, C } abc;
represents an idiom that is used in C but would be prohibitedunder this resolution.
It is unclear to what extent entities without names matchacross translation units. For example,
struct S { int :2; enum { a, b, c } x; static class {} *p; };
If this declaration appears in multiple translation units, areall these members "the same" in each declaration?
A similar question can be asked about non-member declarations:
// Translation unit 1: extern enum { d, e, f } y; // Translation unit 2: extern enum { d, e, f } y; // Translation unit 3: enum { d, e, f } y;
Is this valid C++? Is it valid C?
James Kanze:S::p cannot be defined, because todo so requires a type specifier and the type cannot be named.::y is valid C because C only requires compatible, notidentical, types. In C++, it appears that there is a new type ineach declaration, so it would not be valid. This differs fromS::x because the unnamed type is part of a named type— but I don't know where or if the Standard says that.
John Max Skaller:It's not valid C++, because the type is a synthesised, unique namefor the enumeration type which differs across translation units, as if:
extern enum _synth1 { d,e,f} y; .. extern enum _synth2 { d,e,f} y;
had been written.
However, within a class, the ODR implies the types are the same:
class X { enum { d } y; };
in two translation units ensures that the type of memberyis the same: the twoX's obey the ODR and so denote the same class,and it follows that there's only one membery and one type that ithas.
Rationale (February, 2021):
The resolution ofissue 2300and paper P2115R0 have resolved these questions.
Consider:
namespace { extern "C" void f() { } }
Doesf have internal or external linkage? Implementationsseem to givef external linkage, but the standard prescribesinternal linkage per 6.6 [basic.link] paragraph 4.
Rationale (November, 2016):
The specification is as intended.
For certain data types on some hardware, a given object canbe accessed most efficiently with one alignment but can besuccessfully accessed if allocated at a less-stringent boundary.Should the Standard specify the minimum or the preferred alignmentas the value of thealignof?
Rationale (June, 2014):
The existing wording is clear that the resultalignofis the minimal alignment. If an operator returning the preferredalignment is desired, that request should be addressed to EWG.
6.7.4 [basic.life] and 11.4.7 [class.dtor] discussexplicit management of object lifetime. It seems clear that mostobject lifetime issues apply to sub-objects (array elements, and datamembers) as well. The standard supports
struct X { T t } x; T* pt = &x.t; pt->~T(); new(pt) T;
and this kind of behavior is useful in allocators.
However the standard does not seem to prohibit the same operationson base sub-objects.
struct D: B{ ... } d; B* pb = &d; pb->~B(); new(pb) B;
However ifB and/orD have virtual memberfunctions or virtual bases, it is unlikely that this code will resultin a well-formedD object in current implementations (notethat the various lines may be in different functions).
Suggested resolution: 11.4.7 [class.dtor] should bemodified so that explicit destruction of base-class sub-objects bemade illegal, or legal only under some restrictive conditions.
Rationale (04/01):
Reallocation of a base class subobject is already disallowedby 6.7.4 [basic.life] paragraph 7.
6.7.4 [basic.life] was never adjusted for threads. In particular,it describes what may be done with objects in various intervals. Ingeneral when the Standard uses words like “during,” it isreferring to intervals defined by “sequenced before” ordering.In this context, however, all the specifications need to use the“happens before” ordering.
Suggested resolution:
Add the following at the beginning of 6.7.4 [basic.life]:
All statements about the ordering of evaluations in this section,using words like “before,” “after,” and“during,” refer to the “happens before”order defined in 6.9.2 [intro.multithread]. [Note: Weignore situations in which evaluations are unordered by“happens before,” since these require a data race(6.9.2 [intro.multithread]), which already results in undefinedbehavior. —end note]
Rationale (August, 1020):
The text is already in the FCD.
The restrictions in 6.7.4 [basic.life] paragraph 7 on whenthe storage for an object containing a reference member can be reusedseem overly restrictive.
Rationale (August, 2011):
CWG did not find a persuasive use case for a change to the existingrules.
The Standard is self-contradictory regarding which destructor callsend the lifetime of an object. 6.7.4 [basic.life] paragraph 1says,
The lifetime of an object of type T ends when:
ifT is a class type with a non-trivial destructor(11.4.7 [class.dtor]), the destructor call starts, or
the storage which the object occupies is reused or released.
i.e., the lifetime of an object of a class type with atrivial destructor persists until its storage is reused or released.However, 11.4.7 [class.dtor] paragraph 15 says,
Once a destructor is invoked for an object, the object no longerexists; the behavior is undefined if the destructor is invoked for anobject whose lifetime has ended (6.7.4 [basic.life]).
implying that invoking any destructor, even a trivial one, endsthe lifetime of the associated object. Similarly, 11.9.5 [class.cdtor] paragraph 1says,
For an object with a non-trivial destructor, referring to anynon-static member or base class of the object after the destructorfinishes execution results in undefined behavior.
A similar question arises forpseudo-destructors for non-class types.
Notes from the August, 2011 meeting:
CWG will need a paper exploring this topic before it can act onthe issue.
Rationale (February, 2021):
The resolution ofissue 2256makes it clear that the destruction of an object, no matterhow accomplished, ends its lifetime.
Subclause 6.7.4 [basic.life] bullet 8.5 says that o1 isonly transparently replaceable by o2 if
either o1 and o2 are both complete objects, or o1 and o2 are directsubobjects of objects p1 and p2, respectively, and p1 istransparently replaceable by p2.
This disallows most of the intended uses of the transparentreplacement rule, including example 3 in 11.5.1 [class.union.general],which is similar to:
union A { int n; string s; }; A a; // Does not transparently replaceA::s subobject, because // the created object is a complete object. new (&a.s) string("hello"); string t = a.s;
The rule was changed in response to NB comment US 041 (C++20 CD) inwhat appears to be an over-reach: US 041 says that a member subobjectshould not transparently replace an unrelated member subobject, but issilent about complete objects transparently replacing members.
CWG 2023-01-06
Issues2676 and2677 were split off from this issue.
Subclause 6.7.2 [intro.object] paragraph 2 specifies that "thecreated object is a subobject of [the original] containing object" forthe example above. This issue is therefore NAD.
The global allocation functions are implicitly declared in everytranslation unit withexception-specifications(6.7.6.5 [basic.stc.dynamic] paragraph 2). It is not clear whatshould happen if a replacement allocation function is declared withoutanexception-specification. Is that a conflict with theimplicitly-declared function (as it would be with explicitly-declaredfunctions, and presumably is if the<new> header isincluded)? Or does the new declaration replace the implicit one,including the lack of anexception-specification? Or does theimplicit declaration prevail? (Regardless of theexception-specification or lack thereof, it is presumablyundefined behavior for an allocation function to exit with anexception that cannot be caught by a handler of typestd::bad_alloc (6.7.6.5.2 [basic.stc.dynamic.allocation] paragraph3).)
Rationale (November, 2014):
The predeclared allocation functions no longer have anexception-specification, so formally this issue is no longerapplicable. As noted in the rationale ofissue 1948, however, the intent is thatthe predeclarations are no different from ordinary declarations,so the replacement functions must have compatibleexception-specifications.
Some implementations accept code like
#include <cstddef> // to getsize_t void* operator new(std::size_t) noexcept { ... }
This declaration conflicts with the predeclaration ofoperator new with noexception-specification.
See alsoissue 967.
Rationale (November, 2014):
The specification intentionally makes such replacement functionsill-formed.
Speaking of the value returned by an allocation function,6.7.6.5.2 [basic.stc.dynamic.allocation] paragraph 2 says,
The pointer returned shall be suitably aligned so that it can be convertedto a pointer of any complete object type with a fundamental alignmentrequirement
However, the various “Effects” specificationsin 17.6.3 [new.delete] have a different formulation:
...allocatesize bytes of storage suitably aligned to representany object of that size.
These should be reconciled.
Rationale (November, 2016):
The adoption of paper P0035R4 has rendered this issue moot.
6.7.7 [class.temporary] paragraph 4seemsself-contradictory:
the temporary that holds the result of the expression shall persistuntil the object's initialization is complete... the temporaryis destroyed after it has been copied, before or when theinitialization completes.How can it be destroyed "before the initialization completes" ifit is required to "persist until the object's initialization iscomplete?"
Rationale (04/00):
It was suggested that "before the initialization completes" refersto the case in which some part of the initialization terminates bythrowing an exception. In that light, the apparent contradiction doesnot apply.
The resolution of issues616 and1213, making the result of a member access orsubscript expression applied to a prvalue an xvalue, means that binding areference to such a subobject of a temporary does not extend thetemporary's lifetime. 6.7.7 [class.temporary] should be revised to ensurethat it does.
Proposed resolution (February, 2014): [SUPERSEDED]
This issue is resolved by the resolution ofissue 1299.
Rationale (February, 2019):
This concern is already covered by 6.7.7 [class.temporary] paragraph 6:
The temporary object to which the reference is bound or thetemporary object that is the complete object of a subobjectto which the reference is bound persists for the lifetime ofthe reference if...
If aninit-capture binds a const reference to a temporary,is the lifetime of the temporary extended to match that of the lambda?For example,
struct S { ~S(); }; const S f(); auto &&lambda = [&x(f())] () -> auto& { return x; }; auto &y = lambda(); // ok?
Notes from the September, 2013 meeting:
CWG agreed that there is no lifetime extension in this example.
Rationale (June, 2014):
After further consideration, CWG agreed that this example shouldextend the lifetime of the temporary (because the notional variableis a reference) and that the existing text is clear enough in thisregard.
Following the definition in Clause 11 [class] paragraph 4the following is a valid POD (actually a POD-struct):
struct test { const int i; };
The legality of PODs with const members is also implied by the text of7.6.2.8 [expr.new] bullet 15.1, sub-bullet 2 and11.9.3 [class.base.init] bullet 4.2.
6.8 [basic.types] paragraph 3 states that
For any POD typeT, if two pointers toT point to distinct objectsobj1 andobj2, if the value ofobj1 is copiedintoobj2, using thememcpy library function,obj2shall subsequently hold the same value asobj1.
[Note: this text was changed by TC1, but the essential pointstays the same.]
This implies that the following is required to work:
test obj1 = { 1 }; test obj2 = { 2 }; memcpy( &obj2, &obj1, sizeof(test) );
The memcpy of course changes the value of the const member, surelysomething that shouldn't be allowed.
Suggested resolution:
It is recommended that 6.8 [basic.types] paragraph 3 bereworded to exclude PODs which contain (directly or indirectly) membersof const-qualified type.
Rationale (October, 2004):
9.2.9.2 [dcl.type.cv] paragraph 4 already forbidsmodifying a const member of a POD struct. The prohibition neednot be repeated in 6.8 [basic.types].
6.8 [basic.types] paragraph 11 requires that a classtype have a trivial copy constructor in order to be classified as aliteral type. This seems overly restrictive; presumably having aconstexpr copy constructor would suffice. (Note that a trivialcopy constructor is a constexpr constructor according to9.2.6 [dcl.constexpr] paragraph 4.)
Rationale (June, 2008):
A copy constructor takes a reference as its first parameter, thusno user-declared copy constructor can be constexpr.
Should cv-qualified and cv-unqualified versions of fundamental typesbe considered to be layout-compatible types?
Rationale (August, 2011):
The purpose of “layout compatible” types in C++ isfor C compatibility with respect to the common initial sequence ofstructs appearing in unions. However, C requires that correspondingmembers have compatible types, and compatible types must have thesame cv-qualification. Consequently, this issue is not a defect.
6.8.2 [basic.fundamental] paragraph 6 states,
As described below,bool values behave as integral types.
This sentence looks definitely out of order: how can a value behaveas a type?
Suggested resolution:
Remove the sentence entirely, as it doesn't supply anything thatisn't already stated in the following paragraphs and in thereferenced section about integral promotion.
Rationale (July, 2007):
This is, at most, an editorial issue with no substantiveimpact. The suggestion has been forwarded to the project editorfor consideration.
6.8.2 [basic.fundamental] paragraph 5 refers to a C header insteadof to its C++ equivalent:
...Typeschar16_t andchar32_t denote distinct typeswith the same size, signedness, and alignment asuint_least16_t anduint_least32_t, respectively, in<stdint.h>, called the underlying types.
Rationale (August, 2011)
This is an editorial issue that has been transmitted to the projecteditor.
Although 6.8.2 [basic.fundamental] paragraph 7 classifiesbool as an integral type, the values oftrue andfalse are not specified — only that the results ofconverting them to another integral type are1 and0, respectively. This omission leaves unspecified whetherfalse is an integral null pointer constant or not.
Rationale (February, 2012):
The resolution ofissue 903 makes itclear thatfalse is not a null pointer constant.
This issue is for tracking various concerns that are raised in paperN3057.
Rationale (August, 2010):
The paper was voted in in Pittsburgh.
It is not clear from the wording of 6.9.2 [intro.multithread] thatdifferent statements in the same function cannot be executed by differentthreads.
Rationale (September, 2013):
SG-1 determined that the existing wording is clear enough.
According to 6.9.2 [intro.multithread] paragraph 24,
The implementation may assume that any thread will eventually do one ofthe following:
terminate,
make a call to a library I/O function,
access or modify a volatile object, or
perform a synchronization operation or an atomic operation.
[Note: This is intended to allow compiler transformations such asremoval of empty loops, even when termination cannot beproven. —end note]
Some programmers find this liberty afforded to implementations tobe disadvantageous; seethis blog post for a discussion of the subject.
Rationale (October, 2015)
SG1 reaffirms the original intent of this specification.
(From submission#636.)
Subclause 6.9.2.2 [intro.races] paragraph 4 specifies:
All modifications to a particular atomic object M occur in someparticular total order, called themodification order of M. ...
Is the total order a strict total order? If not, modifications mayappear to occur simultaneously.
Suggested resolution:
Change in 6.9.2.2 [intro.races] paragraph 4 as follows:
All modifications to a particular atomic object M occur in someparticularstrict total order, called themodificationorder of M. ...
Change in 32.5.4 [atomics.order] paragraph 4 as follows:
There is a singlestrict total order S on allmemory_order::seq_cst operations, including fences, that satisfies thefollowing constraints. ...
Additional notes (November, 2024)
Forwarded to SG1 and LWG by decision of the CWG chair, viapaper issue 2137.
CWG 2024-11-08
If "modifications [...] occur in some particular total order", onecan equivalently define a strict or non-strict total order over them,as those are isomorphic. Phrases like "A is earlier than B in themodification order of M" plainly refer to <, not <=.
According to 6.3 [basic.def.odr] paragraph 5, it is possiblefor a static data member of a class template to be defined more thanonce in a given program provided that each such definition occurs in adifferent translation unit and the ODR is met.
Now consider the following example:
src1.cpp:
#include <iostream> int initializer() { static int counter; return counter++; } int g_data1 = initializer(); template<class T> struct exp { static int m_data; }; template<class T> int exp<T>::m_data = initializer(); int g_data2 = initializer(); extern int g_data3; int main() { std::cout << exp<char>::m_data << ", " << g_data1 << ", " << g_data2 << ", " << g_data3 << std::endl; return 0; }
src2.cpp:
extern int initializer(); int g_data3 = initializer(); template<class T> struct exp { static int m_data; }; template<class T> int exp<T>::m_data = initializer(); void func() { exp<char>::m_data++; }
The specializationexp<char>::m_data is implicitlyinstaniated in both translation units, hence (13.9.2 [temp.inst] paragraph 1) its initialization occurs. And for bothdefinitions ofexp<T>::m_data the ODR is met. Accordingto 6.9.3.2 [basic.start.static] paragraph 1:
Objects with static storage duration defined in namespace scope in thesame translation unit and dynamically initialized shall be initializedin the order in which their definition appears in the translationunit.
But forexp<T>::m_data we have two definitions. Doesit mean that bothg_data1 andg_data3 are guaranteedto be dynamically initialized beforeexp<char>::m_data?
Suggested Resolution:Insert the following sentence before the last two sentences of6.3 [basic.def.odr] paragraph 5:
In the case ofD being a static data member of a classtemplate the following shall also hold:
- for a given (not explicit) specialization ofD initialized dynamically(6.9.3.2 [basic.start.static]), the accumulated set of objects initialized dynamicallyin namespace scope before the specialization ofD shall be the same in everytranslation unit that contains the definition for this specialization.
Notes from 10/01 meeting:
It was decided that this issue is not linked toissue 270 and that there is no problem, becausethere is only one instantiation (see 5.2 [lex.phases] paragraph 8).
The subject line pretty much says it all. It's a possibility thathadn't ever occurred to me. I don't see any prohibition in thestandard, and I also don't think the possibility introduces any logicalinconsistencies. The proper behavior, presumably, would be to gothrough the list of already-constructed objects (not including thecurrent one, since its constructor wouldn't have finished executing)and destroy them in reverse order. Not fundamentally hard, and I'msure lots of existing implementations already do that.
I'm just not sure whether the standard was intended to support this, orwhether it's just that nobody else thought of it either. If theformer, then a non-normative note somewhere in6.9.3.2 [basic.start.static] might be nice.
Rationale (October 2004):
There is nothing in the Standard to indicate that this usageis prohibited, so it must be presumed to be permitted.
According to 6.9.3.2 [basic.start.static] paragraph 2,
Aconstant initializer for an objecto is an expressionthat is a constant expression, except that it may alsoinvokeconstexpr constructors foro and its subobjectseven if those objects are of non-literal class types [Note: such aclass may have a non-trivial destructor —end note].
This would be clearer if worded as something like,
Aconstant initializer for an objecto is an expressionthat would be a constant expression if everyconstexpr constructorinvoked foro and its subobjects were a constructor for a literalclass type.
Rationale (February, 2014):
CWG felt that the existing wording is clear enough.
6.9.3.3 [basic.start.dynamic] paragraph 2 says,
If a function contains a local object of static storage durationthat has been destroyed and the function is called during thedestruction of an object with static storage duration, theprogram has undefined behavior if the flow of control passesthrough the definition of the previously destroyed local object.
I would like to turn this behavior from undefined towell-defined behavior for the purpose of achieving a gracefulshutdown, especially in a multi-threaded world.
Background: Alexandrescu describes the “phoenixsingleton” inModern C++ Design. This is a classused as a function local static, that will reconstruct itself,and reapply itself to theatexit chain, if the programattempts to use it after it is destructed in theatexitchain. It achieves this by setting a “destructedflag” in its own state in its destructor. If the object islater accessed (and a member function is called on it), themember function notes the state of the “destructedflag” and does the reconstruction dance. The phoenixsingleton pattern was designed to address issues only insingle-threaded code where accesses among static objects can havea non-scoped pattern. When we throw in multi-threading, and thepossibility that threads can be running aftermainreturns, the chances of accessing a destroyed staticsignificantly increase.
The very least that I would like to see happen is to standardize whatI believe is existing practice: When an object is destroyed in theatexit chain, the memory the object occupied is left inwhatever state the destructor put it in. If this can only bereliably done for objects with standard layout, that would be anacceptable compromise. This would allow objects to set “I'mdestructed” flags in their state and then do somethingwell-defined if accessed, such as throw an exception.
A possible refinement of this idea is to have the compiler setup a 3-state flag around function-local statics instead of thecurrent 2-state flag:
We have the first two states today. We might choose to addthe third state, and if execution passes over a function-localstatic with “destroyed” state, an exception could bethrown. This would mean that we would not have to guaranteememory stability in destroyed objects of static duration.
This refinement would break phoenix singletons, and is notrequired for the~mutex()/~condition() I'vedescribed and prototyped. But it might make it easier for JoeCoder to apply this kind of guarantee to his own types.
Rationale (CWG 2023-05-12)
This is an extension that requires a paper targeted at EWG.
An operator expression can, according toClause 7 [expr]paragraph 2, require transformation into function call syntax. Thereference in that paragraph is to12.4 [over.oper], but it should be to12.2.2.3 [over.match.oper].
Rationale (04/99): The subsections12.4.2 [over.unary],12.4.3 [over.binary], etc. of the referencedsection are in fact relevant.
The C++ standard says in 7.2.1 [basic.lval], in paragraph 15:
an aggregate or union type that includes one of theaforementioned types among its members (including, recursively, amember of a subaggregate or contained union),
Note that it is a literal copy from the C standard, but this is ofcourse not the problem.
In C, union is not defined as an aggregate type. Therefore it isappropriate to say “aggregate or union.” But thingschanged in C++: aggregate type includes union type now (though not allunions are aggregates), and it becomes clear that the“union” in “aggregate or union” is redundantand should be deleted.
The above cited paragraph could be changed to:
an aggregate type that includes one of the aforementionedtypes among its members (including, recursively, a member of asubaggregate)
Rationale (October, 2006):
As noted in the issue, not all unions are aggregates, but thosethat are not aggregates still allow aliasing. That part of thespecification would be lost with the suggested change.
Historically, based on C's treatment, cv-qualification of non-classrvalues has been ignored in C++. With the advent of rvalue references,it's not quite as clear that this is desirable. For example, someimplementations are reported to printconst rvalue for thefollowing program:
const int bar() { return 5;}void pass_int(int&& i) { printf("rvalue\n");}void pass_int(const int&& i) { printf("const rvalue\n");}int main() { pass_int(bar());}
Rationale (August, 2010):
The current specification is as intended.
According to 7.2.1 [basic.lval] paragraph 1,
An xvalue is the result of certain kinds of expressionsinvolving rvalue references (9.3.4.3 [dcl.ref]).
However, there are now expressions not involving rvaluereferences whose results are xvalues, e.g., a member accessexpression in which the left operand is a prvalue.
Rationale (November, 2014):
The cited wording does not preclude other kinds ofexpressions that result in xvalues. This wording could beexpanded editorially if a more extensive coverage is desired.
According to 7.3.2 [conv.lval] paragraph 1, applying thelvalue-to-rvalue conversion to any uninitialized object results inundefined behavior. However, character types are intended to allowany data, including uninitialized objects and padding, to be copied(hence the statements in 6.8.2 [basic.fundamental] paragraph 1that “For character types, all bits of the object representationparticipate in the value representation” and in7.2.1 [basic.lval] paragraph 15 thatchar andunsigned char types can alias any object). Thelvalue-to-rvalue conversion should be permitted on uninitializedobjects of character type without evoking undefined behavior.
Rationale (February, 2021):
The Standard now clearly specifies the handling ofindeterminate values forunsigned charandstd::byte types; see6.7.5 [basic.indet].
Paragraph 3 of section 7.3.7 [conv.prom] contains a statementsaying that if a bit-field is larger than int or unsigned int, no integralpromotions apply to it. This phrase needs further clarification, as itis hardly possible to fugure out what it means. See below.
Assuming a machine with a size of general-purpose register equal 32bits (where a byte takes up 8 bits) and a C++ implementation where anint is 32 bits and a long is 64 bits. And the following snippet ofcode:
struct ExternalInterface { long field1:36, field2:28; }; int main() { ExternalInterface myinstance = { 0x100000001L, 0x12,}; if(myinstance.field1 < 0x100000002L) { //do something } }
Does the standard prohibit the implementation from promoting field1'svalue into two general purpose registers? And imposes a burden ofusing shift machine instructions to work with the field's value? Whatelse could that phrase mean?
Either alternative is implementation specific, so I don't understandwhy the phrase "If the bit-field is larger yet, no integral promotionsapply to it" made it to the standard.
Notes from 10/01 meeting:
The standard of course does not dictate what an implementationmight do with regard to use of registers or shift instructions inthe generated code. The phrase cited means only that a largerbit-field does not undergo integral promotions, and therefore itretains the type with which it was declared (long inthe above example). The Core Working Group judged that thiswas sufficiently clear in the standard.
Note that 11.4.10 [class.bit] paragraph 1indicates that any bits in excess of the size of the underlying typeare padding bits and do not participate in the value representation.Therefore thefield1 bit field in the above example is notcapable of holding the indicated values, which require more than 32 bits.
Section 7.3.11 [conv.fpint] paragraph 1 states:
An rvalue of a floating point type can be converted to an rvalue of aninteger type. The conversion truncates; that is, the fractional partis discarded.
Here, the concepts of “truncation” and“fractional part” seem to be used without precisedefinitions. When -3.14 is converted into an integer, is thetruncation toward zero or away from zero? Is the fractional part -0.14or 0.86? The standard seem to give no clear answer to these.
Suggested resolution:
Replace “truncates” with “truncates towardzero.”
Replace “the fractional part” with “thefractional part (where that ofx is definedasx-floor(x) for nonnegativexandx-ceiling(x) for negativex);” thereshould be a better wording for this, or the entire statement“that is, the fractional part is discarded” can be removed,once the meaning of “truncation” becomes unambiguous asabove.
Rationale (October, 2006):
The specification is clear enough: “fractional part”refers to the digits following the decimal point, so that -3.14converted toint becomes -3.
There is no normative requirement regarding the abilityof floating-point values to represent integer valuesexactly; however, 7.3.11 [conv.fpint] paragraph 2appears to implicitly rely on their ability to represent thevalues 0 and 1:
If the source type isbool, the valuefalse is converted tozero and the valuetrue is converted to one.
Rationale (October, 2015):
CWG felt that the cited passage should be read as indicatingthat converting true and false should have the same result asconverting 1 and 0 and thus do not imply a requirement thatthose values be represented exactly.
In the following code, I expect both "null" and "FALSE" to be nullpointer constants -- and that the code should compile and output thestring "int*" twice to cout:
#include <iostream>using namespace std;void foo(int* p){ cout << "int*" << endl;}int main(void){ const int null = 0; foo(null); const bool FALSE = false; foo(FALSE);}
ISO/IEC 14882-1998 7.3.12 [conv.ptr] states:
An integral constant expression rvalue of integer type that evaluatesto zero (called a /null pointer constant/) can be converted to apointer type.
Stroustrup appears to agree with me -- he states (3rd edition page 88):
In C, it has been popular to define a macro NULL to represent the zeropointer. Because of C++`s tighter type checking, the use of plain 0,rather than any suggested NULL macro, leads to fewer problems. If youfeel you must define NULL, use:const int NULL = 0;
However gcc 3.3.1 rejects this code with the errors:
bug.cc:17: error: invalid conversion from `int' to `int*' bug.cc:19: error: cannot convert `const bool' to `int*' for argument `1' to ` void foo(int*)'
I have reported this as a bug(http://gcc.gnu.org/bugzilla/show_bug.cgi?id=13867), but the gcc teamstates that 4.10 requires that a null pointer constant must be an rvalue-- and no implicit conversion from an lvalue to an rvalue is required(http://gcc.gnu.org/bugzilla/show_bug.cgi?id=396):
a null pointer constant is an integral constant expression rvalue thatevaluates to zero [4.10/1] in this case `null' is an lvalue. Thestandard does not specify that lvalue->rvalue decay happens here, so`null' is not a null pointer constant.
I disagree with the gcc teams interpretation -- I don't see why7.2.1 [basic.lval] doesn't apply:
Whenever an lvalue appears in a context where an rvalue is expected,the lvalue is converted to an rvalue;
The insertion of the word rvalue appears to have occurred duringstandardization -- it is not present in either Stroustrup 2nd edition orthe 3rd edition. Does the committee deliberately intend to exclude anlvalue as a null pointer constant by adding the word rvalue? If so, itleads to the rather bizarre fact that "null" is not a null pointerconstant, but "null + 0" is!
Notes from the March 2004 meeting:
We think this is just a bug in gcc. The const variable doesget converted to an rvalue in this context. This case is notreally any different than cases like
const int null = 0; int i = null;or
const int i = 1; int a[i];(which are accepted by gcc).No one would argue that the second lines of those examplesare invalid because the variables are lvalues, and yet the conversionsto rvalue happen implicitly for the same reason cited above -- thecontexts require an rvalue.
Currently both implicit (7.3.13 [conv.mem]) and explicit(7.6.1.9 [expr.static.cast]) conversions of pointers to members permitonly cases in which the type of the member is the same except forcv-qualification. It would seem reasonable to allow conversions inwhich one member type is a base class of the other. For example:
struct B { }; struct D: B { }; struct X { D d; }; struct Y: X { }; B Y::* pm = &X::d; // Currently ill-formed: type of d is D, not B
(See alsoissue 170.)
EWG 2022-11-11
The change is plausible, but needs a paper to EWG.
7.5.6 [expr.prim.lambda] paragraph 2 says,
A closure object behaves as a function object (22.10 [function.objects])...
This linkage to<functional> increases the dependencyof the language upon the library and is inconsistent with the definitionof “freestanding” in 16.4.2.5 [compliance].
Rationale (July, 2009):
The reference to 22.10 [function.objects] appears in a note, notin normative text, and is intended only to clarify the meaning of theterm “function object.” The CWG does not believe that thisreference creates any dependency on any library facility.
The following case is ill-formed:
int f (int&); void* f (const int&); int main() { int i; [=] ()-> decltype(f(i)) { return f(i); }; }
Thedecltype(f(i)) is not of the formdecltype((x)), and also not within the body of the lambda, sothe special rewriting rule doesn't apply. So, the call in the decltypeselects the first overload, and the call in the body selects thesecond overload, and there's no conversion fromvoid* toint, so the return-statement is ill-formed.
This pattern is likely to arise frequently because of theretrictions on deducing the return type from the body of the lambda.
Daveed Vandevoorde: The pattern may be common, but itprobably doesn't matter much in practice. It's most likely thatoverloaded functions that differ only in the cv-qualification of theirparameters will have related return types.
Rationale (October, 2009):
The consensus of the CWG was that this is not a sufficientlyimportant problem to warrant changing the existing specification.
According to 7.5.6 [expr.prim.lambda] paragraph 21,
When thelambda-expression is evaluated, the entitiesthat are captured by copy are used to direct-initialize eachcorresponding non-static data member of the resultingclosure object.
This apparently means that if the capture-defaultis to copy, entities captured by default, implicitly,are copied even in cases where the copy constructors ofsuch entities are explicit. It should be required thatsuch entities be captured explicitly instead.
See alsoissue 1020.
Rationale (August, 2010):
The behavior is according to the original design and issimilar to what would happen if the constructor of theclosure object initialized the members for the capturedentities usingmem-initializers. CWG did not seesufficient motivation to change the design.
The conditions under which a closure class has a conversion functionto a pointer-to-function type are given in 7.5.6 [expr.prim.lambda] paragraph 6:
The closure type for a non-genericlambda-expression withnolambda-capture has a public non-virtual non-explicit constconversion function to pointer to function...
Does this apply to a lambda whoselambda-capture is emptyby virtue of being an empty pack expansion? For example, is thefollowing well-formed?
#include <cstdlib> template <typename ...Args> void foo(Args ...args) { auto xf = [args ...] { }; std::atexit(xf); }
This is likely a violation of the rule in 13.8 [temp.res] paragraph 8,
If every valid specialization of a variadic template requires an emptytemplate parameter pack, the template is ill-formed, no diagnosticrequired.
Does this need to be clarified?
Rationale (September, 2013):
The statement in 7.5.6 [expr.prim.lambda] paragraph 6 is asyntactic constraint, not a semantic one. The example has alambda-capture, regardless of its expansion in a giveninstantiation. This is consistent with the intent expressed in13.7.4 [temp.variadic] paragraph 6:
WhenN is zero, the instantiation of the expansion produces an emptylist. Such an instantiation does not alter the syntactic interpretation ofthe enclosing construct...
According to 7.6.1.2 [expr.sub] paragraph 11,
No entity is captured by aninit-capture.
It should be made clearer that a variable, odr-used byaninit-capture in a nested lambda, is still captured by thecontaining lambda as a result of theinit-capture.
Rationale (October, 2015):
Subsequent edits have removed the offending phraseXS.
Consider the following example:
void f() { thread_local int n = 10; std::thread([&] { std::cout << n << std::endl; }).join(); }
This function prints0, because:
The lambda does not capturen
n is not initialized on the spawned threadprior to the invocation of the lambda.
Additional note, March, 2016:
SG1 discussed this issue and concluded that the issues should beresolved follows:
If the program would result in a capture by reference of a localthread-local variable, then it is ill-formed.
If the program has a capture by value of a local thread-local variable,then a copy of the value from the calling thread is captured (andinitialized in the calling thread, if necessary).
The rationale for #1 is that, if we allowed capture of localthread-locals, some programmers will have one intuition of what to expectand other programmers will have the opposite intuition. It's better toforbid both interpretations. We don't want to say simply that there isno capture by reference of thread-locals, because simply ignoringthe local thread-local might result in name-lookup finding a globalvariable by the same name, which would be very confusing.
Rationale (March, 2017):
Only automatic variables are captured. A lambda accessing a thread-localvariable would be ill-formed.
Currently function types with different language linkage are notcompatible, and 7.6.1.3 [expr.call] paragraph 1 makes it undefinedbehavior to call a function via a type with a different language linkage.These features are generally not enforced by most current implementations(although some do) between functions with C and C++ language linkage.Should these restrictions be relaxed, perhaps as conditionally-supportedbehavior?
Rationale (October, 2012):
CWG felt that this language design question would be betterconsidered by EWG.
EWG 2022-11-11
Any changes in this area should be pursued via a paper to EWG.
Should the determination of array bounds from an initializer, describedin 9.5.2 [dcl.init.aggr] paragraph 4, apply to creation of a temporaryarray using theT{expr} syntax? E.g., is thefollowing example well-formed?
typedef int ARR[]; int* p = ARR{1,2,3};
(See also issues1300,1307, and1326.)
Rationale (October, 2012):
The example is valid, according to the new wording of9.5.2 [dcl.init.aggr] paragraph 4.
According to 7.6.1.5 [expr.ref] paragraph 4,
IfE2 is declared to have type “reference toT,” thenE1.E2 is an lvalue...
This applies to rvalue reference types as well as to lvaluereference types, based on the rationale from Clause 7 [expr] paragraph 7that
In general... named rvalue references are treated as lvalues andunnamed rvalue references to objects are treated as xvalues...
Since a non-static data member has a name, it would appear mostnaturally to fall into the lvalue category. This makes sense as wellfrom the perspective that the target of such a reference does not bearany necessary correlation with the value category of the objectexpression; in particular, an xvalue object might have an rvaluereference member referring to a different object from which it wouldbe an error to move.
On the other hand, rvalue reference members have limited utilityand are likely only to occur as the result of template argumentdeduction in the context of perfect forwarding, such as using astd::pair to forward values. In such cases, afirstorsecond member of rvalue reference type would be mostnaturally treated as having the same value category as that of theobject expression. The utility of this usage may outweigh the safetyconsiderations that shaped the current policy.
Rationale (April, 2013):
The design of rvalue references in the language is complex, and CWG feltthat an attempt to change the existing rules to accommodate this case ran therisk of breaking other cases. Treating named rvalue reference members aslvalues, consistently with other named rvalue references, is also safer in thatit prevents the inadvertent theft of resources from an object to which sucha member refers.
Consider:
struct A { template<class T> static int X; }; template<class T> int A::X = T{}; A{}.X<int>; //error A::X<int>; //OK
Implementations seem to reject the class member access, despite7.6.1.5 [expr.ref] bullet 6.1 stating the contrary.
Rationale (November, 2016):
The specification is as intended.
7.6.1.8 [expr.typeid] paragraph 4 says,
Whentypeid is applied to atype-id, the result refersto astd::type_info object representing the type ofthetype-id. If the type of thetype-id is a referencetype, the result of thetypeid expression refers toastd::type_info object representing the referenced type. Ifthe type of thetype-id is a class type or a reference to aclass type, the class shall be completely-defined.
I'm wondering whether this is not overly restrictive. I can'tthink of a reason to require thatT be completely-definedintypeid(T) whenT is a class type. In fact,several popular compilers enforce that restrictionfortypeid(T), but not fortypeid(T&). Cananyone explain this?
Nathan Sidwell: I think this restriction is so that wheneverthe compiler has to emit a typeid object of a class type, it knowswhat the base classes are, and can therefore emit an array ofpointers-to-base-class typeids. Such a tree is necessary to implementdynamic_cast and exception catching (in a commonlyimplemented and obvious manner). If the class could be incomplete,the compiler might have to emit a typeid for incompleteFooin one object file and a typeid for completeFoo in anotherobject file. The compilation system will then have to make sure that(a) those compare equal and (b) the completeFoo getspriority, if that is applicable.
Unfortunately, there is a problem with exceptions that means therestill can be a need to emit typeids for incomplete class. Namely onecan throw a pointer-to-pointer-to-incomplete. To implement thematching of pointer-to-derived being caught by pointer-to-base, it isnecessary for the typeid of a pointer type to contain a pointer to thetypeid of the pointed-to type. In order to do the qualificationmatching on a multi-level pointer type, one has a chain of pointertypeids that can terminate in the typeid of an incomplete type. Youcannot simply NULL-terminate the chain, because one must distinguishbetween different incomplete types.
Dave Abrahams: So if implementations are still required tobe able to do it, for all practical purposes, why aren't we lettingthe user have the benefits?
Notes from the April, 2006 meeting:
There was some concern expressed that this might be difficult underthe IA64 ABI. It was also observed that while it is necessary tohandle exceptions involving incomplete types, there is no requirementthat the RTTI data structures be used for exception handling.
Rationale (2023-05-12)
This is an extension that requires a paper targeted at EWG,investigating any ABI concerns.
According to 7.6.1.8 [expr.typeid] paragraphs 2-3,
Whentypeid is applied to a glvalue whose type is apolymorphic class type (11.7.3 [class.virtual]), theresult refers to astd::type_info objectrepresenting the type of the most derived object(6.7.2 [intro.object]) (that is, the dynamic type) towhich the glvalue refers...
Whentypeid is applied to an expression otherthan a glvalue of a polymorphic class type, the resultrefers to astd::type_info object representing thestatic type of the expression.
The status of a glvalue of incomplete class type is notclear from this specification. Since it is not knownwhether an incomplete class type is polymorphic or not,the existing wording could be read either as giving thatcase undefined behavior or as falling into paragraph 3 andalways returning the static type.
The wording fordynamic_cast requires classtypes to be complete, as does paragraph 4, describingtypeid applied to atype-id.
Rationale (December, 2021):
The change was already applied via the editorialreview process, with approval from CWG at the2021-08-24 teleconference.
[Picked up by evolution group at October 2002 meeting.]
Is it okay for astatic_cast to drop exceptionspecifications?
void f() throw(int); int main () { static_cast<void (*)() throw()>(f); // Okay? void (*p)() throw() = f; // Error }
The fact that astatic_cast is defined, more or less,as an initialization suggests that a check ought to be made.
One tricky point: this is another case where the general rule thatthe reverse of an implicit cast is allowed as astatic_castbites you -- the reverse conversion doesn't drop exceptionspecifications, and so is okay. Perhaps this should be treatedlike casting away constness.
Mike Miller comments:I don't think that case can arise. According to 14.5 [except.spec],
An exception-specification shall appear only on a functiondeclarator in a function, pointer, reference, or pointer tomember declaration or definition.
We strengthened that inissue 87(voted to DR status in Copenhagen) to
An exception-specification shall appear only on a functiondeclarator for a function type, pointer to function type,reference to function type, or pointer to member functiontype that is the top-level type of a declaration ordefinition, or on such a type appearing as a parameter orreturn type in a function declarator.
As I read that, you can't put an exception-specification on thetype-id in a static_cast, which means that a static_cast canonly weaken, not strengthen, the exception specification.
The core WG discussed this at the 10/01 meeting and agreed.
Note (March, 2008):
The Evolution Working Group recommended closing this issue with nofurther consideration. See paper J16/07-0033 = WG21 N2173.
Consider:
struct X; // declared, but not defined int i; X* p = static_cast<X*>(static_cast<void*>(&i));
Is the value ofp unspecified per7.6.1.9 [expr.static.cast] paragraph 14?
A prvalue of type “pointer to cv1 void” can be convertedto a prvalue of type “pointer to cv2 T”, where T is anobject type and cv2 is the same cv-qualification as, or greatercv-qualification than, cv1. If the original pointer value representsthe address A of a byte in memory and A does not satisfy the alignmentrequirement of T, then the resulting pointer value is unspecified.Otherwise, ...
Is that a case where implementations have to possibly pessimize ause of an undefined class, because a later definition can have theworst possible properties? Such a situation can also occur forpointer-to-members of undefined classes.
CWG 2023-12-15
The resulting pointer value should be unspecified ifT isincomplete.
CWG 2024-03-18
CWG decided to reverse direction here and to constrainimplementations, not programs.
Proposed resolution [SUPERSEDED]:
Change in 7.6.1.9 [expr.static.cast] paragraph 14 and add bullets asfollows:
A prvalue of type “pointer to cv1 void” can be convertedto a prvalue of type “pointer to cv2 T”, where T is anobject type and cv2 is the same cv-qualification as, or greatercv-qualification than, cv1. If the original pointer value representsthe address A of a byte in memory, T is complete, andA does not satisfy the alignment requirement of T,then the resulting pointer value is unspecified. Otherwise, ...
Possible resolution:
Change in 7.6.1.9 [expr.static.cast] paragraph 14 and add bullets asfollows:
A prvalue of type “pointer to cv1 void” can be convertedto a prvalue of type “pointer to cv2 T”, where T is anobject type and cv2 is the same cv-qualification as, or greatercv-qualification than, cv1. If the original pointer value representsthe address A of a byte in memory, T is complete anywhere in the program, andA does not satisfy the alignment requirement of T,then the resulting pointer value is unspecified. Otherwise, ...
CWG 2024-06-28
It is understood that a pointer value of typeT* may pointto an object whose type is unrelated toT. (Any accessthrough such a pointer value is undefined behavior.) However, C++maintains the invariant that the address represented by a pointervalue of typeT* is always suitably aligned for aT.The rule that is the concern of this issue ensures the invariant holdseven when values of typeT* are obtained by casting, and notby taking the address of an object. For the latter case, theinvariant holds because an object cannot exist unless its storage isaligned suitably.
The rule that is the concern of this issue therefore primarilyconstrains user programs, not implementations. It is unclear which"pessimizations" are expected from casting to a type whose alignmentrequirements are unknown at the point of the cast. Note that theresulting unspecified pointer value may be an invalid pointer value(6.8.4 [basic.compound]), which is essentially useless. Note alsothat incomplete class types may have (minimum) alignment requirements,because analignment-specifier can appear inanelaborated-type-specifier.
Additional notes (January, 2025)
// Maybe in a different translation unit struct X; X *f(void *p) { return static_cast<X*>(p); } // translation unit boundary struct alignas(2) X { char c[2]; } x; void h(X *p) { // #1 } int main() { // Details of q aren't relevant here, the point is simply that this pointer is not properly aligned for an X. void *q = &x.c[1]; X *misaligned = f(q); h(misaligned); }
Consider the value ofp at #1. Ifp was obtainedby casting, the status quo rule in 7.6.1.9 [expr.static.cast] paragraph 13 says thatp has an unspecified pointer value ifmisaligned. That pointer value can be an invalid pointer value(6.8.4 [basic.compound] paragraph 3.4). An lvalue-to-rvalue conversionon an object storing an invalid pointer value isimplementation-defined (7.3.2 [conv.lval] paragraph 3.3), andthus might trap. There is no guarantee that invalid pointer valueshave a predictable bit-pattern.
Effectively, the implementation can assume thatp issuitably aligned atp.
It is not specified under what conditions an object pointer createdby converting a function pointer, as described in 7.6.1.10 [expr.reinterpret.cast] paragraph 8, will be safely-derived, particularly inlight of the conditionally-supported, implementation-defined nature ofsuch conversions.
Notes from the March, 2009 meeting:
If this is to be addressed, the result should not be as suggested,i.e., a requirement for implementation documentation appearing only in anote. At the least, such a requirement must be in normative text.
Rationale (July, 2009):
The definition of “safely-derived pointer” is clearly andexclusively formulated in terms of pointers to objects. So noimplementation is required to maintain safe pointer derivation throughconversion to and from a function-pointer type.
On the other hand, any garbage-collecting implementation is free totreat function pointers the same as object pointers for purposes ofcollection. This would provide the effect of safe pointer derivationthrough function-pointer types. An implementation is even free todocument this behavior, if it so chooses.
However, converting a pointer to a dynamically-allocated objectinto a function pointer would be a very strange and almost alwayspointless and unsafe thing to do. There is no need for the standard toencourage this sort of behavior, even to the extent of adding a notementioning the possibility.
During the discussion ofissue 799, whichspecified the result of usingreinterpret_cast to convert anoperand to its own type, it was observed that it is probably reasonableto allowreinterpret_cast between any two types that have thesame size and alignment.
Additional note, April, 2015:
It has been suggested that this question may more properly bethe province of EWG, especially in light of discussions duringthe resolution ofissue 330.
Rationale (May, 2015):
CWG agreed that this question should be considered from alanguage design perspective and is thus being referred to EWG.
Rationale (June, 2021):
EWG resolved to close this issue. Thebit_cast functionaddresses some of the use-cases. Supporting other use-cases wouldneed a paper.Seevote.
Consider this inconsistency:
void func(long l, float f) { (void)reinterpret_cast<long *>(&l); // ok (void)reinterpret_cast<long>(l); // ok (void)reinterpret_cast<float *>(&f); // ok (void)reinterpret_cast<float>(f); // ill-formed }
Suggested resolution:
Change in 7.6.1.10 [expr.reinterpret.cast] paragraph 2 as follows:
... An expression ofintegralarithmetic,enumeration, pointer, or pointer-to-member type can be explicitlyconverted to its own type; such a cast yields the value of itsoperand.
Rationale (November, 2016):
The specification is as intended.
7.6.2.2 [expr.unary.op] paragraph 2indicatesthat the type of an address-of-member expression reflects the class inwhich the member was declared rather than the class identified in thenested-name-specifier of thequalified-id. Thistreatment is unintuitive and can lead to strange code and unexpectedresults. For instance, in
struct B { int i; }; struct D1: B { }; struct D2: B { }; int (D1::* pmD1) = &D2::i; // NOT an errorMore seriously, template argument deduction can give surprisingresults:
struct A { int i; virtual void f() = 0; }; struct B : A { int j; B() : j(5) {} virtual void f(); }; struct C : B { C() { j = 10; } }; template <class T> int DefaultValue( int (T::*m) ) { return T().*m; } ... DefaultValue( &B::i ) // Error: A is abstract ... DefaultValue( &C::j ) // returns 5, not 10.
Suggested resolution:7.6.2.2 [expr.unary.op] should be changed toread,
If the member is a nonstatic member (perhaps by inheritance) of theclass nominated by thenested-name-specifier of thequalified-id having typeT, the type of the result is"pointer to member of classnested-name-specifier of typeT."and the comment in the example should be changed to read,
//has typeint B::*
Notes from 04/00 meeting:
The rationale for the current treatment is to permit the widestpossible use to be made of a given address-of-member expression.Since a pointer-to-base-member can be implicitly converted to apointer-to-derived-member, making the type of the expression apointer-to-base-member allows the result to initialize or be assignedto either a pointer-to-base-member or a pointer-to-derived-member.Accepting this proposal would allow only the latter use.
Additional notes:
Another problematic example has been mentioned:
class Base { public: int func() const; }; class Derived : public Base { }; template<class T> class Templ { public: template<class S> Templ(S (T::*ptmf)() const); }; void foo() { Templ<Derived> x(&Derived::func); //ill-formed }
In this example, even though the conversion of&Derived::func toint (Derived::*)() const ispermitted, the initialization ofx cannot be done becausetemplate argument deduction for the constructor fails.
If the suggested resolution were adopted, the amount of code brokenby the change might be reduced by adding an implicit conversion frompointer-to-derived-member to pointer-to-base-member for appropriateaddress-of-member expressions (not for arbitrary pointers to members,of course).
Additional notes (September, 2012):
Tomasz Kamiński pointed out three additional motivatingexamples:
struct Very_base { int a; }; struct Base1 : Very_base {}; struct Base2 : Very_base {}; struct Derived : Base1, Base2 {} int main() { Derived d; int Derived:: * a_ptr = &Derived::Base1::a; //error:Very_base ambiguous despite qualification };
Also:
struct Base { int a; }; struct Derived : Base { int b; }; template<typename Class, typename Member_type, Member_type Base:: * ptr> Member_type get(Class &c) { return c.*ptr; } void call(int (*f)(Derived &)); int main() { call(&get<Derived, int, &Derived::b>); // Works correctly call(&get<Derived, int, &Derived::a>); // Fails because&Derived::a returns anint Base::* // and no conversions are applied to pointer to member // (as specified in 13.4.3 [temp.arg.nontype] paragraph 5) call(&get<Base, int, &Derived::a>); //Template function is instantiated properly but has invalid type }
Finally:
struct Base { int a; }; struct Derived : private Base { public: using Base::a; //makea accessible }; int main() { Derived d; d.a; // valid int Derived::* ptr = &Derived::a; // Conversion fromint Base::* toint Derived::* // is ill-formed because the base class is inaccessible }
Rationale (October, 2012):
CWG felt that such a change to the existing semantics would be betterconsidered by EWG rather than as a defect.
Additional note, April, 2015:
EWG has determined that the utility of such a change is outweighedby the fact that it would break code. See EWG issue 89.
At least a couple of places in the IS state that indirectionthrough a null pointer produces undefined behavior: 6.9.1 [intro.execution] paragraph 4 gives "dereferencing the null pointer" as anexample of undefined behavior, and 9.3.4.3 [dcl.ref] paragraph 4(in a note) uses this supposedly undefined behavior asjustification for the nonexistence of "null references."
However, 7.6.2.2 [expr.unary.op] paragraph 1, which describesthe unary "*" operator, doesnot say that the behavior isundefined if the operand is a null pointer, as one might expect.Furthermore, at least one passage gives dereferencing a null pointerwell-defined behavior: 7.6.1.8 [expr.typeid] paragraph 2says
If the lvalue expression is obtained by applying the unary * operatorto a pointer and the pointer is a null pointer value (7.3.12 [conv.ptr]), thetypeid expression throws thebad_typeid exception (17.7.5 [bad.typeid]).
This is inconsistent and should be cleaned up.
Bill Gibbons:
At one point we agreed that dereferencing a null pointer wasnot undefined; only using the resulting value had undefinedbehavior.
For example:
char *p = 0; char *q = &*p;
Similarly, dereferencing a pointer to the end of an array should beallowed as long as the value is not used:
char a[10]; char *b = &a[10]; // equivalent to "char *b = &*(a+10);"
Both cases come up often enough in real code that they should beallowed.
Mike Miller:
I can see the value in this, but it doesn't seem to be wellreflected in the wording of the Standard. For instance, presumably*p above would have to be an lvalue in order to be theoperand of "&", but the definition of "lvalue" in7.2.1 [basic.lval] paragraph 2 says that "an lvalue refers toan object." What's the object in*p? If we were to allowthis, we would need to augment the definition to include the result ofdereferencing null and one-past-the-end-of-array.
Tom Plum:
Just to add one more recollection of the intent: I wasveryhappy when (I thought) we decided that it was only the attempt toactually fetch a value that creates undefined behavior. The wordswhich (I thought) were intended to clarify that are the first threesentences of the lvalue-to-rvalue conversion, 7.3.2 [conv.lval]:
An lvalue (7.2.1 [basic.lval]) of a non-function, non-arraytypeT can be converted to an rvalue. IfT is anincomplete type, a program that necessitates this conversion isill-formed. If the object to which the lvalue refers is not an objectof typeT and is not an object of a type derived fromT, or if the object is uninitialized, a program thatnecessitates this conversion has undefined behavior.
In other words, it is only the act of "fetching", oflvalue-to-rvalue conversion, that triggers the ill-formed or undefinedbehavior. Simply forming the lvalue expression, and then for exampletaking its address, does not trigger either of those errors. Idescribed this approach to WG14 and it may have been incorporated intoC 1999.
Mike Miller:
If we admit the possibility of null lvalues, as Tom is suggestinghere, that significantly undercuts the rationale for prohibiting "nullreferences" -- what is a reference, after all, but a named lvalue? Ifit's okay to create a null lvalue, as long as I don't invoke thelvalue-to-rvalue conversion on it, why shouldn't I be able to capturethat null lvalue as a reference, with the same restrictions on itsuse?
I am not arguing in favor of null references. I don't want them inthe language. What I am saying is that we need to think carefullyabout adopting the permissive approach of saying that it's all rightto create null lvalues, as long as you don't use them in certain ways.If we do that, it will be very natural for people to question why theycan't pass such an lvalue to a function, as long as the functiondoesn't do anything that is not permitted on a null lvalue.
If we want to allow&*(p=0), maybe we should changethe definition of "&" to handle dereferenced nullspecially, just astypeid has special handling, rather thanchanging the definition of lvalue to include dereferenced nulls, andsimilarly for the array_end+1 case. It's not as general, but I thinkit might cause us fewer problems in the long run.
Notes from the October 2003 meeting:
See alsoissue 315, which deals withthe call of a static member function through a null pointer.
We agreed that the approach in the standard seems okay:p = 0; *p; is not inherently an error. Anlvalue-to-rvalue conversion would give it undefined behavior.
Proposed resolution (October, 2004):
(Note: the resolution ofissue 453also resolves part of this issue.)
Add the indicated words to 7.2.1 [basic.lval] paragraph 2:
An lvalue refers to an object or functionor is an empty lvalue(7.6.2.2 [expr.unary.op]).
Add the indicated words to 7.6.2.2 [expr.unary.op] paragraph 1:
The unary* operator performsindirection: theexpression to which it is applied shall be a pointer to an objecttype, or a pointer to a function type and the result is an lvaluereferring to the object or function to which the expressionpoints, if any. If the pointer is a null pointer value(7.3.12 [conv.ptr]) or points one past the last elementof an array object (7.6.6 [expr.add]), the result is anempty lvalue and does not refer to any object or function.An empty lvalue is not modifiable. If the type of theexpression is “pointer toT,” the type ofthe result is “T.” [Note: a pointer to anincomplete type (other than cv void) can be dereferenced. Thelvalue thus obtained can be used in limited ways (to initialize areference, for example); this lvalue must not be converted to anrvalue, see 7.3.2 [conv.lval].—end note]
Add the indicated words to 7.3.2 [conv.lval] paragraph 1:
If the object to which the lvalue refers is not an object of typeT and is not an object of a type derived fromT, or if the object is uninitialized,or if thelvalue is an empty lvalue (7.6.2.2 [expr.unary.op]), aprogram that necessitates this conversion has undefined behavior.
Change 6.9.1 [intro.execution] as indicated:
Certain other operations are described in this InternationalStandard as undefined (for example, the effect ofdereferencingthe null pointerdivision by zero).
Note (March, 2005):
The 10/2004 resolution interacts with the resolution ofissue 73. We added wording to 6.8.4 [basic.compound] paragraph 3 to the effect that a pointer containingthe address one past the end of an array is considered to “pointto” another object of the same type that might be located there.The 10/2004 resolution now says that it would be undefined behavior touse such a pointer to fetch the value of that object. There is atleast the appearance of conflict here; it may be all right, but it atneeds to be discussed further.
Notes from the April, 2005 meeting:
The CWG agreed that there is no contradiction between thisdirection and the resolution ofissue 73.However, “not modifiable” is a compile-time concept, whilein fact this deals with runtime values and thus should produceundefined behavior instead. Also, there are other contexts in whichlvalues can occur, such as the left operand of.or.*, which should also be restricted. Additional draftingis required.
(See alsoissue 1102.)
CWG 2023-11-06
There is no consensus to pursue the introduction of empty lvalues,without prejudice to a potential future paper addressed to EWG. Theimplicit undefined behavior in 7.6.2.2 [expr.unary.op] paragraph 1is handled inissue 2823.
In 7.6.2.2 [expr.unary.op], part of paragraph 7 describeshow to compute the negative of an unsigned quantity:
The negative of an unsigned quantity is computed by subtractingits value from 2n, wheren is the numberof bits in the promoted operand. The type of the result is thetype of the promoted operand.
According to this method,-0U will get the value2n - 0 = 2n, wheren is the number of bits in an unsigned int. However,2n is obviously out of the range of valuesrepresentable by an unsigned int and thus not the actual valueof-0U. To get the result, a truncating conversionmust be applied.
Rationale (April, 2007):
As noted in the issue description, a “truncatingconversion” is needed. This conversion is supplied withoutneed of an explicit mention, however, by the nature of unsignedarithmetic given in 6.8.2 [basic.fundamental] paragraph 4:
Unsigned integers, declaredunsigned, shall obey the laws ofarithmetic modulo 2n wheren is the number ofbits in the value representation of that particular size ofinteger.
There does not seem to be any significant technical obstacle toallowing avoid* pointer to be dereferenced, and that wouldavoid having to use weighty circumlocutions when casting to areference to an object designated by such a pointer.
Rationale (June, 2014):
This request for a language extension should be evaluated by EWGbefore any action is taken.
EWG 2022-11-11
This is a request for a new feature, which should be proposed in apaper to EWG.
Paper P0913R0 proposed adding support for a symmetric-transfercapability intended to allow coroutines to be recursivelyresumed without consuming an unbounded amount of stack space.However, the current wording does not required this, onlysuggesting it in a note in bullet 5.1.1 of7.6.2.4 [expr.await]. This should be a normativerequirement.
Rationale (July, 2020):
This issue is essentially about implementation limits, whichare impossible to specify normatively, and it is inappropriateto specify the desired and forbidden implementation techniquesin the Standard.
According to 7.6.2.4 [expr.await] bullets 3.3 and 3.4,
Evaluation of anawait-expression involves thefollowing auxiliary types, expressions, and objects:
...
o is determined by enumerating the applicableoperator co_await functions for anargumenta (12.2.2.3 [over.match.oper]), andchoosing the best one through overload resolution(12.2 [over.match]). If overload resolution isambiguous, the program is ill-formed. If no viable functionsare found,o isa. Otherwise,o is acall to the selected function with theargumenta. Ifo would be a prvalue, thetemporary materialization conversion(7.3.5 [conv.rval]) is applied.
e is an lvalue referring to the result ofevaluating the (possibly-converted)o.
...
However, the temporary materialization conversionproduces an xvalue, not an lvalue. Shouldebe a glvalue instead of an lvalue?
Rationale (February, 2021):
The specification is as intended;o is convertedto an lvalue if it is an xvalue result of the temporarymaterialization conversion.e is used in both bullets3.7 and 3.8; if it were an xvalue instead of an lvalue,the call toawait_suspend could steale'sresources and leave the call toawait_resume witha defunct object, which would be undesirable.
The standard forbids a lambda from appearing in asizeofoperand:
Alambda-expression shall not appear in an unevaluated operand(Clause 7 [expr]).
(7.5.6 [expr.prim.lambda] paragraph 2). However, there appears to beno prohibition of the equivalent usage when a variable or data member has aclosure class as its type:
int main() { int i = 1; int j = 1; auto f = [=]{ return i + j;}; return sizeof(f); }
According to 7.5.6 [expr.prim.lambda] paragraph 3, the sizeof a closure class is not specified, so that it could vary betweentranslation units or between otherwise link-compatible implementations,which could result in ODR violations if the size is used as a templatenon-type argument, for example. Should the Standard forbid taking thesize of a closure class? Or should this simply be left as an ABI issue,as is done with other size and alignment questions?
Additional note, April, 2013:
It was observed that generic function wrappers likestd::function rely on the ability to make compile-timedecisions based on the size of the function object, and forbiddingthe application ofsizeof to closure classes would makethat unnecessarily difficult.
Rationale (April, 2013):
CWG agreed that the ODR and portability considerations were notsufficient to outweigh the utility of applyingsizeof toclosure classes as mentioned in the April, 2013 note and that theissues are more appropriately dealt with in an ABI specification.
According to 7.6.2.5 [expr.sizeof] paragraph 1,
Thesizeof operator shall not be applied to an expression that hasfunction or incomplete type, to an enumeration type whose underlying typeis not fixed before all its enumerators have been declared, to an array ofruntime bound, to the parenthesized name of such types, or to a glvaluethat designates a bit-field.
However, it is not possible to name the type of an array of runtimebound, neither bytypedef nordecltype, so thereference to “the parenthesized name of such types” shouldprecede rather than follow “to an array of unknown bound.”
Rationale (September, 2013):
Arrays of runtime bound were moved from the normative specificationto a proposed Technical Specification.
The current specification of thealignof operator(7.6.2.6 [expr.alignof]) allows it to be applied only to types,not to objects. Since thealign attribute may be appliedto objects, and since existing practice permits querying thealignment of objects, it should be considered whether to allowthis in Standard C++ as well.
Additional note, April, 2020:
Asurvey of currentimplementations shows that most have already implemented theextension; the example there illustrates one motivation for itsuse. The principle of least astonishment would suggest that itis surprising forsizeof andalignof to behavedifferently in this regard.
Additional note (April, 2022)
This is a request for an extension, which is pursued by paperP2152 (Querying the alignment of an object).
According to 7.6.2.7 [expr.unary.noexcept] paragraph 2,
The result of thenoexcept operator is a constant oftypebool and is an rvalue.
Obviously, the result should be a prvalue.
(See alsoissue 1642, which deals withmissing specifications of value categories.)
Notes from the September, 2013 meeting:
This issue is being handled editorially and is being placed in"review" status to ensure that the change has been made.
Rationale (February, 2014):
The change has been made editorially.
Section 11.4.11 [class.free] paragraph 4says:
If adelete-expression begins with a unary:: operator, thedeallocation function's name is looked up in global scope. Otherwise,if thedelete-expression is used to deallocate a class object whose statictype has a virtual destructor, the deallocation function is the one foundby the lookup in the definition of the dynamic type's virtual destructor(11.4.7 [class.dtor]).Otherwise, if thedelete-expression is used to deallocatean object of classT or array thereof, the static and dynamic types ofthe object shall be identical and the deallocation function's name islooked up in the scope ofT. If this lookup fails to find the name, thename is looked up in the global scope. If the result of the lookup is ambiguousor inaccessible, or if the lookup selects a placement deallocation function,the program is ill-formed.I contrast that with 7.6.2.8 [expr.new]paragraphs 16 and 17:
If thenew-expression creates an object or an array of objectsof class type, access and ambiguity control are done for the allocationfunction, the deallocation function (11.4.11 [class.free]), and the constructor(11.4.5 [class.ctor]). If thenew-expression creates an array of objects of classtype, access and ambiguity control are done for the destructor (11.4.7 [class.dtor]).I think nothing in the latter paragraphs implies that the deallocationfunction found is the same as that for a correspondingdelete-expression.I suspect that may not have been intended and that the lookup should occur"as if for adelete-expression".If any part of the object initialization described above terminatesby throwing an exception and a suitable deallocation function can be found,the deallocation function is called to free the memory in which the objectwas being constructed, after which the exception continues to propagatein the context of thenew-expression. If no unambiguous matching deallocationfunction can be found, propagating the exception does not cause the object'smemory to be freed. [Note: This is appropriate when the called allocationfunction does not allocate memory; otherwise, it is likely to result ina memory leak. ]
Rationale:
Paragraphs 16 through 18 are sufficiently correct and unambiguous aswritten.
Clause 7 [expr] paragraph 4appears togrant an implementation the right to generate code for a functioncall like
f(new T1, new T2)in the order
Suggested resolution: either forbid the ordering above or expandthe requirement for reclaiming storage to include exceptions thrownin all operations between the allocation and the completion of theconstructor.
Rationale (10/99): Even in the "traditional" ordering of the callsto allocation functions and constructors, memory can still leak. Forinstance, ifT1 were successfully constructed and then theconstruction ofT2 were terminated by an exception, thememory forT1 would be lost. Programmers concerned aboutmemory leaks will avoid this kind of construct, so it seemsunnecessary to provide special treatment for it to avoid the memoryleaks associated with one particular implementation strategy.
Looking upoperator new in anew-expressionuses a different mechanism from ordinary lookup. According to7.6.2.8 [expr.new] paragraph 9,
If thenew-expression begins with a unary::operator, the allocation function's name is looked up in theglobal scope. Otherwise, if the allocated type is a class typeT or array thereof, the allocation function's name islooked up in the scope ofT. If this lookup fails tofind the name, or if the allocated type is not a class type, theallocation function's name is looked up in the global scope.
Note in particular that the scope in which thenew-expression occurs is not considered. For example,
void f() { void* operator new(std::size_t, void*); int* i = new int; // okay? }
In this example, the implicit reference tooperatornew(std::size_t) finds the global declaration, even thoughthe block-scope declaration ofoperator new with adifferent signature would hide it from an ordinary reference.
This seems strange; either the block-scope declaration shouldbe ill-formed or it should be found by the lookup.
Notes from October 2004 meeting:
The CWG agreed that the block-scope declaration should not befound by the lookup in anew-expression. It would,however, be found by ordinary lookup if the allocation functionwere invoked explicitly.
Rationale
The wording correctly reflects the intent; block-scope allocationfunctions are not found by the special lookup inanew-expression.
Anew-expression that creates an object whose type is aspecialization ofstd::initializer_list initialized from aninitializer list results in undefined behavior if the object survivespast the end of the full-expression, when the lifetime of the underlyingarray object ends. Since such anew-expression is effectivelyuseless, should it be made ill-formed?
Notes from the October, 2012 meeting:
The consensus of CWG was that this usage should be ill-formed.
Rationale (February, 2013):
Because the library emulation ofstd::is_constructible usesunevaluatednew-expressions in the implementation, making anew ofstd::initializer_list ill-formed would give thewrong results for its constructibility. CWG determined that it would beacceptable to leave diagnosing of actual undefined behavior resulting fromsuch constructs to the discretion of the implementation.
It is currently undefined behavior to delete a derived-class objectvia a pointer to a base class unless the base class has a virtualdestructor. It has been suggesed that this could be allowed for astandard-layout class. If so, presumably the caveats about adeallocation function or non-trivial destructor found in 7.6.2.9 [expr.delete] paragraph 5 that currently apply to incomplete typeswould need to be extended to apply to the derived class in such cases.
Another objection that was raised is that such a change would make itmore difficult to extend C++ in the future to have global deallocationfunctions that can take the size of the object being deleted as anargument, as is currently possible for member deallocation functions.
Rationale (August, 2011):
The specification is as intended; changes to the restriction wouldneed to be considered in a larger context by EWG.
Additional note, April, 2015:
EWG has decided not to make a change in this area. See EWG issue99.
According to 7.6.2.9 [expr.delete] paragraph 10, deletion of anarray of a class with both sized and non-sized deallocation functions isnot required to call the sized version if the destructor is trivial:
If deallocation function lookup finds both a usual deallocation functionwith only a pointer parameter and a usual deallocation function with both apointer parameter and a size parameter, the function to be called isselected as follows:
If the type is complete and if, for the second alternative(delete array) only, the operand is a pointer to a class type with anon-trivial destructor or a (possibly multi-dimensional) array thereof, thefunction with two parameters is selected.
Otherwise, it is unspecified which of the two deallocation functionsis selected.
However, if only a sized deallocation function is specified as aclass-specific deallocation function, it is not clear how the sizeargument is to be determined if the class has a trivial destructor.
Rationale (November, 2016):
The adoption of paper P0035R4 has rendered this issue moot.
(From submission#597.)
PaperP3144R2(Deleting a Pointer to an Incomplete Type Should be Ill-formed),approved in June, 2024, only covered deletion of incomplete classtypes. However, enumeration types can also be incomplete per9.8.1 [dcl.enum] paragraph 6:
An enumeration whose underlying type is fixed is an incomplete typeuntil immediately after itsenum-base (if any), at which pointit becomes a complete type. An enumeration whose underlying type isnot fixed is an incomplete type until the closing } ofitsenum-specifier , at which point it becomes a complete type.
This leads to the following example that ought to be ill-formed as well:
enum E { zero = decltype(delete static_cast<E*>(nullptr), 0){} };
Suggested resolution:
Change in 7.6.2.9 [expr.delete] paragraph 4 as follows:
If thestatic type of the objectbeingtobe deleted has incompleteclasstype at the point ofdeletion, the program is ill-formed.
CWG 2024-09-13
Since an enumeration type can never have a user-declareddestructor, there is no risk of introducing undefined behavior thatwould be prevented by making deletions involving incompleteenumeration types ill-formed. In short, the situation discussed inthis issue is harmless and thus lacks rationale to be madeill-formed.
According to 7.6.3 [expr.cast] paragraph 4, one possibleinterpretation of an old-style cast is as astatic_castfollowed by aconst_cast. One would therefore expect thatthe expressions marked #1 and #2 in the following example would havethe same validity and meaning:
struct S { operator const int* (); }; void f(S& s) { const_cast<int*>(static_cast<const int*>(s)); // #1 (int*) s; // #2 }
However, a number of implementations issue an error on #2.
Is the intent that(T*)x should be interpreted assomething like
const_cast<T*>(static_cast<const volatile T*>(x))
Rationale (July, 2009):
According to the straightforward interpretation of the wording,the example should work. This appears to be just a compiler bug.
According to 7.6.4 [expr.mptr.oper] paragraph 6,
The result of a.* expression whose second operandis a pointer to a data member is of the same value category(7.2.1 [basic.lval]) as its first operand.
This is incorrect if the member has a reference type, inwhich case the result is an lvalue.
Rationale (September, 2010):
There are no pointers to member of reference type; see9.3.4.4 [dcl.mptr] paragraph 3.
An expression of the form pointer + enum (see paragraph 5) is not givenmeaning, and ought to be, given that paragraph 2 of this section makesit valid. Presumably, the enum value should be converted to an integralvalue, and the rest of the processing done on that basis. Perhapswe want to invoke the integral promotions here.
[Should this apply to (pointer - enum) too?]
Rationale (04/99):Paragraph 1 invokes "the usual arithmetic conversions" for operandsof enumeration type.
(It was later pointed out that the builtin operatorT* operator+(T*, ptrdiff_t)(12.5 [over.built] paragraph 13) isselected by overload resolution. Consequently, according to12.2.2.3 [over.match.oper] paragraph 7,the operand of enumeration type is converted toptrdiff_tbefore being interpreted according to the rules in7.6.6 [expr.add].)
Code that was portable in C90 and C++98 is no longer portable withthe introduction of data types longer thanlong; code thatcould previously castsize_t andptrdiff_t tolong without loss of precision (becauselong wasthe largest type) can no longer rely on that idiom.
The CWG discussed this during the Berlin (April, 2006) meeting. Thegeneral consensus was that this was unavoidable: there are validreasons for implementations to keeplong at a size less thanthat required for address arithmetic.
See paper J16/06-0053 = WG21 N1983, which also suggests thepossibility of required diagnostics for problematic cases as analternative to restricting the size ofsize_t andptrdiff_t.
Rationale (October, 2006):
This is not an area in which the Standard should override thedecisions of implementors who wish to maintain the size oflong for backward compatibility but need alargersize_t to deal with expanded address spaces. Also,diagnostics of the sort described are better treated as quality ofimplementation issues rather than topics for standardization.
According to 6.8 [basic.types] paragraph 4,
Theobject representation of an object of typeT isthe sequence ofNunsigned char objects taken up bythe object of typeT, whereN equalssizeof(T).
and 6.7.2 [intro.object] paragraph 5,
An object of trivially copyable or standard-layout type (6.8 [basic.types]) shall occupy contiguous bytes of storage.
Do these passages make pointer arithmetic (7.6.6 [expr.add] paragraph 5) within a standard-layout object well-defined (e.g., forwriting one's own version ofmemcpy?
Rationale (August, 2011):
The current wording is sufficiently clear that this usage ispermitted.
Consider
int main() { using IA = int[]; IA{ 1, 2, 3 } + 0; }
This appears to be ill-formed given the current wording, becausethe operand is already a prvalue, thus 7.2.1 [basic.lval] paragraph 6 does not apply and the array-to-pointer conversion is notapplied:
Whenever a glvalue appears as an operand of an operator that expects aprvalue for that operand, the lvalue-to-rvalue(7.3.2 [conv.lval]), array-to-pointer(7.3.3 [conv.array]), or function-to-pointer(7.3.4 [conv.func]) standard conversions are applied toconvert the expression to a prvalue.
This outcome might be an oversight in the resolution forissue 1232.
See alsoclang issue 54016.
Proposed resolution [SUPERSEDED]:
Change in 7.6.6 [expr.add] paragraph 1 as follows:
The additive operators + and - group left-to-right. The usualarithmetic conversions (7.4 [expr.arith.conv]) are performed foroperands of arithmetic or enumeration type.The array-to-pointerconversion (7.3.3 [conv.array]) is applied to an operand ofarray type.
CWG 2023-07-14
CWG is generally in favor of the proposed resolution, but theinteraction with the surrounding text needs to be checked in moredetail after a Working Draft reflecting the Varna straw polls hasbecome available.
Additional notes (September, 2023)
It is questionable which use-cases are being addressed by thischange. On the other hand,IA{1,2,3}[1] already works today(7.2.1 [basic.lval] paragraph 6 and 7.6.1.2 [expr.sub] paragraph 2). However, the latter does cause temporary lifetimeextension (6.7.7 [class.temporary] paragraph 6), whereas thenear-equivalent*(IA{1,2,3} + 1) does not.
Similar to the addition case, indirection through an array prvalueis also not valid today. Consistency seems desirable.
Related vendor tickets:clang,gcc.
Possible resolution (amended 2023-10-09):
Change in 7.6.2.2 [expr.unary.op] paragraph 1 as follows:
The unary* operator performs indirection.Thearray-to-pointer conversion (7.3.3 [conv.array]) is applied toan operand of array type; the possibly convertedItsoperand shall be a prvalue of type “pointer to T”, where Tis an object or function type. The operator yields an lvalue of type Tdenoting the object or function to which the operand points.
Change in 7.6.2.2 [expr.unary.op] paragraph 7 as follows:
Theunary+ operator performs promotion. Thearray-to-pointer conversion (7.3.3 [conv.array]) is applied toan operand of array type; the possibly converted operandofthe unary + operatorshall be a prvalue of arithmetic, unscopedenumeration, or pointer typeand the result is the value of theargument. Integralpromotion(7.3.7 [conv.prom]) is performed onintegral or enumeration operands.The type of the result is thetype of the promoted operand.The operator yields a prvalue of the type of the convertedoperand; the result is the value of the converted operand.
Change in 7.6.6 [expr.add] paragraph 1 as follows:
The additive operators + and - group left-to-right. Each operand shallbe a prvalue. If both operands have arithmetic or unscoped enumerationtype, the usual arithmetic conversions (7.4 [expr.arith.conv]) areperformed. Otherwise, if one operand has arithmetic or unscopedenumeration type, integral promotion is applied(7.3.7 [conv.prom]) to that operand.Thearray-to-pointer conversion (7.3.3 [conv.array]) is applied toan operand of array type. A converted or promoted operand isused in place of the corresponding original operand for the remainderof this section.
Change in 7.6.1.2 [expr.sub] paragraph 2 as follows:
... Theresult of the expressionE1[E2] is identical (by definition)tothat of*((E1)+(E2)), except that in the case of an array operand,the result is an lvalue if that operand is an lvalue and an xvalueotherwise.[ Note: The semantics ofE1[E2] differ fromthose of*((E1)+(E2)) also for-- end note ]
- lifetime extension of temporaries (6.7.7 [class.temporary]) and
- changing the active member of a union via assignment(11.5.1 [class.union.general]).
CWG 2023-09-15 (amended 2023-10-09)
CWG seeks advice from EWG whether support for array prvalueoperands should be added for the indirection operator, theunary+ operator, and the additive operators, along the linesof the wording above. Similar to the[] operator, thespecification of the indirection operation could be amended to yieldan xvalue if the argument is a prvalue. The unary+ operatoris occasionally used to force an array-to-pointer decay.
Forwarded to EWG viapaper issue 1633.
PaperP2865R2seeks to remove the array-to-pointer conversion for the operands ofrelational operators, wheras this issue suggests adding moreapplications for the array-to-pointer conversion. These twodirections appear not to be in harmony.
EWG 2023-11-07
The lack of support for the corner-case in this issue is not adefect given the direction in P2865 (Remove Deprecated ArrayComparisons from C++26).
A shift of zero bits should result in the left operandregardless of its sign. However, the current wording of7.6.7 [expr.shift] paragraph 2 makes it undefinedbehavior.
Notes from the February, 2016 meeting:
CWG felt that a reasonable approach might be todefine<<N as equivalent to multiplying by2N in all cases; see also the resolution ofissue 1457. The resolution ofthis question should also be coordinated with SG6 and SG12,as well as with WG14.
Rationale (February, 2019):
This issue is resolved by the adoption of paper P1236.
The relational operators have unspecified results when comparingpointers that refer to objects that are not members of the same objector elements of the same array (7.6.9 [expr.rel] paragraph 2,second bullet). This restriction (which dates from C89) stems fromthe desire not to penalize implementations on architectures withsegmented memory by forcing them essentially to simulate a flataddress space for the purpose of these comparisons. If such animplementation requires that objects and arrays to fit within a singlesegment, this restriction enables pointer comparison to be done simplyby comparing the offset portion of the pointers, which couldbemuch faster than comparing the full pointer values.
The problem with this restriction in C++ is that it forces users ofthe Standard Library containers to useless<T*> insteadof the built-in< operator to provide a total ordering onpointers, a usage that is inconvenient and error-prone. Can theexisting restriction be relaxed in some way to allow the built-inoperator to provide a total ordering? (John Spicer pointed out thatthe actual comparison for a segmented architecture need only supply atotal ordering of pointer values, not necessarily the completelinearization of the address space.)
Rationale (April, 2007):
The current specification is clear and was well-motivated.Analysis of whether this restriction is still needed should be donevia a paper and discussed in the Evolution Working Group rather thanbeing handled by CWG as an issue/defect.
Additional note, April, 2015:
EWG has decided not to make a change in this area. See EWG issue91.
According to 7.6.10 [expr.eq] paragraph 2, two functionpointers only compare equal if they point to the same function.However, as an optimization, implementations are currently aliasingfunctions that have identical definitions. It is not clear whether theStandard needs to deal explicitly with this optimization or not.
Rationale (February, 2012):
The Standard is clear on the requirements, and implementations arefree to optimize within the constraints of the “as-if”rule.
According to 7.6.10 [expr.eq] bullet 2.1,
Comparing pointers is defined as follows:
If one pointer represents the address of a complete object, andanother pointer represents the address one past the last element of adifferent complete object87, the result of the comparison isunspecified.
The use of the term “complete object” is confusing. Acomplete object is one that is not a subobject of any other object(6.7.2 [intro.object] paragraph 2), so this restriction apparentlydoes not apply to non-static data members. Is the following guaranteedto work?
struct S { int i[2]; int j[2]; }; constexpr bool check1() { S s = { { 1, 2 }, { 3, 4 } }; return &s.i[2] == &s.j[0]; } static_assert(check1(), "Guaranteed?");
In particular, is there a guarantee that there is no paddingbetween nonstatic data members of the same type?
Rationale (July, 2017):
CWG determined that the existing wording is correct: the result ofthe comparison is implementation-defined, but not unspecified, so theprogram is well-formed but the assertion is not guaranteed to pass.
P0145 caused this situation:
extern "C" void abort(); struct A { int i; int data[10000]; } a; A& aref() { a.i++; return a; } int main() { aref() = a; if (a.i != 0) abort(); }
Isa.i now required to be 0?
A related example is this:
int b; int& bref() { ++b; return b; } int main() { bref() = b; if (b != 0) abort(); }
Here,b is required to be 0 after the assignment, becausethe value computation of the RHS of the assignment is sequenced beforeany side-effects on the LHS. The difference in guaranteed behaviorbetween class and non-class types is disturbing.
Rationale (April, 2017):
Class copy assignment binds aconst T&, sotheA example actually yieldsa.i == 1 after theassignment.
Consider:
int* p = false; // Well-formed? int* q = !1; // What about this?>From 6.8.2 [basic.fundamental] paragraph 6:"As described below,bool values behave as integral types."
From 7.3.12 [conv.ptr] paragraph 1: "A nullpointer constant is an integral constant expression rvalue of integertype that evaluates to zero."
From 7.7 [expr.const] paragraph 1: "Anintegral constant-expression can involve only literals,enumerators, const variables or static members of integral orenumeration types initialized with constant expressions, ..."
In 5.13.2 [lex.icon]: No mention oftrue orfalse as an integer literal.
From 5.13.7 [lex.bool]:true andfalse are Boolean literals.
So the definition ofq is certainly valid, but thevalidity ofp depends on how the sentence in7.7 [expr.const] is parsed. Does it mean
If the latter, then(3.0 < 4.0) is a constant expression,which I don't think we ever wanted. If the former, though, we have theanomalous notion thattrue andfalse are not constantexpressions.
Now, you may argue that you shouldn't be allowed to convertfalse to a pointer. But what about this?
static const bool debugging = false; // ... int table[debugging? n+1: n];Whether the definition of table is well-formed hinges on whetherfalse is an integral constant expression.
I think that it should be, and that failure to make it so was justan oversight.
Rationale (04/99): A careful reading of7.7 [expr.const] indicates that all types ofliterals can appear in integral constant expressions, butfloating-point literals must immediately be cast to an integral type.
Does an explicit temporary of an integral type qualify as anintegral constant expression? For instance,
void* p = int(); // well-formed?
It would appear to be, sinceint() is an explicit typeconversion according to 7.6.1.4 [expr.type.conv] (at least, it'sdescribed in a section entitled "Explicit type conversion") and typeconversions to integral types are permitted in integral constantexpressions (7.7 [expr.const]). However, this reasoning issomewhat tenuous, and some at least have argued otherwise.
Note (March, 2008):
This issue should be closed as NAD as a result of the rewrite of7.7 [expr.const] in conjunction with the constexpr proposal.
Rationale (August, 2011):
As given in the preceding note.
According to 7.7 [expr.const] paragraph 1,
In particular, except insizeof expressions, functions,class objects, pointers, or references shall not be used, andassignment, increment, decrement, function-call, or commaoperators shall not be used.
Given a case like
enum E { e }; int operator+(int, E); int i[4 + e];
does this mean that the overloadedoperator+ is notconsidered (because it can't be called), or is it selected byoverload resolution, thus rendering the program ill-formed?
Rationale (April, 2005):
All expressions, including constant expressions, are subject tooverload resolution. The example is ill-formed.
typeidexpressions can never be constant, whether or not theoperand is a polymorphic class type. The result of theexpression is a reference, and thetypeinfo class that thereference refers to is polymorphic, with a virtualdestructor - it can never be a literal type.
Rationale (July, 2009):
The intent of this specification was that the address of such atypeinfo object could be treated as an address constant andthus usable in constant initialization (contrary to the statement inthe comment, the result oftypeid is an lvalue, not areference).
reinterpret_cast was forbidden in constant expressions toprevent type-punning operations at compile time. This may have beentoo strict, as there are uses for the operator that do not involvetype punning. For example, a portable implementation of theaddressof function could be written as aconstexprfunction ifreinterpret_cast could be used in a constantexpression.
Rationale (October, 2012):
Althoughreinterpret_cast was permitted in address constantexpressions in C++03, this restriction has been implemented in somecompilers and has not proved to break significant amounts of code.CWG deemed that the complications of dealing with pointers whose tpeschanged (pointer arithmetic and dereference could not be permitted onsuch pointers) outweighed the possible utility of relaxing the currentrestriction.
The following example appears to be ill-formed, although currentimplementations accept it:
template<bool> struct S { }; S<0> s;
The reason this is ill-formed is that the non-type templateargument is a converted constant expression of typebool(see 13.4.3 [temp.arg.nontype] paragraph 5), and the secondconversion in the implicit conversion sequence is a booleanconversion, which is not allowed in the conversion for aconverted constant expression (see 7.7 [expr.const] paragraph 3). Conversions in the other direction (frombool to integer types) are permitted here, since they'reintegral promotions.
Rationale (February, 2012):
The analysis is correct, and the example is ill-formed. Implementationsthat accept it are in error.
Some classes that would produce a constant when initialized byvalue-initialization are not considered literal types.For example:
struct A { int a; }; // non-constexpr default constructor struct B : A {}; // non-literal type constexpr int i = B().a; // OK, trivial constructor not called constexpr B b = b (); // error, constexpr object of non-literal type
Additional note (February, 2017):
This is effectivelyissue 644, which wasresolved once, then had its resolution backed out via the resolutionofissue 1071 (actual drafting inissue 981).
Rationale (February, 2021):
The adoption of paper P1331R2 (at the July, 2019 meeting)rendered the question in the issue moot, as the requirementthat a constexpr constructor initialize all its non-staticdata members was removed, so the defaultedBdefault constructor is now constexpr.
It appears that the current specification of constant expressions in7.7 [expr.const] pararaph 2 permits examples like
constexpr const char* p = "asdf"; constexpr char ch = p[2];
This seems unnecessarily complicated for both users andimplementers. If subscripting were defined directly, rather than interms of pointer arithmetic and indirection (seeissue 1213), we could still support the obvious casesof things like
constexpr char ch2 = "asdf"[2];
without requiring compilers and users to track the target ofaddress-constant pointers and references.
Rationale (October, 2012):
CWG was comfortable with this implication of the current wording.
Aconst integer initialized with a constant can beused in constant expressions, but aconst floatingpoint variable initialized with a constant cannot. This wasintentional, to be compatible with C++03 while encouraging theconsistent use ofconstexpr. Some people have foundthis distinction to be surprising, however.
It was also observed that allowingconst floatingpoint variables as constant expressions would be an ABI-breakingchange, since it would affect lambda capture.
One possibility might be to deprecate the use ofconstintegral variables in constant expressions.
Additional note, April, 2015:
EWG requested CWG to allow use ofconst floating-pointvariables in constant expressions.
Rationale (May, 2015):
CWG felt that the current rules should not be changed and thatprogrammers desiring floating point values to participate inconstant expressions should useconstexpr instead ofconst.
In an example like
extern int const x; struct A { constexpr A () { } int value = x; }; int const x = 123; constexpr A a;
it is not clear whether the constructor forA iswell-formed (because the initialization forx has notyet been seen) and whether that constant value is used in theinitialization ofa. There is implementation divergenceon these questions.
Rationale (June, 2014):
The requirements for aconstexpr constructor in9.2.6 [dcl.constexpr] do not require that an initializer beconstant at the point of definition, similar to the provision formutually-recursiveconstexpr functions, which require thatat least one of the functions will contain a reference to anot-yet-definedconstexpr function. Determination ofwhether an expression is constant or not is made in the context inwhich the expression appears, by which time the constant value ofx in the exampe above is known. CWG feels that thecurrent wording is clear enough that the example is well-formed.
There is implementation divergence on the handling oftypeid in constant expressions, for example:
static_assert(&typeid(int) == &typeid(int), ""); // #1
According to the current wording, it is unspecified whethertwo evaluations of thetypeid operator produce the sameresult, even thoughtypeid can be used in constantexpressions as long as its operand is not a glvalue of apolymorphic class type. Of particular concern is the case wheretypeid might be evaluated in different translationunits.
Rationale (November, 2014):
Because the result of two separate evaluations of thetypeid operator are not guaranteed to produce thesame result, the comparison in the example is not permitted ina constant expression.
Consider an example like:
constexpr int f() { return 5; } // function must be constexpr constexpr int && q = f(); // but result is not constant constexpr int const & r = 2; // temporary is still not constant int main() { q = 11; // OK const_cast< int & >( r ) = 3; // OK (temporary object is not ROMable) constexpr int && z = 7; // Error? Temporary does not have static storage duration? }
A constexpr reference must be initialized by a constantexpression (9.2.6 [dcl.constexpr] paragraph 9),yet it may refer to a modifiable temporary object.Such a temporary is guaranteed static initialization, but it's not ROMable.
A non-const constexpr reference initialized with an lvalueexpression is useful, because it indicates that theunderlying storage of the reference may be staticallyinitialized, or that no underlying storage is required atall.
When the initializer is a temporary, finding its address istrivial. There is no reason to declare any intent thecomputation of its address. On the other hand, an initialvalue is provided, and that is also required to be aconstant expression, although it's never treated as aconstant.
The situation is worse for local constexpr references. Theinitializer generates a temporary when the declaration isexecuted. The temporary is a locally scoped, uniqueobject. This renders constexpr meaningless, because althoughthe address computation is trivial, it still must be donedynamically.
C++11 constexpr references required initialization byreference constant expressions, which had to “designate anobject with static storage duration or a function”(C++11 7.7 [expr.const] paragraph 3). Atemporary with automatic storage duration granted by thereference fails this requirement.
C++14 removes reference constant expressions and the staticstorage requirement, rendering the program well-defined withan apparently defeated constexpr specifier. (GCC and Clangcurrently provide the C++11 diagnosis.)
Suggested resolution: a temporary bound to a constexprreference should itself be constexpr, implyingconst-qualified type. Forbid binding a constexpr referenceto a temporary unless both have static storage duration. (Inlocal scope, the static specifier fixes the issue nicely.)
Rationale (November, 2014):
This issue is already covered by 7.7 [expr.const] paragraph 4, which includes conversions and temporaries in theanalysis.
There is implementation variance in the treatment of the followingexample:
constexpr int f(int x) { return x; } int main() { struct { int x = f(x = 37); } constexpr a = { }; }
Is the assignment tox considered to satisfy the requirememtsof 7.7 [expr.const] bullet 2.17,
modification of an object (7.6.19 [expr.assign],7.6.1.6 [expr.post.incr], 7.6.2.3 [expr.pre.incr]) unless it isapplied to a non-volatile lvalue of literal type that refers to anon-volatile object whose lifetime began within the evaluationofe;
assuming thate is the full-expression encompassing theinitialization ofa?
Notes from the October, 2018 teleconference:
This kind of example was previously ill-formed but it wasinadvertently allowed by the change to the “non-vacuousinitialization” rule. That rule should be restricted toclass and array types, making this example again ill-formed.
Rationale, February, 2021:
The resolution of2256 makesclear that the lifetime ofx has not begun becauseits initialization is not yet complete, so the assignment isundefined behavior and thus ill-formed in a constantexpression.
According to Clause 8 [stmt] paragraph 3,
A name introduced by a declaration in acondition (either introducedby thedecl-specifier-seq or thedeclarator of the condition)is in scope from its point of declaration until the end of thesubstatements controlled by the condition. If the name is redeclared in theoutermost block of a substatement controlled by the condition, thedeclaration that redeclares the name is ill-formed.
This does not exempt class and enumeration names, which can ordinarilycoexist with non-type names in the same scope (_N4868_.6.4.10 [basic.scope.hiding] paragraph 2). However, there is implementation variance in the handlingof examples like:
void g() { if (int N = 3) { struct N { } n; // ill-formed but not diagnosed by some implementations } }
Should the rule forconditions be updated to allow for thiscase?
Rationale (April, 2018):
Hiding of tag names by non-type names was added for C compatibility.C does not support conditions, and there was no consensus to extend thetag/non-type hiding rules into contexts where C compatibility is notrequired.
According to Clause 8 [stmt] paragraph 3,
A name introduced by a declaration in a condition (either introduced bythedecl-specifier-seq or the declarator of the condition) is inscope from its point of declaration until the end of the substatementscontrolled by the condition. If the name is redeclared in the outermostblock of a substatement controlled by the condition, the declaration thatredeclares the name is ill-formed.
Should there be a similar rule about redeclaring names introducedbyinit-statements?
Notes from the April, 2018 teleconference:
CWG agreed that such a rule should be added.
Rationale (January, 2019):
Theinit-statement case is covered by6.4.3 [basic.scope.block] paragraph 3. These two referencesshould be harmonized and cross-referenced appropriately asan editorial change.
Consider:
template<typename Iter> void f(Iter a, Iter b) { const int v = 10; auto do_something = [&] (auto thing) { if constexpr (is_random_access_iterator<Iter> && is_integral<decltype(thing)>) *(a + 1) = v; }; do_something(5); do_something("foo"); }
Determining whetherv is captured requires instantiatingthe "if constexpr", but that results in a hard error for a statementthat will eventually be discarded.
Rationale (February, 2018):
These questions were resolved by the adoption of paper P0588R1 at theNovember, 2017 meeting.
The effect ofconstexpr if in non-templated code is primarilylimited to not requiring definitions for entities that are odr-used indiscarded statements. This eliminates a plausible implementation techiqueof simply skipping the tokens of a discarded statement. Should theStandard allow such an approach? One needed change might be to say thatall diagnosable rules become “no diagnostic required” insidediscarded statements.
Rationale (April, 2018):
The design was thoroughly discussed before adopting the feature andthe current specification was intentionally adopted. Any request for achange should go through the normal EWG process at this point.
The expansion of a range-basedfor in 8.6.5 [stmt.ranged] paragraph 1involves a declaration of the form
auto && __range = range-init;
However, it is not permitted to bind a reference to an array ofruntime bound (9.3.4.3 [dcl.ref] paragraph 5), even though itis intended that such arrays can be used in a range-basedfor.
Rationale (September, 2013):
Arrays of runtime bound were moved from the normative specificationto a proposed Technical Specification.
When jumping past initialization of a local static variable thevalue of the static becomes indeterminate. Seems like thisbehavior should be illegal just as it is for local variableswith automatic linkage.
Here is an example:
struct X { X(int i) : x(i) {} int x;};int f(int c) { if (c) goto ly; // error here for jumping past next stmt. static X a = 1;ly: return a.x; // either 1 or 0 depending on implementation.}
8.9 [stmt.dcl] P3 should be changed to:
A program that jumps from a point where a local variable with automaticor static storage duration is not in scope to a point where it is inscope isill-formed unless the variable has POD type (3.9) and is declared withoutan initializer (8.5).This would imply "static X a = 1;" should be flagged as an error.Note that this behavior a may be a "quality of implementation issue"which may be covered in 6.7 P4. Paragraph 4 seems to make thechoice of static/dynamic initialization indeterminate.Making this an error and thus determinate seems the correct thingto do since that is what is already required of automatic variables.
Steve Adamczyk:Some version of this may be appropriate, but it's common to have codethat is executed only the first time it is reached, and to havean initialization of a static variable inside such a piece of code.In such a case, on executions after the first there is indeed ajump over the declaration, but the static variable is correctlyinitialized -- it was initialized the first time the routinewas called.
void f() { static bool first_time = true; if (!first_time) goto after_init; static int i = g(); first_time = false; after_init: ... }
Rationale (October, 2004):
The CWG sees no reason to change this specification. Localstatic variables are different from automatic variables:automatic variables, if not explicitly initialized, can haveindeterminate (“garbage”) values, including traprepresentations, while local static variables are subject to zeroinitialization and thus cannot have garbage values.
The latitude granted to implementations regarding performingdynamic initialization of local static objects as if it werestatic initialization is exactly parallel to namespace scopeobjects (6.9.3.2 [basic.start.static]), as are the restrictionson programmer assumptions.
Subclause 8.9 [stmt.dcl] paragraph 2 specifies:
Upon each transfer of control (including sequential execution ofstatements) within a function from point P to point Q, all variableswith automatic storage duration that are active at P and not at Q aredestroyed in the reverse order of their construction. ... when afunction returns, Q is after its body.
Another description of the same rule is in a note in8.7.1 [stmt.jump.general] paragraph 2:
[Note 1: On exit from a scope (however accomplished), objectswith automatic storage duration (6.7.6.4 [basic.stc.auto]) thathave been constructed in that scope are destroyed in the reverse orderof their construction. ... —end note]
However, 12.2.2.3 [over.match.oper] paragraph 2 specifies:
... Therefore, the operator notation is first transformed to theequivalent function-call notation as summarized in Table 18 (where @denotes one of the operators covered in the specifiedsubclause). However, the operands are sequenced in the orderprescribed for the built-in operator (7.6 [expr.compound]).
Foroperator+= (for example), 7.6.19 [expr.assign] paragraph 1 requires:
... The right operand is sequenced before the left operand. ...
Now, consider an ABI with callee-cleanup and this example:
void operator<<(A, B); void operator+=(A, B); void (*p)(A, B) = cond ? &operator<< : &operator+=;
InA() << B(),A() is sequencedbeforeB(). InA() += B(),B() is sequencedbefore A(). For an ABI with callee-cleanup, this means thatoperator<< must destroy theB parameter beforetheA parameter, andoperator+= must destroytheA parameter before theB parameter.
But this means we have a problem emitting a call top(A(),B()): no order of parameter initialization guarantees thatparameters are destroyed in reverse construction order.
A possible implementation strategy would be to emit a seconddefinition ofoperator+= with the opposite parameterdestruction order that is used when naming the functionoperator+=, e.g. when taking the address or using functioncall notation (as opposed to using operator notation).
Another alternative is to relax the rules about destruction ofparameters in the reverse order of construction for the case of binaryoperator functions andoperator new andoperatordelete.
Additional notes (June, 2023)
Forwarded to EWG viapaper issue 1583,per decision of the CWG chair, to either affirm the intentof the rules (requiring a more involved implementation strategy asoutlined above) or agree on a relaxation of the rules for the order ofdestruction of parameters.
EWG 2023-11-07
Not a defect; the suggested implementation strategy is viable.
Because a definition is also a declaration, it might make senseto change uses of "declaration or definition" to simply "declaration".
Notes from the March 2004 meeting:
Jens Maurer prepared drafting for this issue, but we find ourselvesreluctant to actually make the changes. Though correct, they seemedmore likely to be misread than the existing wording.
Proposed resolution:
Remove in Clause 3 [intro.defs] “parameter”the indicated words:
an object or reference declared as part of a function declarationordefinition, or in the catch clause of an exception handler, thatacquires a value on entry to the function or handler; ...
Remove in 13.2 [temp.param] paragraph 10the indicated words:
The set of defaulttemplate-arguments available for use with atemplate declarationor definitionis obtained bymerging the default arguments from the definition (if in scope) andall declarations in scope in the same way default function argumentsare (...).
Remove in 13.8 [temp.res] paragraph 2 the indicated words:
A name used in a template declarationor definitionand that is dependent on atemplate-parameter is assumed not toname a type unless the applicable name lookup finds a type name or thename is qualified by the keywordtypename.
Remove in 13.8.4.1 [temp.point] paragraph 1 the indicated words:
Otherwise, the point of instantiation for such a specializationimmediately follows the namespace scope declarationordefinitionthat refers to the specialization.
Remove in 13.8.4.1 [temp.point] paragraph 3 the indicated words:
Otherwise, the point of instantiation for such a specializationimmediately precedes the namespace scope declarationordefinitionthat refers to the specialization.
Remove in 13.9.4 [temp.expl.spec] paragraph 21 the indicated words:
Default function arguments shall not be specified in a declarationor a definitionfor one of the following explicitspecializations:[Note: default function arguments may be specified in thedeclaration
- ...
or definitionof a member function of aclass template specialization that is explicitly specialized. ]
Remove in 13.10.3.6 [temp.deduct.type] paragraph 18the indicated words:
[Note: a defaulttemplate-argument cannot be specified in afunction template declarationor definition; ...]
Remove in 16.4.3.2 [using.headers] paragraph 3the indicated words:
A translation unit shall include a header only outside of any externaldeclarationor definition, and shall include theheader lexically before the first reference to any of the entities itdeclaresor first definesin that translation unit.
Rationale (October, 2004):
CWG felt that readers might misunderstand“declaration” as meaning “non-definitiondeclaration.”
According to 9.1 [dcl.pre] paragraph 3,
In asimple-declaration, the optionalinit-declarator-list can be omitted only when declaring a class(Clause 11 [class]) or enumeration (9.8.1 [dcl.enum]), that is, when thedecl-specifier-seq containseither aclass-specifier, anelaborated-type-specifierwith aclass-key (11.3 [class.name]), or anenum-specifier.
This does not allow for the new simplifiedfrienddeclaration syntax (11.8.4 [class.friend] paragraph 3), whichpermits the forms
Rationale (May, 2014):
Thefriend specifier can only appear in amember-declaration, which contains amember-declarator-list,not aninit-declarator-list.
Subclause 9.1 [dcl.pre] paragraph 1definessimple-declaration as:
simple-declaration : decl-specifier-seq init-declarator-listopt ; attribute-specifier-seq decl-specifier-seq init-declarator-list ; ...
However, 9.1 [dcl.pre] paragraph 2 then refers toasimple-declaration using a different producction:
Asimple-declaration ornodeclspec-function-declaration of the formattribute-specifier-seqopt decl-specifier-seqopt init-declarator-listopt ;is divided into three parts...
It appears that the latter redefines the grammarnon-terminalsimple-declaration in an inconsistent way.
Rationale (April, 2017):
The unification of the “in the form” pattern is confusing,so the question was based on a misunderstanding of the text.
11.5 [class.union] paragraph 3impliesthat anonymous unions in unnamed namespaces need not be declaredstatic (it only places that restriction on anonymous unions"declared in a named namespace or in the global namespace").
However, 9.2.2 [dcl.stc] paragraph 1saysthat "global anonymous unions... shall be declaredstatic."This could be read as prohibiting anonymous unions in unnamednamespaces, which are the preferred alternative to the deprecated useof static.
Rationale (10/99): An anonymous union in an unnamednamespace is not "a global anonymous union," i.e., it is not amember of the global namespace.
9.2.2 [dcl.stc] paragraph 7 seems out of place in thecurrent organization of the Standard:
The linkages implied by successive declarations for a givenentity shall agree. That is, within a given scope, eachdeclaration declaring the same variable name or the sameoverloading of a function name shall imply the samelinkage. Each function in a given set of overloadedfunctions can have a different linkage, however.[Example:...
The preceding two paragraphs onstatic andextern simply defer to 6.6 [basic.link] todescribe their interaction with linkage, so it seems appropriatefor this paragraph to move there as well so that all theinformation on linkage is in one place.
Rationale (May, 2015):
The material in 6.6 [basic.link] deals with linkageconcepts, while 9.2.2 [dcl.stc] is concerned withthe syntactic constructs in a program that result in thelinkages described in 6.6 [basic.link]. CWG felt thatthe referenced paragraph falls more into the latter categorythan the former.
In 9.2.3 [dcl.fct.spec], para. 3, the following sentence
A function defined within a classdefinition is an inlinefunction.
should, if I am not mistaken, instead be:
A function defined within a classdeclaration is an inlinefunction."
Notes from October 2002 meeting:
This is not a defect. Though there is a long history, goingback to the ARM, of use of the term "class declaration" to mean thedefinition of the class, we believe "class definition" is clearer.We have openedissue 379 to dealwith changing all other uses of "class declaration" to "classdefinition" where appropriate.
A customer reports that when he attempts to replace::operator new witha user-defined function, the standard library calls the defaultfunction by preference if the user-defined function is inline. Ibelieve that our compiler is correct, and that such a replacementfunction isn't allowed to be inline, but I'm not sure there'ssufficiently explicit language in the standard.
In general, of course, the definition of an inline function must bepresent in every translation unit where the function is called.(9.2.3 [dcl.fct.spec],par 4) It could be argued that this requirement doesn't quite addressreplacement functions: what we're dealing with is the odd case wherewe've already got one definition and the user is supplying a differentone. I'd like to see something specifically addressing the case of areplacement function.
So what do we have? I see discussion of requirement for a replacement::operator new in three places:16.4.5.6 [replacement.functions],17.6.3.2 [new.delete.single] par 2, and6.7.6.5 [basic.stc.dynamic] par2-3. I don't see anything explicitly saying that the replacementfunction may not be inline. The closest I can find is17.6.3.2 [new.delete.single] par 2,which says that "a C++ program may define a function with this functionsignature that displaces the default version defined by the C++Standard library". One might argue that "with this function signature"rules out inline, but that strikes me as a slight stretch.
Have I missed anything?
Andrew Koenig:I think you've turned up a problem in9.2.3 [dcl.fct.spec] paragraph 4. Consider:
// Translation unit 1 #include <iostream> extern void foo(void (*)()); inline void bar() { std::cout << "Hello, world!" << std::endl; } int main() { foo(bar); } // Translation unit 2 void foo(void (*f)()) { (*f)(); }
Are you really trying to tell me that this program is ill-formed becausethe definition of bar is not available in translation unit 2?
I think not. The actual words in9.2.3 [dcl.fct.spec] par 4 are
An inline function shall be defined in every translation unit inwhich it is used...and I think at in this context, ``used'' should be interpreted to meanthat foo is used only in translation unit 1, where it is converted toa value of type void(*)().
Notes from October 2003 meeting:
We don't think Andy Koenig's comment requires any action;"used" is already defined appropriately.
We agree that this replacement should not be allowed, butwe think it's a library issue (in the rules for allowedreplacements). Forwarded to library group; it's issue 404on the library issues list.
Is the following valid?
template <class T> void f(T) { typedef int x; typedef T x; } int main() { f(1); }
There is an instantiation where the function is valid. Is animplementation allowed to issue an error on the template declarationbecause the types on the typedef are not the same(9.2.4 [dcl.typedef])?
How about
typedef T x; typedef T2 x;?
It can be argued that these cases should be allowed because theyaren't necessarily wrong, but it can also be argued that there's noreason to write things like the first case above, and if sucha case appears it's more likely to be a mistake than some kindof intentional test that int and T are the same type.
Notes from the October 2003 meeting:
We believe that all these cases should be allowed, and thaterrors should be required only when an instance of the template isgenerated. The current standard wording does not seem todisallow such cases, so no change is required.
The grammar does not allow for a declaration of the form
using T = enum class E : int;
However, it is widely accepted by current implementations. Shouldthe rules be changed to accommodate this usage?
Rationale (November, 2014):
Atype-id is intended as a reference to a type, butthe opaque enumeration syntax is intended as a declaration, nota reference like anelaborated-type-specifier, so thecurrent rules are as intended.
Aconstexpr function is required to have literal argumentand return types. Consider an example like:
template <class T> struct B { constexpr B(T) { } }; struct A { B<A> b; };
WhetherB(A) isconstexpr depends on whetherA is literal, which depends on whetherB<A> isliteral, which depends on whetherB(A) isconstexpr.
Rationale (August, 2011):
The requirements apply to definitions, not declarations.
Consider the following example:
struct A { template <class T> constexpr void* f(T) { return nullptr; } A* ap = (A*)f(A()); template <class ...T> constexpr A() {} };
A default constructor template instance would recurse infinitelyvia the member initializer forA::ap. However, since it's atemplate, by 9.2.6 [dcl.constexpr] paragraph 6, that would justmean that the instance shouldn't be treated asconstexpr.
Is an implementation really expected to handle that? In effect, we haveto try to evaluate the expression and if that fails, nullify theconstexpr-ness of theA::A<>() instance, andre-examine the initializer with the new understanding of thatinstance?
Rationale (April, 2013):
In the cited example, the constructor isconstexpr; itsimply cannot be used in a constant expression. The error would bedetected at the time of such a use.
There does not appear to be language in the current wording statingthatconstexpr cannot be applied to a variable ofvolatile-qualified type. Also, the wording in 7.7 [expr.const] paragraph 2referring to “a non-volatile object defined withconstexpr” might lead one to infer that the combination ispermitted but that such a variable cannot appear in a constant expression.What is the intent?
Rationale (September, 2013):
The combination is intentionally permitted and could be used insome circumstances to force constant initialization.
Neither 9.2.6 [dcl.constexpr] nor 9.2.2 [dcl.stc]forbids combining thethread_local andconstexprspecifiers in the declaration of a variable. Should this combinationbe permitted?
Rationale (January, 2014):
Such an object could have mutable subobjects. Theconstexprspecifier guarantees static initialization.
[ The paragraph in question was removed by P2448R2, adopted at the July, 2022 meeting. ]
According to 9.2.6 [dcl.constexpr] paragraph 6,
If no specialization of the template would satisfy therequirements for a constexpr function or constexprconstructor when considered as a non-template function orconstructor, the template is ill-formed; no diagnosticrequired.
This should say “instantiated template specialization”instead of just “specialization” to clarify thatan explicit specialization is not in view here.
Given an example like:
struct S { constexpr S(): i(42) { } ~S(); int i; }; double x[S().i]; // Error
such aconstexpr constructor is completely useless, butthere doesn't appear to be anything in the current wording making itill-formed. Should it be?
Rationale (November, 2016):
Such constructors can be useful for guaranteeing static initializationof namespace-scope objects.
11.4 [class.mem] paragraph 2 says,
A class is considered a completely-defined object type (6.8 [basic.types]) (or complete type) at the closing} of theclass-specifier. Within the classmember-specification,the class is regarded as complete within function bodies, defaultarguments, andexception-specifications (including such thingsin nested classes). Otherwise it is regarded as incomplete within itsown classmember-specification.
In particular, the return type of a member function is not listedas a context in which the class type is considered complete; instead,that case is handled as an exception to the general rule in9.3.4.6 [dcl.fct] paragraph 6 requiring a complete type inthe definition of a function:
The type of a parameter or the return type for a function definitionshall not be an incomplete class type (possibly cv-qualified) unlessthe function definition is nested withinthemember-specification for that class (including definitionsin nested classes defined within the class).
These rules have implications for the use ofdecltype.(The following examples use the not-yet-accepted syntax for specifyingthe return type of a function after its declarator, but the questionsapply to the current syntax as well.) Consider:
struct deduced { int test() { return 0; } auto eval( deduced& d )->decltype( d.test() ) { return d.test(); } };
7.6.1.5 [expr.ref] paragraph 1 requires that the classtype of the object or pointer expression in a class member accessexpression be complete, so this usage is ill-formed.
A related issue is the use ofthis in adecltypespecifier:
struct last_one { int test() { return 0; } auto eval()->decltype( this->test() ) { return test(); } };
_N4868_.11.4.3.2 [class.this] paragraph 1 allows use ofthisonly in the body of a non-static member function, and the return typeis not part of thefunction-body.
Do we want to change the rules to allow these kinds ofdecltype expressions?
Rationale (February, 2008):
In the other cases where a class type is considered completewithin the definition of the class, it is possible to defer handlingthe construct until the end of the definition. That is not possiblefor types, as the type may be needed immediately in subsequentdeclarations.
It was also noted that the primary utility ofdecltype isin generic contexts; within a single class definition, othermechanisms are possible (e.g., use of a member typedef in both thedeclaration of the operand of thedecltype and to replacethedecltype itself).
The first bullet of 9.2.9.3 [dcl.type.simple] paragraph 4 says,
There are two clarifications to this specification that wouldassist the reader. First, it would be useful to have a notehighlighting the point that a parenthesized expression is neitheranid-expression nor a member access expression.
Second, the phrase “the type of the entity namedbye” is unclear as to whether cv-qualification in theobject or pointer expression is or is not part of that type.Rephrasing this to read, “thedeclared type of theentity,” or adding “(ignoring any cv-qualification in theobject expression or pointer expression),” would clarify theintent.
Rationale (February, 2008):
The text is clear enough. In particular, both of these points areillustrated in the last two lines of the example contrastingdecltype(a->x) anddecltype((a->x)): in theformer, the expression has no parentheses, thus satisfying therequirements of the first bullet and yielding the declared type ofA::x, while the second has parentheses, falling into thethird bullet and picking up theconst from the objectexpression in the member access.
Because type deduction for theauto specifier isdescribed in 9.2.9.7 [dcl.spec.auto] paragraph 6 asequivalent to the deduction that occurs in a call to a functiontemplate, the adjustment of the argument type fromA toA& specified in 13.10.3.2 [temp.deduct.call] paragraph3 is performed when the initializer is an lvalue. As a result, inthe following example,ra has the typeA& andnot, as might be expected,A&&:
class A { }; void f() { A a; auto&& ra = a; }
It is unclear whether this is surprising enough, and potentiallywidely-enough used, to warrant making an exception to the currentrules to handle this case differently.
Rationale (September, 2008):
It is important that the deduction rules be the same in thefunction andauto cases. The result of this examplemight be surprising, but maintaining a consistent model fordeduction is more important.
An initializer list is treated differently in deducing the type ofanauto specifier and in a function call. In9.2.9.7 [dcl.spec.auto] paragraph 6, an initializer list isgiven special treatment so thatauto is deduced as aspecialization ofstd::initializer_list:
Once the type of adeclarator-id has been determined accordingto 9.3.4 [dcl.meaning], the type of the declared variable usingthedeclarator-id is determined from the type of itsinitializer using the rules for template argument deduction. LetT be the type that has been determined for a variableidentifierd. ObtainP fromT by replacingthe occurrences ofauto with either a new invented typetemplate parameterU or, if the initializer is abraced-init-list (9.5.5 [dcl.init.list]), withstd::initializer_list<U>. The type deduced for thevariabled is then the deducedA determined usingthe rules of template argument deduction from a function call(13.10.3.2 [temp.deduct.call]), whereP is a functiontemplate parameter type and the initializer ford is thecorresponding argument.
In a function call, however, an initializer-list argument is anon-deduced context:
Template argument deduction is done by comparing each functiontemplate parameter type (call itP) with the type of the correspondingargument of the call (call itA) as described below. If removingreferences and cv-qualifiers fromP givesstd::initializer_list<P'> for someP' and the argument is an initializer list(9.5.5 [dcl.init.list]), then deduction is performed instead foreach element of the initializer list, takingP' as a functiontemplate parameter type and the initializer element as itsargument. Otherwise, an initializer list argument causes the parameterto be considered a non-deduced context (13.10.3.6 [temp.deduct.type]). [Example:
template<class T> void f(std::initializer_list<T>); f({1,2,3}); //T deduced toint f({1,"asdf"}); // error:T deduced to bothint andconst char* template<class T> void g(T); g({1,2,3}); // error: no argument deduced forT
This seems inconsistent, but it is not clear in which direction theinconsistency should be resolved. The use of an initializer list ina range-basedfor is an argument in favor of the9.2.9.7 [dcl.spec.auto] treatment, but the utility of thisdeduction in other contexts is not apparent.
Rationale (October, 2012):
CWG felt that this language design question would be betterconsidered by EWG.
Additional note, April, 2015:
EWG has decided not to make a change in this area. See EWG issue109.
The treatment of a declaration like the following is not clear:
auto (*f())() -> int; // #1
9.3.4.6 [dcl.fct] paragraph 2 appears to requiredetermining the type of the nested declarator
auto (*f()); // #2
which, because it does not have atrailing-return-type,would be ill-formed by (C++11) 9.2.9.7 [dcl.spec.auto]. (InC++14, anauto return type without atrailing-return-type is, of course, permitted.)
Rationale (September, 2013):
The intent of the C++11 wording is that the requirement for atrailing return type applies only at the top level of the declaratorto whichauto applies, not to each possible recursivestage in the declarator processing. Also, as noted, the issue becomesmoot with the changes enabling return type deduction.
Paper N3922 changed the rules for deduction from abraced-init-list containing a singleexpressionin a direct-initialization context. Should a correspondingchange be made fordecltype(auto)? E.g.,
auto x8a = { 1 }; //decltype(x8a) isstd::initializer_list<int> decltype(auto) x8d = { 1 }; // ill-formed, a braced-init-list is not an expression auto x9a{ 1 }; //decltype(x9a) isint decltype(auto) x9d{ 1 }; //decltype(x9d) isint
See alsoissue 1467, which alsoeffectively ignores braces around a singleexpression,this change would be parallel to that one, even though the primarymotivation fordelctype(auto) is in the return type of aforwarding function, where direct-initialization does not apply.
Rationale (November, 2014):
CWG felt that this was a question of language design and thus moreproperly dealt with by EWG.
EWG 2022-11-11
This is a request for a new feature, which should be proposed in apaper to EWG.
The Standard does not indicate whether an explicit specialization ofa function template can have a deduced return type. It seems a bit toomuch to require parsing the entire function body in order to tell whichtemplate is being specialized. In extreme cases, that could meandeferring access checks for the entire body of the function.
Rationale (May, 2015):
An explicit specialization with a deduced return type canonly match a template declared with a deduced return type,so the actual return type is not needed in order to matchthe explicit specialization with the template beingspecialized.
According to 9.2.9.7.1 [dcl.spec.auto.general] paragraph 15,
A function declared with a return type that uses aplaceholder type shall not be a coroutine(9.6.4 [dcl.fct.def.coroutine]).
This should also apply to coroutine lambdas.
Rationale (July, 2020):
No change is needed. The restriction applies to functions, andthe lambda'soperator() is a function.
Do we really need the&ref-qualifier? Wecould get the same behavior without it if we relaxed the restrictionon ref-qualified and non-ref-qualified overloads in the same set:
with the&ref-qualifier | without the&ref-qualifier |
struct S { void f(); }; | struct S { void f(); }; |
struct S { void f() &; }; | struct S { void f(); void f() && = delete; }; |
struct S { void f() &&; }; | struct S { void f() &&; }; |
struct S { void f() &; void f() &&; }; | struct S { void f(); void f() &&; }; |
The main objection I can see to this change is that we would losethe notational convenience of the&ref-qualifier,which would need to be replaced by a pair of declarations. We mightovercome this by still allowing a single& on a function(although it would not be aref-qualifier) as a synonym to anon-ref-qualified declaration plus a deleted ref-qualified declaration.
The biggest asymmetry between the implicit object parameter andregular parameters is not in reference binding but in type deduction.Consider:
template <class R, class C, class A> void f(R (C::*p)(A));
With these members:
struct S { void mv(std::string); void mr(std::string&); void ml(std::string&&); };
then
f(&S::mv); // deduces A = string f(&S::mr); // deduces A = string& f(&S::ml); // deduces A = string&&
On the other hand, with these members:
struct S { void mv(std::string); void mr(std::string) &; void ml(std::string) && };
then
f(&S::mv); // deduces C = S f(&S::mr); // illegal f(&S::ml); // illegal
To make templatef work with any pointer to memberfunction, I need three overloads off. Add cv-qualifiersand it's twelve overloads!
And then there is the interaction with concepts. Consider thistype:
struct Value { Value& operator=(const Value&) &; };
Is it, say,Regular? If so, will the following compile,and what is the outcome?
template <Regular T> void f() { T() = T(); } void g() { f<Value>(); }
IfValue is notRegular, that is a goodmotivation to avoid ever using&ref-qualifiersonoperator= (and probably on any member functions).
IfValue isRegular, then eitherf<Value>() doesn't compile, violating one of theprincipal motivations for concepts, or it callsValue::operator=on an rvalue, which was explicitly prohibited.
Rationale, March, 2009:
The CWG did not feel that the suggested change was a signficantimprovement over the existing specification.
The production forparameters-and-qualifiers is long and willbe even longer with the changes for the Transactional Memory TS. Itmight be beneficial to refactor it into more manageable chunks.
Rationale (May, 2015):
CWG felt that recent changes to the grammar are sufficient.
In deciding whether a construct is an object declaration ora function declaration, 9.3.3 [dcl.ambig.res] contains the followinggem: "In that context, the choice is between a function declaration[...] and an object declaration [...]Just as for the ambiguities mentioned in 8.10 [stmt.ambig],the resolution is toconsider any construct that could possibly be a declarationa declaration."
To what declaration do the last two "declarations" refer? Object,function, or (following from the syntax) possibly parameterdeclarations?
Notes from the 4/02 meeting:
This is not a defect. Section 9.3.3 [dcl.ambig.res] reads:
The ambiguity arising from the similarity between a function-stylecast and a declaration mentioned in 8.10 [stmt.ambig]can also occur in the contextof a declaration. In that context, the choice is between a functiondeclaration with a redundant set of parentheses around a parametername and an object declaration with a function-style cast as theinitializer. Just as for the ambiguities mentioned in8.10 [stmt.ambig], theresolution is to consider any construct that could possibly be adeclaration a declaration.
The wording "any construct" in the last sentence is not limitedto top-level constructs. In particular, the function declarationencloses a parameter declaration, whereas the object declarationencloses an expression. Therefore, in case of ambiguity betweenthese two cases, the declaration is parsed as a function declaration.
Consider the following program:
struct Point { Point(int){} }; struct Lattice { Lattice(Point, Point, int){} }; int main(void) { int a, b; Lattice latt(Point(a), Point(b), 3); /* Line X */ }
The problem concerns the line marked/* Line X */, which is anambiguous declarations for either an object or a function.The clause that governs this ambiguity is 9.3.3 [dcl.ambig.res] paragraph 1, and reads:
The ambiguity arising from the similarity between a function-stylecast and a declaration mentioned in 8.10 [stmt.ambig]can also occur in the contextof a declaration. In that context, the choice is between a functiondeclaration with a redundant set of parentheses around a parametername and an object declaration with a function-style cast as theinitializer. Just as for the ambiguities mentioned in8.10 [stmt.ambig], theresolution is to consider any construct that could possibly be adeclaration a declaration. [Note: a declaration can be explicitlydisambiguated by a nonfunction-style cast, by a= to indicateinitialization or by removing the redundant parentheses around theparameter name. ]
Based on this clause there are two possible interpretations of thedeclaration in line X:
Note that the last sentence before the "[Note:" is not much help,because both options are declarations.
Steve Adamczyk: a number of people replied to this postingon comp.std.c++ saying that they did not see a problem. Theoriginal poster replied:
I can't do anything but agree with your argumentation.So there is only one correct interpretation of9.3.3 [dcl.ambig.res] paragraph 1, but Ihave to say that with some rewording, the clause can be made a lotclearer, like stating explicitly that the entire declaration must betaken into account and that function declarations are preferred overobject declarations.
I would like to suggest the following as replacement for the current9.3.3 [dcl.ambig.res] paragraph 1:
The ambiguity arising from the similarity between a functionstylecast and a declaration mentioned in8.10 [stmt.ambig] can also occur in the contextof a declaration. In that context, the choice is between a functiondeclaration with a redundant set of parentheses around a parametername and an object declaration with a function-style cast as theinitializer. The resolution is to consider any construct that couldpossibly be a function declaration a function declaration. [Note: Todisambiguate, the whole declaration might have to be examined todetermine if it is an object or a function declaration.] [Note: adeclaration can be explicitly disambiguated by a nonfunction-stylecast, by a= to indicate initialization or by removing the redundantparentheses around the parameter name. ]
Notes from the 4/02 meeting:
The working group felt that the current wording is clear enough.
In an example like,
namespace N { enum E { X }; } struct S { S(N::E); }; S s(S(N::X));
the last line disambiguates as an (ill-formed) function declaration,because the restriction requiring unqualified parameter names issemantic, not syntactic. Should the language be changed to use thepresence of aqualified-id in this case as disambiguation?There is implementation divergence in the handling of this example.
Rationale (June, 2014):
CWG noted that the grammar change to allow disambiguation based onthe parameter name being qualified is large, so the cost outweighs therelatively small benefit for disambiguating this particular cornercase.
The disambiguation of a fragment like
(T())*x
whereT is a type andx is a variable, isunclear. Is it a cast to typeT() of the expression*x, or is it a binary operator* multiplying avalue-initializedT byx? Current implementationstreat it as the former, which is not helpful since the specifiedtype is a function type and thus always ill-formed.
Rationale (November, 2014):
According to 9.3.3 [dcl.ambig.res],T() is to betaken as a function type, so the cast interpretation iscorrect, and one of the examples in this section is very nearlyexactly this case.
Splitoff fromissue 453.
It is in general not possible to determine at compile time whethera reference is used before it is initialized. Nevertheless, there issome sentiment to require a diagnostic in the obvious cases that canbe detected at compile time, such as the name of a reference appearingin its own initializer. The resolution ofissue 453 originally made such uses ill-formed, but the CWG decidedthat this question should be a separate issue.
Rationale (October, 2005):
The CWG felt that this error was not likely to arise veryoften in practice. Implementations can warn about such constructs,and the resolution forissue 453 makesexecuting such code undefined behavior; that seemed to address thesituation adequately.
Note (February, 2006):
Recent discussions have suggested that undefined behavior bereduced. One possibility (broadening the scope of this issue toinclude object declarations as well as references) was to require adiagnostic if the initializer uses the value, but not just theaddress, of the object or reference being declared:
int i = i; // Ill-formed, diagnostic required void* p = &p; // Okay
Rationale (2023-05-12)
This is an extension whose detailed specification requires a papertargeted at EWG. Some implementations warn about the situation inquestion.
Consider the following example:
struct S { virtual void v() = 0; }; void f(S sa[10]); // permitted?
9.3.4.5 [dcl.array] paragraph 1 says that adeclaration like that ofsa is ill-formed:
T is called the arrayelement type; this typeshall not be a reference type, the (possibly cv-qualified) typevoid, a function type or an abstract class type.
On the other hand, 9.3.4.6 [dcl.fct] paragraph 3says that the type ofsa is adjusted toS*,which would be permitted:
The type of each parameter is determined from its owndecl-specifier-seq anddeclarator. Afterdetermining the type of each parameter, any parameter of type“array ofT” or “function returningT” is adjusted to be “pointer toT” or “pointer to function returningT,” respectively.
It is not clear whether the parameter adjustment trumps theprohibition on declaring an array of an abstract class type ornot. Implementations differ in this respect: EDG 2.4.2 andMSVC++ 7.1 reject the example, while g++ 3.3.3 and Sun Workshop 8accept it.
Rationale (April, 2005):
The prohibition in 9.3.4.5 [dcl.array] is absoluteand does not allow for exceptions. Even though such a type in aparameter declaration would decay to an allowed type, the prohibitionapplies to the type before the decay.
This interpretation is consistent with the resolution ofissue 337, which causes template typededuction to fail if such types are deduced. It was also observedthat pointer arithmetic on pointers to abstract classes is verylikely to fail, and the fact that the programmer used array notationto declare the pointer type is a strong indication that he/sheexpected to use subscripting.
According to 9.3.4.5 [dcl.array] paragraph 1,
In a declarationT D whereD has the form
D1 [constant-expressionopt]attribute-specifieropt
and the type of the identifier in the declarationT D1 is“derived-declarator-type-listT”, then thetype of the identifier ofD is an array type; if the type ofthe identifier ofD contains theautotype-specifier, the program is ill-formed.
This has the effect of prohibiting a declaration like
int v[1]; auto (*p)[1] = &v;
This restriction is unnecessary and presumably unintentional.
Note also that the statement that “the type of the identifierofD is an array type” is incorrect when the nesteddeclarator is not simply adeclarator-id. A similar problemexists in the wording of 9.5.4 [dcl.init.ref] paragraph 3 forfunction types.
Rationale (March, 2011):
The functionality of theauto specifier was intentionallyrestricted to simple cases; supporting complex declarators likethis was explicitly discussed and rejected when the feature wasadopted.
The runtime check for violating the maximum size of a stack-basedarray object is ill-advised. Many implementations cannot easilydetermine the available stack space, and checking against a fixed limitis not helpful.
Proposed resolution (September, 2013):
Change 9.3.4.5 [dcl.array] paragraph 1 as follows:
...Theexpression is erroneous if:
its value before converting...
its value is such that the size of the allocated object would exceedthe implementation-defined limitfor the maximum size of anobject (Annex Clause Annex B [implimits]);
...
...If theexpression is erroneous, an exception of a type thatwould match a handler (14.4 [except.handle]) oftypestd::bad_array_length (_N3690_.18.6.2.2 [bad.array.length]) isthrown[Footnote: Implementations are encouraged also tothrow such an exception if the size of the object would exceed theremaining stack space. —end footnote].
This resolution also resolvesissue 1675.
C-style variable-length arrays (which have been widely implementedas extensions to C++) permit a zero-length array. Similarly, arrayscreated bynew-expressions can have a length of zero. Forbiddingzero-length arrays of runtime bound is a gratuitous incompatibility.
Proposed resolution (September, 2013):
Change 9.3.4.5 [dcl.array] paragraph 1 as follows:
...Theexpression is erroneous if:
its value before converting tostd::size_t or, in thecase of an expression of class type, before application of the secondstandard conversion (12.2.4.2.3 [over.ics.user]) is less than
orequal tozero;...
If theexpression, after converting tostd::size_t, is acore constant expression and the expression is erroneousor its valueis zero, the program is ill-formed. If theexpression...std::bad_array_length (_N3690_.18.6.2.2 [bad.array.length]) is thrown.
An object of array typeIfN is zero, an object ofarray type has no elements. Otherwise, it contains a contiguouslyallocated non-empty set ofN subobjects of typeT. Thetype...
Rationale (February, 2014):
The specification was removed from the WP and moved into aTechnical Specification.
9.3.4.6 [dcl.fct] paragraph 2says:
If theparameter-declaration-clause is empty, the functiontakes no arguments. The parameter list(void) is equivalent tothe empty parameter list.Can a typedef to void be used instead of the type void in the parameterlist?
Rationale:The IS is already clear that this is not allowed.
There doesn't appear to be an explicit prohibition of a functiondeclaration of the form
auto f() -> decltype(f());
Presumably there should be.
Rationale (February, 2012):
As noted inissue 1433, the point ofdeclaration of the function name is after the complete declarator, i.e.,after the trailing return type, so the recursion posited in this issuecannot occur.
Paragraph 9 of says that extra default arguments added after ausing-declaration but before a call are usable in the call, while9.10 [namespace.udecl] paragraph 9says thatextra function overloads are not. This seems inconsistent, especiallygiven the similarity of default arguments and overloads.
Rationale (10/99): The Standard accurately reflects theintent of the Committee.
Consider:
struct A { int i; A() { void foo(int=i); } };
It's not clear whether that is well-formed or not. It usesthis, which might be thought of as a kind of parameter orlocal variable, which would make the default argument ill-formed.On the other hand, there doesn't seem to be a good reason to banthe code, either.
Rationale (February, 2012):
9.3.4.7 [dcl.fct.default] paragraphs 8-9 should be interpreted asmaking the example ill-formed.
The resolution ofissue 1214 makes itill-formed to use an initializer of the form({...}) with avariable of a non-class type. This can cause problems with amem-initializer of the form
constexpr cond_variable() : cond(PTHREAD_COND_INITIALIZER) {}
Ifpthread_cond_t is an array,PTHREAD_COND_INITIALIZER will be abraced-init-listand themem-initializer will be ill-formed.
Rationale (August, 2011):
A non-static data member initializer can be used in this case.
The semantics of a parenthesizedbraced-init-list are notclear, whether appearing as amem-initializer or standalone.
Rationale (February, 2012):
CWG feels that the semantics are sufficiently clear without anychanges to the current wording.
The resolution ofissue 1301 changed thestatus ofT{}, whereT is an aggregate, from beingvalue-initialization to being aggregate initialization. This changebreaks the description ofDefaultConstructible in16.4.4.2 [utility.arg.requirements] Table 19. LWG has opened an issue forthis (2170) but would like CWG to consider a core approach that wouldcategorizeT{} as value initialization, even whenTis an aggregate.
Rationale (April, 2013):
There is a distinction in the core language between aggregateinitialization and value initialization. For example, a class witha deleted default constructor can be list-initialized via aggregateinitialization but not value-initialized.
Paper P0960R3 enabled initialization of aggregates (includingarrays) from parenthesized expression lists. The rather similar caseof initializating a reference to an aggregate was not addressed. Forexample:
const int (&ra)[2](1,2);
There is no evidence in 9.5.1 [dcl.init.general] paragraph 16 thatsuch support is intended, nor does 9.5.4 [dcl.init.ref] paragraph 5admit an initializer other than a (single) expression for areference.
There is implementation divergence: gcc accepts, others reject.Also, the parenthesized aggregate initialization paper P0960R3apparently originally envisioned a textual transformation to abraced-init-list, which presumably would have permitted the example.It is not clear whether the reference case was intentionally oraccidentially dropped during the multiple revisions of the paper.
Additional notes (March, 2023)
Forwarded to EWG for consideration viacplusplus/papers#1494by decision of the CWG chair.
EWG 2023-05-11
It was noted that an attempt to initialize a reference-to-classtype using a parenthesizedexpression-list is also ill-formedeven if the class has a suitable constructor. Since parenthesizedaggregate initialization was modeled after a constructor invocationfor aspects such as temporary lifetimes, the status quo seemsconsistent in that regard.
The current wording of the WP allows aggregateinitialization of parameters in function calls. For example,12.2.4.2.6 [over.ics.list] paragraph 4 reads:
Otherwise, if the parameter has an aggregate type which canbe initialized from the initializer list according to therules for aggregate initialization (9.5.2 [dcl.init.aggr]), the implicit conversion sequence is auser-defined conversion sequence. [Example:
struct A { int m1; double m2; }; void f(A); f( {'a', 'b'} ); // OK:f(A(int,double)) user-defined conversion f( {1.0} ); // error: narrowing—end example]
The rules for aggregate initialization in 9.5.2 [dcl.init.aggr] paragraph 11allow braces to be elided in the initializer
In a declaration of the form
T x = { a };
It is not clear whether this phrasing should be interpretedas allowing brace elision only in asimple-declaration andthus not in a function argument or whether this restriction isinadvertent and should be removed.
Rationale (November, 2010):
The restriction is intentional. Support for aggregate initializationis principally intended for legacy code and C compatibility, not forcode written using the new facilities of the language.
Existing practice appears to be to allow C++03-style aggregateinitialization from a parenthesized string literal, e.g.,
struct S { char arr[4]; } s = {("abc")};
This should be standardized, to allow examples like
struct S { char arr[4]; }; void f(S); void g() { f({("abc")}); }
Rationale (October, 2012):
CWG agreed that this is already permitted by virtue of_N4567_.5.1.1 [expr.prim.general] paragraph 6.
The correct interpretation of an example like the followingis not clear:
struct A { int x[] = { 0 }; };
Should the initializer be considered as implicitly determiningthe omitted array bound?
Rationale (November, 2014):
The requirement for determining an omitted bound in anaggregate is that it be “initialized”(9.5.5 [dcl.init.list] paragraph 4); since thebrace-or-equal-initializer might, in fact, beignored in some or all uses of the class, it should not beconsidered as definitively initializing the member andthus does not determine the array bound. Clarification ofthis intent could be done editorially, but CWG felt thatno normative change was required.
There is implementation divergence with respect to anexample like:
constexpr int f(int &r) { r *= 9; return r - 12; } struct A { int &&temporary; int x; int y; }; constexpr A a1 = { 6, f(a1.temporary), a1.temporary }; // #1
Some implementations accept this code and others saythata1.temporary is not a constant expressionin the initializer at #1.
Rationale (July, 2019)
The example is valid; the constant evaluation is the entireinitializationconstexpr A a1, thus the temporary boundtoa1.temporary started its lifetime within the constantevaluation.
In section 9.5.4 [dcl.init.ref], paragraph 5, there isfollowing note:
Note: the usual lvalue-to-rvalue (4.1), array-to-pointer (4.2), andfunction-to-pointer (4.3) standard conversions are not needed, andtherefore are suppressed, when such direct bindings to lvalues are done.
I believe that this note is misleading. There should be either:
The problem:
int main(){ const int ci = 10; int * pi = NULL; const int * & rpci = pi; rpci = &ci; *pi = 12; // circumvent constness of "ci"}
int main(){ int * pi = NULL; const int * const & rcpci = pi; // 1 int i = 0; pi = &i; // 2 if (pi == rcpci) std::cout << "bound to lvalue" << std::endl; else std::cout << "bound to temporary rvalue" << std::endl;}
There has been discussion on this issue on comp.lang.c++.moderated monthago, seehttp://groups.google.pl/groups?threadm=9bed99bb.0308041153.1c79e882%40posting.google.comand there seems to be some confusion about it. I understand that note isnot normative, but apparently even some compiler writers are misled (tryabove code snippets on few different compilers, and using differentcompilation options - notably GCC 3.2.3 with -Wall -pedantic), thus itshould be cleared up.
My proposal is to change wording of discussed note to:
Note: result of every standard conversion is never an lvalue, andtherefore all standard conversions (clause 4) are suppressed, when suchdirect bindings to lvalues are done.
Rationale (April, 2005):
As acknowledged in the description of the issue, the referencedtext is only a note and has no normative impact. Furthermore, theexamples cited do not involve the conversions mentioned in the note,and the normative text is already sufficiently clear that the types inthe examples are not reference-compatible.
According to the logic in 9.5.4 [dcl.init.ref] paragraph 5,the following example should create a temporary array and bind thereference to that temporary:
const char (&p)[10] = "123";
That is presumably not intended (issue 450calls a similar outcome for rvalue arrays “implausible”).Current implementations reject this example.
Rationale (August, 2010):
The Standard does not describe initialization of array temporaries, soa program that requires such is ill-formed.
Note (October, 2010):
Although in general an object of array type cannot be initializedfrom another object of array type, there is special provision in9.5.3 [dcl.init.string] for doing so when the source object is astring literal, as in this example. The issue is thus being reopenedfor further consideration in this light.
Notes from the November, 2010 meeting:
The CWG agreed that the current wording appears to permit thisexample but still felt that array temporaries are undesirable.Wording should be added to disallow this usage.
Proposed resolution (November, 2010):
Change 9.5.4 [dcl.init.ref] paragraph 5 as follows:
...
If the initializer expression is a string literal(5.13.5 [lex.string]), the program is ill-formed.
Otherwise, a temporary of type...
(See alsoissue 1232, which argues infavor of allowing array temporaries.)
Rationale (March, 2011):
In consideration of the arguments made inissue 1232, CWG agreed to allow arraytemporaries and there is thus no reason to prohibit them in thiscase.
As described in the “additional note, January, 2012” inissue 1287, questions were raised regarding thetreatment of class prvalues in the original proposed resolution and theproposed resolution was revised (February, 2012) to address those concerns.The revised resolution raised its own set of concerns with regard toslicing and performance, however, and the issue was moved back to "review"status to allow further discussion.
At the April, 2013 meeting, it was decided to proceed with the originalresolution ofissue 1287 and split off theconcerns regarding class prvalues into this issue.
Notes from the September, 2013 meeting:
The resolution forissue 1604 results inindirect binding to a subobject and will no longer cause slicing.
Rationale (November, 2013):
CWG determined that, in light of the resolution ofissue 1604, no further change was necessary.
The current wording of the Standard appears to permit code like
void f(const char (&)[10]); void g() { f("123"); f({'a','b','c','\0'}); }
creating a temporary array of ten elements and binding the parameterreference to it. This is controversial and should be reconsidered.(See issues1058 and1232.)
Rationale (March, 2016):
Whether to support creating a temporary array in such cases isa question of language design and thus should be considered byEWG.
EWG 2022-11-11
The intent is adequately expressed in the specification.
The exposition of list initialization using an array in9.5.5 [dcl.init.list] paragraph 4 raises the question of whetheran empty initializer list is permitted, as declaration of an arraywith a zero bound is ill-formed.
Rationale (October, 2009):
The description is intended as an aid to understanding the concepts,not as a literal transformation that is performed. An implementationis permitted to allocate a zero-length array, even if such as arraycannot be decclared (e.g., via anew-expression).
Consider the example,
struct A { char c; }; void f (char d) { A a = { d + 1 }; }
This code is now ill-formed because of the narrowing conversionfrom theint result type of the addition, not because ofany real narrowing. This seems like an embarrassment for C++0x.It would be better not to get an error about any arithmeticinvolving non-constant operands just because it might overflow withsome values.
Rationale (November, 2010):
The CWG agreed that this behavior is unfortunate but felt thatit would be too difficult to formulate a satisfactory set of rulesfor handling complex expressions correctly for a small gain inutility (the user can simply add a cast in order to avoid theerror).
The Standard does not specify whetherstd::initializer_list may be an aggregate or not.Strictly speaking, the order of the bullets in 9.5.5 [dcl.init.list] paragraph 3 depends on the answer. The existenceof a constructor declaration in 17.11 [support.initlist]suggests that it is not an aggregate but does not say sodefinitively.
Rationale (February, 2012):
The presence of the constructor declaration in 17.11 [support.initlist]is sufficient to establish thatstd::initializer_list is not anaggregate.
Issue 1030 clarified that elements of aninitializer-list are evaluated in the order they are written, but doesthat also apply to implied expressions? That is, given:
struct A { A(); ~A(); }; struct B { B(int, const A& = A()); ~B(); }; struct C { B b1, b2; }; int main() { C{1,2}; }
Do we know that the firstB is constructed before thesecondA? I suppose that's what we want, even though itcomplicates exception region nesting since theAs needto live longer than theB subobject cleanups.
Rationale (October, 2012):
Because this is an expression, not a declaration, theAslive until the end of the full-expression.
Dealing with aggregate-initialized temporaries has been a bit of aheadache because unlike aggregate initialization of variables, eachelement initialization is not a full-expression, so various thingsbehave differently because they are in the context of initializing atemporary.
This can either be inconsistent with aggregateinitialization of a variable (in which each element is afull-expression) or inconsistent with list-initialization viaconstructor (in which each element is a subexpression).
Rationale (October, 2012):
The rules are acceptable as written; declaration and expressioncontexts are different.
The definition of a “narrowing conversion” in9.5.5 [dcl.init.list] paragraph 7 is couched in terms of thetype of the target. A conversion to a too-small bit-field shouldpresumably also be categorized as a narrowing conversion. (See alsoissue 1449.)
Additional note (August, 2012):
It was observed thatthe proposed narrowing error, unlike in other contexts, cannot becircumvented by adding a cast. The only way to avoid a narrowingerror would be to avoid using the brace syntax or to mask the valueto an appropriate width. Even the latter approach could conceivablyrequire an implementation to track the maximum number of bits neededby operations applied on top of the masked value, unless the maskingwere required to be at the top level of the initializer expression.
Rationale (October, 2012):
CWG felt that this was more of a language design question and wouldbe better considered by EWG.
Rationale (February, 2014):
EWG determined that no action should be taken on this issue.
The specification of list-initialization in 9.5.5 [dcl.init.list] paragraph 3has a bullet that reads,
Otherwise, if the initializer list has a single element of typeE and eitherT is not a reference type or itsreferenced type is reference-related toE, the object orreference is initialized from that element
It is not clear what is meant by being “ initialized from theelement.” If one assumes that it means “go back to9.5 [dcl.init] and follow the logic ladder there with theelement,” the logical result is that an initializer for a scalarcould be arbitrarily deeply nested in braces, with each trip throughthe 9.5 [dcl.init] / 9.5.5 [dcl.init.list] recursionpeeling off one layer. Presumably that is not intended.
Rationale (October, 2012):
The wording “a single element of typeE” excludesthe case of a nested braced initializer, because such an element hasno type.
If aninitializer_list object is copied and the copyis elided, is the lifetime of the underlying array object extended?E.g.,
void f() { std::initializer_list<int> L = std::initializer_list<int>{1, 2, 3}; // Lifetime of array extended? }
The current wording is not clear.
(See alsoissue 1299.)
Notes from the October, 2012 meeting:
The consensus of CWG was that the behavior should be the same,regardless of whether the copy is elided or not.
Rationale (November, 2016):
With the adoption of paper P0135R1, there is no longer any copyin this example to be elided.
The resolution ofissue 1467 now allowsfor initialization of aggregate classes from an object of the same type.Similar treatment should be afforded to array aggregates.
Notes from the June, 2014 meeting:
This is a request for extended language facilities and thus shouldbe evaluated by EWG.
EWG 2022-11-11
This is a request for a new feature that should be proposed in apaper to EWG.
According to 9.5.5 [dcl.init.list] bullet 7.3, animplicit conversion
from an integer type or unscoped enumeration type to afloating-point type, except where the source is a constantexpression and the actual value after conversion will fitinto the target type and will produce the original valuewhen converted back to the original type
is a narrowing conversion. There does not seem to be agood reason why a conversion from, for example, an unsignedchar value to a floating point value should be consideredto be narrowing, since floating point types should be ablerepresent all the values.
Rationale (November, 2014):
CWG felt that type-based (in contrast to value-based)restrictions such as this should not depend on theplatform-specific characteristics of the type, so thegeneral rule should apply.
According to 9.6.2 [dcl.fct.def.default] paragraph 2,
An explicitly-defaulted function may be declaredconstexpr only if itwould have been implicitly declared asconstexpr...
This is relevant for wrapper functions like
template<class T> struct wrap { T t; constexpr wrap() = default; constexpr wrap(const wrap&) = default; };
It is not clear how the new wording forconstexprmember functions of class templates in the proposed resolutionissue 1358 affects this:
If the instantiated template specialization of aconstexprfunction template or member function of a class template would fail tosatisfy the requirements for aconstexpr function orconstexpr constructor, that specialization is still aconstexpr function orconstexpr constructor, eventhough a call to such a function cannot appear in a constantexpression.
Rationale (April, 2013):
The specification is as intended. The defaulted constructor will beconstexpr if it can be, so it should not be explicitly declaredconstexpr in order to avoid the problems mentioned.
It would seem intuitively that a deleted function cannot throwan exception, but 9.6.3 [dcl.fct.def.delete] does not mention that.This could conceivably be useful in SFINAE contexts.
Rationale (November, 2010):
Any reference to a deleted function is ill-formed, so it doesn'treally matter whether they arenoexcept or not.
Whether or not structured bindings can be captured by a lambda and,if so, with what semantics, is unclear from the current wording.
Rationale (March, 2018):
Paper P0588R1, adopted at the October, 2017 meeting, answers thequestion by explicitly prohibiting such captures.
Although in most contexts “=expression”can be replaced by “{expression}”,enumerator-definitions accept only the “=”form. This could be surprising.
Additional note (October, 2009):
The Committee may wish to consider default arguments in this lightas well.
Rationale (August, 2010):
This suggestion was considered and rejected by EWG.
The text of 9.8.1 [dcl.enum] paragraph 2 explicitly forbidsunnamed scoped enumerations:
The optionalidentifier shall not be omitted in the declarationof a scoped enumeration.
There does not appear to be a good rationale for this restrictionsince a typedef name can be used to name the enumerators. It is alsoinconsistent with similar constructs. For example,
typedef enum class { e } E; E x = E::e;
is ill-formed, but
typedef struct { enum { s }; } S; int y = S::s;
is well-formed.
Rationale (August, 2011):
The use of typedef names for linkage purposes is intended forC compatibility and should not be extended to features that are notpart of the C subset of C++.
9.3.4 [dcl.meaning] paragraph 1 and Clause 11 [class] paragraph 11prohibit decltype-qualified declarators and class names,respectively. There is no such prohibition in 9.8.1 [dcl.enum]for enumeration names. Presumably that is an oversight that should berectified.
Rationale (February, 2021):
The resolution ofissue 2156includes the required prohibition.
I received an inquiry/complaint that you cannot re-open a namespace using aqualified name. For example, the following program is ok, but if you uncommentthe commented lines you get an error:
namespace A { namespace N {int a; } int b; namespace M {int c; }}//namespace A::N {// int d;//}namespace A { namespace M { int e; }}int main(){ A::N::a = 1; A::b = 2; A::M::c = 3;// A::N::d = 4; A::M::e = 5;}
Andrew Koenig:There's a name lookup issue lurking here. For example:
int x; namespace A {int x;namespace N { int y;}; } namespace A::N { int* y = &x; // which x? }
Jonathan Caves:I would assume that any rule would state that:
namespace A::B {would be equivalent to:
namespace A { namespace B {so in your example 'x' would resolve to A::x
BTW: we have received lots of bug reports about this "oversight".
Lawrence Crowl:Even worse is
int x; namespace A { int x; } namespace B { int x; namespace ::A { int* y = &x; } }I really don't think that the benefits of qualified names here is worththe cost.
Notes from April 2003 meeting:
We're closing this because it's on the Evolution working grouplist.
Current implementations reject an example like:
namespace X { int n; } namespace A = X; namespace { namespace A = X; } int k = A::n;
This seems curious, since a similar example withusing-declarations or withalias-declarationsis valid.
Rationale (November, 2014):
The current wording of the Standard makes this exampleambiguous, and CWG did not find the similarities mentionedcompelling enough to warrant a change.
Daveed Vandevoorde:While readingCore issue 11I thought it implied the followingpossibility:
template<typename T> struct B { template<int> void f(int); }; template<typename T> struct D: B<T> { using B<T>::template f; void g() { this->f<1>(0); } // OK, f is a template };
However, the grammar for ausing-declaration reads:
andnested-name-specifier never ends in "template".
Is that intentional?
Bill Gibbons:
It certainly appears to be, since we have:
Rationale (04/99): Any semantics associated with thetemplate keyword inusing-declarations should be considered anextension.
Notes from the April 2003 meeting:
We decided to make no change and to close this issue as not-a-defect.This is not needed functionality; the example above, for example, canbe written with->template. This issue has been on theissues list for years as an extension, and there has been no clamorfor it.
It was also noted that knowing that something is a template isnot enough; there's still the issue of knowing whether it is aclass or function template.
Additional note (February, 2011):
This issue is being reopened for further consideration afteradditional discussion
using T::template X; // ill-formed
for a class template memberX of base classT,one could write
template<U> using X = typename T::template X<U>;
Rationale (March, 2011):
There was insufficient motivation for a change at this point.
9.10 [namespace.udecl]says,
Ausing-declaration shall not name atemplate-id.It is not clear whether this prohibition applies to the entity forwhich theusing-declaration is a synonym or to any name thatappears in theusing-declaration. For example, is thefollowing code well-formed?
template <typename T> struct base {void bar (); }; struct der : base<int> {using base<int>::bar; // ill-formed ? };
Rationale (10/99):9.10 [namespace.udecl] paragraph 1says,"Ausing-declaration introduces a name..." It is the namethat is thus introduced that cannot be atemplate-id.
According to 9.10 [namespace.udecl] paragraph 17,
The base class members mentioned byausing-declaration shall be visible in the scope ofat least one of the direct base classes of the class wheretheusing-declaration is specified.
The rationale for this restriction is not clear and should bereconsidered.
Rationale (November, 2014):
The rule was introduced because the hiding of a base class memberby an intermediate derived class is potentially intentional andshould not be capable of circumvention by ausing-declarationin a derived class. The consensus of CWG preferred not to changethe restriction.
Additional note (November, 2020):
The changes in P1787R6, adopted at the November, 2020 meeting,removes the quoted wording, affirming the rationale in a differentmanner.
Now that the concept of "conditionally-supported" is available (seeN1564), perhapsasm should not be required of everyimplementation.
Rationale (October, 2004):
This is covered in paper N1627. We would like to keepasm as a keyword for all implementations, however, toenhance portability by preventing programmers from inadvertentlyusing it as an identifier.
[Picked up by evolution group at October 2002 meeting.]
How can we write a function template, or member function of a classtemplate that takes a C linkage function as a parameter when the functiontype depends on one of the template parameter types?
extern "C" void f(int); void g(char); template <class T> struct A { A(void (*fp)(T)); }; A<char> a1(g); // okay A<int> a2(f); // errorAnother variant of the same problem is:
extern "C" void f(int); void g(char); template <class T> void h( void (*fp)(T) ); int main() { h(g); // okay h(f); // error }
Somehow permit a language linkage to be specified as part of a functionparameter declaration. i.e.
template <class T> struct A { A( extern "C" void (*fp)(T) ); }; template <class T> void h( extern "C" void (*fp)(T) );Suggested resolution: (Bill Gibbons)
The whole area of linkage needs revisiting. Declaring callingconvention as a storage class was incorrect to beginwith; it should be a function qualifier, as in:
void f( void (*pf)(int) c_linkage );instead of the suggested:
void f( extern "C" void (*pf)(int) );I would like to keep calling convention on the "next round" issues list,including the alternative of using function qualifiers.
And to that end, I suggest that the use of linkage specifiers to specifycalling convention be deprecated - which would make any use of linkagespecifiers in a parameter declaration deprecated.
Martin Sebor:9.12 [dcl.link], paragraph 4 says that "Alinkage-specification shall occur only in namespace scope..." I'mwondering why this restriction is necessary since it prevents, amongother things, the use of the functions defined<cmath>in generic code that involves function objects. For example, theprogram below is ill-formed sincestd::pointer_to_binary_function<> takes a pointer to afunction with extern "C++" linkage which is incompatible with the typeof thedouble overload ofstd::pow.
Relaxing the restriction to allow linkage specification indeclarations of typedefs in class scope would allowstd::pointer_to_binary_function<> ctor to be overloadedon both types (i.e., extern "C" and extern "C++"). An alternativewould be to allow for the linkage specification to be deduced alongwith the type.
#include <cmath> #include <functional> #include <numeric> int main () { double a[] = { 1, 2, 3 }; return std::accumulate (a, a + 3, 2.0, std::pointer_to_binary_function<double, double, double>(std::pow)); }
Rationale (February, 2014):
EWG determined that no action should be taken on this issue.
Issue 1
9.12 [dcl.link]paragraph 6 says the following:
extern "C" int f(void); namespace A { extern "C" int f(void); }; using namespace A; int i = f(); // Ok because only one function f() or // ill-formedFor name lookup, both declarations of f are visible and overloading cannotdistinguish between them. Has the compiler to check that these functionsare really the same function or is the program in error?
Rationale: These are the same function for all purposes.
Issue 2
A similar question may arise with typedefs:
// vendor A typedef unsigned int size_t; // vendor B namespace std { typedef unsigned int size_t; } using namespace std; size_t something(); // error?Is this valid because the typedefsize_t refers to the same type in bothnamespaces?
Rationale (04/99):In9.9.4 [namespace.udir]paragraph 4:
If name lookup finds a declaration for a name in two differentnamespaces, and the declarations do not declare the same entity and donot declare functions, the use of the name is ill-formed.The termentity applied to typedefs refers to the underlying typeor class (6.1 [basic.pre] paragraph 3); thereforeboth declarations ofsize_t declare the same entity andthe above example is well-formed.
[Picked up by evolution group at October 2002 meeting.]
Steve Clamage:I can't find anything in the standard that prohibits a languagelinkage on an operator function. For example:
extern "C" int operator+(MyInt, MyInt) { ... }
Clearly it is a bad idea, you could have only oneoperator+with "C" linkage in the entire program, and you can't call the functionfrom C code.
Mike Miller:Well, you can'tname an operator function in C code, but ifthe arguments are compatible (e.g., not references), youcancall it from C code via a pointer. In fact, because the languagelinkage is part of the function type, you couldn't pass theaddress of an operator function into C code unless you coulddeclare the function to be extern "C".
Fergus Henderson:In the general case, for linkage to languages other than C,this could well make perfect sense.
Steve Clamage:
But is it disallowed (as opposed to being stupid), and if so, wherein the standard does it say so?
Mike Miller:I don't believe there's a restriction. Whether that is becauseof the (rather feeble) justification of being able to call anoperator from C code via a pointer, or whether it was simplyoverlooked, I don't know.
Fergus Henderson:I don't think it is disallowed. I also don't think there is any needto explicitly disallow it.
Steve Clamage:I don't think the standard is clear enough on this point. I'dlike to see a clarification.
I think either of these two clarifications would be appropriate:
extern "C" T operator+(T,T); // ok extern "C" T operator-(T,T); // ok extern "C" U operator-(U); // error, two extern "C" operator-
Mike Miller:I think the point here is that something like
extern "xyzzy" bool operator<(S&,S&)could well make sense, if language xyzzy is sufficiently compatible withC++, and the one-function rule only applies to extern "C", not to otherlanguage linkages. Given that it might make sense to have generallanguage linkages for operators, is it worthwhile to make an exceptionto the general rule by saying that you can have any language linkageon an operator function except "C" linkage? I don't like exceptions togeneral rules unless they're very well motivated, and I don't seesufficient motivation to make one here.
Certainly this capability isn't very useful. There are lotsof things in C++ that aren't very useful but just weren'tworth special-casing out of the language. I think this fallsinto the same category.
Mike Ball:I DON'T want to forbid operator functions within anextern "C". Rather I want to add operator functions to that sentencein paragraph 4 of9.12 [dcl.link] which reads
A C language linkage is ignored for the names of class members andthe member function type of class member functions.My reason is simple: C linkage makes a total hash of scope. Any "C" functionsdeclared with the same name in any namespace scope are the same function.In other words, namespaces are totally ignored.
This provision was added in toward the end of the standardization process,and was, I thought, primarily to make it possible to put the C libraryin namespacestd. Otherwise, it seems an unwarrented attack on thevery concept of scope. We (wisely) didn't force this on static memberfunctions, since it would essentially promote them to the global scope.
Now I think that programmers think of operator functions as essentiallypart of a class. At least for one very common design pattern they aretreated as part of the class interface. This pattern is the reason weinvented Koenig lookup for operator functions.
What happens when such a class definition is included, deliberately ornot, in an extern "C" declaration? The member operators continue towork, but the non-member operators can suddenly get strange andhard to understand messages. Quite possibly, they get the messagesonly when combined with other classes in other compilation units.You can argue that the programmer shouldn't put the class headerin a linkage delaration in the first place, but I can still find booksthat recommend putting `extern "C"' around entire header files,so it's going to happen.
I think that including operator functions in the general exclusion fromextern "C" doesn't remove a capability, rather it ensurs a capabilitythat programmers already think they have.
Rationale (10/00):
The benefits of creating an exception for operator functionswere outweighed by the complexity of adding another special caseto the rules.
Note (March, 2008):
The Evolution Working Group recommended closing this issue withno further consideration. See paper J16/07-0033 = WG21 N2173.
[Picked up by evolution group at October 2002 meeting.]
9.12 [dcl.link] paragraph 4says,
A C language linkage isignored for the names of class members and the member function types ofclass member functions.This makes good sense, since C linkage namestypically aren't compatible with the naming used for member functions atlink time, nor is C language linkage function type necessarily compatiblewith the calling convention for passingthis to a non-static memberfunction.
But C language linkage type (not name) for a static member function isinvaluable for a common programming idiom. When calling a C function thattakes a pointer to a function, it's common to use a private static memberfunction as a "trampoline" which retrieves an object reference (perhaps bycasting) and then calls a non-static private member function. If a staticmember function can't have a type with C language linkage, then a global orfriend function must be used instead. These alternatives expose more of aclass's implementation than a static member function; either the friendfunction itself is visible at namespace scope alongside the class definitionor the private member function must be made public so it can be called by anon-friend function.
Suggested Resolution: Change the sentence cited aboveto:
A C language linkage is ignored for the names of class members and themember function types ofnon-static class member functions.The exampleneed not be changed because it doesn't involve a static member function.
The following workaround accomplishes the goal of not exposingthe class's implementation, but at the cost of significantsuperstructure and obfuscation:
// foo.h extern "C" typedef int c_func(int); typedef int cpp_func(int); class foo { private: c_func* GetCallback(); static int Callback(int); }; // foo.cpp #include "foo.h" // A local pointer to the static member that will handle the callback. static cpp_func* cpp_callback=0; // The C function that will actually get registered. extern "C" int CFunk(int i) { return cpp_callback(i); } c_func* foo::GetCallback() { cpp_callback = &Callback; // Only needs to be done once. return &CFunk; }
Rationale (10/99): The Standard correctly reflects theintent of the Committee.
Note (March, 2008):
The Evolution Working Group recommended closing this issue with nofurther consideration. See paper J16/07-0033 = WG21 N2173.
Is this code valid:
extern "C" void f(); namespace N { int var; extern "C" void f(){ var = 10; } }
The two declarations of f refer to the same external function,but is this a valid way to declare and define f?
And is the definition of f considered to be in namespace Nor in the global namespace?
Notes from October 2002 meeting:
Yes, this example is valid. See 9.12 [dcl.link] paragraph 6, which contains a similar example with the definition inthe global namespace instead.There is only one f, so the question of whether the definitionis in the global namespace or the namespace N is not meaningful.The same function is found by name lookup whether it is foundfrom the declaration in namespace N or the declaration inthe global namespace, or both (9.9.4 [namespace.udir] paragraph 4).
Issue 4 separated the concepts of languagelinkage for names and language linkage for types; since the names offunctions with internal linkage are not visible outside their (C++)translation unit, there is no need to restrict overloading ofextern "C" functions with internal linkage, e.g.,
extern "C" { static void f(); static void f(int); }
although the types of such functions still have C language linkageand thus can be called via a function pointer from C code.
The change permitting such overloading, however, has not been widelyimplemented since the resolution ofissue 4,leading some to suggest that the unnecessary restriction on functionoverloading of such functions should be reimposed.
If it is decided to keep the resolution ofissue 4, 9.12 [dcl.link] paragraph 6 should be clarified:
At most one function with a particular name can have C language linkage.
Presumably this was overlooked in implementing the intent of theresolution for the issue and is a likely explanation for the reasonit is not more widely implemented.
Rationale (September, 2013):
There was no consensus in CWG for a change to the current rules.9.12 [dcl.link] paragraph 6 should be read as applying tothe C language linkage of the name, not the function type.
According to 9.12 [dcl.link] paragraph 7,
A declaration directly contained in alinkage-specification istreated as if it contains theextern specifier(9.2.2 [dcl.stc]) for the purpose of determining the linkage ofthe declared name and whether it is a definition. Such a declaration shallnot specify a storage class.
This prohibits a declaration like
extern "C++" thread_local int n;
Should this be changed?
Rationale (June, 2014):
This restriction is the same forstatic and simplyrequires that the braced form oflinkage-specificationbe used.
The grammar foralignment-specifier in 9.13.1 [dcl.attr.grammar] paragraph 1is:
There is no such nonterminal asalignment-expression; itshould beassignment-expression instead.
Rationale (August, 2011)
This is an editorial issue that has been transmitted to the projecteditor.
P0028R4 contains this example:
[[ using CC: opt(1), debug ]] void f() {} // Same as[[ CC::opt(1), CC::debug ]] void f() {} [[ using CC: opt(1)]][[ CC::debug ]] void g() {} // Okay (same effect as above).
However, there appears to be no normative justification for theclaim that these twoattribute-lists have the same effect.
Rationale (February, 2017):
The effects of such attributes are implementation-defined.
According to 9.13.1 [dcl.attr.grammar] paragraph 5,
Eachattribute-specifier-seq is said to appertain to some entity orstatement, identified by the syntactic context where it appears (Clause 8 [stmt], 9.1 [dcl.pre],9.3 [dcl.decl]). If anattribute-specifier-seq thatappertains to some entity or statement contains anattributeoralignment-specifier that is not allowed to apply to that entityor statement, the program is ill-formed.
This does not, but presumably should,mentioncontract-attribute-specifiers.
Proposed resolution (March, 2019):
Change 9.13.1 [dcl.attr.grammar] paragraph 5 as follows:
Eachattribute-specifier-seq is said to appertain tosome entity or statement, identified by the syntacticcontext where it appears (Clause 8 [stmt],9.1 [dcl.pre], 9.3 [dcl.decl]). Ifanattribute-specifier-seq that appertains to someentity or statement contains anattribute,contract-attribute-specifier,oralignment-specifier that is not allowed to applyto that entity or statement, the program is ill-formed. Ifanattribute-specifier-seq appertains to a frienddeclaration (11.8.4 [class.friend]), that declarationshall be a definition. Noattribute-specifier-seqshall appertain to an explicit instantiation(13.9.3 [temp.explicit]).
Rationale (July, 2019):
With the adoption of paper P1823R0, removing contracts fromC++20, this issue is moot.
Although 9.13.2 [dcl.align] paragraph 6 requires that alldeclarations of a given entity must have the same alignment, enforcingthat requirement for class templates would require instantiating alldeclarations of the template, a process not otherwise needed. Forexample:
template<int M, int N> struct alignas(M) X; template<int M, int N> struct alignas(N) X {};
The same problem would presumably afflict any attribute appliedto a class template.
Rationale (April, 2013):
9.13.2 [dcl.align] paragraph 6 requires that the alignmentsbe “equivalent,” which in a dependent context is specifiedby 13.7.7.2 [temp.over.link] paragraph 5. The expressions in thisexample are not equivalent.
The[[noreturn]] attribute, as specified in9.13.10 [dcl.attr.noreturn], applies to function declarations and isnot integrated with the type system. This is incompatible withexisting practice (as in gcc) and should be reconsidered.
Rationale (July, 2009):
The CWG reaffirmed the previous decisions not to have attributesapply to types and did not believe that the benefits were sufficientfor this case to make an exception to the general rule.
C has rejected the notion of attributes, and introducedthe noreturn facility as a keyword. To continue writingclean, portable code we should replace the[[noreturn]]attribute with anoreturn keyword, following the usualconvention that while C obfuscates new keywords with_Capital and adds a macro to map to the comfortablespelling, C++ simply adopts the all-lowercase spelling.
Rationale (August, 2010):
CWG felt that an attribute was a more appropriate representationfor this feature.
The definition of a “potentially-overlappingsubobject” in 6.7.2 [intro.object] paragraph7 does not exclude non-class subobjects; in particular,9.13.11 [dcl.attr.nouniqueaddr] makes no restrictions onthe types of members declared with theno_unique_address attribute. It is not clear that apotentially-overlapping scalar member or array of scalarelements is useful. Should there be a restriction on the typeof potentially-overlapping subjects?
CWG 2022-11-11
Restricting the type of a potentially-overlapping subobject wouldmake it difficult to useno_unique_address on a subobject ofdependent type, which may be a non-class type in some, but not all,specializations. Compilers can warn about non-sensical uses innon-dependent contexts.
Non-static data member initializers should not be part of C++0xunless they have implementation experience.
Notes from the August, 2010 meeting:
The C++/CLI dialect has a very similar feature that has beenimplemented.
Rationale (March, 2011):
The full Committee voted not to remove this feature.
The grammar formember-declarator (11.4 [class.mem])does not, but should, allow for abrace-or-equal-initializer ona bit-field declarator.
Rationale (October, 2015):
Such a change would introduce a new syntactic ambiguity. CWGalso felt uncomfortable with a construct that is visually
expression = expression
not being an assignment expression.
[Detailed description pending.]
Rationale (November, 2016):
The reported issue is no longer relevant to the current working paper.
According to 11.4.1 [class.mem.general] paragraph 7,anoexcept-specifier is a complete-classcontext. This raises an issue when the function is a friendfunction; for example, consider:
using T = int; struct B { friend void g(B b) noexcept(sizeof(b.m) >= 4) { } T m = T(); }; int main() { B b; g(b); }
For friend declarations you need to be able to decideat the point of declaration whether it matches a priordeclaration, and you can't do that if you treat thenoexcept-specifier as a complete-class context.
There is implementation divergence in the treatment ofthis example.
Notes from the December, 2021 teleconference:
CWG questioned why the declaration matching couldn't bedeferred until the end of the class.
CWG 2022-11-10
CWG believes that, in general, a "when needed" approach to parsingcomplete-class contexts is superior. In the present case, theexisting wording clearly requires that thenoexcept-specifierbe delayed-parsed, which implies that matching the declaration of afriend function to declarations at namespace scope is alsodelayed.
It's not clear how lookup of a non-dependent qualified nameshould be handled in a non-static member function of a classtemplate. For example,
struct A { int f(int); static int f(double); }; struct B {}; template<typename T> struct C : T { void g() { A::f(0); } };
The call toA::f insideC::g() appearsnon-dependent, so one might expect that it would be bound attemplate definition time toA::f(double). However, theresolution forissue 515 changed11.4.3 [class.mfct.non.static] paragraph 3 to transform anid-expression to a member access expression using(*this). if lookup resolves the name to a non-staticmember ofany class, making the reference dependent. Theresult is that ifC is instantiated withA,A::f(int) is called; ifC is instantiated withB, the call is ill-formed (the call is transformed to(*this).A::f(0), and there is noA subobject inC<B>). Both these results seem unintuitive.
(See alsoissue 1017.)
Notes from the November, 2010 meeting:
The CWG agreed that the resolution ofissue 515was ill-advised and should be reversed.Rationale (March, 2011):
The analysis is incorrect; whether the reference is dependent ornot, overload resolution choosesA::f(int) because of therules in 12.2.2.2.2 [over.call.func] paragraph 3 dealing withcontrived objects for static member functions.
Move semantics for*this should not be part of C++0xunless they have implementation experience.
Rationale (March, 2011):
The full Committee voted not to remove this feature.
An implicitly-declared special member function is defined as deleted(11.4.5 [class.ctor] paragraph 5, 11.4.7 [class.dtor] paragraph 3, 11.4.5.3 [class.copy.ctor] paragraphs 5 and 10) if any ofthe corresponding functions it would call from base classes isinaccessible. This is inconsistent with the treatment of access controlin overload resolution and template argument deduction, whereaccessibility is ignored (but may result in an ill-formed program).This should be made consistent.
Rationale (July, 2009):
The current treatment is sufficiently useful to warrant the inconsistencywith the other handling of access control. In particular, it enables suchcases to be detected by SFINAE.
The list of causes for a defaulted default constructor to be definedas deleted, given in 11.4.5 [class.ctor] paragraph 5, should havea case for subobjects of a type with a destructor that is deleted orinaccessible from the defaulted constructor.
Rationale (January, 2012):
The supposedly-missing text is actually already present.
The working paper is quite explicit about
struct X { X(X, X const& = X()); };being illegal (because of the chicken & egg problem wrt copying.)
Shouldn't it be as explicit about the following?
struct Y { Y(Y const&, Y = Y()); };Rationale:There is no need for additional wording. This example leads toa program which either fails to compile (due to resource limits on recursiveinlining) or fails to run (due to unterminated recursion). In either casethe implementation may generate an error when the program is compiled.
Jack Rouse:In 11.4.5.3 [class.copy.ctor] paragraph 8,the standard includesthe following about the copying of class subobjects in such aconstructor:
Mike Miller:I'm more concerned about11.4.5.3 [class.copy.ctor] paragraph 7,which lists the situations inwhich an implicitly-defined copy constructor can render aprogram ill-formed. Inaccessible and ambiguous copyconstructors are listed, but not a copy constructor with acv-qualification mismatch. These two paragraphs taken togethercould be read as requiring the calling of a copy constructorwith a non-const reference parameter for a const data member.
Proposed Resolution (November, 2006):
This issue is resolved by the proposed resolution forissue 535.
Rationale (August, 2011):
These concerns have been addressed by other changes.
Section 11.4.5.3 [class.copy.ctor] paragraph 8 says thecompiler-generated copy constructor copiesscalar elements via the built-in assignment operator.Seems inconsistent. Why not the built-in initialization?
Notes from October 2002 meeting:
The Core Working Group believes this should not be changed. Thestandard already mentionsbuilt-in operators and theassignment operator does clearly define what must be done forscalar types. There is currently no concept ofbuilt-in initialization.
Paper N2987 suggests that an implicitly-declared copy or moveconstructor should be explicit if the corresponding constructor of anyof its subobjects is explicit. During the discussion at the October,2009 meeting, the CWG deemed this a separable question from the majoremphasis of that paper, and this issue was opened as a placeholder forthat discussion.
See alsoissue 1051.
Rationale (November, 2010):
The CWG did not see a correlation between the explicitness of abase class constructor and that of an implicitly-declared derivedclass constructor.
11.4.5.3 [class.copy.ctor] paragraph 12 says that a defaulted moveconstructor is defined as deleted if the class has a non-static datamember or direct or virtual base class with a type that does not havea move constructor and is not trivially copyable. This seems morestrict than is necessary; the subobject need not be trivially copyable,it should be enough for the selected constructor not to throw. In anycase, the wording should be phrased in terms of the function selectedby overload resolution rather than properties of the subobject type,and similarly for move assignment.
Rationale (November, 2010):
The CWG felt that the current specification was consistent andnot overly problematic; users can add their own move constructorif needed.
Consider an example like,
struct A { A() = default; A(A&) = default; A(const A&) = default; }; static_assert(!__is_trivially_copyable(A),""); struct B { A a; B() = default; B(const B&) = default; }; static_assert(__is_trivially_copyable(B),""); struct C { mutable A a; C() = default; C(const C&) = default; }; static_assert(!__is_trivially_copyable(C),"");
Presumably, allstatic_assert() conditions above are desired toevaluatetrue. Implementations diverge on this.
To decide whether a class is trivially copyable (Clause 11 [class] paragraph 6), we need to see whether it has a non-trivial copyconstructor. So eventually we hit 11.4.5.3 [class.copy.ctor] paragraph12:
A copy/move constructor for classX is trivial if it is notuser-provided, its declared parameter type is the same as if it had beenimplicitly declared, and if
classX has no virtual functions(11.7.3 [class.virtual]) and no virtual base classes(11.7.2 [class.mi]), and
the constructor selected to copy/move each direct base classsubobject is trivial, and
for each non-static data member ofX that is of class type(or array thereof), the constructor selected to copy/move that member istrivial;
otherwise the copy/move constructor isnon-trivial.
which seem to imply that the copy constructor needs to have beenimplicitly defined before we consider this rule. But copy ctors are notodr-used in this example, so they're not implicitly-defined (paragraph13).
The same considerations apply to copy/move assignment operators.
It might be sufficient to clarify this specification by replacing“selected to” with “that would be selected to.”
Rationale (September, 2013):
CWG felt that the existing wording was clear enough.
According to 11.4.5.3 [class.copy.ctor] paragraph 11,
A defaulted move constructor that is defined as deleted isignored by overload resolution (12.2 [over.match],12.3 [over.over]). [Note: A deleted moveconstructor would otherwise interfere with initializationfrom an rvalue which can use the copy constructorinstead. —end note]
Limiting this provision to defaulted move constructorsintroduces an unfortunate distinction between implicitly andexplicitly deleted move constructors. For example, given
#include <iostream> #include <memory> using namespace std; struct Expl { Expl() = default; Expl(const Expl &) { cout << " Expl(const Expl &)" << endl; } Expl(Expl &&) = delete; }; struct Impl : Expl { Impl() = default; Impl(const Impl &) { cout << " Impl(const Impl &)" << endl; } Impl(Impl &&) = default; }; struct Moveable { Moveable() { } Moveable(const Moveable &) { cout << " Moveable(const Moveable &)" << endl; } Moveable(Moveable &&) { cout << " Moveable(Moveable &&)" << endl; } }; template<typename T> struct Container { Moveable moveable[2]; T t; }; int main() { cout << "Expl:" << endl; Container<Expl> c1(move(Container<Expl>())); cout << "Impl:" << endl; Container<Impl> c2(move(Container<Impl>())); }
The output of this program is
Expl: Moveable(const Moveable &) Moveable(const Moveable &) Expl(const Expl &) Impl: Moveable(Moveable &&) Moveable(Moveable &&) Impl(const Impl &)
Should the specification be changed to allow overload resolutionto ignore all deleted move constructors instead of only the defaultedones?
From one perspective, at least, the principal reason to delete amove constructor explicitly is to elicit an error if a move isattempted, and such a change would violate that intent. On theother hand, minimizing the difference between explicit default andimplicit default seems like an important goal.
Rationale (February, 2014):
The specification is as intended.
The current wording of the Standard does not make clear whethera special member function that is defaulted and implicitly deleted istrivial. Triviality is visible in various ways that don't involveinvoking the function, such as determining whether a type istrivially copyable and determining the result of various type traits.It also factors into some ABI specifications.
(See alsoissue 1734.)
Notes from the June, 2014 meeting:
CWG felt that deleted functions should be trivial. See alsoissue 1590.
Additional note, November, 2014:
See paper N4148.
Additional note, October, 2015:
Moved from "extension" status to "open" to allow considerationby CWG. See the additional discussion inissue 1734 for further details. See alsoissue 1496.
Rationale (October, 2015):
CWG feels that the triviality of a deleted function shouldbe irrelevant. Any cases in which the triviality of a deletedfunction is observable should be amended to remove thatdependency.
EWG has indicated that they are not currently in favor ofremoving the implicitly declared defaulted copy constructorsand assignment operators that are deprecated in11.4.5.3 [class.copy.ctor] paragraphs 7 and 18. Should thisdeprecation be removed?
EWG 2022-11-11
EWG expressed no interest to remove the deprecation.
EDG (and g++, for that matter) picks the explicit copy assignmentoperator, which we think is wrong in this case:
#include <stdio.h>struct D; // fwd declarationstruct B { D& operator=(D&);};struct D : B { D() {} D(int ii) { s = ii; } using B::operator=; int s;};int main() { D od, od1(10); od = od1; // implicit D::operator=(D&) called, not BASE::operator=(D&)}D& B::operator=(D& d) { printf("B::operator called\n"); return d;}
If you look at 11.4.5.3 [class.copy.ctor] paragraph 10 itexplicitly states thatin such a case the "using B::operator=" will not be considered.
Steve Adamczyk:The fact that the operator= you declared is (D&) and not (const D&)is fooling you. As the standard says, the operator= introducedby the using-declaration does not suppress the generation ofthe implicit operator=. However, the generated operator= has the(const D&) signature, so it does not hide B::operator=;it overloads it.
Kerch Holt:I'm not sure this is correct. Going by 12.8 P10 first paragraphwe think that the other form "operator=(D&)" is generated becausethe two conditions mentioned were not met in this case.1) there is no direct base with a "const [volatile] B&" or "B" operator2) And no member has a operator= either.This implies the implicit operator is "operator=(D&)".So, if that is the case the "hiding" should happen.
Also, in the last paragraph it seems to state that operatorsbrought in from "using", no matter what the parameter is, arealways hidden.
Steve Adamczyk:Not really. I think this section is pretty clear about the fact thatthe implicit copy assignment operator is generated. The question is whether ithides or overloads the one imported by the using-declaration.
Notes from the March 2004 meeting:
(a) Class B does get an implicitly-generatedoperator=(const B&); (b) the using-declaration brings intwooperator= functions from B, the explicitly-declaredone and the implicitly-generated one; (c) those two functions overloadwith the implicitly-generatedoperator=(const D&) inthe derived class, rather than being hidden by the derived-classfunction, because it does not match either of their signatures;(d) overload resolution picks the explicitly-declaredfunction from the base class because it's the best match in thiscase. We think the standard wording says this clearly enough.
Is the following a “copy assignment operator?”
struct A { const A& operator=(const A&) volatile; };
11.4.5.3 [class.copy.ctor] paragraph 9 doesn't say one way or theother whether cv-qualifiers on the function are allowed. (A similarquestion applies to theconst case, but I avoided that examplebecause it seems so wrong one tends to jump to a conclusion beforeseeing what the standard says.)
Since the point of the definition of “copy assignmentoperator” is to control whether the compiler generates a defaultversion if the user doesn’t, I suspect the correct answer isthat neitherconst norvolatile cv-qualificationonoperator= should be allowed for a “copy assignmentoperator.” A user can write anoperator= like that, butit doesn't affect whether the compiler generates the default one.
Proposed Resolution (November, 2006):
Change 11.4.5.3 [class.copy.ctor] paragraph 9 as follows:
A user-declaredcopy assignment operatorX::operator=is a non-static non-templatenon-volatile non-const memberfunction of classX with exactly one parameter oftypeX,X&,const X&,volatileX& orconst volatile X&.
[Drafting note: If a user-declared volatileoperator=prevented the implicit declaration of the copy assignment operator,all assignments for objects of the given class (even to non-volatileobjects) would pay the penalty for volatile write accesses in theuser-declaredoperator=, despite not needing it.]
Additional note (December, 2008):
The proposed resolution addresses only cv-qualified assignmentoperators and is silent on ref-qualified versions. However, it wouldseem that the spirit of the resolution would indicate that a ref-qualifiedassignment operator would not be considered a copy assignment operator.
There appears to be an emerging idiom that relies on the idea thatproviding an lvalue-only assignment operator would prevent assignmentto rvalues:
struct A { A& operator=(const A&) &; // disable assignemt to rvalue };
The resolution should also be reconsidered in light of the use ofa const-qualified assignment operator as part of the implementationof a proxy class, where the proxy object itself is constant and shouldnot be changed, but the copy assignment operator would apply to theobject to which the proxy object refers.
Rationale (March, 2009):
It was decided that cv-qualified and ref-qualified assignmentoperators should be considered copy assignment operators if theyhave the required parameter type.
For increased regularity between built-in types and class types,the copy assignment operator can be qualified with&,preventing assignment to an rvalue. The LWG is making that change inthe Standard Library. It would seem a good idea to make a similarchange, where possible, in the specification of implicitly-declaredassignment operators. This would be the case when all subobjects ofclass type have a non-deleted copy assignment operator that is&-qualified.
Rationale (July, 2009):
The LWG decided not to add reference qualifiers in the library,which reduces the motivation for making this change to implicitassignment operators.
See alsoLWG issue 941and paperN2819.
In 11.4.5.3 [class.copy.ctor] paragraph 25, a move assignment operatoris defined as deleted if it has any direct or indirect virtual base class.This could be relaxed to apply only if a virtual base is derived frommore than once in the DAG.
Rationale (August, 2010):
CWG felt that there was insufficient motivation to change thisat this time.
It is not clear whether ausing-declaration naming anassignment operator from a base class can be considered todeclare a copy assignment operator or not. For example:
struct A; struct B { constexpr A & operator= (const A &); }; struct A : B { using B::operator=; } a { a = a };
There is implementation divergence on the treatment ofthis code: should theusing-declaration suppress orconflict with the implicit declarationofA::operator=?
Rationale (June, 2019):
This question is addressed explicitly by 9.10 [namespace.udecl] paragraph 4:
If a constructor or assignment operator brought from a base class into aderived class has the signature of a copy/move constructor or assignmentoperator for the derived class (11.4.5.3 [class.copy.ctor],11.4.6 [class.copy.assign]), theusing-declaration does not byitself suppress the implicit declaration of the derived class member; themember from the base class is hidden or overridden by theimplicitly-declared copy/move constructor or assignment operator of thederived class, as described below.
Use of adecltype-specifier to name a destructor in anexplicit destructor call is explicitly permitted in11.4.7 [class.dtor] paragraph 13. However, the moststraightforward attempt to do so, e.g.,
p->~decltype(*p)()
does not work, because*p is an lvalue and thusdecltype(*p) is a reference type, not a class type.Even simply eliminating the reference is not sufficient, becausep could be a pointer to a cv-qualified class type.
Either the provision fordecltype-specifiers in explicitdestructor calls should be removed or the specification should beexpanded to allow reference and cv-qualified types to be considered as“denot[ing] the destructor's class type.”
Notes from the April, 2013 meeting:
CWG favored replacing the existing syntax with something more flexible,for example,p->~auto(). This new syntax would also apply topseudo destructors.
Rationale (November, 2013):
CWG felt that the suggested change should be considered by EWGbefore the issue is resolved.
Additional note, April, 2015:
EWG has decided not to make a change in this area. See EWG issue112.
According to 11.4.7 [class.dtor] paragraph 5,
A destructor is trivial if it is not user-provided and if:
the destructor is notvirtual,
...
It is not clear why this restriction is needed, and it should beremoved if it is not needed.
Rationale (February, 2014):
A trivial destructor is known to perform no actions and thus neednot be invoked. A virtual destructor, however, might be member of abase class of an unknown derived class; it must therefore be calledvirtually in case an overriding virtual function performs some actions.
A posting in comp.lang.c++.moderated prompted me totry the following code:
struct S { template<typename T, int N> (&operator T())[N]; };
The goal is to have a (deducible) conversion operatortemplate to a reference-to-array type.
This is accepted by several front ends (g++, EDG), butI now believe that 11.4.8.3 [class.conv.fct] paragraph 1actually prohibits this.The issue here is that we do in fact specify (part of)a return type.
OTOH, I think it is legitimate to expect that this isexpressible in the language (preferably not using thesyntax above ;-). Maybe we should extend the syntaxto allow the following alternative?
struct S { template<typename T, int N> operator (T(&)[N])(); };
Eric Niebler:If the syntax is extended to support this, similar constructs should also beconsidered. For instance, I can't for the life of me figure out how towrite a conversion member function template to return a member functionpointer. It could be useful if you were defining a null_t type. This isprobably due to my own ignorance, but getting the syntax right is tricky.
Eg.
struct null_t { // null object pointer. works. template<typename T> operator T*() const { return 0; } // null member pointer. works. template<typename T,typename U> operator T U::*() const { return 0; } // null member fn ptr. doesn't work (with Comeau online). my error? template<typename T,typename U> operator T (U::*)()() const { return 0; } };
Martin Sebor:Intriguing question. I have no idea how to do it in a singledeclaration but splitting it up into two steps seems to work:
struct null_t { template <class T, class U> struct ptr_mem_fun_t { typedef T (U::*type)(); }; template <class T, class U> operator typename ptr_mem_fun_t<T, U>::type () const { return 0; } };
Note: In the April 2003 meeting, the core working groupnoticed that the above doesn't actually work.
Note (June, 2010):
It has been suggested that template aliases effectivelyaddress this issue. In particular, an identity alias like
template<typename T> using id = T;
provides the necessary syntactic sugar to be able to specifytypes with trailing declarator elements as aconversion-type-id.For example, the two cases discussed above could be written as:
struct S { template<typename T, int N> operator id<T[N]>&(); template<typename T, typename U> operator id<T (U::*)()>() const; };
This issue should thus be closed as now NAD.
Rationale (August, 2011):
As given in the preceding note.
Another instance to consider is that of invoking a member function from anull pointer:
struct A { void f () { } }; int main () { A* ap = 0; ap->f (); }
Which is explicitly noted as undefined in 11.4.3 [class.mfct.non.static],even though one could argue that sincef() is empty, there is nolvalue->rvalue conversion.
Iff is static, however, there seems to be no such rule, andthe call is only undefined if the dereference implicit in the->operator is undefined. IMO it should be.
Incidentally, another thing that ought to be cleaned up is the inconsistentuse of "indirection" and "dereference". We should pick one.(Thisterminology issue has been broken out asissue 342.)
This is related toissue 232.
Rationale (October 2003):
We agreed the example should be allowed.p->f()is rewritten as(*p).f() according to 7.6.1.5 [expr.ref].*p is not an error whenp is null unless thelvalue is converted to an rvalue (7.3.2 [conv.lval]), which itisn't here.
Additional notes (June, 2024)
Issue 2823 represents the currentlanguage rules; the example has undefined behavior.
The current wording of 11.4.9.3 [class.static.data] only allows astatic data member to be initialized within the class definition if itisconst. This restriction should be removed.
Rationale (July, 2009):
The consensus of the CWG was that there is insufficient motivationfor such a change. A non-const static data member must still bedefined outside the class if it is used, and the value of such amember cannot be used in a constant expression, unlike a constantstatic data member, so there is no real advantage to putting theinitializer inside the class definition instead of in the definitionof the static data member. The apparent parallel with non-static datamember initialization is also not compelling; for example, theinitializer for a non-static data member can contain forwardreferences to members declared later in the class, while the same isnot true of static data member initializers.
The Standard requires that a const static data member that is initializedin the class definition must still be defined in namespace scope if it isodr-used. This seems unnecessary.
Rationale (August, 2011):
This is a request for an extension.
Additional note, April, 2015:
EWG has decided not to make a change in this area. See EWG issue 100.
Given an example like
struct X { static constexpr const char *p = "foo"; }; static const char *q = X::p;
if this appears in more than one translation unit, must thevalue ofq be the same in each? The implication ofthe one-definition rule would be that it must be, but currentimplementations do not give that result.
Rationale (November, 2014):
The interpretation is correct and the implementations areincorrect.
According to 11.4.9.3 [class.static.data] paragraph 3,
An inline static data member may be defined in the classdefinition and may specifyabrace-or-equal-initializer. If the member isdeclared with theconstexpr specifier, it may beredeclared in namespace scope with no initializer (thisusage is deprecated; see _N4778_.D.4 [depr.static_constexpr]).
The out-of-class declaration of a static data member wasformerly a definition and thus limited to occurring only once.This limitation was lost when the in-class declaration of inlinestatic data members became the definition; the currentspecification has no apparent prohibition against multipleout-of-class declarations of aconstexpr static datamember. Should the restriction be reinstated?
Rationale (November, 2018):
Current implementations support this usage and it does notappear to cause any problems.
11.4.2 [class.mfct] paragraph 5 says this about memberfunctions defined lexically outside the class:
the member function name shall be qualified by its class name usingthe :: operator
11.4.9.3 [class.static.data] paragraph 2 says this about staticdata members:
In the definition at namespace scope, the name of the static datamember shall be qualified by its class name using the :: operator
I would have expected similar wording in 11.4.12 [class.nest] paragraph 3for nested classes. Without such wording, the followingseems to be legal (and is allowed by all the compilers I have):
struct base { struct nested; }; struct derived : base {}; struct derived::nested {};
Is this just an oversight, or is there some rationale for this behavior?
Rationale (July, 2008):
The wording in Clause 11 [class] paragraph 10 (added by theresolution ofissue 284, which was approvedafter this issue was raised) makes the example ill-formed:
If aclass-head contains anested-name-specifier, theclass-specifier shall refer to a class that was previouslydeclared directly in the class or namespace to whichthenested-name-specifier refers (i.e., neither inherited norintroduced by ausing-declaration), andtheclass-specifier shall appear in a namespace enclosing theprevious declaration.
Is this legal? Should it be?
struct E { union { struct { int x; } s; } v; };
One compiler faults a type definition (i.e. of the anonymousstruct) since it is in an anonymous union [11.5 [class.union] paragraph 2:"Themember-specification of an anonymous union shall only definenon-static data members."].
I would suggest that compiler B is correctly interpreting thestandard but that this is a defect in the standard. There isno reason to disallow definition of anonymous structs.
Furthermore, is it really necessary to disallow definition ofnamed types in anonymous unions in general, as long as thetypes do not need fully qualified names for external linkage?Why should this be illegal?
struct E { union { typedef int Int; struct X { X *next; Int n; } list; } v; };
Notes from October 2002 meeting:
There was agreement that the standard says such declarationsare invalid; therefore this must be considered as an extension.There was general feeling that this extension would notbe too useful, thoughJason Merrill was sympathetic to the argument. It was alsoagreed that if this were to be changed it would require carefulwording so as not to allow too many cases.
Note (March, 2008):
The Evolution Working Group recommended closing this issue with nofurther consideration. See paper J16/07-0033 = WG21 N2173.
Can a member of a union be of a class that has a user-declarednon-default constructor? The restrictions on union membership in11.5 [class.union] paragraph 1 only mention default andcopy constructors:
An object of a class with a non-trivial default constructor(11.4.5 [class.ctor]), a non-trivial copy constructor(11.4.5.3 [class.copy.ctor]), a non-trivial destructor (11.4.7 [class.dtor]), or a non-trivial copy assignment operator(12.4.3.2 [over.assign], 11.4.5.3 [class.copy.ctor]) cannot bea member of a union...
(11.4.5 [class.ctor] paragraph 11 does say,“a non-trivial constructor,” but it's not clear whetherthat was intended to refer only to default and copy constructorsor to any user-declared constructor. For example,6.7.7 [class.temporary] paragraph 3 also speaks of a“non-trivial constructor,” but the cross-referencesthere make it clear that only default and copy constructors arein view.)
Note (March, 2008):
This issue was resolved by the adoption of paper J16/08-0054 =WG21 N2544 (“Unrestricted Unions”) at the Bellevue meeting.
Rationale (August, 2011):
As given in the preceding note.
According to 11.6 [class.local] paragraph 1,
Declarations in a local class shall not odr-use (6.3 [basic.def.odr])a variable with automatic storage duration from an enclosing scope.
This restriction should apply as well to thethis pointerwhen the class is local to a non-static member function.
Rationale (January, 2014):
The restrictions in _N4567_.5.1.1 [expr.prim.general] limiting the locationsin whichthis may appear already prevent uses of the containingmember function'sthis where the local class'sthisdoes not hide it.
In an example like
struct W {}; struct X : W {}; struct Y : W {}; struct Z : X, Y {}; //Z has twoW subobjects struct A { virtual W *f(); }; struct B : A { virtual X *f(); }; struct C : B { virtual Z *f(); //C::f overridesA::f andB::f };
it is not clear whether the return type ofC::f()satisfies the requirement of 11.7.3 [class.virtual] bullet 7.2that the return type in the base class of the function be anunambiguous base of the return type in the derived class. Shouldthe conversion fromZ* toX* in overridingB::f() be considered to disambiguate the conversion fromZ* toW* in overridingA::f()? Thereis implementation divergence on this question.
Rationale (May, 2015):
CWG determined that the current wording of the Standard iscorrect:C::f() overrides bothB::f() andA::f(), and the latter overriding is ill-formed becauseof the ambiguity.
According to 11.7.4 [class.abstract] paragraph 6,
Member functions can be called from a constructor (or destructor) ofan abstract class; the effect of making a virtual call (11.7.3 [class.virtual]) to a pure virtual function directly or indirectly forthe object being created (or destroyed) from such a constructor (ordestructor) is undefined.
This prohibition is unnecessarily restrictive. It should not applyto cases in which the pure virtual function has been defined.
Currently the "pure" specifier for a virtual member function hastwo meanings that need not be related:
The prohibition of virtual calls to pure virtual functions arisesfrom the first meaning and unnecessarily penalizes those who only needthe second.
For example, consider a scenario such as the following. A classB is defined containing a (non-pure) virtual functionf that provides some initialization and is thus called fromthe base class constructor. As time passes, a number of classes arederived fromB and it is noticed that each needs to overridef, so it is decided to makeB::f pure to enforcethis convention while still leaving the original definition ofB::f to perform its needed initialization. However, the actof makingB::f pure means that every reference tofthat might occur during the execution of one ofB'sconstructors must be tracked down and edited to be a qualifiedreference toB::f. This process is tedious and error-prone:needed edits might be overlooked, and calls that actually should bevirtual when the containing function is called other than duringconstruction/destruction might be incorrectly changed.
Suggested resolution: Allow virtual calls to pure virtualfunctions if the function has been defined.
Rationale (February, 2012):
In light of the nontrivial implementation issues such a changewould raise, as well as the fact that this restriction has beenaccepted into the C++ design lexicon for many years, CWG decided notto make a change at this point. Further consideration, if any,should occur within EWG.
Rationale (February, 2014):
EWG determined that no action should be taken on this issue.
An abstract class is permitted to be a final class. Such classesare very nearly useless and should probably be made ill-formed.
Rationale (February, 2012):
Such classes could be used for static members and for accesscontrol. CWG saw no need to prohibit them.
class Foo { public: Foo() {} ~Foo() {} }; class A : virtual private Foo { public: A() {} ~A() {} }; class Bar : public A { public: Bar() {} ~Bar() {} };~Bar() calls~Foo(), which is ill-formed due to accessviolation, right? (Bar's constructor has the same problem since itneeds to callFoo's constructor.) There seems to be some disagreementamong compilers. Sun, IBM and g++ reject the testcase, EDG and HPaccept it. Perhaps this case should be clarified by a note in thedraft.
In short, it looks like a class with a virtual private base can't bederived from.
Rationale: This is what was intended.
Footnote 98 says:
As specified previously in 11.8 [class.access], private membersof a base class remain inaccessible even to derived classes unless frienddeclarations within the base class declaration are used to grant accessexplicitly.This footnote does not fit with the algorithm provided in11.8.3 [class.access.base] paragraph 4becauseit does not take into account the naming class concept introduced in thisparagraph.
(See also paper J16/99-0002 = WG21 N1179.)
Rationale (10/99): The footnote should be read as referringto immediately-derived classes, and is accurate in that context.
The Standard does not appear to specify how to handle cases inwhich conflicting access specifications for a member areinherited from different base classes. For example,
struct A { public: int i; }; struct B : virtual public A { protected: using A::i; }; struct C : virtual public A, public B { // "i" is protected from B, public from A };
This question affects both the existing wording of11.8.3 [class.access.base] paragraph 4 (“m as amember ofN is public ...m as a member ofNis private ...m as a member ofN isprotected”) and the proposed wording forissue 385 (“when a nonstatic data member ornonstatic member function is a protected member of its namingclass”).
One possible definition of “is public” would besomething like, “if any visible declaration of the entityhas public access.” One could also plausibly define theaccess ofm inN to be the minimum of all thevisible declarations, or even an error if the visibledeclarations are inconsistent.
11.8.3 [class.access.base] paragraph 1 describes the access ofinherited members, so a clarifying statement resolving this issuemight plausibly be inserted at the end of that paragraph.
Proposed resolution (October, 2004):
Add the following text as a new paragraph after 11.8.3 [class.access.base] paragraph 1:
If a given base class can be reached along more than one paththrough a derived class's sub-object lattice (11.7.2 [class.mi]), a member of that base class could have differentaccessibility in the derived class along different paths. Insuch cases, the most permissive accessprevails. [Example:
struct B { static int i; }; class I: protected B { }; class D1: public B, public I { }; class D2: public I, private B { };i is accessible as a public member ofD1 andas a protected member ofD2. —endexample]
Rationale (03/2005): This question is already covered, inalmost identical words, in 11.8.7 [class.paths].
Consider the following example:
template <typename T> struct S1 { }; struct S2 : private S1<int> { }; struct S3 : S2 { void f() { S1<int> s1; // #1 } };
The reference in #1 toS1 finds theinjected-class-name ofS1<int>, which is privateinS2 and thus inaccessible inS3. However,there is implementation divergence on the treatment of thisreference, with many accepting the declaration without error,presumably because of the use of the name in atemplate-id. Should the Standard give special treatmentto this usage?
Rationale (November, 2014):
The specification is as intended, and the example is ill-formed.
11.8.4 [class.friend], paragraph 7, says
A name nominated by a friend declaration shall be accessible in thescope of the class containing the friend declaration.
Does that mean the following should be illegal?
class A { void f(); }; class B { friend void A::f(); }; // Error: A::f not accessible from B
I discussed this with Bjarne in email, and he thinks it was aneditorial error and this was not the committee's intention. Theparagraph seems to have been added in the pre-Kona (24 Sept 1996)mailing, and I could not find anything in the previous meeting's(Stockholm) mailings which led me to believe this was intentional. Theonly compiler vendor which I think currently implements it is thelatest release (2.43) of the EDG front end.
Proposed resolution (10/00):
Remove the first sentence of 11.8.4 [class.friend], paragraph 7.
Rationale (04/01):
After the 10/00 vote to accept this issue as a DR with theproposed resolution, it was noted that the first two sentencesof 11.8 [class.access] paragraph 3 cause the proposed changeto have no effect:
Access control is applied uniformly to all names, whether the namesare referred to from declarations or expressions. [Note: accesscontrol applies to names nominated byfriend declarations(11.8.4 [class.friend]) andusing-declarations(9.10 [namespace.udecl]). ]
In addition to the obvious editing to the text of the note,an exception to the blanket statement in the first sentencewould also be required. However, discussion during the 04/01meeting failed to produce consensus on exactlywhich namesin thefriend declaration should be exempted from therequirements of access control.
One possibility would be that only the name nominated asfriend should be exempt. However, that approachwould make it impossible to name a function as a friend if itused private types in its parameters or return type. Anothersuggestion was to ignore access for every name used in afriend declaration. That approach raised a questionabout access within the body of afriend functiondefined inline in the class body — the body is part ofthe declaration of a function, but references within the bodyof afriend function should still be subject to theusual access controls.
Other possibilities were raised, such as allowing thedeclaration of afriend member function if thedeclaration were permissible in its containing class, ortaking the union of the access within the befriending classand the befriended entity. However, these suggestions wouldhave been complex and difficult to specify correctly.
Ultimately it was decided that the original perceiveddefect was not sufficiently serious as to warrant the degreeof complexity required to resolve it satisfactorily and theissue was consequently declared not to be a defect. It wasobserved that most of the problems involved with the currentstate of affairs result from inability to declare aparticular member function as afriend; in such cases,an easy workaround is simply to befriend the entire classrather than the specific member function.
Thus says the section11.8.4 [class.friend]/7 in ISO 14882 C++ standard:
A name nominated by a friend declaration shall be accessiblein the scope of the class containing the friend declaration.
The obvious intention of this is to allow a friend declaration tospecify a function (or nested class, enum, etc.) that is declared"private" or "protected" in its enclosing class. However, literalinterpretation seems to allow a broader access to the befriendedfunction by the whole class that is declaring the friendship.
If the rule were interpreted literally as it is currently written, thiswould compile (when it, of course, shouldn't be allowed at all):
class C{private: static void f();};class D{ friend void C::f(); // A name nominated by friend declaration... D() { C::f(); // ... shall be accessible in scope of class declaring friendship }};
Suggested fix is to reword "in the scope of the class containingthe friend declaration" to exclude all other references from thescope of the declaring class, except the friend-declaration itself.
Notes from the March 2004 meeting:
We considered this and concluded that the standard is clear enough.
I just received a query from a user of why line #1 in the followingsnippet is ill-formed:
void g(int (*)(int)); template<class T> class A { friend int f(int) { return 0; } void h() { g(f); // #1 } };
I believe that the special invisibility rule about friends is toocomplicated and makes life too complicated, especially consideringthat friends in templates are not templates, nor can they beconveniently rewritten with a “first declare at the namespacescope” rule. I can understand the rules when they makeprogramming easier or prevent some obvious “silly”mistakes; but that does not seem to be the case here.
John Spicer: See two papers that discuss this issue: N0878by Bill Gibbons, which ultimately gave rise to our current rules, andN0913 by me as an alternative to N0878.
Rationale (April, 2005):
The Standard is clear and consistent; this rule is the result of anexplicit decision by the Committee.
After the adoption of the wording for extended friend declarations,we now have this new paragraph in 11.8.4 [class.friend]:
Afriend declaration that does not declare a function shallhave one of the following forms:
friendelaborated-type-specifier;
friendsimple-type-specifier;
friendtypename-specifier;
But what about friend class templates? Should the following examplescompile in C++0x?
template< template <class> class T > struct A{ friend T; }; template< class > struct C; struct B{ friend C; };
Proposed resolution (June, 2008):
Change 11.8.4 [class.friend] paragraph 3 as follows:
Afriend declaration that does not declare a function shall haveone of the following forms:
friendelaborated-type-specifier;
friendsimple-type-specifier;
friendtypename-specifier;
friend::opt nested-name-specifieropt template-name;
friendidentifier;
In the last alternative, theidentifier shall name a templatetemplate-parameter. [Note: afrienddeclaration may be thedeclaration in atemplate-declaration (Clause 13 [temp],13.7.5 [temp.friend]). —end note] If the
typespecifier in afriend declaration designates a (possiblycv-qualified) class typeor a class template, that classortemplate is declared as a friend; otherwise, thefrienddeclaration is ignored. [Example:...
Rationale (September, 2008):
The proposed extension is not needed. The template case can behandled simply by providing a template header:
template <typename T> friend class X<T>;
With the change from a scope-based to an entity-based definitionof friendship (see issues372 and580), it could well make sense togrant friendship to enumerations and variables, for example:
enum E: int; class C { static const int i = 5; // Private friend E; friend int x; }; enum E { e = C::i; }; // OK:E is a friend int x = C::i; // OK:x is a friend
According to the current wording of 11.8.4 [class.friend] paragraph 3, the friend declaration ofE is well-formedbut ignored, while the friend declaration ofx isill-formed.
Additional notes (February, 2023)
Forwarding to EWG for review of this extension, per decision of theCWG chair. Seecplusplus/papers#1466.
EWG 2023-05-11
This is an extension that needs a well-motivated paper to EWG.
It appears that naming an implicitly-declared member function ina friend declaration requires the full set of decorations to bespecified. For example,
struct A { }; struct B { friend constexpr A::A() noexcept; };
There is implementation variation regarding the enforcement of thisrequirement, however. Should the Standard provide default treatmentfor such cases, allowing the simpler
friend A::A();
?
Additional note, April, 2015:
EWG has decided not to make a change in this area.
According to 11.8.4 [class.friend] paragraph 3,
A friend declaration that does not declare a function shall have one ofthe following forms:
friendelaborated-type-specifier;
friendsimple-type-specifier;
friendtypename-specifier;
However, many implementations accept
friend enum E;
even though that form is explicitly not allowed by9.2.9.5 [dcl.type.elab] paragraph 1 (which only permitsclass-key and notenum-key infriend declarations).Some implementations also accept opaque enumeration declarations like
friend enum E : int;
The latter form could plausibly be used in an example like:
class C { constexpr static int priv = 15; friend enum class my_constants; }; enum class my_constants { pub = C::priv; // OK because of friend decl };
(See alsoissue 2131.)
Notes from the October, 2018 teleconference:
The suggested plausible use for the feature would requireadditional wording, because the effect of friendship is currentlyonly described for classes and functions, not for enumerations.There does not appear to be a demand for the change.
11.8.5 [class.protected] paragraph 1says:
When a friend or a member function of a derived class referencesa protected nonstatic member of a base class, an access check applies inaddition to ...Instead of saying "references a protected nonstatic member of a base class",shouldn't this be rewritten to use the concept of naming class as11.8.3 [class.access.base]paragraph 4 does?
Rationale (04/99): This rule is orthogonal to the specificationin 11.8.3 [class.access.base] paragraph 4.
The restrictions on protected access in 11.8.5 [class.protected]apply only to forming pointers to members and to member accessexpressions. It should be considered whether to extend theserestrictions to pointer-to-member expressions as well. For example,
struct base { protected: int x; }; struct derived : base { void foo(base* b) { b->x = 123; // not ok (b->*(&derived::x)) = 123; // ok?! } };
Rationale (August, 2010):
Access applies to use of names, so the check must be done at thepoint at which the pointer-to-member is formed. It is not possibleto tell from the pointer to member at runtime what the access was.
Is the following code well-formed?
struct A { /* */ }; int main() { A a=a; }
Note, that { int a=a; } is pretty legal.
And if so, what is the semantics of the self-initialization of UDT? Forexample
#include <stdio.h> struct A { A() { printf("A::A() %p\n", this); } A(const A& a) { printf("A::A(const A&) %p %p\n", this, &a); } ~A() { printf("A::~A() %p\n", this); } }; int main() { A a=a; }
can be compiled and prints:
A::A(const A&) 0253FDD8 0253FDD8A::~A() 0253FDD8
(on some implementations).
Notes from October 2002 meeting:
6.7.4 [basic.life] paragraph 6 indicates that thereferences here are valid. It's permitted to take the addressof a class object before it is fully initialized, and it'spermitted to pass it as an argument to a reference parameteras long as the reference can bind directly.Except for the failure to cast the pointerstovoid * for the%p in theprintfs,these examples are standard-conforming.
There appears to be no prohibition of assignments in memberinitializer expressions (neithermem-initializers norbrace-or-equal-initializers):
struct A { int x; int y = x = 37; };
This seems surprising. Should it be allowed?
Rationale (April, 2013):
CWG saw no problems with the example. It did note, however, thatthe assignment tox is not an initialization, soxwould not be considered to have been initialized by this example.
According to 11.9.3 [class.base.init] paragraph 7,
Amem-initializer where themem-initializer-iddenotes a virtual base class is ignored during execution ofa constructor of any class that is not the most derivedclass.
Presumably “ignored” here means that therewill be no runtime effect but that semantic restrictionssuch as access checking and the ODR must still be applied,but this is not completely clear.
Rationale (October, 2015):
The fact that “ignored” applies only toruntime effects is indicated by the phrase “duringexecution” in the existing wording. This seems clearenough.
11.9.3 [class.base.init] paragraph 3 singles out base classes whenindicating the allowance of typedefs, etc. for the naming of types inamem-initializer-list. It appears that the omission of theclass of the constructor is unintentional.
Rationale (November, 2016):
There was no actual issue; the question was based on a misunderstandingof the current specification.
The second paragraph of section 11.9.5 [class.cdtor] contains thefollowing text:
To explicitly or implicitly convert a pointer (anlvalue) referring to an object of class X to a pointer (reference) toa direct or indirect base class B of X, the construction of X and theconstruction of all of its direct or indirect bases that directly orindirectly derive from B shall have started and the destruction ofthese classes shall not have completed, otherwise the conversionresults in undefined behavior.
Now suppose we have the following piece of code:
struct a { a() : m_a_data(0) { } a(const a& rhsa) : m_a_data(rhsa.m_a_data) { } int m_a_data; }; struct b : virtual a { b() : m_b_data(0) { } b(const b& rhsb) : a(rhsb), m_b_data(rhsb.m_b_data) { } int m_b_data; }; struct c : b { c() : m_c_data(0) { } c(const c& rhsc) : a(rhsc),// Undefined behaviour when constru- // cting an object of type 'c' b(rhsc), m_c_data(rhsc.m_c_data) { } int m_c_data; }; int main() { c ac1, ac2(ac1); }
The problem with the above snippet is that when the value 'ac2' isbeing created and its construction gets started,c's copy constructorhas first to initialize the virtual base class subobject 'a'. Whichrequires that the lvalue expression 'rhsc' be converted to the type ofthe parameter ofa's copy constructor,which is 'const a&'. Accordingto the wording quoted above, this can be done without undefinedbehaviour if and only ifb's construction has already started, whichis not possible since 'a', being a virtual base class, has to beinitialized first by a constructor of the most derived object(11.9.3 [class.base.init]).
The issue could in some cases be alleviated when 'c' has auser-defined copy constuctor. The constructor could default-initializeits 'a' subobject and then initializea's membersas needed takingadvantage of the latitude given in paragraph 2 of11.9.3 [class.base.init].
But if 'c' ends up having the implicitly-defined copy constuctor,there's no way to evade undefined behaviour.
struct c : b { c() : m_c_data(0) { } int m_c_data; }; int main() { c ac1, ac2(ac1); }
Paragraph 8 of 11.4.5.3 [class.copy.ctor] states
Theimplicitly-defined copy constructor for class X performs a memberwisecopy of its subobjects. The order of copying is the same as the orderof initialization of bases and members in a user-defined constructor(see 11.9.3 [class.base.init]).Each subobject is copied in the manner appropriate toits type:
- if the subobject is of class type, the copy constructor for theclass is used;
Which effectively means that the implicitly-defined copy constructorfor 'c' will have to initialize its 'a'base class subobject first andthat must be done witha's copy constructor, which will always requirea conversion of an lvalue expression of type 'const c' to an lvalue oftype 'const a&'. The situation would be the same if all the threeclasses shown had implicitly-defined copy constructors.
Suggested resolution:
Prepend to paragraph 2 of 11.9.5 [class.cdtor] the following:
Unless the conversion happens in a mem-initializer whosemem-initializer-id designates a virtual base class of X, to explicitlyor implicitly convert ...
Notes from the 10/01 meeting:
There is no problem in this example.ac1 is fully initializedbefore it is used in the initialization ofac2.
[Picked up by evolution group at October 2002 meeting.]
(See also paper J16/99-0005 = WG21 N1182.)At the London meeting,11.4.5.3 [class.copy.ctor] paragraph 31was changed tolimit the optimization described to only the following cases:
Can we find an appropriate description for the desired cases?
Rationale (04/99): The absence of this optimization doesnot constitute a defect in the Standard,although the proposed resolution in the papershould be considered when the Standard is revised.
Note (March, 2008):
The Evolution Working Group has accepted the intent of this issue andreferred it to CWG for action (not for C++0x). See paper J16/07-0033 =WG21 N2173.
Notes from the June, 2008 meeting:
The CWG decided to take no action on this issue until an interestedparty produces a paper with analysis and a proposal.
Rationale (2023-05-12):
This is an extension that needs a paper targeted at EWG.
Currently, 11.4.5.3 [class.copy.ctor] paragraphs 31-32 apply only tothe name of a local variable in determining whether a return expressionis a candidate for copy elision or move construction. Would it makesense to extend that to include the right operand of a comma operator?
EWG 2022-11-11
This is a request for a new feature, which should be proposed in apaper to EWG.
The following example does not work as one might expect:
namespace N { class C {}; } int operator +(int i, N::C) { return i+1; } #include <numeric> int main() { N::C a[10]; std::accumulate(a, a+10, 0); }According to6.5.3 [basic.lookup.unqual]paragraph 6, I would expect that the "+" call insidestd::accumulate would find the globaloperator+.Is this true, or am Imissing a rule? Clearly, theoperator+ would be found by Koenig lookupif it were in namespaceN.
Daveed Vandevoorde:But doesn't unqualified lookup oftheoperator+ in the definitionofstd::accumulate proceed in the namespace where the implicitspecialization is generated; i.e., in namespacestd?
In that case, you may find a non-empty overload set foroperator+in namespacestd and the surrounding (global) namespace is no longerconsidered?
Nathan Myers:Indeed,<string> definesoperator+,as do<complex>,<valarray>,and<iterator>. Any of these might hide the global operator.
Herb Sutter:These examples fail for the same reason:
struct Value { int i; }; typedef map<int, Value > CMap; typedef CMap::value_type CPair; ostream & operator<< ( ostream &os, const CPair &cp ) { return os << cp.first << "/" << cp.second.i; } int main() { CMap courseMap; copy( courseMap.begin(), courseMap.end(), ostream_iterator<CPair>( cout, "\n" ) ); } template<class T, class S> ostream& operator<< (ostream& out, pair<T,S> pr) { return out << pr.first << " : " << pr.second << endl; } int main() { map <int, string> pl; copy( pl.begin(), pl.end(), ostream_iterator <places_t::value_type>( cout, "\n" ) ); }This technique (copying from a map to another container or stream)should work. If it really cannot be made to work, that would seem brokento me. The reason is does not work is thatcopy andpair are innamespacestd and the name lookup rules do not permit the globaloperator<< to be found because the otheroperator<<'sin namespacestdhide the global operator. (Aside: FWIW, I think most programmers don'trealize that a typedef likeCPair is actually in namespacestd, and notthe global namespace.)
Bill Gibbons: It looks like part of this problem is that the libraryis referring to names which it requires the client to declare in the global namespace(the operator names) while also declaring those names in namespacestd.This would be considered very poor design for plain function names; but theoperator names are special.
There is a related case in the lookup of operator conversion functions. Thedeclaration of a conversion function in a derived class does not hide anyconversion functions in a base class unless they convert to the same type.Should the same thing be done for the lookup of operator function names,e.g. should an operator name in the global namespace be visible in namespacestd unless there is a matching declaration instd?
Because the operator function names are fixed, it it much more likely thata declaration in an inner namespace will accidentally hide a declarationin an outer namespace, and the two declarations are much less likely tointerfere with each other if they are both visible.
The lookup rules for operator names (when used implicitly) are alreadyquite different from those for ordinary function names. It might be worthwhileto add one more special case.
Mike Ball: The original SGI proposal said that non-transitive points ofinstantiation were also considered. Why, when, and by whom was it added?
Rationale (10/99): This appears to be mainly a programdesign issue. Furthermore, any attempt to address it in the corelanguage would be beyond the scope of what can be done in aTechnical Corrigendum.
Is the following well-formed?
template <typename T> class test { public: operator T& (){ return m_i; } private: T m_i; }; int main() { test<int*> t2; t2 += 1; // Allowed? }
Is it possible that by "assignment operators"(12.2.2.3 [over.match.oper] paragraph 4) only the built-incandidates for operator= (i.e. excluding +=, *=, etc.) were meant? On one handthe plural ("operators") seems to imply that all the assignment operators areconsidered. OTOH, there has already been a core DR (221) about a missingdistinction between "assignment operator" and "compound assignment operators".Is there a similar defect here?
Steve Adamczyk:The standard is ambiguous. However,I think the ARM was fairly clear about "assignment operators" meaning only"=", and I find that Cfront 3.0.1 accepts the test case (with typenamechanged to class). I don't know whether that's good or bad, but it'sat least a precedent. Given the change ofCore Issue 221, if we do nothingfurther conversions are valid on += and therefore this case is valid.
Note that "t2++;" is unquestionably valid, so one couldalso argue for the status quo (post-221) on the basis of consistency.
Notes from the October 2003 meeting:
We believe the example is well-formed, and no change other thanthat in issue 221 is needed.
The rules for selecting candidate functions in copy-list-initialization(12.2.2.8 [over.match.list]) differ from those of regularcopy-initialization (12.2.2.5 [over.match.copy]): the latter specifythat only the converting (non-explicit) constructors areconsidered, while the former include all constructors but state thatthe program is ill-formed if anexplicit constructor isselected by overload resolution. This is counterintuitive and canlead to surprising results. For example, the call to the functionobjectp in the following example is ambiguous because theexplicit constructor is a candidate for the initialization ofthe operator's parameter:
struct MyList { explicit MyStore(int initialCapacity); }; struct MyInt { MyInt(int i); }; struct Printer { void operator()(MyStore const& s); void operator()(MyInt const& i); }; void f() { Printer p; p({23}); }
Rationale (March, 2011):
The current rules are as intended.
According to 12.2.3 [over.match.viable] paragraph 4,
Third, forF to be a viable function, there shallexist for each argument an implicit conversion sequence(12.2.4.2 [over.best.ics]) that converts that argumentto the corresponding parameter ofF. If theparameter has reference type, the implicit conversionsequence includes the operation of binding the reference,and the fact that an lvalue reference to non-constcannot be bound to an rvalue and that an rvalue referencecannot be bound to an lvalue can affect the viability of thefunction (see 12.2.4.2.5 [over.ics.ref]).
The description of an implicit conversion sequence in12.2.4.2 [over.best.ics] paragraph 6 only discusses therelationship of the types. For example, for a class type, itsays,
When the parameter has a class type and the argumentexpression has the same type, the implicit conversionsequence is an identity conversion.
This ignores whether the conversion can actually beperformed, consideringexplicit qualification ofconstructors and conversion functions. There is implementationdivergence in the handling of an example like:
template<typename T> void f(T); template<typename T> void f(const T &); struct Woof { explicit Woof() = default; explicit Woof(const Woof&) = default; explicit Woof(Woof&&) = default; Woof& operator=(const Woof&) = default; Woof& operator=(Woof&&) = default; }; int main() { const Woof cw{}; f(cw); }
Iff(Woof) is viable, the call is ambiguous, eventhough callingf(Woof) would be ill-formed because ofthe explicit copy constructor.
This seems to be consistent with the general approachdescribed in 12.2.4.2 [over.best.ics] paragraph 2, eventhough explicitness is not explicitly mentioned:
Implicit conversion sequences are concerned only with thetype, cv-qualification, and value category of the argumentand how these are converted to match the correspondingproperties of the parameter. Other properties, such as thelifetime, storage class, alignment, accessibility of theargument, whether the argument is a bit-field, and whether afunction is deleted (9.6.3 [dcl.fct.def.delete]), areignored. So, although an implicit conversion sequence can bedefined for a given argument-parameter pair, the conversionfrom the argument to the parameter might still be ill-formedin the final analysis.
Rationale (November, 2018):
The intent is that the example should be ambiguous. As aneditorial matter, the “such as” and “so”remarks should be turned into notes.
It's not clear how overloading and partial ordering handle non-deduced pairsof corresponding arguments. For example:
template<typename T>struct A { typedef char* type; };template<typename T> char* f1(T, typename A<T>::type); // #1template<typename T> long* f1(T*, typename A<T>::type*); // #2long* p1 = f1(p1, 0); // #3
I thought that #3 is ambiguous but different compilers disagree on that.Comeau C/C++ 4.3.3 (EDG 3.0.3) accepted the code, GCC 3.2 and BCC 5.5selected #1 while VC7.1+ yields ambiguity.
I intuitively thought that the second pair should prevent overloading fromtriggering partial ordering since both arguments are non-deduced and hasdifferent types - (char*, char**). Just like in the following:
template<typename T> char* f2(T, char*); // #3template<typename T> long* f2(T*, char**); // #4long* p2 = f2(p2, 0); // #5
In this case all the compilers I checked found #5 to be ambiguous.The standard and DR214 is not clearabout how partial ordering handle suchcases.
I think that overloading should not trigger partial ordering (in step12.2.4 [over.match.best]/1/5) if some candidates havenon-deduced pairs with different(specialized) types. In this stage the arguments are already adjusted so noneed to mention it (i.e. array to pointer). In case that one of thearguments is non-deuced then partial ordering should only consider the typefrom the specialization:
template<typename T> struct B { typedef T type; };template<typename T> char* f3(T, T); // #7template<typename T> long* f3(T, typename B<T>::type); // #8char* p3 = f3(p3, p3); // #9
According to my reasoning #9 should yield ambiguity since second pair is (T,long*). The second type (i.e. long*) was taken from the specializationcandidate of #8.EDG and GCC accepted the code. VC and BCC found an ambiguity.
John Spicer:There may (or may not) be an issue concerning whether nondeducedcontexts are handled properly in the partial ordering rules. Ingeneral, I think nondeduced contexts work, but we should walk throughsome examples to make sure we think they work properly.
Rani's description of the problem suggests that he believes thatpartial ordering is done on the specialized types. This is notcorrect. Partial ordering is done on the templates themselves,independent of type information from the specialization.
Notes from October 2004 meeting:
John Spicer will investigate further to see if any action isrequired.
(See alsoissue 885.)
CWG 2022-11-11
The second function parameter contains template parameters notdeducible in its context, thus that parameter does not contribute topartial ordering. There is no implementation divergence. Close asNAD.
It is not clear whether the current treatment of an example likethe following is what we want:
template<typename T> void foo(T, int); template<typename T> void foo(T&, ...); struct Q; void fn1(Q &data_vectors) { foo(data_vectors, 0); }
According to 12.2.4.2 [over.best.ics] paragraph 8,
If no conversions are required to match an argument to a parameter type,the implicit conversion sequence is the standard conversion sequenceconsisting of the identity conversion (12.2.4.2.2 [over.ics.scs]).
This would select the first overload and then fail in attemptingto call it because of the incomplete type. On the other hand, it isill-formed to define or call a function with an incomplete parametertype, although it can be declared, so it might be reasonable to takecompleteness of the parameter type into consideration for SFINAEpurposes. 13.10.3 [temp.deduct] bullet 8.11 says,
[Note: Type deduction may fail for the following reasons:
...
Attempting to create a function type in which a parameter type orthe return type is an abstract class type(11.7.4 [class.abstract]).
If a definition ofQ were available, we would need toinstantiate it to see if it is abstract.
It would seem reasonable for an incomplete type to be invalid aswell. That would be consistent with the other rules and general desirenot to select functions you can't call based on the template argumenttypes.
Rationale (July, 2017):
CWG determined that no change was needed.
There is a moderately serious problem withthe definition of overload resolution. Consider this example:
struct B; struct A { A(B); }; struct B { operator A(); } b; int main() { (void)A(b); }
This is pretty much the definition of "ambiguous," right? You wantto convert aB to anA, and there are two equallygood ways of doing that: a constructor ofA that takes aB, and a conversion function ofB that returns anA.
What we discover when we trace this through the standard,unfortunately, is that the constructor is favored over the conversionfunction. The definition of direct-initialization (the parenthesizedform) of a class considers only constructors of that class. In thiscase, the constructors are theA(B) constructor and the(implicitly-generated)A(const A&) copy constructor.Here's how they are ranked on the argument match:
A(B) | exact match (need aB, have aB) |
A(const A&) | user-defined conversion (B::operator A used toconvertB toA) |
In other words, the conversion function does get considered, butit's operating with, in effect, a handicap of one user definedconversion. To put that a different way, this problem is a problem ofweighting, not a problem that certain conversion paths are notconsidered.
I believe the reason that thestandard's approach doesn't yield the "intuitive" result isthat programmers expect copy constructor elisionto be done whenever reasonable, so the intuitive cost of usingthe conversion function in the example above is simply the costof the conversion function, not the cost of the conversion functionplus the cost of the copy constructor (which is what the standardcounts).
Suggested resolution:
In a direct-initialization overloadresolution case, if the candidate function being called is a copyconstructor and its argument (after any implicit conversions) is atemporary that is the return value of a conversion function, and thetemporary can be optimized away, the cost of the argument match forthe copy constructor should be considered to be the cost of theargument match on the conversion function argument.
Notes from 10/01 meeting:
It turns out that there is existing practice both ways on thisissue, so it's not clear that it is "broken". There is some reason tofeel that something that looks like a "constructor call" should call aconstructor if possible, rather than a conversion function.The CWG decided to leave it alone.
The structure of 12.2.3 [over.match.viable] paragraph 3 is of theform
X is better thanY if
condition 1, or, if not that,
condition 2, or, if not that,
...
It would be better to de-bullet this description, define theconditions, and then say, “X is better thanYif condition 1, condition 2, ...” This would also avoid theawkward “or, if not that,” phrasing.
Rationale (November, 2014):
CWG expressed a preference for the existing structure of thisparagraph over the suggested rewrite. The change from “or,if not that,” to “otherwise” can be handlededitorially, if desired.
Consider the following example:
struct S { operator int() const & { return 0; } operator char() && { return 0; } }; void foo(int) {} void foo(char) {} int main() { foo(S{}); //OK, callsfoo(char) }
Here, the ICS for each function is a user-definedconversion involving the same user-defined conversionfunction,operator char() &&, because of12.2.4.3 [over.ics.rank] bullet 3.2.3:
S1 andS2 include reference bindings(9.5.4 [dcl.init.ref]) and neither refers to animplicit object parameter of a non-static member functiondeclared without aref-qualifier, andS1binds an rvalue reference to an rvalue andS2 bindsan lvalue reference
foo(int) is a promotion,whilefoo(char) is an exact match, so the latter ischosen.
Replacingint andchar in this examplewith non-interconvertible types results in a differentoutcome:
class A{}; class B{}; struct S { operator A() const & { return A{}; } operator B() && { return B{}; } }; void foo(A) {} void foo(B) {} int main() { foo(S{}); //error: call tofoo is ambiguous }
Here, only one of the two user-defined conversionoperators is viable for each overload. Consequently,12.2.4.3 [over.ics.rank] bullet 3.3,
User-defined conversion sequenceU1 is a betterconversion sequence than another user-defined conversionsequenceU2 if they contain the same user-definedconversion function or constructor or they initialize thesame class in an aggregate initialization and in either casethe second standard conversion sequence ofU1 isbetter than the second standard conversion sequenceofU2.
does not apply, unlike the earlier case, becausedifferent user-defined conversion functions appear ineach conversion sequence and thus the sequences areindistinghishable.
This seems inconsistent.
Suggested resolution:
Change 12.2.4.3 [over.ics.rank] bullet 3.3 asfollows:
User-defined conversion sequenceU1 is a betterconversion sequence than another user-defined conversionsequenceU2 if
the initial standard conversion sequence ofU1 is better than the initial standard conversionsequence ofU2, or
they contain the same user-defined conversionfunction or constructor or they initialize the same class inan aggregate initialization and in either case the secondstandard conversion sequence ofU1 is better thanthe second standard conversion sequence ofU2.
CWG 2022-11-11
This is an extension that is best addressed by a paper to EWG.
Canp->f, wheref refers to a set of overloaded functionsall of which are static member functions, be used as an expression in anaddress-of-overloaded-function context? A strict reading of thissection suggests "no", because "p->f" is not the name of an overloadedfunction (it's an expression). I'm happy with that, but the coregroup should decide and should add an example to document the decision,whichever way it goes.
Rationale (10/99): The "strict reading" correctly reflectsthe intent of the Committee, for the reason given, and no clarificationis required.
It is unclear whether the following code is well-formed or not:
class A { }; struct B : public A {void foo ();void foo (int); }; int main () {void (A::*f)() = (void (A::*)())&B::foo; }
12.3 [over.over] paragraph 1 says,
The function selected is the one whose type matches the targettype required in the context. The target can be
- ...
- an explicit type conversion (7.6.1.4 [expr.type.conv],7.6.1.9 [expr.static.cast], 7.6.3 [expr.cast]).
This would appear to make the program ill-formed, since thetype in the cast is different from that of either interpretationof the address-of-member expression (its class isA,while the class of the address-of-member expression isB)
However, 12.3 [over.over] paragraph 3 says,
Nonstatic member functions match targets of type"pointer-to-member-function;" the function type of the pointerto member is used to select the member function from the setof overloaded member functions.
The class of which a function is a member isnot part of the"function type" (9.3.4.6 [dcl.fct] paragraph 4).Paragraph 4 is thus either a misuse of the phrase "function type," acontradiction of paragraph 1, or an explanation of what "matchingthe target type" means in the context of a pointer-to-membertarget. By the latter interpretation, the example is well-formedandB::foo() is selected.
Bill Gibbons: I think this is an accident due to vaguewording. I think the intent was
The function selected is the one which will make the effect ofthe cast be that of the identity conversion.
Mike Miller: The "identity conversion" reading seemsto me to be overly restrictive. It would lead to the following:
struct B { void f(); void f(int); }; struct D: B { }; void (D::* p1)() = &D::f; // ill-formed void (D::* p2)() = (void (B::*)()) &D::f; // okay
I would find the need for an explicit cast here surprising, sincethe downcast is a standard conversion and since the declaration ofp1 certainly has enough information to disambiguate theoverload set. (See alsoissue 203.)
Bill Gibbons: There is an interesting situation withusing-declarations. If a base class member function is presentin the overload set in a derived class due to ausing-declaration, it is treated as if it were a derived classmember function for purposes of overload resolution in a call(12.2.2 [over.match.funcs] paragraph 4):
For non-conversion functions introduced by ausing-declarationinto a derived class, the function is considered to be a member of thederived class for the purpose of defining the type of the implicitobject parameter
There is no corresponding rule for casts. Such a rule would bepractical, but if the base class member function were selected itwould not have the same class as that specified in the cast. Sincebase-to-derived pointer to member conversions are implicitconversions, it would seem reasonable to perform this conversionimplicitly in this case, so that the result of the cast has the righttype. The usual ambiguity and access restrictions on thebase-to-derived conversion would not apply since they do not apply tocalling through theusing-declaration either.
On the other hand, if there is no special case for this, we end up withthe bizarre case:
struct A { void foo(); }; struct B : A { using A::foo; void foo(int); } int main() { // Works because "B::foo" contains A::foo() in its overload set. (void (A::*)())&B::foo; // Does not work because "B::foo(int)" does not match the cast. (void (A::*)(int))&B::foo; }
I think the standard should be clarified by either:
Adding a note to 12.3 [over.over] saying thatusing-declarations do not participatein this kind of overload resolution; or
Modifying 12.3 [over.over] saying thatusing-declarations are treated as members of the derived classfor matching purposes, and if selected, the resulting pointer tomember is implicitly converted to the derived type with no access orambiguity errors. (Theusing-declaration itself has alreadyaddressed both of these areas.)
Rationale (10/00): The cited example is well-formed.The function type, ignoring the class specification, is used toselect the matching function from the overload set as specifiedin 12.3 [over.over] paragraph 3. If the targettype is supplied by an explicit cast, as here, the conversion isthen performed on the selected pointer-to-member value, with theusual restrictions on what can and cannot be done with theconverted value (7.6.1.9 [expr.static.cast] paragraph 9,7.6.1.10 [expr.reinterpret.cast] paragraph 9).
I understand that the lvalue-to-rvalue conversion was removed in London.I generally agree with this, but it means that?: needs to befixed:
Given:
bool test; Integer a, b; test ? a : b;What builtin do we use? The candidates are
operator ?:(bool, const Integer &, const Integer &) operator ?:(bool, Integer, Integer)which are both perfect matches.
(Not a problem in the C++11 FDIS, but misleading.)
Rationale:The description of the conditional operator in7.6.16 [expr.cond]handles the lvalue case before the prototype is considered.
Now that the restriction against local classes being used astemplate arguments has been lifted, they are more useful, yet they arestill crippled. For some reason or oversight, the restriction againstlocal classes being templates or having member templates was notlifted. Allowing local classes to have member templates facilitatesgeneric programming (the reason for lifting the other restriction),especially when it comes to the visitor-pattern (see theboost::variant documentation and the following example) asimplemented in boost and theboost::MPL library (sincefunctors have to be template classes in mpl, and higher-order functorshave to have member templates to be useful). A local class with amember template would allow this desirable solution:
#include <boost/variant.hpp> int main() { struct times_two_generic: public boost::static_visitor<> { template <typename T> void operator()(T& operand) const { operand += operand; } }; std::vector<boost::variant<int, std::string>> vec; vec.push_back(21); vec.push_back("hello "); times_two_generic visitor; std::for_each(vec.begin(), vec.end(), boost::apply_visitor(visitor)); }
Is there any compelling reason not to allow this code? Is thereany compelling reason not to allow local classes to be templates, havefriends, or be able to define their static data members at functionscope? Wouldn't this symmetry amongst local and non-local classesmake the language more appealing and less embarrassing?
Rationale (June, 2021):
EWG resolved to pursue this topic with paper P2044.It is no longer tracked as a core issue.Seevote.
It would be niceto allow template alias within a function scope, andpossibly a scoped concept map. As these affect name lookupand resolution, rather than defining new callable code,they are not seen to present the same problems thatprevented class and function templates in the past.
Rationale (July, 2009):
This suggestion needs a paper and discussion in EWG before CWG canconsider it.
Additional note, April, 2015:
EWG has decided not to make a change in this area. See EWG issue 95.
There doesn't seem to be a good reason for prohibiting C languagelinkage for function templates with internal linkage, and that couldbe useful in implementations where the calling convention of a functionis determined by its language linkage.
Rationale (August, 2011):
The specification is as desired.
Given an example like
template<const int I> struct S { decltype(I) m; };
what is the type ofm? 13.2 [temp.param] paragraph 5is clear on the question:
The top-levelcv-qualifiers on thetemplate-parameterare ignored when determining its type.
It's not clear that this is the desired outcome, however,particularly in light of the resolution ofissue 1130. (This does not make any difference for the currentlanguage, as a non-type template parameter is a prvalue andnon-class prvalues are never cv-qualified. It would have an impact,however, if a future revision of the language were to allow literalclass types as non-type template parameters, so if a change is needed,it might be a good idea to do it now.)
Rationale (November, 2010):
As noted, the treatment of cv-qualification of the type ofnon-type template parameters is irrelevant because they are currentlyalways non-class prvalues. If the language is extended to allowliteral class types, a change to the handling of cv-qualification wouldbe upwardly compatible, so nothing needs to be done now.
The example in 13.2 [temp.param] paragraph 15 contains theline
template<T... Values> apply { }; //Values is a non-type template parameter pack // and a pack expansion
This should presumably bestruct apply or some such.
Rationale (August, 2011)
This is an editorial issue that has been transmitted to the projecteditor.
Although 13.2 [temp.param] paragraph 9 forbids defaultarguments for template parameter packs, allowing them would makesome program patterns easier to write. Should this restriction beremoved?
Rationale (April, 2013):
CWG felt that removing the restriction was an extension bestconsidered by EWG.
Additional note, April, 2015:
See EWG issue 15.
EWG 2022-11-11
This is a request for a possibly desirable feature, which should beproposed in a paper to EWG.
According to 13.2 [temp.param] paragraph 9,
A defaulttemplate-argument shall not be specified inthetemplate-parameter-lists of the definition of a member of aclass template that appears outside of the member's class.
This presumably is intended to apply to the parameters of thecontaining class template, not to the parameters of a membertemplate, but the wording should be clarified. (Default argumentsare permitted for a template member of a non-template class, andthere does not appear to be a good rationale for treating membersof a class template differently in this regard.)
Rationale (June, 2014):
CWG felt that the existing wording os clear enough.
Consider the following example:
template<typename T, T V, int n = sizeof(V)> using X = int[n]; template<typename T> void f(X<T, 0>*) {} void g() { f<char>(0); }
Current implementations get confused here because theysubstituteV=0 into the default argument ofXbefore knowing the typeT and end up withf having typevoid (int (*)[sizeof(0)]),that is, the array bound does not depend onT. It'snot clear what should happen here.
Rationale (March, 2016):
There is no problem with the specification, only with theimplementations; the default argument forn isdependent becauseV has a dependent type.
The intended treatment of an example like the following is notclear:
template<class ...Types> struct Tuple_ { // _VARIADIC_TEMPLATE template<Types ...T> int f() { return sizeof...(Types); } }; int main() { Tuple_<char,int> a; int b = a.f(); }
According to 13.2 [temp.param] paragraph 19,
If atemplate-parameter is atype-parameter with an ellipsisprior to its optionalidentifier or isaparameter-declaration that declares a pack(9.3.4.6 [dcl.fct]), then thetemplate-parameter is atemplate parameter pack (13.7.4 [temp.variadic]). A templateparameter pack that is aparameter-declaration whose type containsone or more unexpanded packs is a pack expansion. Similarly, a templateparameter pack that is atype-parameter withatemplate-parameter-list containing one or more unexpanded packs isa pack expansion. A template parameter pack that is a pack expansion shallnot expand a template parameter pack declared in thesametemplate-parameter-list.
with the following example:
template <class... T> struct value_holder { template <T... Values> struct apply { }; //Values is a non-type template parameter pack }; // and a pack expansion
There is implementation divergence on the treatment of the example, withsome rejecting it on the basis that the arguments forTuple_::fcannot be deduced, while others accept it.
Rationale (December, 2018):
The example is ill-formed because the packs have differentsizes:Types has 2,T has 0 (from the call).
The discussion in of the use oftypename with aqualified-id in a templateparameter-declaration in13.3 [temp.names] paragraph 2 is confusing:
typename followed by anunqualified-id names atemplate type parameter.typename followed byaqualified-id denotes the type in a non-typeparameter-declaration.
This rule would be clearer if theunqualified-id case weredescribed in terms of resolving the ambiguity of declaring atemplate parameter name versus referring to atype-namefrom the enclosing scope, and if thequalified-id casereferred to the use of thetypename keyword withdependent types in 13.8 [temp.res]. An example wouldalso be helpful.
Rationale (April, 2006):
The CWG felt that the wording was already clear enough.
None of my compilers accept this, which surprised me a little. Isthe base-to-derived member function conversion considered to be aruntime-only thing?
template <class D> struct B { template <class X> void f(X) {} template <class X, void (D::*)(X) = &B<D>::f<X> > struct row {}; }; struct D : B<D> { void g(int); row<int,&D::g> r1; row<char*> r2; };
John Spicer:This is not among the permitted conversions listed in 14.3.
I'm not sure there is a terribly good reason for that. Some of thetemplate argument rules for external entities were made conservativelybecause of concerns about issues of mangling template argument names.
David Abrahams:I'd really like to see that restriction loosened. It is a seriousinconvenience because there appears to be no way to supply a usabledefault in this case. Zero would be an OK default if I could use thefunction pointer's equality to zero as a compile-time switch tochoose an empty function implementation:
template <bool x> struct tag {}; template <class D> struct B { template <class X> void f(X) {} template <class X, void (D::*pmf)(X) = 0 > struct row { void h() { h(tag<(pmf == 0)>(), pmf); } void h(tag<1>, ...) {} void h(tag<0>, void (D::*q)(X)) { /*something*/} }; }; struct D : B<D> { void g(int); row<int,&D::g> r1; row<char*> r2; };
But there appears to be no way to get that effect either. The resultis that you end up doing something like:
template <class X, void (D::*pmf)(X) = 0 > struct row { void h() { if (pmf) /*something*/ } };
which invariably makes compilers warn that you're switching on aconstant expression.
Additional notes (February, 2023)
Even the following simple case fails on all major implementations:
struct B { void f(); }; struct D : B {}; template<void (D::*)()> int g(); int x = g<&D::f>();
Subclause 7.7 [expr.const] paragraph 12 is unambiguous thatthe code is ill-formed, but that seems unfortunate.
CWG 2023-06-15
CWG noted that the derived-to-base conversion is also not allowedfor arguments for non-type template parameters. Permitting suchconversions is an extension, not a defect.
According to 13.7.3 [temp.mem] paragraph 4,
A specialization of a member function template does not overridea virtual function from a base class.Bill Gibbons:I think that's sufficiently surprising behavior that it should be ill-formedinstead.
As I recall, the main reason why a member function template cannot bevirtual is that you can't easily construct reasonable vtables for aninfinite set of functions. That doesn't apply to overrides.
Another problem is that you don't know that a specialization overridesuntil the specialization exists:
struct A { virtual void f(int); }; struct B : A { template<class T> void f(T); // does this override? };But this could be handled by saying:
template<int I> struct X; struct A { virtual void f(A<5>); }; struct B : A { template<int I, int J> void f(A<I+J>); // does not overrride }; void g(B *b) { X<t> x; b->f<3,2>(x); // specialization B::f(A<5>) makes program ill-formed }So I think there are reasonable semantics. But is it useful?
If not, I think the creation of a specialization that would have beenan override had it been declared in the class should be an error.
Daveed Vandevoorde:There is real code out there that is written with this rulein mind. Changing the standard on them would not be goodform, IMO.
Mike Ball:Also, if you allow template functions to be specialized outsideof the class you introduce yet another non-obvious ordering constraint.
Please don't make such a change after the fact.
John Spicer: This is the result of an explicit committeedecision. The reason for this rule is that it is too easy tounwittingly override a function from a base class, which was probablynot what was intended when the template was written. Overridingshould be a conscious decision by the class writer, not something doneaccidentally by a template.
Rationale (10/99): The Standard correctly reflects theintent of the Committee.
Notes from October 2002 meeting:
This was reopened because of a discussion while reviewing possibleextensions.
Notes from April 2003 meeting:
This was discussed again, and the consensus was that we didnot want to make a change, and in particular we did not want tomake it an error and risk breaking existing code.
The list of contexts in which pack expansions can occur, in13.7.4 [temp.variadic] paragraph 4, does not include afunction call, in spite of the comments in the example therethat assume that a function call is such a context.
Rationale (August, 2010):
initializer-list, mentioned in 13.7.4 [temp.variadic],is used in the argument list of a function call.
A specialization of a variadic function template can produce the samefunction signature as a non-variadic one; in particular, a class canend up with multiple default constructors if a pack expansion is empty.It would be helpful if such a specialization could be suppressed sothat the non-variadic function were preferred.
Rationale (October, 2012):
It can be argued that this is not a defect in the language butsimply something that must be considered by the programmer: if thedefault constructor and the empty-pack-expansion constructor do thesame thing, the default constructor is superfluous, while if they dodifferent things there may be a logic error in one or the other. EWGshould resolve the policy question of whether this situation shouldreceive special treatment in the language to make it well-formed.
Rationale (February, 2014):
EWG determined that no action should be taken on this issue. There isan existing workaround for the problem, and it will also be addressed bythe Concepts Lite proposal.
Issue 1
Paragraph 1 says that a friend of a class template can be a template.Paragraph 2 says: A friend template may be declared within a non-templateclass. A friend function template may be defined within a non-templateclass.
I'm not sure what this wording implies about friend template definitionswithin template classes. The rules for class templates and normal classesshould be the same: a function template can be declared or defined, buta class template can only be declared in a friend declaration.
Issue 2
Paragraph 4 says: When a function is defined in a friend function declarationin a class template, the function is defined when the class template isfirst instantiated. I take it that this was intended to mean that a functionthat is defined in a class template is not defined until the first instantiation.I think this should say that a function that is defined in a class templateis defined each time the class is instantiated. This means that a functionthat is defined in a class template must depend on all of the templateparameters of the class template, otherwise multiple definition errorscould occur during instantiations. If we don't have a rule like this, compilerswould have to compare the definitions of functions to see whether theyare the same or not. For example:
template <class T> struct A { friend int f() { return sizeof(T); } }; A<int> ai; A<long> ac;I hope we would all agree that this program is ill-formed, even if longand int have the same size.
From Bill Gibbons:
[1] That sounds right.
[2] Whenever possible, I try to treat instantiated class templates asif they were ordinary classes with funny names. If you write:
struct A_int { friend int f() { return sizeof(int); } }; struct A_long { friend int f() { return sizeof(long); } };it is a redefinition (which is not allowed) and an ODR violation. And ifyou write:
template <class T, class U> struct A { friend int f() { return sizeof(U); } }; A<int,float> ai; A<long,float> ac;the corresponding non-template code would be:
struct A_int_float { friend int f() { return sizeof(float); } }; struct A_long_float { friend int f() { return sizeof(float); } };then the two definitions of "f" are identical so there is no ODRviolation, but it is still a redefinition. I think this is just an editorialclarification.
Rationale (04/99): The first sub-issue reflects wording that waschanged to address the concern before the IS was issued. A close andcareful reading of the Standard already leads to the conclusion that theexample in the second sub-issue is ill-formed, so no change is needed.
The status of an example like the following is not clear:
template<class> struct x { template<class T> friend bool operator==(x<T>, x<T>) { return false; } }; int main() { x<int> x1; x<double> x2; x1 == x1; x2 == x2; }
Such afriend definition is permitted by13.7.5 [temp.friend] paragraph 2:
A friend function template may be defined within a class or classtemplate...
Paragraph 4 appears to be related, but deals only with friend functions,not friend function templates:
When a function is defined in a friend function declaration in a classtemplate, the function is instantiated when the function isodr-used. The same restrictions on multiple declarations anddefinitions that apply to non-template function declarations anddefinitions also apply to these implicit definitions.
Rationale (February, 2021):
The resolution ofissue 2174 deletedthe paragraph in question and makes clear the treatment of friendfunction templates.The current wording is not clear how to declare that a nestedclass template of a class template is a friend of its containingtemplate. For example, is
template <class T> struct C { template <bool b> class Foo; template <bool b> friend class Foo; };
correct, or should it be
template <class T> struct C { template <bool b> class Foo; template <class X> template <bool b> friend class C<X>::Foo; };
Rationale (June, 2018)
The submitter asked that the issue be withdrawn.
Library issue 225 poses the following questions:
For example, a programmer might want to provide a version ofstd::swap that would be used for any specialization of aparticular class template. It is possible to do that for specifictypes, but not for all specializations of a template.
The problem is due to the fact that programmers are forbidden toadd overloads to namespacestd, although specializations arepermitted. One suggested solution would be to allow partialspecialization of function templates, analogous to partialspecialization of class templates.
Library issue 225 contains a detailed proposal for adding partialspecialization of function templates (not reproduced here in theinterest of space and avoiding multiple-copy problems). This Coreissue is being opened to provide for discussion of the proposal withinthe core language working group.
Notes from 10/00 meeting:
A major concern over the idea of partial specialization offunction templates is that function templates can be overloaded,unlike class templates. Simply naming the function template inthe specialization, as is done for class specialization, is notadequate to identify the template being specialized.
In view of this problem, the library working group is exploringthe other alternative, permitting overloads to be added to functionsin namespacestd, as long as certain restrictions (to bedetermined) are satisfied.
(See also documents N1295 and N1296 andissue 285.)
Notes from 10/01 meeting:
The Core Working Group decided to ask the Library Working Groupfor guidance on whether this feature is still needed to resolve alibrary issue. The answer at present is "we don't know."
Rationale (October, 2004):
The Core Working Group decided that the Evolution Working Group isthe appropriate forum in which to explore the desirability and form ofthis feature.
Note (March, 2008):
The Evolution Working Group recommended closing this issue with nofurther consideration. See paper J16/07-0033 = WG21 N2173.
There does not appear to be a way to declare (not define) a partialspecialization of a static data member template outside itsclass. The rule for explicit specializations (13.9.4 [temp.expl.spec] paragraph 13) is that the presence or absence of an initializer determineswhether the explicit specialization is a definition or not. Applyingthis rule to the partial specialization case, however, would conflict withbeing able to provide an initializer on the declaration within the class.
Do we need to support declaring partial specializations of staticdata member templates outside their class?
Rationale (February, 2014):
CWG felt that this issue is more appropriately considered by EWG.
Additional note, April, 2015:
EWG has decided not to make a change in this area. See EWG issue132.
One of the restrictions on partial specializations found in13.7.6.1 [temp.spec.partial.general] paragraph 9 is:
The template parameter list of a specialization shall not containdefault template argument values. [Footnote: There is no way inwhich they could be used. —end footnote]
The rationale for this restriction is incorrect, since defaulttemplate argument values can be used to trigger SFINAE and thuscontrol whether a particular partial specialization is used. Anexample of this use is:
template <typename T> struct a; template <typename T, typename = typename std::enable_if<some property>::type> struct a<std::vector<T>> { ... };
which is forbidden by this point. Note also that an example like
template <typename T> struct b; template <typename T, typename = typename std::enable_if<some property>::type> struct b<T> { ... };
is likely forbidden by the previous bullet:
The argument list of the specialization shall not be identical to theimplicit argument list of the primary template.
This restriction may also need to be weakened.
Rationale (April, 2013)
CWG felt that consideration of these suggestions was more appropriatelydone by EWG.
Additional note, April, 2015:
EWG has decided not to make a change in this area. See EWG issue110.
Although 14.5 [except.spec] paragraph 3 says,
Twoexception-specifications arecompatible if:
...
both have theformnoexcept(constant-expression)and theconstant-expressions are equivalent,or
...
it is not clear whether “equivalent” in this contextshould be taken as a reference to the definition ofequivalent given in 13.7.7.2 [temp.over.link] paragraph5:
Two expressions involving template parameters are consideredequivalent if two function definitions containing theexpressions would satisfy the one definition rule(6.3 [basic.def.odr]), except that the tokens used toname the template parameters may differ as long as a tokenused to name a template parameter in one expression isreplaced by another token that names the same templateparameter in the other expression.
since the context there is expressions that appear in functiontemplate parameters and return types.
There is implementation variance on this question.
Rationale (February, 2021):
The text in question no longer appears in the Standard.
Issue 1:
13.7.7.3 [temp.func.order] paragraph 2says:
Given two overloaded function templates, whether one is morespecialized than another can be determined by transforming each templatein turn and using argument deduction (13.10.3 [temp.deduct]) to compare it to the other.13.10.3 [temp.deduct] now has 4 subsections describing argument deduction in differentsituations. I think this paragraph should point to a subsection of13.10.3 [temp.deduct].
Rationale:
This is not a defect; it is not necessary to pinpoint cross-referencesto this level of detail.
Issue 2:
13.7.7.3 [temp.func.order] paragraph 4says:
Using the transformed function parameter list, perform argumentdeduction against the other function template. The transformed templateis at least as specialized as the other if, and only if, the deductionsucceeds and the deduced parameter types are an exact match (so the deductiondoes not rely on implicit conversions).In "the deduced parameter types are an exact match", the terms exact matchdo not make it clear what happens when a type T is compared to the referencetype T&. Is that an exact match?
Issue 3:
13.7.7.3 [temp.func.order] paragraph 5says:
A template is more specialized than another if, and only if,it is at least as specialized as the other template and that template isnot at least as specialized as the first.What happens in this case:
template<class T> void f(T,int); template<class T> void f(T, T); void f(1,1);For the first function template, there is no type deduction for the secondparameter. So the rules in this clause seem to imply that the second functiontemplate will be chosen.
Rationale:
This is not a defect; the standard unambiguously makes the above exampleill-formed due to ambiguity.
Additional note (April, 2011):
These points appear to have been addressed by previous resolutions,so presumably the issue is now NAD.
Rationale (August, 2011):
As given in the preceding note.
The relative order of template parameter pack expansion andalias template substitution is not clear in the current wording. Forexample, in
template<typename T> using Int = int; template<typename ...Ts> struct S { typedef S<Int<Ts>...> other; };
it is not clear whetherint is substituted forInt<Ts> first, leaving the ellipsis with no parameterpack to expand, or whether the pack expansion is to be applied first,producing a list of specializations ofInt<T>.
(See alsoissue 1558.)
Rationale (October, 2012):
The latter interpretation (a list of specializations) is the correctinterpretation; a parameter pack can't be substituted into anything,including an alias template specialization. CWG felt that this isclear enough in the current wording.
Consider the following example:
template <class T> struct Outer { struct Inner { Inner* self(); }; }; template <class T> Outer<T>::Inner* Outer<T>::Inner::self() { return this; }
According to 13.8 [temp.res] paragraph 3 (before thesalient wording was inadvertently removed, seeissue 559),
Aqualified-id that refers to a type and in which thenested-name-specifier depends on atemplate-parameter(13.8.3 [temp.dep]) but does not refer to a member of thecurrent instantiation (13.8.3.2 [temp.dep.type]) shall beprefixed by the keywordtypename to indicate thatthequalified-id denotes a type, formingatypename-specifier.
BecauseOuter<T>::Inner is a member of the currentinstantiation, the Standard does not currently require that it beprefixed withtypename when it is used in the return type ofthe definition of theself() member function. However, it isdifficult to parse this definition correctly without knowing that thereturn type is, in fact, a type, which is what thetypenamekeyword is for. Should the Standard be changed to requiretypename in such contexts?
Rationale (February, 2021):
The current wording of 13.8.1 [temp.res.general]bullet 5.2.1 makes clear that thetypename keywordis not required for the given example.
The following appears to be well-formed, with templatesfoo() being distinct since any typeT willproduce an invalid type for the second parameter for atleast onefoo() whenT is replaced withinthe non-deduced context:
template <typename T> bool *foo(T *, enum T::u_type * = 0) { return 0; } template <typename T> char *foo(T *, struct T::u_type * = 0) { return 0; } struct A { enum u_type { I }; }; int main(void) { foo((A*)0); }
In particular, while determining the signaturefor the function templatesfoo(), anelaborated-type-specifier qualifies as part of thedecl-specifier-seq under 9.3.4.6 [dcl.fct] paragraph 5in determining the type of a parameter in theparameter-type-list (absent additional wording). Also, thereturn type is included in the signature of a functiontemplate.
Implementations do not appear to support this case andthe ability to do so brings little value since type traitssuch asis_enum andis_class cannot bedefined using this and equivalent functionality can beachieved using the aforementioned type traits.
Rationale (August, 2010):
The specification is as intended; compilers should handlecases like these.
Recently a customer sent us code of the form,
template<typename T> void f(); template<> void f<int>() { } template<typename T> void f() { static_assert(false, "f() instantiated with non-int type."); }
The intent, obviously, was to do compile-time diagnosis ofspecializations of the template that were not supported, and code ofthis form is supported by at least some implementations. However,the current wording of 13.8 [temp.res] paragraph 8,appears to invalidate this approach:
If no valid specialization can be generated for a template, and thattemplate is not instantiated, the template is ill-formed, nodiagnostic required.
In this example, thestatic_assert will fail for everygenerated specialization off(), so an implementation canissue the error, regardless of whetherf() is everinstantiated with a non-int type or not.
A relatively straightforward but somewhat ugly workaround is todefine a template like
template<typename> struct always_false { static const bool val = false; };
and replace the use offalse in thestatic_assertwithalways_false<T>::val, making thestatic_assertdependent.
Considering the fact that a non-dependentstatic_assert-declaration in a template is otherwise prettyuseless, however, it might be worth considering whether to supportthis usage somehow, especially in light of the fact that it issupported by some implementations, perhaps by treatingstatic_assert-declarations as always dependent, even if thecondition is not otherwise dependent.
Rationale (October, 2012):
Although this usage is supported by some implementations and usedin some libraries, CWG felt that=delete is the appropriatemechanism for making a function template or member function of a classtemplate unavailable for specialization.
Given that thetype-id in analias-declaration isunambiguously a type, is there a reason to require thetypenamekeyword for dependent types appearing there? In other contexts where adependent name can only be a type (e.g., in abase-specifier),the keyword can/must be omitted.
Rationale (October, 2012):
CWG felt that having a simple rule (advising use oftypenamewith all dependent nested types wherever syntactically permitted) wasmore important than reducing the number of contexts in which therequirement applied.
According to 13.8 [temp.res] paragraph 8,
No diagnostic shall be issued for a template for which a validspecialization can be generated.
One sentence later, it says,
If every valid specialization of a variadic template requires an emptytemplate parameter pack, the template is ill-formed, no diagnosticrequired.
This appears to be a contradiction: in the latter case, there ispostulated to exist a “valid” specialization (with an emptypack expansion), for which a diagnostic might or might not be issued. Thefirst quoted sentence, however, forbids issuing a diagnostic for a templatethat has at least one valid specialization.
Rationale (February, 2017):
The text in question was revised editorially and the issue is nowmoot.
Paragraphs 3-4 of 13.8 [temp.res] read, in part,
When aqualified-id is intended to refer to a typethat is not a member of the current instantiation(13.8.3.2 [temp.dep.type]) anditsnested-name-specifier refers to a dependent type,it shall be prefixed by the keywordtypename, formingatypename-specifier. If thequalified-id inatypename-specifier does not denote a type, theprogram is ill-formed.
If a specialization of a template is instantiated for a setoftemplate-arguments such thatthequalified-id prefixed by typename does not denotea type, the specialization is ill-formed.
The former requirement is intended to apply to the definitionand the latter to an instantiation of a template, but that intentis not completely clear, leading to the perception that they areredundant.
Rationale (February, 2021):
The specification, now found in 13.8.1 [temp.res.general],particularly in bullet 8.5, is clearer in this regard.
A gcc hacker recently sent in a patch to make the compiler give an error oncode like this:
template <template <typename> class T> struct A { }; template <typename U> struct B { A<B> *p; };presumably because the DR fromissue 176says that we decide whether or notB is to be treated as a template depending on whether atemplate-argument-list is supplied. I think this is a drafting oversight,and that B should also be treated as a template when passed as a templatetemplate parameter. The discussion in the issue list only talks aboutmaking the name usable both as a class and as a template.
John Spicer:This case was explicitly discussed and it was agreed that to use the injectedname as a template template parameter you need to use the non-injectedname.
A (possibly unstated) rule that I've understood about templatearguments is thatthe form of the argument (type/nontype/template) is based only on theargument and not on the kind of template parameter. An example is that"int()" is always "function taking no arguments returning int"and never a convoluted way of saying zero.
In a similar way, we now decide whether or not something is a template basedonly on the form of the argument.
I think this rule is important for two kinds of cases. The first caseinvolves explicit arguments of function templates:
template <template <typename> class T> void f(){} // #1 template <class T> void f(){} // #2 template <typename U> struct B {void g() {f<B>();} }; int main() {B<int> b;b.g(); }
With the current rules, this uses B as a type argument to template #2.
The second case involves the use of a class template for which the templateparameter list is unknown at the point where the argument list is scanned:
template <class T> void f(){} template <typename U> struct B {void g() {f< U::template X<B> >(); // what is B?} }; struct Z1 {template <class T> struct X {}; }; struct Z2 {template <template <class> class T> struct X {}; }; int main() {B<Z1> b1;b1.g();B<Z2> b2;b2.g(); }
If B could be used as a template name we would beunable to decide how to treatB at the point that it was scanned in the template argument list.
So, I think it is not an oversight and that it should beleft the way it is.
Notes from the 4/02 meeting:
It was agreed that this is Not a Defect.Currently, member of nondependent base classes hidereferences to template parameters in the definitionof a derived class template.
Consider the following example:
class B { typedef void *It; // (1) // ... }; class M: B {}; template<typename> X {}; template<typename It> struct S // (2) : M, X<It> { // (3) S(It, It); // (4) // ... };
As the C++ language currently stands, the name "It"in line (3) refers to the template parameter declaredin line (2), but the name "It" in line (4) refers tothe typedef in the private base class (declared inline (1)).
This situation is both unintuitive and a hindranceto sound software engineering. (See also the Usenetdiscussion at http://tinyurl.com/32q8d .) Amongother things, it implies that the private sectionof a base class may change the meaning of the derivedclass, and (unlike other cases where such thingshappen) there is no way for the writer of the derivedclass to defend the code against such intrusion (e.g.,by using a qualified name).
Changing this can break code that is valid today.However, such code would have to:
It has been suggested to make situations like theseill-formed. That solution is unattractive howeverbecause it still leaves the writer of a derived classtemplate without defense against accidental nameconflicts with base members. (Although at least theproblem would be guaranteed to be caught at compiletime.) Instead, since just about everyone's intuitionagrees, I would like to see the rules changed tomake class template parameters hide members of thesame name in a base class.
See alsoissue 458.
Notes from the March 2004 meeting:
We have some sympathy for a change, but the current rules fallstraightforwardly out of the lookup rules, so they're not“wrong.” Making private members invisible also would solvethis problem. We'd be willing to look at a paper proposing that.
Additional discussion (April, 2005):
John Spicer: Base class members are more-or-less treated asmembers of the class, [so] it is only natural that the base [member]would hide the template parameter.
Daveed Vandevoorde: Are base class members really“more or less” members of the class from a lookupperspective? After all, derived class members can hide base classmembers of the same name. So there is some pretty definiteboundary between those two sets of names. IMO, the templateparameters should either sit between those two sets, or theyshould (for lookup purposes) be treated as members of the classthey parameterize (I cannot think of a practical differencebetween those two formulations).
John Spicer: How is [hiding template parameters]different from the fact that namespace members can be hiddenby private parts of a base class? The addition ofint CtoN::A breaks the code in namespaceM in thisexample:
namespace N { class A { private: int C; }; } namespace M { typedef int C; class B : public N::A { void f() { C c; } }; }
Daveed Vandevoorde: C++ has a mechanism in place to handlesuch situations: qualified names. There is no such mechanism in placefor template parameters.
Nathan Myers: What I see as obviously incorrect ... issimply that a name defined right where I can see it, and directlyattached to the textual scope ofB's class body, is ignoredin favor of something found in some other file. I don't care thatC1 is defined inA, I have aC1 righthere that I have chosen to use. If I wantA::C1, I cansay so.
I doubt you'll find any regular C++ coder who doesn't find thestandard behavior bizarre. If the meaning of any code is changedby fixing this behavior, the overwhelming majority of cases willbe mysterious bugs magically fixed.
John Spicer: I have not heard complaints that this isactually a cause of problems in real user code. Where is theevidence that the status quo is actually causing problems?
In this example, theT2 that is found is the one fromthe base class. I would argue that this is natural because baseclass members are found as part of the lookup in classB:
struct A { typedef int T2; }; template <class T2> struct B : public A { typedef int T1; T1 t1; T2 t2; };
This rule that base class members hide template parameters wasformalized about a dozen years ago because it fell out of theprinciple that base class members should be found at the samestage of lookup as derived class members, and that to do otherwisewould be surprising.
Gabriel Dos Reis: The bottom line is that:
Unless presented with real major programming problems the currentrules exhibit, I do not think the simple rule “scopesnest” needs a change that silently mutates program meaning.
Mike Miller: The rationale for the current specification isreally very simple:
That's it. Because template parameters are not members, theyare hidden by member names (whether inherited or not). I don't findthat “bizarre,” or even particularly surprising.
I believe these rules are straightforward and consistent, so Iwould be opposed to changing them. However, I am not unsympathetictoward Daveed's concern about name hijacking from base classes. Howabout a rule that would make a program ill-formed if a direct orinherited member hides a template parameter?
Unless this problem is a lot more prevalent than I've heard sofar, I would not want to change the lookup rules; making this kind ofcollision a diagnosable error, however, would prevent hijackingwithout changing the lookup rules.
Erwin Unruh: I have a different approach that is consistentand changes the interpretation of the questionable code. At presentlookup is done in this sequence:
If we change this order to
it is still consistent in that no lookup is placed between the baseclass and the derived class. However, it introduces anotherinconsistency: now scopes do not nest the same way as curly bracesnest — but base classes are already inconsistent this way.
Nathan Myers: This looks entirely satisfactory. If even thisseems like too big a change, it would suffice to say that finding adifferent name by this search order makes the program ill-formed.Of course, a compiler might issue only a portability warning in thatcase and use the name found Erwin's way, anyhow.
Gabriel Dos Reis: It is a simple fact, even withouttemplates, that a writer of a derived class cannot protect himselfagainst declaration changes in the base class.
Richard Corden: If a change is to be made, then making itill-formed is better than just changing the lookup rules.
struct B { typedef int T; virtual void bar (T const & ); }; template <typename T> struct D : public B { virtual void bar (T const & ); }; template class D<float>;
I think changing the semantics of the above code silently wouldresult in very difficult-to-find problems.
Mike Miller: Another case that may need to be considered indeciding on Erwin's suggestion or the “ill-formed”alternative is the treatment offriend declarationsdescribed in 6.5.3 [basic.lookup.unqual] paragraph 10:
struct A { typedef int T; void f(T); }; template<typename T> struct B { friend void A::f(T); // CurrentlyT isA::T };
Notes from the October, 2005 meeting:
The CWG decided not to consider a change to the existing rules atthis time without a paper exploring the issue in more detail.
CWG 2023-12-01
CWG has no consensus to pursue a change in this area, without prejudice to a potential future paper addressed to EWG.
There is some question as to whether 13.8.3 [temp.dep] paragraph 3applies to the definition of an explicitly-specializedmember of a class template:
In the definition of a class template or a member of a class template,if a base class of the class template depends onatemplate-parameter, the base class scope is not examinedduring unqualified name lookup either at the point of definition ofthe class template or member or during an instantiation of the classtemplate or member.
Consider an example like the following:
template <class T> struct A { void foo() {} }; template <class T> struct B: A<T> { int bar(); }; int foo() { return 0; } template <> int B<int>::bar() { return foo(); } int main() { return B<int>().bar(); }
Doesfoo in the definition ofB<int>::bar()refer to::foo() or toA<int>::foo()?
Rationale (April, 2006):
An explicitly-specialized member of a class template is not, infact, a member of a class template but a member of a particularspecialization of that template. The special treatment of lookupvis-a-vis dependent base classes in 13.8.3 [temp.dep]thus does not apply, and base class members are found by unqualifiedname lookup.
Consider the following example:
struct A { virtual void f() { /* base */ } }; struct B : virtual A { virtual void f() { /* derived */ } }; template<typename T> struct C : virtual A, T { void g() { this->f(); } }; int main() { C<B> c; c.g(); }
This is reasonable C++03 code that is invalidated by the resolutionofissue 1043. In the presence of virtualnon-dependent base classes and other dependent base classes, onecannot rely on something being found for real when doing the lookup inthe instantiation context (therefore, one cannot know whether a"typename" is actually valid or not, without knowing all dependentbase classes).
Rationale (August, 2011):
This example is not sufficient motivation to revisit the outcome ofissue 1043.((T*)this)->f() canbe used to allow lookup in a dependent base.
It does not appear to be possible to use the name of an alias template(without a template argument list) to refer to the current instantiation.
Rationale (August, 2011):
The rules are as intended.
According to 13.8.2 [temp.local] paragraph 1, when theinjected-class-name of a class template is not followed by atemplate-argument-list or otherwise used as atemplate-name,
it is equivalent to thetemplate-name followed bythetemplate-parameters of the class template enclosedin<>.
This use of thetemplate-parameters of the class templateshould make the injected-class-name a dependent type; however, thedefinition of dependent types in 13.8.3.2 [temp.dep.type] paragraph 8applies to the injected-class-name only when it appearsin asimple-template-id. An additional case is needed forthe bare injected-class-name.
Rationale (June, 2014):
The fact that the use of the bare injected-class-name isdescribed as “equivalent” tothesimple-template-id is sufficiently clear regarding itsstatus that no additional entry is needed in the list ofdependent types.
Is the comma expression in the following dependent?
template <class T> static void f(T) { } template <class T> void g(T) { f((T::x, 0)); } struct A { static int x; }; void h() { g(A()); }
According to the standard, it is, because 13.8.3.3 [temp.dep.expr]says that an expression is dependent if any of its sub-expressions isdependent, but there is a question about whether the language shouldsay something different. The type and value of the expression are not reallydependent, and similar cases (like castingT::xtoint) are not dependent.
Mark Mitchell: If the first operand is dependent, howdo we know it does not have an overloaded comma operator?
Rationale (October, 2004):
CWG agreed that such comma expressions are and ought to bedependent, for the reason expressed in Mark Mitchell'scomment.
Does Koenig lookup create a point of instantiation for class types?I.e., if I say:
TT<int> *p; f(p);The namespaces and classes associated withp are those associated withthe type pointed to, i.e.,TT<int>. However, to determine thoseI need to knowTT<int> bases and its friends, which requiresinstantiation.
Or should this be special cased for templates?
Rationale:The standard already specifies that this creates a point of instantiation.
A related question to that raised inissue 488 is whether member function templates must beinstantiated if the compiler can determine that they will not beneeded by the function selected by overload resolution. That isexplicitly specified for class templates in 13.9.2 [temp.inst] paragraph 5:
If the overload resolution process can determine the correct functionto call without instantiating a class template definition, it isunspecified whether that instantiation actually takes place.
Should the same be true for member function templates? In theexample fromissue 488,
struct S { template <typename T> S(const T&); }; void f(const S&); void f(int); void g() { enum E { e }; f(e); // ill-formed? }
a compiler could conceivably determine thatf(int)would be selected by overload resolution (because it involvesonly an integral promotion, while the alternative requires auser-defined conversion) without instantiating the declaration oftheS constructor. Should the compiler have thatfreedom?
Rationale (April, 2005):
In order for this question to come up, there would need to be a“gap” between the the normal rules and the rules fortemplate argument deduction failure. The resolution forissue 488 will close the only suchgap of which the CWG is aware. The issue can be reopened if othersuch cases turn up.
Subclause 7.5.6.2 [expr.prim.lambda.closure] paragraph 2 specifies:
The closure type is declared in the smallest block scope, class scope,or namespace scope that contains thecorrespondinglambda-expression.
This means the closure type is a local class ifthelambda-expression appears at function scope. A note in13.9.2 [temp.inst] paragraph 2 claims that default argumentsinside local classes are not separately instantiated:
[Note 3: Within a template declaration, a local class(11.6 [class.local]) or enumeration and the members of a localclass are never considered to be entities that can be separatelyinstantiated (this includes their defaultarguments,noexcept-specifier s, and non-static data memberinitializers, if any, but not theirtype-constraintsorrequires-clauses). As a result, the dependent names arelooked up, the semantic constraints are checked, and any templatesused are instantiated as part of the instantiation of the entitywithin which the local class or enumeration is declared. —endnote]
However, 13.9.2 [temp.inst] paragraph 3 is not in harmonywith the note:
The implicit instantiation of a class template specialization does notcause the implicit instantiation of default argumentsornoexcept-specifier s of the class member functions.
Example:
template<typename T> void ft() { [](T p = T::value) {}; // error even though the lambda is never called } template void ft<int>();
Even for a lambda declared at namespace scope is an unused defaultargument instantiated by major implementations:
template<typename T> using ta = decltype([](T p = T::value) { // error return p; }); auto g = ta<int>{}(0);
CWG 2023-11-11
The behavior is as intended. Note that 13.9.2 [temp.inst] paragraph 3 applies to a class template specialization and its memberfunctions. The examples do not involve class templates, but merelytemplated classes.
(From submission#535.)
Consider:
void g() { auto f = [](auto s) { s.x = 0; }; if constexpr (false) { f(0); // well-formed? } }
Note that the lambda has a deduced return type, yet per the currentwording,f is not instantiated here.
Possible resolution:
Add a new paragraph before 13.9.2 [temp.inst] paragraph 9 as follows:
The existence of a definition of a function isconsidered to affect the semantics of the program if the function isnamed by an expression (6.3 [basic.def.odr]) and the function'sdeclared return type is a placeholder type(9.2.9.7 [dcl.spec.auto]).[ Example:
void g() { auto f = [](auto s) { s.x = 0; //#1 }; if constexpr (false) { f(0); // error at #1: typeint has no member namedx } }-- end example ]
If the function selected by overload resolution(12.2 [over.match]) can be determined without ...
CWG 2024-06-14
The original example involving a deduced return type of the lambdais ill-formed, because the semantics of the program are affected byits return type, and thus an instantion is required(13.9.2 [temp.inst] paragraph 5): If the lambda returned a classtype with a private destructor, the program would be ill-formed.
The modified example with avoid return type of the lambdais well-formed; clang's implementation divergence is believed to be abug.
As a side note, odr-use is not affected by discarded statements;just some of the effects of odr-use do not materialize(6.3 [basic.def.odr] paragraph 12).
Is the second explicit instantiation below well-formed?
template <class T> struct A { template <class T2> void f(T2){} }; template void A<int>::f(char); // okay template template void A<int>::f(float); // ?Since multiple "template<>" clauses are permitted in an explicitspecialization, it might follow that multiple "template" keywordsshould also be permitted in an explicit instantiation. Are multiple "template"keywords not allowed in an explicit instantiation? The grammar permitsit, but the grammar permitslots of stuff far weirder than that.My opinion is that, in the absence of explicit wording permitting thatkind of usage (as is present for explicit specializations) that such usageis not permitted for explicit instantiations.
Rationale (04/99): The Standard does not describe the meainingof multipletemplate keywords in this context, so the exampleshould be considered as resulting in undefined behavior according toClause 3 [intro.defs] “undefined behavior.”
The current language specification allows suppression of implicitinstantiations of templates via an explicit instantiation declaration;if all uses of a particular specialization follow an explicitinstantiation declaration for that specialization, and there is oneexplicit instantiation definition in the program, there will be onlya single copy of that instance. However, the Standard does not requirethe presence of an explicit instantiation declaration prior to use, soimplementations must still be prepared (using weak symbols, for example)to handle multiple copies of the instance at link time. This can bea significant overhead, particularly in shared libraries where weaksymbols must be resolved at load time. Requiring the presence of anexplicit instantiation declaration in every translation unit in whichthe specialization is used would allow the compiler to emit strongsymbols for the explicit instantiation definition and reduce theoverhead.
On the other hand, the current definition allows use of multipleindependent libraries with explicit instantiation directives of thesame specializations (from a common third-party library, for instance),as well as incremental migration of libraries to use of explicitinstantiation declarations rather than requiring all libraries tobe updated at once.
Rationale (August, 2010):
CWG prefers the current specification.
According to 13.9.3 [temp.explicit] paragraph 9,
Except for inline functions and class templatespecializations, explicit instantiation declarations havethe effect of suppressing the implicit instantiation of theentity to which they refer.
This means that an implementation cannot do inline expansionof anextern template function or member function,because that would require its instantiation. As a result, addingan explicit instantiation declaration can affect performance, eventhough the user only intended to suppress out-of-line copies offunctions.
Rationale (August, 2010):
If implementations are allowed to do speculative instantiationfor the purpose of inlining, there could be silent changes ofmeaning depending on whether the instantiation is done or not.
Consider the following example:
template <typename T> extern const decltype(sizeof 0) Sz = sizeof(T); extern template const decltype(sizeof 0) Sz<int>; constexpr decltype(sizeof 0) x = Sz<int>;
C++14 allows this, exempting “const variables ofliteral type" from the effects of an explicit instantiationdeclaration:
Except for inline functions, declarations with types deducedfrom their initializer or return value(9.2.9.7 [dcl.spec.auto]), const variables of literaltypes, variables of reference types, and class templatespecializations, explicit instantiation declarations havethe effect of suppressing the implicit instantiation of theentity to which they refer. [Note: The intent is thatan inline function that is the subject of an explicitinstantiation declaration will still be implicitlyinstantiated when odr-used (6.3 [basic.def.odr]) sothat the body can be considered for inlining, but that noout-of-line copy of the inline function would be generatedin the translation unit. —end note]
Should there be a DR against C++11 for the similar case ofa static data member of a class template?
Rationale (October, 2015):
CWG agreed that this was a defect in C++11, but it isaddressed in C++14.
Consider:
template <class F> F foo() { return 1; } template <class F> struct S { F foo() { return 1; } }; extern template int foo<int>(); extern template struct S<int>; int bar() { return foo<int>() + S<int>().foo(); }
An implementation is permitted to instantiate (and thus locallyinline)S<int>::foo, but notS<int>, because13.9.2 [temp.inst] paragraph 10 states:
Except for inline functions, declarations with types deduced fromtheir initializer or return value (9.2.9.7 [dcl.spec.auto]),const variables of literal types, variables of reference types, andclass template specializations, explicit instantiation declarationshave the effect of suppressing the implicit instantiation of theentity to which they refer.
Additional note (February, 2022):
The paragraph in question was removed by P1815R2(Translation-unit-local entities) (adopted 2020-02).
EWG 2022-11-11
Any change would require a paper.
[N1065 issue 1.19] An explicit specialization declaration may not bevisible during instantiation under the template compilation model rules,even though its existence must be known to perform the instantiation correctly.For example:
translation unit #1
template<class T> struct A { }; export template<class T> void f(T) { A<T> a; }translation unit #2
template<class T> struct A { }; template<> struct A<int> { }; // not visible during instantiation template<class T> void f(T); void g() { f(1); }Rationale:This issue was addressed in the C++11 FDIS and should have been closed.
Is this valid C++?The question is whether a member constant can be specialized.My inclination is to say no.
template <class T> struct A { static const T i = 0; }; template<> const int A<int>::i = 42; int main () { return A<int>::i; }John Spicer: This is ill-formed because11.4.9.3 [class.static.data]paragraph 4 prohibits an initializer on a definition of a static data memberfor which an initializer was provided in the class.
The program would be valid if the initializer were removed from thespecialization.
Daveed Vandevoorde: Or at least, the specialized member shouldnot be allowed in constant-expressions.
Bill Gibbons: Alternatively, the use of a member constant withinthe definition could be treated the same as the use of "sizeof(memberclass)". For example:
template <class T> struct A { static const T i = 1; struct B { char b[100]; }; char x[sizeof(B)]; // specialization can affect array size char y[i]; // specialization can affect array size }; template<> const int A<int>::i = 42; template<> struct A<int>::B { char z[200] }; int main () { A<int> a; return sizeof(a.x) // 200 (unspecialized value is 100) + sizeof(a.y); // 42 (unspecialized value is 1) }For the member template case, the array size "sizeof(B)" cannotbe evaluated until the template is instantiated becauseB mightbe specialized. Similarly, the array size "i" cannot be evaluateduntil the template is instantiated.
Rationale (10/99): The Standard is already sufficiently clearon this question.
John Spicer:Certain access checks are suppressed on explicit instantiations.13.9.3 [temp.explicit] paragraph 8says:
The usual access checking rules do not apply to names used to specifyexplicit instantiations. [Note: In particular, the template argumentsand names used in the function declarator (including parameter types,return types and exception specifications) may be private types orobjects which would normally not be accessible and the template may bea member template or member function which would not normally beaccessible. ]I was surprised that similar wording does not exist (that I could find) forexplicit specializations. I believe that the two cases should be handledequivalently in the example below (i.e., that the specialization should bepermitted).
template <class T> struct C { void f(); void g(); }; template <class T> void C<T>::f(){} template <class T> void C<T>::g(){} class A { class B {}; void f(); }; template void C<A::B>::f(); // okay template <> void C<A::B>::g(); // error - A::B inaccessible void A::f() { C<B> cb; cb.f(); }
Mike Miller:According to the note in13.4 [temp.arg] paragraph 3,
if the name of atemplate-argument is accessibleat the point where it is used as atemplate-argument,there is no further access restriction in theresulting instantiation where the correspondingtemplate-parameter name is used.
(Is this specified anywhere in the normative text? Shouldit be?)
In the absence of text to the contrary, this blanketpermission apparently applies to explicitly-specializedtemplates as well as to implicitly-generated ones (is thatright?). If so, I don't see any reason that an explicitinstantiation should be treated differently from an explicitspecialization, even though the latter involves new programtext and the former is just a placement instruction to theimplementation.
Proposed Resolution (4/02):
In 13.9.3 [temp.explicit] delete paragraph 8:
The usual access checking rules do not apply to names used to specify explicitinstantiations. [Note: In particular, the template arguments and names used inthe function declarator (including parameter types, return types and exceptionspecifications) may be private types or objects which would normally not beaccessible and the template may be a member template or member function whichwould not normally be accessible. ]
In 13.9 [temp.spec] add the paragraph deleted above asparagraph 7 with the changes highlighted below:
The usual access checking rules do not apply to names used to specify explicitinstantiationsor explicit specializations.[Note: Inparticular, tThe template arguments and names used in thefunction declarator (including parameter types, return types and exceptionspecifications) may be private types or objects which would normally not beaccessible and the template may be a member template or member functionwhich would not normally be accessible.]
Rationale (October 2002):
We reconsidered this and decided that the difference between thetwo cases (explicit specialization and explicit instantiation) isappropriate. The access rules are sometimes bent when necessary toallow naming something, as in an explicit instantiation, butexplicit specialization requires not only naming the entity butalso providing a definition somewhere.
The Standard does not describe how to handle an examplelike the following:
template <class T> int f(T, int); template <class T> int f(int, T); template<> int f<int>(int, int) { /*...*/ }
It is impossible to determine which of the function templatesis being specialized. This problem is related to the discussionofissue 229, in which one of the objectionsraised against partial specialization of function templates isthat it is not possible to determine which template is beingspecialized.
Notes from 10/01 meeting:
It was decided that while this is true, it's not a problem. Youcan't call such functions anyway; the call would be ambiguous.
It is not clear whether there is any necessary relationship betweenthe type specified in a primary variable template declaration and thetype in an explicit or partial specialization. For example:
template<typename T> T var = T(); template<> char var<char> = 'a'; // #1. template<typename T> T* var<T> = new T(); // #2. template<> float var<int> = 1.5; // #3.
Rationale (September, 2013):
CWG affirmed that there is no required relationship between the typeof the template and the type of a partial or explicit specialization ofthat template.
The example in 13.9.4 [temp.expl.spec] paragraph 6 reads, inpart,
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 // whenA<char> was instantiated template<> enum class A<char>::S : int { schar }; // OK
Theintenum-base in the last two lines appears tobe incorrect; the reference toA<char> in thenested-name-specifier will have instantiated the declarationsofE andS with anenum-base ofchar,and the explicit specializations must agree.
Rationale (February, 2014):
The problem was fixed editorially in N3797.
A desire has been expressed for a mechanism to prevent explicitlyspecializing a given class template, inparticularstd::initializer_list and perhaps some others in thestandard library. It is not clear whether simply adding a prohibition tothe description of the templates in the library clauses would besufficient or whether a core language mechanism is required.
Rationale (June, 2014):
This request for a new language feature should be considered byEWG before any action is taken.
EWG 2022-11-11
The library clauses already prohibit user specializations ofstandard library templates. This is a request for new feature, whichshould be proposed in a paper to EWG.
It is not clear whether the following common practice is validby the current rules:
// foo.h template<typename T> struct X { int f(); // never defined }; // foo.cc #include "foo.h" template<> int X<int>::f() { return 123; } // main.cc #include "foo.h" int main() { return X<int>().f(); }
Relevant rules include Clause 13 [temp] paragraph 6,
A function template, member function of a class template,variable template, or static data member of a class templateshall be defined in every translation unit in which it isimplicitly instantiated (13.9.2 [temp.inst]) unlessthe corresponding specialization is explicitly instantiated(13.9.3 [temp.explicit]) in some translation unit; nodiagnostic is required.
13.9.2 [temp.inst] paragraph 2,
Unless a member of a class template or a member template hasbeen explicitly instantiated or explicitly specialized, thespecialization of the member is implicitly instantiated whenthe specialization is referenced in a context that requiresthe member definition to exist...
and 13.9.4 [temp.expl.spec] paragraph 6:
If a template, a member template or a member of a classtemplate is explicitly specialized then that specializationshall be declared before the first use of thatspecialization that would cause an implicit instantiation totake place, in every translation unit in which such a useoccurs; no diagnostic is required. If the program does notprovide a definition for an explicit specialization andeither the specialization is used in a way that would causean implicit instantiation to take place or the member is avirtual member function, the program is ill-formed, nodiagnostic required. An implicit instantiation is nevergenerated for an explicit specialization that is declaredbut not defined.
The intent appears to be that the reference inmain.cc violates two rules: it implicitly instantiatessomething for which no definition is provided and that is notexplicitly instantiated elsewhere, and it also causes animplicit instantiation of something explicitly specializedin another translation unit without a declaration of theexplicit specialization.
Rationale (March, 2016):
As stated in the analysis, the intent is for the example to beill-formed, no diagnostic required.
Given
template<class T, class U> struct A { }; template<class... T, class ... U> void f( A<T,U>...p); void g() { f<int>( A<int,unsigned>(), A<short,unsigned short>() ); }
I would expect this to work, but all the recent compilers I triedreject it, indicating deduction failure.
Rationale (April, 2013):
This is well-formed.
According to 13.10.2 [temp.arg.explicit] paragraph 9,
Template argument deduction can extend the sequence oftemplate arguments corresponding to a template parameterpack, even when the sequence contains explicitly specifiedtemplate arguments.
However, it is not clear how to handle an example like:
template<class...> struct Z { Z (int); }; template<class... Ts> void f (Z<Ts...>); int main () { f<void, void> (0); }
Rationale (November, 2014):
CWG was not convinced that such cases are sufficiently usefulto warrant the additional complexity in the rules required tosupport them.
Consider:
struct S { operator int(); }; template<int N> void f(const int (&)[N]); int main() { S s; f<2>({s, s}); // #1 f({s, s}); // #2 }
Since the array element type is not deduced, implicit conversionsought to be permitted in #2 but other implementations disagree. Isthere a compelling reason to disallow them?
For comparison:
#include <initializer_list> struct S { operator int(); }; template<typename T> void f(std::initializer_list<T>); int main() { S s; f<int>({s, s}); }
Because T is not deduced, implicit conversions are allowed, and thenumber of elements in the underlying temporary array is determinedfrom the number of elements in the initializer list. It seems thatthe intention ofissue 1591 was to allowthe underlying temporary array to be deduced directly, so the factthat the array bounds are deduced in the case above shouldn't inhibitimplicit conversions.
Rationale (November, 2016):
Although there is an argument to be made for the suggesteddirection, the current rule is simple and easy to explain, so therewas no consensus for a change.
Andrei Iltchenko points out that the standard has no wording thatdefines how to determine which template is specialized by anexplicit specialization of a function template.He suggests "template argument deductionin such cases proceeds in the same way as when taking the addressof a function template,which is described in 13.10.3.3 [temp.deduct.funcaddr]."
John Spicer points out that the same problem exists for allsimilar declarations, i.e., friend declarations and explicitinstantiation directives. Finding a corresponding placementoperator delete may have a similar problem.
John Spicer:There are two aspects of "determining which template" is referred to bya declaration: determining the function template associated with thenamed specialization, and determining the values of the template argumentsof the specialization.
template <class T> void f(T); #1 template <class T> void f(T*); #2 template <> void f(int*);
In other words, whichf is being specialized (#1 or #2)?And then, what are the deduced template arguments?
13.7.7.3 [temp.func.order] does say that partial ordering isdone in contexts such as this.Is this sufficient, or do we need to say more about the selection of thefunction template to be selected?
13.10.3 [temp.deduct] probably needs a new section to coverargument deduction for cases like this.
Rationale (February, 2021):
The missing specification was added by C++11; see13.10.3.7 [temp.deduct.decl].
The Standard currently specifies (9.2.4 [dcl.typedef] paragraph 9,13.4.2 [temp.arg.type] paragraph 4) that an attempt to create areference to a reference (via a typedef or template typeparameter) is effectively ignored. The same is not true of an attemptto form a pointer to a reference; that is, assuming thatT isspecified to be a reference type,
template <typename T> void f(T t) { T& tr = t; // OK T* tp = &t; // error }
It would be more consistent to allow pointers to references tocollapse in the same way that references to references do.
Rationale (February, 2008):
In the absence of a compelling need, the CWG felt that it wasbetter not to change the existing rules. Allowing this case couldcause a quiet change to the meaning of a program, because attemptingto create a pointer to a reference type is currently a deductionfailure.
Additional discussion (May, 2009):
Consider the following slightly extended version of the exampleabove:
template <typename T> void f(T t) { T& tr = t; // OK T* tp = &t; // error auto * ap = &t; // OK! }
This means that a template that expects a reference type will needto useauto just to work around the failure to collapsepointer-to-reference types. The result might, in fact, be subtlydifferent withauto, as well, in case there is an overloadedoperator& function that doesn't return exactlyT*. This contradicts one of the main goals of C++0x, to makeit simpler, more consistent, and easier to teach.
Rationale (July, 2009):
The CWG reaffirmed its early decision. In general, templates willneed to be written differently for reference and non-reference typeparameters. Also, the Standard library provides a facility,std::remove_reference, that can be used easily for suchcases.
With the resolution ofissue 1170, whichtakes access into account in template argument deduction, it is nowpossible to have instantiation-dependent expressions (seeissue 1172) that do not directly involve atemplate parameter. For example:
template <class T> struct C; class A { int i; friend struct C<int>; } a; class B { int i; friend struct C<float>; } b; template <class T> struct C { template <class U> decltype (a.i) f() { } // #1 template <class U> decltype (b.i) f() { } // #2 }; int main() { C<int>().f<int>(); // calls #1 C<float>().f<float>(); // calls #2 }
Rationale (August, 2011):
The specification is as intended. To the extent that there is anissue here, it is covered byissue 1172.
Given an example like
template <class T> void f (T, int = T()); template <class T> auto g(T t) -> decltype (f(t)); void g(int); struct A { A(int); operator int(); }; int main() { g(A(42)); }
it seems that since the default argument is treated as a separatetemplate, its ill-formedness causes a hard error, rather than asubstitution failure forg. Is this what we want?
Rationale (October, 2012):
CWG felt that this was acceptable; also, there is discussion inEWG regarding changes to the SFINAE rules that could affect thiscase.
Consider:
template <class T> void f(T&); template <class T> void f(const T&); void m() { const int p = 0; f(p); }Some compilers treat this as ambiguous; others preferf(const T&).The question turns out to revolve around whether13.10.3.2 [temp.deduct.call]paragraph 2 says what it ought to regarding the removal of cv-qualifiersand reference modifiers from template function parameters indoing type deduction.
John Spicer:The partial ordering rules as originally proposed specified that, forpurposes of comparing parameter types, you remove a top level reference,and after having done that you remove top level qualifiers. This is notwhat is actually in the IS however. The IS says that you remove toplevel qualifiers and then top level references.
The original rules were intended to preferf(A<T>) overf(const T&).
Rationale (10/99): The Standard correctly reflects the intentof the Committee.
(October 2002)This is resolved byissue 214.
In the following example,
template<typename T> void f(const T&); // #1 template<typename T> void f(T&&); // #2 void g() { const int x = 5; f(x); }
the callf(x) is ambiguous by the current rules. For #1,T is deduced asint, giving
f<int>(const int&)
For #2, because of the special case forT&& in13.10.3.2 [temp.deduct.call] paragraph 3,T is deduced asconst int&; application of the reference-collapsingrules in 9.3.4.3 [dcl.ref] paragraph 6 to the substitutedparameter type yields
f<const int&>(const int&)
These are indistinguishable in overload resolution, resulting inan ambiguity. It's not clear how this might be addressed.
Rationale (August, 2010):
The two functions are distinguished by partial ordering, so thecall is not actually ambiguous.
Simple pointer:
Template argument:const T*
Actual argument:char*
We deduceT to bechar, and by compatibility ofcv-qualifiers, this deduction works.
Now suppose that (somehow) we deducedT to beconstchar. We fold the resultingconst const char* intoconstchar*, and again the deduction works. Does the standard disallow thisdeduction? The reason for this question is in the next example.
Pointer to member:
Template argument:const T A<T>::*, whereA is a class template
Actual argument:char A<const char>::*
Compilers reject this case, because if we deduce the firstT tobechar, we cannot matchA<T>::*. But suppose welook first at the secondT, deducing it to beconstchar. Then we getconst char A<const char>::*, which isOK. Alternatively, as in the hypothetical case in example 1, suppose wededuce the firstT to beconst char, again we get amatch.
Arbitrarily adding extra cv-qualifiers in looking for a match, or tryingdifferent matching orders to find one that works, seems wrong. But arethese approaches disallowed?
For completeness, here is a code example:
template <typename Q> struct A { int i; }; template <typename T> void foo(const T A<T>::*) { } int main() { A<const int> a; int A<const int>::*p = &A<const int>::i; foo(p); // rejected by all compilers, but why? }
Rationale (September, 2013):
There are no rules that would result in deducingconst charin the specified example.
An rvalue reference type involving a template parameter receivesspecial treatment in template argument deduction in13.10.3.2 [temp.deduct.call] paragraph 3:
IfP is an rvalue reference to a cv-unqualified template parameterand the argument is an lvalue, the type “lvalue referencetoA” is used in place ofA for type deduction.
Does this rule apply when the parameter type involves an aliastemplate instead of using a template parameter directly? Forexample:
template<class T> using RR = T&&; template<class T> struct X {}; template<class T> struct X<T&&>; // Leave incomplete to possibly trigger an error template<class T> void g(RR<T> p) { X<decltype(p)> x; } int main() { int x = 2; g(x); }
There is implementation variance on the treatment of this example.
Additional note (October, 2013):
It was observed thatthe type of the parameter would be the same whether written asT&& or asRR<T>, which would requirethat deduction be performed the same, regardless of how the type waswritten.
Rationale (September, 2013):
Because the types of the function parameters are the same, regardlessof whether written directly or via an alias template, deduction must behandled the same way in both cases.
Consider the following example:
template <typename T> void f(T** p, void (*)()); // #1 template <typename T> void f(T* p, void (&)()); // #2 void x(); void g(int** p) { f(p, x); // #3 }
The question is whether the call at #3 is ambiguous or not.
The synthesized declarations for overload resolution are:
void f<int>(int**, void(*)()); // From #1 void f<int*>(int**, void(&)()); // From #2
Neither of these is a better match on the basis of conversionsequences (the function-to-pointer conversion and the referencebinding have “exact match” rank), and both are functiontemplate specializations, so the tiebreaker in 12.2.4 [over.match.best] paragraph 1 comes down to whether #1 is morespecialized than #2 or vice versa.
The determination of whether one of these templates is morespecialized than the other is done (as described in 13.7.7.3 [temp.func.order]) by synthesizing a type for the template parameter ofeach function template (call them@1 and@2,respectively), substituting that synthesized type for each occurrenceof the template parameter in the function type of the template, andthen performing deduction on each pair of corresponding functionparameters as described in 13.10.3.5 [temp.deduct.partial].
For the first function parameter, #1 is more specialized: deductionsucceeds withP=T* andA=@1**, givingT=@1*, but it fails withP=T** andA=@2*.For the second parameter, deduction fails in both directions, withP=void(*)() andA=void() as well as withP=void() andA=void(*)() (the reference is droppedfrom both the parameter and argument types, as described in13.10.3.5 [temp.deduct.partial] paragraph 5). This means that neitherparameter type is at least as specialized as the other (paragraph8).
According to 13.10.3.5 [temp.deduct.partial] paragraph 10,
If for each type being considered a given template is atleast as specialized for all types and more specializedfor some set of types and the other template is not morespecialized for any types or is not at least asspecialized for any types, then the given template is morespecialized than the other template. Otherwise,neither template is more specialized than the other.
According to this rule, #1 is not more specialized than #2because it is not “at least as specialized” for the secondparameter type, so the call at #3 is ambiguous.
Results vary among implementations, with some rejecting thecall as ambiguous and others resolving it to #1.
Would it be better to say that a function template F1 is morespecialized than F2 if at least one of F1's types is morespecialized than the corresponding F2 type and none of F2'stypes is more specialized than the corresponding F1 type? Thatwould be simpler and, for examples like this, arguably moreintuitive. The rationale for this change would be that if,for a given parameter pair, neither is more specialized than theother, then that parameter pair simply says nothing about whetherone of the templates is more specialized than the other, ratherthan indicating that the templates cannot be ordered.
(See alsoissue 455.)
Rationale (October, 2009):
The consensus of the CWG is that this issue, in which correspondingparameters cannot be compared but the functions are equivalent interms of overload resolution, arises so infrequently in practice thatno change is warranted at this time.
Consider an example like:
template<typename T> struct A { A(const T&); // #1 A(T&&); // #2 }; template<typename U> A(U&&)->A<double>; // #3 int main(){ int i =0; const int ci =0; A a1(0); A a2(i); A a3(ci); }
This example is covered by 13.10.3.5 [temp.deduct.partial] paragraph 9:
If, for a given type, deduction succeeds in both directions (i.e., thetypes are identical after the transformations above) and both P and A werereference types (before being replaced with the type referred toabove):
if the type from the argument template was an lvalue referenceand the type from the parameter template was not, the parameter type is notconsidered to be at least as specialized as the argument type;otherwise,
if the type from the argument template is more cv-qualified than thetype from the parameter template (as described above), the parameter typeis not considered to be at least as specialized as the argumenttype.
Fora2(i), the deduction guide is the best match, so this isanA<double>.
Fora3(ci), the first bullet applies, which prefers #1 to #3since #1 comes from an lvalue reference and #3 does not, resulting in anA<int>.
Fora1(0), the case is not covered by partial ordering, so12.2.4 [over.match.best] bullet 1.10 applies and prefers #3 to #2, whichis again anA<double>.
It seems inconsistent to prefer #1 to #3 (T const & toU&&), but to prefer #3 to #2 (U&& toT&&). Should the rules be expanded to basically preferany non-forwarding-reference to a forwarding reference?
Rationale (June, 2018):
There was no consensus to make a change at this point; the behavior isas intended.
Consider the following:
template <typename T> struct X {}; // #1template <typename T> struct X<const T>; //#2template struct X<int&>; //#3
Which specialization are we instantiating in #3? The "obvious" answeris #1, because "int&" doesn't have a top level cv-qualification.However, there's also an argument saying that we should actually beinstantiating #2. The argument is: int& can be taken as a match foreither one (top-level cv-qualifiers are ignored on references, sothey're equally good), and given two equally good matches we mustchoose the more specialized one.
Is this a valid argument? If so, is this behavior intentional?
John Spicer:I don't see the rationale for any choice other than #1. While it istrue that if you attempt to apply const to a reference type it justgets dropped, that is very different from saying that a reference typeis acceptable where a const-qualified type is required.
If the type matched both templates, the const one would bemore specialized, but "int&" does not match "const T".
Nathan Sidwell:thanks for bringing this one to the committee. However this isresolved, I'd like clarification on the followup questions in thegcc bug report regarding deduced and non-deduced contexts andfunction templates. Here're those questions for y'all,
template <typename T> void Foo (T *); // #1template <typename T> void Foo (T const *); // #2void Baz ();Foo (Baz); // which?template <typename T> T const *Foo (T *); // #1void Baz ();Foo (Baz); // well formed?template <typename T> void Foo (T *, T const * = 0);void Baz ();Foo (Baz); // well formed?
BTW, I didn't go trying to break things, I implemented the cv-qualifierignoring requirements and fell over this. I could find nothing in thestandard saying 'don't do this ignoring during deduction'.
Issue 226 removed the originalprohibition on defaulttemplate-arguments for functiontemplates. However, the note in 13.10.3.6 [temp.deduct.type] paragraph 19still reflects that prohibition. It should be revisedor removed.
Rationale (February, 2010):
The problematic note was removed as a by-product of the changesfor variadic templates, document N2284.
The special rule in 13.10.3.6 [temp.deduct.type] paragraph 10 forhandlingT&& in template argument deduction appliesonly to function parameters. It also needs to apply to function returntypes (including for conversion function templates,13.10.3.4 [temp.deduct.conv]).
Rationale (February, 2012):
The specification is as intended: the special treatment of lvaluearguments in deduction is to make “perfect forwarding” workand should not be applied in other contexts.
It would be useful to be able to deduce the type of a functiontemplate argument from a corresponding default function argumentexpression, for example:
template <class T> int f(T = 0); int x = f();
A more realistic use case would be
template <class T, class U> int f(T x, U y = pair<T, T>());
Ideally one would also like
template <class T, class U> int f(T x, U y = g(x));
These capabilities are part of the Boost parameter library,so there should not be issues of implementability.
Rationale (February, 2014):
EWG determined that no action should be taken on this issue.
The grammar should be changed so that constructorfunction-try-blocks must end with athrow-expression.
Rationale (04/00):
No change is needed. It is already the case that flowing off theend of afunction-try-block of a constructor rethrows theexception, andreturn statements are prohibited inconstructorfunction-try-blocks(14.4 [except.handle] paragraphs 15-16.
Questions regarding when a throw-expression temporary object is destroyed.
Section 14.2 [except.throw]paragraph 4 describes when the temporary is destroyed when a handler is found.But what if no handler is found:
struct A { A() { printf ("A() \n"); } A(const A&) { printf ("A(const A&)\n"); } ~A() { printf ("~A() \n"); } }; void t() { exit(0); } int main() { std::set_terminate(t); throw A(); }DoesA::~A() ever execute here?(Or, in case two constructions are done,are there two destructions done?) Is it implementation-defined, analogouslyto whether the stack is unwound beforeterminate() is called(14.4 [except.handle] paragraph 9)?
Or what if an exception specification is violated? There are severaldifferent scenarios here:
int glob = 0; // or 1 or 2 or 3 struct A { A() { printf ("A() \n"); } A(const A&) { printf ("A(const A&)\n"); } ~A() { printf ("~A() \n"); } }; void u() { switch (glob) { case 0: exit(0); case 1: throw "ok"; case 2: throw 17; default: throw; } } void foo() throw(const char*, std::bad_exception) { throw A(); } int main() { std::set_unexpected(u); try { foo(); } catch (const char*) { printf("in handler 1\n"); } catch (std::bad_exception) { printf("in handler 2\n"); } }The case whereu() exits is presumably similar to theterminate() case.But in the cases where the program goes on,A::~A() should be calledfor the thrown object at some point. But where does this happen?The standard doesn't really say. Since an exception is defined tobe "finished" when theunexpected() function exits, it seems to me thatis whereA::~A() should be called — in this case, as the throws ofnew (or what will become new) exceptions are made out ofu().Does this make sense?
Rationale (10/99): Neitherstd::exit(int) norstd::abort() destroys temporary objects, so the exceptiontemporary is not destroyed when no handler is found. The originalexception object is destroyed when it is replaced by anunexpected() handler. The Standard is sufficiently clearon these points.
According to 14.2 [except.throw] paragraph 7,
If the exception handling mechanism, after completing the initialization ofthe exception object but before the activation of a handler for theexception, calls a function that exits via anexception,std::terminate is called (14.6.2 [except.terminate]).
This could be read as indicating that the following example resultsin callingstd::terminate:
// function that exits via an exception void f() { //std::uncaught_exception() returnstrue here throw 0; } struct X { ~X() { // calls a function that exits via an exception try { f(); } catch( ... ) { } } }; int main() { try { X x; throw 0; // callsX's destructor while exception is still uncaught. } catch( ... ) { } }
This seems undesirable, and current implementations do not callstd::terminate. Presumably the intention is that the citedtext only applies to functions that are called directly by theexception handling mechanism, which is not true off() inthis example, but this could be stated more clearly.
Rationale (September, 2013):
The intent of the wording in 14.2 [except.throw] paragraph 7is to callstd::terminate if an exception is propagated intothe exception-handling mechanism; “If the exception handlingmechanism... calls a function that exits via an exception” isthus intended to refer to functions that are directly called bythe exception handling mechanism. In the given example,f()is not called by the exception handling mechanism, it is called byX::~X(). The exception handling mechanism callsX::~X(),but it does not exit via an exception, sostd::terminate shouldnot be called.
According to 14.3 [except.ctor] paragraph 2,
An object of any storage duration whose initialization or destruction isterminated by an exception will have destructors executed for all of itsfully constructed subobjects (excluding the variant members of a union-likeclass)...
The restriction for variant members does not appear to be necessary whenthere is amem-initializer or non-static data member initializer fora union member, as that determines which union member is active during theexecution of the constructor.
Rationale (April, 2013):
Although the active member in a member union is determined by aninitializer, it could change during the execution of the constructor'scompound=statement.
[Detailed description pending.]
Rationale (November, 2016):
The reported issue is no longer relevant after the adoption of paperP0490R0 at the November, 2016 meeting.
[Detailed description pending.]
Rationale (November, 2016):
The reported issue is no longer relevant after the adoption of paperP0490R0 at the November, 2016 meeting.
14.4 [except.handle] paragraph 3 contains the following text:
A handler is a match for a throw-expression with an 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 baseclass of E, or
- the handler is of type cv1 T* cv2 and E is a pointer type that can beconverted to the type of the handler by either or both of
- a standard pointer conversion (7.3.12 [conv.ptr]) not involvingconversions to pointers to private or protected or ambiguous classes
- a qualification conversion
I propose to alter this text to allow to catch exceptions with ambiguouspublic base classes by some of the public subobjects. I'm really sure thatif someone writes:
try { // ... } catch (Matherr& m) { // ... }he really wants to catchallMatherrs rather than to allowsome of theMatherrs to escape:
class SomeMatherr : public Matherr { /* */ }; struct TrickyBase1 : public SomeMatherr { /* */ }; struct TrickyBase2 : public SomeMatherr { /* */ }; struct TrickyMatherr : public TrickyBase1, TrickyBase2 { /* */ };
According to the standardTrickyMatherrwill leak through the catch(Matherr& m) clause. For example:
#include <stdio.h> struct B {}; struct B1 : B {}; struct B2 : B {}; struct D : B1, B2 {}; // D() has two B() subobjects void f() { throw D(); } int main() { try { f(); } catch (B& b) { puts("B&"); } // passed catch (D& d) { puts("D&"); } // really works _after_ B&!!! }
Also I see one more possible solution: to forbid objects with ambiguousbase classes to be "exceptional objects" (for example Borland C++ goes thisway) but it seems to be unnecessary restrictive.
Notes from the 10/01 meeting:
The Core Working Group did not feel this was a significant problem.Catching either of the ambiguous base classes would be surprising, andgiving an error on throwing an object that has an ambiguous base classwould break existing code.
If control reaches the end of handler in adestructor'sfunction-try-block, the exception is rethrown(14.4 [except.handle] paragraph 15). Because of the dangerof destructors that throw exceptions, would it be better totreat this case as an implicitreturn; statement, as ina function body? There could be a transitional period, perhapsusing conditionally-supported behavior or the like, beforemandating the change.
Rationale (October, 2006):
The CWG felt that the current behavior is clearly specified andreflects the intention of the Committee at the time the rules wereadopted. Possible changes to these rules should be pursued throughthe Evolution Working Group.
14.5 [except.spec] paragraph 13 contains the following text.I believe 'implicitLY' marked below should be replaced with'implicit.'
An implicitly declared special member function (11.4.4 [special])shall have an exception-specification. If f is an implicitlydeclared default constructor, copy constructor, destructor, or copyassignment operator, its implicit exception-specificationspecifies thetype-id T if and only if T is allowed by theexception-specification of a function directly invokedby f'simplicitly definition;f shall allow all exceptions if any function it directly invokesallows all exceptions, and f shall allow no exceptions ifevery function it directly invokes allows no exceptions. [Example:
struct A { A(); A(const A&) throw(); ~A() throw(X); }; struct B { B() throw(); B(const B&) throw(); ~B() throw(Y); }; struct D : public A, public B { // Implicit declaration of D::D(); // Implicit declaration of D::D(const D&) throw(); // Implicit declaration of D::~D() throw (X,Y); };Furthermore, if A::~A() or B::~B() were virtual, D::~D() would not be asrestrictive as that of A::~A, and the program wouldbe ill-formed since a function that overrides a virtual function from abase class shall have an exception-specification at least asrestrictive as that in the base class. ]
The example code shows structs whose destructors have exceptionspecifications which throw certain types. There is no defect here, butit doesn't sit well with our general advice elsewhere that destructorsshould not throw. I wish I could think of some other way to illustratethis section.
Notes from October 2002 meeting:
This was previously resolved by an editorial change.
It is unclear whetherstd::unexpected is called beforeor after the destruction of function arguments, partially-constructedbases and members (when called from a constructor or destructor), etc.
Rationale (October, 2009):
The point at whichstd::unexpected is called is specifiedin _N4606_.15.5.2 [except.unexpected] paragraph 1:
If a function with anexception-specification throwsan exception that is not listed in theexception-specification, the functionstd::unexpected() is called (_N4606_.D.6 [exception.unexpected])immediately after completing the stack unwinding for the formerfunction.
That is, it will be called after any local automatic objects andtemporaries are destroyed and before any other objects, such asfunction arguments, are destroyed. (See 7.6.1.3 [expr.call] paragraph 4: “The initialization and destruction of eachparameter occurs within the context of the callingfunction.”)
The consensus at the Pittsburgh (March, 2010) meeting, as reflected inthe adoption of paper N3050, was that it was preferable for violation ofanoexcept guarantee to callstd::terminate; previousversions of the paper had called for undefined behavior in this case.Not everyone was convinced that this was a good decision, however; thisissue is intended to facilitate further investigation and discussion ofthe question with the benefit of more time and resources than wereavailable during the deliberations at the meeting.
Rationale (August, 2010):
CWG reaffirmed the explicit decision of the Committee.
Although 14.5 [except.spec] paragraphs 5-6 requirethat overriding a virtual function and initializing orassigning to a function pointer notweakenexception-specifications, the same is not trueof providing a template argument for a template parameter.For example,
template<void (*FP)() noexcept> void x() { } void f() noexcept(false); template void x<f>();
is currently well-formed, which seems inconsistent. (Note thatifexception-specifications become part of the type system,as proposed inissue 92, this issue willbecome moot.)
See also issues2010,1995,1975, and1946.
Rationale (February, 2014):
Are template declarations that differ only in theexception-specification of the parameter redeclarations orseparate templates distinguished, presumably, by deduction failure?This seems like a question more appropriate for consideration by EWG.
Additional note, April, 2015:
EWG has decided not to make a change in this area. See EWG issue133.
According to 14.5 [except.spec] paragraph 4,
If any declaration of a function has anexception-specification thatis not anoexcept-specification allowing all exceptions, alldeclarations, including the definition and any explicit specialization, ofthat function shall have a compatibleexception-specification.
This seems excessive for explicit specializations, considering thatparagraph 6 applies a looser requirement for virtual functions:
If a virtual function has anexception-specification, alldeclarations, including the definition, of any function that overrides thatvirtual function in any derived class shall only allow exceptions that areallowed by theexception-specification of the base class virtualfunction.
The rule in paragraph 3 is also problematic in regard toexplicit specializations of destructors and defaulted special memberfunctions, as the implicitexception-specification of thetemplate member function cannot be determined.
There is also a related problem with defaulted specialmember functions andexception-specifications.According to 9.6.2 [dcl.fct.def.default] paragraph 3,
If a function that is explicitly defaulted has anexplicitexception-specification that is notcompatible (14.5 [except.spec]) withtheexception-specification on the implicitdeclaration, then
if the function is explicitly defaulted on its firstdeclaration, it is defined as deleted;
otherwise, the program is ill-formed.
This rule precludes defaulting a virtual base classdestructor or copy/move functions if the derived class functionwill throw an exception not allowed by the implicit base classmember function.
This request for a language extension should be evaluated byEWG before any action is taken.
Additional note, November, 2020:
This request applied to full exception specifications andis no longer relevant in the current language, where onlynoexcept-specifiers are permitted.
EWG 2022-11-11
Close as NAD.
The description of the “set of potential exceptions of anexpression” in 14.5 [except.spec] paragraph 15 doesnot appear to be fully recursive, so it can miss the effect of, e.g.,athrow-expression as a subexpression. In addition,bullet 15.1.1, which reads,
If itspostfix-expression is a (possiblyparenthesized)id-expression(_N4567_.5.1.1 [expr.prim.general]), class member access(7.6.1.5 [expr.ref]), or pointer-to-member operation(7.6.4 [expr.mptr.oper]) whosecast-expression isanid-expression,S is the set of potentialexceptions of the entity selected by thecontainedid-expression (after overload resolution,if applicable).
omits the case where thepostfix-expression is afunction call whose return type is a function pointer with anexception specification.
Notes from the June, 2016 meeting:
This text will be replaced by the removal of dynamic exceptionspecifications (P0003) and thus does not need to be changed at thistime. The issue is placed in "review" status until document P0003 isadopted.
Rationale (February, 2017):
The issue is moot after the adoption of paper P0003.
Consider:
#include <type_traits> template<class T> T foo(T) noexcept(std::is_nothrow_move_constructible<T>::value); int main() { sizeof(foo(0)); }According to 14.5 [except.spec] paragraph 13:
An exception specification is considered to be needed when:
- in an expression, the function isselected by overload resolution (12.2 [over.match],12.3 [over.over]);
- ...
Is it intended that the exception specification is needed for theexample? The function call is never evaluated and the exceptionspecification is not queried.
Rationale (November, 2016):
The type of the function is needed to know how to call it, and theexception specification is part of the function type.
Destructors that throw can easily cause programs to terminate,with no possible defense. Example: Given
struct XY { X x; Y y; };
Assume thatX::~X() is the only destructor in the entireprogram that can throw. Assume further thatY construction isthe only other operation in the whole program that can throw. ThenXY cannot be used safely, in any context whatsoever, period— even simply declaring anXY object can crash theprogram:
XY xy; // construction attempt might terminate program: // 1. construct x -- succeeds // 2. construct y -- fails, throws exception // 3. clean up by destroying x -- fails, throws exception, // but an exception is already active, so call // std::terminate() (oops) // there is no defenseSo it is highly dangerous to have even one destructor that could throw.
Suggested Resolution:
Fix the above problem in one of the following two ways. I preferthe first.
Fergus Henderson: I disagree. Code usingXY maywell be safe, ifX::~X() only throws ifstd::uncaught_exception() isfalse.
I think the current exception handling scheme in C++ is certainlyflawed, but the flaws are IMHO design flaws, not minor technicaldefects, and I don't think they can be solved by minor tweaks to theexisting design. I think that at this point it is probably better tokeep the standard stable, and learn to live with the existing flaws,rather than trying to solve them via TC.
Bjarne Stroustrup: I strongly prefer to have the call tostd::terminate() be conforming. I seestd::terminate() as aproper way to blow away "the current mess" and get to the next levelof error handling. I do not want that escape to be non-conforming— that would imply that programs relying on a error handlingbased on serious errors being handled by terminating a process (whichhappens to be a C++ program) instd::terminate() becomesnon-conforming. In many systems, there are — and/or should be— error-handling and recovery mechanisms beyond what is offeredby a single C++ program.
Andy Koenig: If we were to prohibit writing a destructorthat can throw, how would I solve the following problem?
I want to write a class that does buffered output. Among theother properties of that class is that destroying an object ofthat class writes the last buffer on the output device beforefreeing memory.
What should my class do if writing that last buffer indicates ahardware output error? My user had the option to flush the lastbuffer explicitly before destroying the object, but didn't do so, andtherefore did not anticipate such a problem. Unfortunately, theproblem happened anyway. Should I be required to suppress thiserror indication anyway? In all cases?
Herb Sutter (June, 2007): IMO, it's fine to suppressit. The user had the option of flushing the buffer and thusbeing notified of the problem and chose not to use it. If thecaller didn't flush, then likely the caller isn't ready for anexception from the destructor, either. You could also put anassert into the destructor that would trigger ifflush()had not been called, to force callers to use the interface thatwould report the error.
In practice, I would rather thrown an exception, even at the riskof crashing the program if we happen to be in the middle of stackunwinding. The reason is that the program would crash only if ahardware error occurred in the middle of cleaning up from some othererror that was in the process of being handled. I would rather havesuch a bizarre coincidence cause a crash, which stands a chance ofbeing diagnosed later, than to be ignored entirely and leave thesystem in a state where the ignore error could cause other troublelater that is even harder to diagnose.
If I'm not allowed to throw an exception when I detect this problem,what are my options?
Herb Sutter: I understand that some people might feel that"a failed dtor during stack unwinding is preferable in certain cases"(e.g., when recovery can be done beyond the scope of the program), butthe problem is "says who?" It is the application program that shouldbe able to decide whether or not such semantics are correct for it,and the problem here is that with the status quo a program cannotdefend itself against astd::terminate() — period. Thelower-level code makes the decision for everyone. In the originalexample, the mere existence of anXY object puts at riskevery program that uses it, whetherstd::terminate() makes sensefor that program or not, and there is no way for a program to protectitself.
That the "it's okay if the process goes south should a rarecombination of things happen" decision should be made by lower-levelcode (e.g.,X dtor) for all apps that use it, and whichdoesn't even understand the context of any of the hundreds of appsthat use it, just cannot be correct.
Additional note (April, 2011):
The addition of thenoexcept specifier, along with changesto make many destructorsnoexcept by default, may havesufficiently addressed these concerns. CWG should consider changingthis to NAD or extension status.
Rationale (August, 2011):
As given in the preceding note.
In language imported directly from the C Standard,15.3 [cpp.include] paragraph 5 says,
The implementation provides unique mappings for sequences consistingof one or morenondigits (5.11 [lex.name]) followedby a period (.) and a singlenondigit.
This is clearly intended to support C header names likestdio.h. However, C++ has header names likecstdiothat do not conform to this pattern but still presumably require“unique mappings.”
Proposed resolution (April, 2006):
Change 15.3 [cpp.include] paragraph 5 as indicated:
The implementation provides unique mappingsbetween the delimitedsequence and the external source file name for sequences consistingof one or more nondigits or digits (5.11 [lex.name]),optionally followed by a period (.) and a single nondigit...
(Clark Nelson will discuss this revision with WG14.)
Additional notes (October, 2006):
WG14 takes no position on this proposed change.
Rationale (September, 2008):
It is unclear what effect the provision of “uniquemappings” has or if a conforming program could detect thefailure of an implementation to do so. There has been asignificant effort to synchronize this clause with thecorresponding section of the C99 Standard, and given the lack ofperceptible impact of the proposed change, there is insufficientmotivation to introduce a new divergence in the wording.
According to 15.3 [cpp.include] paragraph 4,
A preprocessing directive of the form
# includepp-tokens new-line
(that does not match one of the two previous forms) is permitted. Thepreprocessing tokens afterinclude in the directive are processedjust as in normal text (Each identifier currently defined as a macro nameis replaced by its replacement list of preprocessing tokens.). If thedirective resulting after all replacements does not match one of the twoprevious forms, the behavior is undefined.155 The method bywhich a sequence of preprocessing tokens between a< anda> preprocessing token pair or a pair of" charactersis combined into a single header name preprocessing token isimplementation-defined.
It might be inferred from the phrase “in the directive” thatonly tokens before the terminating newline would be available for macroexpansion, and that consequently the closing right parenthesis of afunction-style macro must appear on the same line. However, it would beclearer if it used language like that of 15.7.2 [cpp.subst] paragraph 1:
each argument's preprocessing tokens are completely macro replaced as ifthey formed the rest of the preprocessing file; no other preprocessingtokens are available.
Rationale (September, 2013):
The wording referring to preprocessing tokens “in thedirective” is a clear enough indication that no tokens afterthe terminating newline are considered.
Isnumeric_limits<int>::radix required to be 2?17.3.5.2 [numeric.limits.members] paragraph 23 specifies:
static constexpr int radix;
For integer types, specifies the base of the representation.
Rationale (November, 2016):
CWG felt that the current specification is sufficiently clear andthere was no consensus for a change.
The definition ofintmax_t anduintmax_t, inheritedfrom C99, leaves open the possibility that the underlying types might notbe the ones with the highest integer conversion rank. The requirementsfor these types deal only with the representation, not the conversionrank, and it is possible for, e.g.,long andlong longto have the same representation, although they have different conversionranks. On such an architecture, chosinglong instead oflong long forintmax_t would be conforming.
Rationale (August, 2011):
This is a C compatibility issue and has ABI implications; therewas no consensus to pursue a change.
Paper N3778 added the following two deallocation signaturesto the standard library:
void operator delete(void* ptr, std::size_t size, const std::nothrow_t&) noexcept; void operator delete[](void* ptr, std::size_t size, const std::nothrow_t&) noexcept;
The core language does not currently provide for calling thesefunctions; they could only be called as the matching deallocationfunction when a constructor throws an exception, but the rulesfor determining the matching deallocation function do not considerthe existence of the sized-deallocation variants.
Rationale (November, 2014):
CWG agreed that the performance gain in using thesized-deallocation variants when a constructor throws anexception would be insignificant compared to the cost of theexception handling itself and thus insufficient motivationfor changing the core language. The issue was referred toLWG for their consideration regarding removal of thesesignatures.
The grammar in Appendix A does not indicate a grammar sentencesymbol and is therefore formally not a grammar.
Rationale (04/01):
Appendix A does not claim to be a formal grammar. Thespecification is clear enough in its current formulation.
9.3.4.5 [dcl.array] paragraph 1 says,
The expression iserroneous if:
...
its value is such that the size of the allocated object would exceedthe implementation-defined limit (Annex Clause Annex B [implimits]);
...
The only relevant limit in Clause Annex B [implimits] is thatof the size of an object, but presumably an implementation might wantto impose a smaller limit on a stack-based object. This separatequantity is referred to in paragraph 4 when describing an array ofunspecified bound:
If the size of the array exceeds the size of the memory available forobjects with automatic storage duration, the behavior is undefined.
but perhaps it needs to be mentioned in Clause Annex B [implimits] aswell.
Proposed resolution (September, 2013):
This issue is resolved by the resolution ofissue 1761.
Rationale (February, 2014):
The specification was removed from the WP and moved into aTechnical Specification.
Notes from the February, 2014 meeting:
CWG discussed adding such a limit, even without the changes forarrays of runtime bound, but decided that it was unneeded; suchhandling could be added by implementations if desirable.
Should there be an entry in Annex Clause Annex B [implimits] for theminimum number of elements an implementation should accept in aninitializer-list, and if so, what should that be?
Rationale (June, 2014):
There are already related limits in Annex Clause Annex B [implimits](array bounds, object size), which should be sufficient.
Annex C lists C compatibility issues. One item not in the annex cameup in a discussion in comp.std.c++.
Consider this C and C++ code:
const int j = 0; char* p = (char*)j;
Rationale (10/99): Becausej is not a constantexpression in C, this code fragment has implementation-defined behaviorin C. There is no incompatibility with C resulting from the fact thatC++ defines this behavior.
The treatment of character literals containing universal-character-namesis not clear. It is reasonable to conclude from 5.13.5 [lex.string] paragraph 15that if a character named by a UCN cannot be represented bya single character in the runtime character set, it becomes a multibytecharacter and thus such a character literal is a multicharacter literal,with typeint and an implementation-defined value. It would benice if 5.13.3 [lex.ccon] had the complete story by itself or atleast a reference to 5.13.5 [lex.string] for the details.
Rationale (February, 2012):
This issue is a duplicate ofissue 912.
The wording in6.3 [basic.def.odr]paragraph 2 about "potentially evaluated" is incomplete. It does not distinguishbetween expressions which are used as "integral constant expressions" andthose which are not; nor does it distinguish between uses in which an objectsaddress is taken and those in which it is not. (A suitable definitionof "address taken" could be written without actually saying "address".)
Currently the definition of "use" has two parts (part (a) and (d) below);but in practice there are two more kinds of "use" as in (b) and (c):
I don't think we discussed (c).
Rationale (04/99): The substantive part of this issue iscovered byCore issue 48
Given the following test case:
enum E { e1, e2, e3 }; void f(int, E e = e1); void f(E, E e = e1); void g() { void f(long, E e = e2); f(1); // calls ::f(int, E) f(e1); // ? }First note that Koenig lookup breaks the concept of hiding functions throughlocal extern declarations as illustrated by the call `f(1)'. Shouldthe WP show this as an example?
Second, it appears the WP is silent as to what happens with the call`f(e1)': do the different default arguments create an ambiguity?is the local choice preferred? or the global?
Tentative Resolution (10/98)In 6.5.4 [basic.lookup.argdep] paragraph 2, change
If the ordinary unqualified lookup of the name finds the declarationof a class member function, the associated namespaces and classes are notconsidered.to
If the ordinary unqualified lookup of the name finds the declarationof a class member function or the declaration of a function at block scope,the associated namespaces and classes are not considered.
Rationale (04/99): The proposal would also apply to localusing-declarations (per Mike Ball) and was therefore deemedundesirable. The ambiguity issue isdealt with inCore issue 1
The last bullet of the second paragraph of section6.5.4 [basic.lookup.argdep] says that:
If T is a template-id, its associated namespaces and classes are thenamespace in which the template is defined; for member templates, the membertemplate's class; the namespaces and classes associated with the types ofthe template arguments provided for template type parameters (excludingtemplate template parameters); the namespaces in which any template templatearguments are defined; and the classes in which any member templates used astemplate template arguments are defined.
The first problem with this wording is that it is misleading, since onecannot get such a function argument whose type would be a template-id. Thebullet should be speaking about template specializations instead.
The second problem is owing to the use of the word "defined" in the phrases"are the namespace in which the template is defined", "in which any templatetemplate arguments are defined", and "as template template arguments aredefined". The bullet should use the word "declared" instead, since scenarioslike the one below are possible:
namespace A { template<class T> struct test { template<class U> struct mem_templ { }; }; // declaration in namespace 'A' template<> template<> struct test<int>::mem_templ<int>; void foo(test<int>::mem_templ<int>&) { }}// definition in the global namespacetemplate<> template<>struct A::test<int>::mem_templ<int> {};int main(){ A::test<int>::mem_templ<int> inst; // According to the current definition of 3.4.2 // foo is not found. foo(inst);}
In addition, the bullet doesn't make it clear whether a T which is a classtemplate specialization must also be treated as a class type, i.e. if thecontents of the second bullet of the second paragraph of section6.5.4 [basic.lookup.argdep].
must apply to it or not. The same stands for a T which is a functiontemplate specialization. This detail can make a difference in an examplesuch as the one below:
- If T is a class type (including unions), its associated classes are: theclass itself; the class of which it is a member, if any; and its direct andindirect base classes. Its associated namespaces are the namespaces in whichits associated classes are defined.[This wording is as updated by core issue 90.]
template<class T>struct slist_iterator { friend bool operator==(const slist_iterator& x, const slist_iterator& y) { return true; }};template<class T>struct slist { typedef slist_iterator<T> iterator; iterator begin() { return iterator(); } iterator end() { return iterator(); }};int main(){ slist<int> my_list; slist<int>::iterator mi1 = my_list.begin(), mi2 = my_list.end(); // Must the the friend function declaration // bool operator==(const slist_iterator<int>&, const slist_iterator<int>&); // be found through argument dependent lookup? I.e. is the specialization // 'slist<int>' the associated class of the arguments 'mi1' and 'mi2'. If we // apply only the contents of the last bullet of 3.4.2/2, then the type // 'slist_iterator<int>' has no associated classes and the friend declaration // is not found. mi1 == mi2;}
Suggested resolution:
Replace the last bullet of the second paragraph of section6.5.4 [basic.lookup.argdep]
with
- IfT is atemplate-id,its associated namespaces and classes are thenamespace in which the template is defined; for member templates, the membertemplate's class; the namespaces and classes associated with the types ofthe template arguments provided for template type parameters (excludingtemplate template parameters); the namespaces in which any template templatearguments are defined; and the classes in which any member templates used astemplate template arguments are defined.
- IfT is a class templatespecialization, its associated namespaces andclasses are those associated withT whenT is regarded as a class type; thenamespaces and classes associated with the types of the template argumentsprovided for template type parameters (excluding template templateparameters); the namespaces in which the primary templates making templatetemplate arguments are declared; and the classes in which any primary membertemplates used as template template arguments are declared.
- IfT is a function template specialization,its associated namespaces andclasses are those associated withT whenT is regarded as a function type;the namespaces and classes associated with the types of the templatearguments provided for template type parameters (excluding template templateparameters); the namespaces in which the primary templates making templatetemplate arguments are declared; and the classes in which any primary membertemplates used as template template arguments are declared.
Replace the second bullet of the second paragraph of section6.5.4 [basic.lookup.argdep]
with
- IfT is a class type (including unions),its associated classes are: theclass itself; the class of which it is a member, if any; and its direct andindirect base classes. Its associated namespaces are the namespaces in whichits associated classes are defined.
- IfT is a class type (including unions),its associated classes are: theclass itself; the class of which it is a member, if any; and its direct andindirect base classes. Its associated namespaces are the namespaces in whichits associated classes are declared [Note: in case of any of the associatedclasses being a class template specialization, its associated namespace isacually the namespace containing the declaration of the primary classtemplate of the class template specialization].
Rationale (September, 2012):
The concerns in this issue were addressed by the resolutions of issues403 and557.
The term “allocated storage” is used in severalplaces in the Standard to refer to memory in which an object maybe created (dynamic, static, or automatic storage), but it has noformal definition.
Rationale (CWG 2023-06-15)
This issue is a dupliate ofissue 2551.
(From submission#505.)
A class may have a trivial default constructor that is not actuallyused to default-initialize a class object, e.g. because theconstructor is not eligible. The definition of "vacuous initialization"in 6.7.4 [basic.life] paragraph 1, as used in8.9 [stmt.dcl] paragraph 2, does not reflect that fact.
Possible resolution:
Change in 6.7.4 [basic.life] paragraph 1 as follows:
The lifetime of an object or reference is a runtime property of theobject or reference.A variable is said to havevacuousinitialization if it is default-initialized and, if it is of classtype or a (possibly multidimensional) array thereof, that class typehas a trivial default constructor.The lifetime of an object oftype T begins when:except that if the object is a union member or subobject thereof, itslifetime only begins if that union member is the initialized member inthe union (9.5.2 [dcl.init.aggr], 11.9.3 [class.base.init]),or as described in 11.5 [class.union],11.4.5.3 [class.copy.ctor], and 11.4.6 [class.copy.assign], andexcept as described in 20.2.10.2 [allocator.members]. ...
- storage with the proper alignment and size for type T is obtained,and
- its initialization (if any) is complete
(including vacuousinitialization)(9.5 [dcl.init]),
Change in 8.9 [stmt.dcl] paragraph 2 as follows:
A variable with automatic storage duration(6.7.6.4 [basic.stc.auto]) is active everywhere in the scope towhich it belongs after itsinit-declarator . Upon each transferof control (including sequential execution of statements) within afunction from point P to point Q, all variables with automatic storageduration that are active at P and not at Q are destroyed in thereverse order of their construction. Then, all variables withautomatic storage duration that are active at Q but not at P areinitialized in declaration order; unless all such variableshavevacuous initialization (6.7.4 [basic.life])are default-initialized and no initialization is performed forany such variable other than calling a trivial defaultconstructor, the transfer of control shall not be a jump. [Footnote: ... ] When adeclaration-statement is executed, P andQ are the points immediately before and after it; when a functionreturns, Q is after its body. ...
CWG 2024-04-05
CWG preferred to keep the termvacuous initialization andfix its definition while adressingissue 2859.
According to 6.7.7 [class.temporary] paragraphs 4-5,
There are two contexts in which temporaries are destroyed at adifferent point than the end of the full-expression...
The second context is when a reference is bound to a temporary. Thetemporary to which the reference is bound or the temporary that is thecomplete object of a subobject to which the reference is boundpersists for the lifetime of the reference...
It is not clear whether this applies to an example like thefollowing:
struct S { }; const S& r = (const S&)S();
In one senser is being bound to the temporary becausethe object to whichr refers is the temporary object. Fromanother perspective, however,r is being bound not to atemporary but to the lvalue expression(const S&)S(), or,more precisely, to the invented temporary variable described in7.6.1.9 [expr.static.cast] paragraph 4:
Otherwise, an expressione can be explicitly converted to atypeT using astatic_cast of the formstatic_cast<T>(e) if the declarationT t(e);is well-formed, for some invented temporary variablet(9.5 [dcl.init]). The effect of such an explicit conversionis the same as performing the declaration and initialization and thenusing the temporary variable as the result of the conversion.
(Since the invented variablet is called a“temporary,” perhaps the intent is that its lifetime isextended to that ofr, and then the lifetime of theS() temporary would be that oft. However, thisreasoning is tenuous, and it may be better to make the intentexplicitly clear.)
(See alsoissue 1299.)
Rationale (April, 2013):
This issue is a duplicate ofissue 1376.
The requirement in 6.8 [basic.types] that aliteral type must have aconstexpr constructor hascaused signficant problems with respect to defaulted defaultconstructors, since the determination of whether aconstructor isconstexpr depends on its definitionand a defaulted special member function is only defined ifit is odr-used. It might be better to remove thatrequirement, at least as it applies to defaulted defaultconstructors.
Rationale (September, 2013):
This issue duplicatesissue 1360.
According to 7.2.1 [basic.lval] paragraph 4,
Class prvalues can have cv-qualified types; non-class prvalues alwayshave cv-unqualified types.
Presumably an array of a class type should also be permitted to havea cv-qualified type.
Rationale (October, 2012):
This issue is a subset of, and resolved by the resolution of,issue 1261.
The current Standard is not clear regarding the lifetime of atemporary created in the initializer of aninit-capture:
void g() { struct S { S(int); ~S(); }; auto x = (S(1), [y = S(2)]{}, S(3)); }
Is the initializer fory considered a full-expression,or does theS(2) temporary persist until the end of thecompletex initializer?
Reationale (June, 2014):
This issue is a duplicate ofissue 1695.
According to 7.6.1.3 [expr.call] paragraph 11, when afunction call is the operand of adecltype-specifier,
a temporary object is not introduced for the prvalue. Thetype of the prvalue may be incomplete. [Note: as aresult, storage is not allocated for the prvalue and it isnot destroyed; thus, a class type is not instantiated as aresult of being the type of a function call in thiscontext. This is true regardless of whether the expressionuses function call notation or operator notation(12.2.2.3 [over.match.oper]). —end note][Note: unlike the rule foradecltype-specifier that considers whetheranid-expression is parenthesized(9.2.9.3 [dcl.type.simple]), parentheses have no specialmeaning in this context. —end note]
This relaxation of requirements on the return type of afunction does not mention abstract classes, so presumably thefollowing example is ill-formed:
struct Abstract { virtual ~Abstract() = 0; }; template<class T> T func(); typedef decltype(func<Abstract>()) type;
However, there is implementation variance on the treatment of thelast line.
Rationale (November, 2014):
This issue is a duplicate ofissue 1646.
In an expression of the formT(), 7.6.1.4 [expr.type.conv] paragraph 2requires thatT not be an array type. Now thattemporary arrays can be created via abraced-init-list (seeissue 1232), this restriction should beeliminated.
Rationale (August, 2011):
The implications of array temporaries for the language should beconsidered by the Evolution Working Group in a comprehensive fashionrather than on a case-by-case basis. See also issues1307,1326, and1525.
Rationale (February, 2014):
This is a duplicate ofissue 914.
According to 7.6.1.4 [expr.type.conv] paragraph 4,
Similarly, asimple-type-specifier ortypename-specifierfollowed by abraced-init-list creates a temporary object ofthe specified type direct-list-initialized (9.5.5 [dcl.init.list]) with the specifiedbraced-init-list, and itsvalue is that temporary object as a prvalue.
This wording does not handle the case whereT is areference type: it is not possible to create a temporary object ofthat type, and presumably the result would be an xvalue, not a prvalue.
(Duplicate ofissue 2894.)
According to 7.6.2.2 [expr.unary.op] paragraph 10,
There is an ambiguity in theunary-expression~X(),whereX is aclass-name ordecltype-specifier.The ambiguity is resolved in favor of treating~ as aunary complement rather than treating~X as referringto a destructor.
It is not clear whether this is intended to apply to anexpression like(~S)(). In large measure, that depends onwhether aclass-name is anid-expression or not. If itis, the ambiguity described in 7.6.2.2 [expr.unary.op] paragraph10 does apply; if not, the expression is an unambiguous reference tothe destructor for classS. There are several places in theStandard that indicate that the name of a type is anid-expression, but that might be more confusing than helpful.
Rationale (February, 2021):
This issue is a duplicate of, and resolved by the resolutionof,issue 1971.
7.6.2.5 [expr.sizeof] paragraph 1 says,
Thesizeof operator shall not be applied... to anenumeration type before all its enumerators have been declared...
This prevents use ofsizeof with an opaque enumerationtype, even though the underlying type of such enumerations is known.
Rationale (May, 2009):
Duplicate ofissue 803.
Should it be allowed to use an object of a class type having a singleconversion function to an integral type as an array size in the firstbound of the type in an array new?
struct A { operator int(); } a; int main () { new int[a]; }
There are similar accommodationsfor the expression in adelete (7.6.2.9 [expr.delete] paragraph 1) and in aswitch (8.5.3 [stmt.switch] paragraph 2). There is also widespread existing practice on this(g++, EDG, MSVC++, and Sun accept it, and even cfront 3.0.2).
Rationale (October, 2004):
Duplicate ofissue 299.
Does the Standard require that the deallocation function willbe called if the destructor throws an exception? For example,
struct S { ~S() { throw 0; } }; void f() { try { delete new S; } catch(...) { } }
The question is whether the memory for the S object will be freedor not. It doesn't appear that the Standard answers the question,although most people would probably assume that it will be freed.
Notes from 04/01 meeting:
There is a widespread feeling that it is a poor programmingpractice to allow destructors to terminate with an exception(seeissue 219). This question isthus viewed as a tradeoff between efficiency and supporting "badcode." It was observed that there is no way in the currentlanguage to protect against a throwing destructor, since thethrow might come from a virtual override.
It was suggested that the resolution to the issue might beto make it implementation-defined whether the storage is freedif the destructor throws. Others suggested that the Standardshould require that the storage be freed, with the understandingthat implementations might have a flag to allow optimizing awaythe overhead. Still others thought that both this issue andissue 219 should be resolved byforbidding a destructor to exit via an exception. No consensuswas reached.
Rationale (October, 2008):
It was noticed thatissue 353, anexact duplicate of this one, was independently opened andresolved.
While discussingissue 2526, it wasnoted that C++ yields an unspecified result for comparing unequalpointers tovoid in all cases, whereas C does specify the resultif the pointer values point to the same object or (possibly different)subobjects thereof, because C considersvoid an objecttype.
February, 2023
Requested confirmation of the C status quo from SG22.
Rationale (June, 2023)
This issue is a duplicate ofissue 2749.
The Standard should make clear that aconstexpr memberfunction cannot be used in a constant expression until its class iscomplete. For example:
template<typename T> struct C { template<typename T2> static constexpr bool _S_chk() { return false; } static const bool __value = _S_chk<int>(); }; C<double> c;
Current implementations accept this, although they reject thecorresponding non-template case:
struct C { static constexpr bool _S_chk() { return false; } static const bool __value = _S_chk(); }; C c;
Presumably the template case should be handled consistently with thenon-template case.
CWG 2024-06-28
CWG opined that, contrary to the assertion in the issue, thetreatment of non-templated class definitions should be changed to moreclosely resemble templated ones. This direction is covered byissue 2335.
According to 8.6.5 [stmt.ranged] paragraph 1, the functionsbegin andend are looked up “withargument-dependent lookup (6.5.4 [basic.lookup.argdep])” fornon-array, non-class types and for class types with no members ofthose names. It seems surprising that the lookup is different fromthe lookup that would result if thefor statement werereplaced by its nominal expansion, i.e., including (as does thereferenced section, 6.5.4 [basic.lookup.argdep]) the result of ordinaryunqualified lookup as well as the lookup in associated namespaces.
Rationale (February, 2012):
This issue is a duplicate ofissue 1442.
Because the reference__range in the expansion of arange-basedfor statement, as described in 8.6.5 [stmt.ranged] paragraph 1, is bound only to the top-level expressionofrange-init, the lifetime of temporaries created at lowerlevels in that expression expires before the body of the loop isreached, leading to dangling references. It would be helpful if thelifetime of those temporaries were extended over the entirestatement.
(See alsoissue 1523 for anotherquestion regarding the rewritten form of the range-basedfor.)
Rationale (October, 2012):
This is a duplicate ofissue 900.
(From submission#521.)
Consider:
namespace N { template<typename T> struct A; } template<> struct N::A<int>; // #1 template<typename T> struct N::A<T*>; // #2
#1 is currently well-formed, but #2 is an ill-formed use ofanelaborated-type-specifier. This is inconsistent and notaligned with implementation practice.
Suggested resolution:
Change in 9.2.9.5 [dcl.type.elab] paragraph 2 as follows:
If anelaborated-type-specifier is the sole constituent of adeclaration, the declaration is ill-formed unless it is an explicitspecialization (13.9.4 [temp.expl.spec]),a partialspecialization (13.7.6 [temp.spec.partial]), an explicitinstantiation (13.9.3 [temp.explicit]) or it has one of thefollowing forms: ...
CWG 2024-05-17
Duplicate ofissue 2874.
Given the example,
struct A{ operator auto(){ return 0; } }; int main(){ A a; a.operator auto(); // #1 a.operator int(); // #2 }
there is implementation divergence regarding which, ifeither, of the calls is well-formed. MSVC and clangreject #2, g++ rejects #1, and EDG rejects both.
According to 9.2.9.7.1 [dcl.spec.auto.general] paragraph6:
A program that uses a placeholder type in a context notexplicitly allowed in 9.2.9.7 [dcl.spec.auto] isill-formed.
The use ofauto as aconversion-type-idin a function call is not mentioned in that section;however, the section is dealing with declarative contextsrather than expressions, so it's not clear how much weightthat observation should carry.
Rationale (December, 2021):
This issue is a duplicate ofissue 1670.
According to 9.3.4 [dcl.meaning] paragraph 1, the declaratorin the definition or explicit instantiation of a namespace member canonly be qualified if the definition or explicit instantiation appearsoutside the member's namespace:
Adeclarator-id shall not be qualified except for thedefinition of a member function (11.4.2 [class.mfct]) or staticdata member (11.4.9 [class.static]) outside of its class, thedefinition or explicit instantiation of a function or variable memberof a namespace outside of its namespace, or the definition of apreviously declared explicit specialization outside of its namespace,or the declaration of a friend function that is a member of anotherclass or namespace (11.8.4 [class.friend]). Whenthedeclarator-id is qualified, the declaration shall refer toa previously declared member of the class or namespace to which thequalifier refers, and the member shall not have been introduced byausing-declaration in the scope of the class or namespacenominated by thenested-name-specifier ofthedeclarator-id.
There is no similar restriction on aqualified-id in aclass definition (Clause 11 [class] paragraph 5):
If aclass-head contains anested-name-specifier,theclass-specifier shall refer to a class that was previouslydeclared directly in the class or namespace to which thenested-name-specifier refers (i.e., neither inherited norintroduced by ausing-declaration), andtheclass-specifier shall appear in a namespace enclosing theprevious declaration.
Anelaborated-type-specifier in an explicit instatiationcontaining aqualified-id is also not prohibited from appearingin the namespace nominated by itsnested-name-specifier(13.9.3 [temp.explicit] paragraph 2):
An explicit instantiation shall appear in an enclosing namespace ofits template. If the name declared in the explicit instantiation is anunqualified name, the explicit instantiation shall appear in thenamespace where its template is declared.
(This asymmetry is due to the removal of inappropriate mentionof classes in 9.3.4 [dcl.meaning] byissue 40 and a failure to insert theintended restrictions elsewhere.)
An example of this inconsistency is:
namespace N { template <class T> struct S { }; template <class T> void foo () { } template struct N::S<int>; // OK template void N::foo<int>(); // ill-formed }
It is not clear that any purpose is served by the “outside ofits namespace” restriction on declarators in definitions andexplicit instantiations; if possible, it would be desirable toreconcile the treatment of declarators and class names by removingthe restriction on declarators (which appears to be widespreadimplementation practice, anyway).
Rationale (April, 2006):
This is the same asissue 482.
The current wording of 9.3.4.6 [dcl.fct] paragraph 6encompasses more than it should:
If the type of a parameter includes a type of the form “pointerto array of unknown bound ofT” or “reference toarray of unknown bound ofT,” the program isill-formed. [Footnote: This excludes parameters of type“ptr-arr-seqT2” whereT2 is“pointer to array of unknown bound ofT” andwhereptr-arr-seq means any sequence of “pointerto” and “array of” derived declarator types. Thisexclusion applies to the parameters of the function, and if aparameter is a pointer to function or pointer to member function thento its parameters also, etc. —end footnote]
The normative wording (contrary to the intention expressed in thefootnote) excludes declarations like
template<class T> struct S {}; void f(S<int (*)[]>);
and
struct S {}; void f(int(*S::*)[]);
but not
struct S {}; void f(int(S::*)[]);
Rationale (November, 2014):
This issue is a duplicate ofissue 393.
9.3.4.6 [dcl.fct] paragraph 5 specifies that cv-qualifiersare deleted from parameter types. However, it's not clear what thisshould mean for function templates. For example,
template<class T> struct A { typedef A arr[3]; }; template<class T> void f(const typename A<T>::arr) { } template void f<int>(const A<int>::arr); // #1 template <class T> struct B { void g(T); }; template <class T> void B<T>::g(const T) { } // #2
If cv-qualifiers are dropped, then the explicit instantiation in#1 will fail to match; if cv-qualifiers are retained, then thedefinition in #2 does not match the declaration.
Rationale (August, 2010):
This is a duplicate ofissue 1001.
9.3.4.7 [dcl.fct.default] paragraph 4says:
For non-template functions, default arguments can be addedin later declarations of a functions in the same scope.Why say for non-template functions? Why couldn't the following allowed?
template <class T> struct B { template <class U> inline void f(U); }; template <class T> template <class U> inline void B<T>::f(U = int) {} // adds default arguments // is this well-formed? void g() { B<int> b; b.f(); }If this is ill-formed, chapter 14 should mention this.
Rationale:This is sufficiently clear in the standard. Allowing additionaldefault arguments would be an extension.
Notes from October 2002 meeting:
The example here is flawed. It's not clear what is being requested.One possibility is the extension introduced byissue 226. Other meanings don't seem to beuseful.
It is not clear whether the following declaration is well-formed:
struct S { int i; } s = { { 1 } };According to 9.5.2 [dcl.init.aggr]paragraph 2, a brace-enclosed initializer is permitted for asubaggregate of an aggregate; however,i is a scalar, not anaggregate. 9.5 [dcl.init] paragraph 13says that a standalone declaration like
int i = { 1 };is permitted, but it is not clear whether this says anything about theform of initializers for scalar members of aggregates.
This is (more) clearly permitted by the C89 Standard.
Rationale (May, 2008):
Issue 632 refers to exactly the samequestion and has a more detailed discussion of the considerationsinvolved.
Issue 1696 did not fully addressrecursive references in default member initializers for aggregates,for example:
struct S { int i = (S{}, 0); };
or
struct S { int i = noexcept(S{}); };
Qualification conversions are not considered when doing referencebinding, which leads to some unexpected results:
template<typename T> T make(); struct B {}; struct D : B {}; const int *p1 = make<int*>(); // ok, qualification conversion const int *const *p2 = make<int**>(); // ok, qualification conversion const int **p3 = make<int**>(); // error, not type safe const int &r1 = make<int&>(); // ok, binds directly const int *const &r2 = make<int*&>(); // weird, binds to a temporary const int *&r3 = make<int*&>(); // error const int &&x1 = make<int&&>(); // ok, binds directly const int *const &&x2 = make<int*&&>(); // weird, binds to a temporary const int *&&x3 = make<int*&&>(); // weird, binds to a temporary
It might make sense to say that similar types are reference-relatedand if there is a qualification conversion they are reference-compatible.
See alsoissue 2023.
Rationale (September, 2023):
This issue is a duplicate ofissue 2352.
In an example like
const int&r {1};
the expectation is that this creates a temporary of typeconstint containing the value1 and binds the reference toit. And it does, but along the way it creates two temporaries. Thewording in 9.5.5 [dcl.init.list] paragraph 3, the bullet onreference initialization, says that a prvalue temporary of typeconst int is created, and then we do reference binding.Because this is a non-class case and the source is a prvalue, we endup in the section of 9.5.4 [dcl.init.ref] that says we create atemporary (again of typeconst int) and initialize it fromthe source. So we've created two temporaries. Now, this may notmatter to anyone, since the discarded temporary is not observable, butit may be a concern that the reference is not binding directly to thetemporary created for the{1}, since we do sometimes basebehavior on the “bind directly” attribute.
Rationale (September, 2012):
This issue is based on the wording prior to the application of theresolution ofissue 1288. With that change,there is no longer a problem.
Consider:
void f() { tuple<int, int> a; auto &[x, y] = a; [x] {}; // ok, captures reference to int member of 'a' by value [&] { use(x); }; // ok, capture reference by reference } void g() { struct T { int a, b; } a; auto &[x, y] = a; [x] {}; // ill-formed, 'x' does not name a variable [&] { use(x); }; // ??? }
The standard is silent on whether and how identifiers of adecomposition declaration can be captured by a lambda.
Rationale (July, 2017):
This issue is a duplicate ofissue 2308.
Given a namespace-scope declaration like
template<typename T> T var = T();
shouldT<const int> have internal linkage by virtueof its const-qualified type? Or should it inherit the linkage of thetemplate?
Notes from the February, 2014 meeting:
CWG noted that linkage is by name, and a specialization of a variabletemplate does not have a name separate from that of the variabletemplate, thus the specialization will have the linkage of the template.
Rationale (February, 2021):
This issue is a duplicate of, and resolved by the resolutionof,issue 2387.
WG14 intends to support alignment specifications in their nextStandard. WG21 should explore possibilities for compatibilitybetween C and C++ for these specifications. See paper N3093.
Rationale (August, 2010):
This is a duplicate ofissue 1115.
The grammar formember-declaration in 11.4 [class.mem]does not allow analias-declaration as a class member. Thisseems like an oversight.
Rationale (August, 2010):
This issue is a duplicate of924.
The class
struct A { const int i; };
was previously considered a POD class, but it no longer is,because it has a non-trivial (deleted) copy assignment operator.The impact of this change is not clear.
Rationale (August, 2010):
This is a duplicate ofissue 1140.
11.4.9.3 [class.static.data] paragraph 3 says,
If astatic data member is ofconst literal type,its declaration in the class definition can specify abrace-or-equal-initializer in which everyinitializer-clause that is anassignment-expression is aconstant expression. Astatic data member of literal typecan be declared in the class definition with theconstexprspecifier; if so, its declaration shall specify abrace-or-equal-initializer in which everyinitializer-clause that is anassignment-expression is aconstant expression. [Note: In both these cases, the member mayappear in constant expressions. —end note]
The note is misleading; to be used for its value in a constantexpression, the static data member must either be declared constexpror have integral or enumeration type. Though strictly speaking, thenote is true, becauseany static data member, initialized ornot, may appear in an address constant expression if its address istaken.
I think the right fix is to change “const literal” backto “const integral or const enumeration.” It would also benice to avoid the duplication of text.
Rationale (November, 2010):
This is a duplicate ofissue 1101.
Although accepted by several compilers and used in popular code,the grammar currently does not permit the use of a dependent templatename in abase-specifier ormem-initializer-id, forexample:
template<typename T, typename U> struct X : T::template apply<U> { };
There does not seem to be a good reason to reject this usage.
Rationale (August, 2010):
This issue is a duplicate ofissue 314.
Consider the following example:
struct B { void f(){} }; class N : protected B { }; struct P: N { friend int main(); }; int main() { N n; B& b = n; // R b.f(); }
This code is rendered well-formed by bullet 3 of 11.8.3 [class.access.base] paragraph 4, which says that a base classB ofN isaccessible atR if
R occurs in a member or friend of a classPderived fromN, and an invented public member ofBwould be a private or protected member ofP
This provision circumvents the additional restrictions on accessto protected members found in 11.8.5 [class.protected] —main() could not callB::f() directly because thereference is not via an object of the class through which access isobtained. What is the purpose of this rule?
Rationale (April, 2010):
This is a duplicate ofissue 472.
The resolution ofissue 597 and anticipatedresolution ofissue 1517 allow access tonon-virtual base classes outside the lifetime of the object. However, forno apparent reason, references to nonstatic data members are stillprohibited. This disparity should be rectified.
Rationale (November, 2014):
This issue is a duplicate ofissue 1530.
It looks like the resolution toissue 1138neglected to update 12.2.2.7 [over.match.ref] (in N3225) orotherwise mention it from 9.5.4 [dcl.init.ref] for the rvaluecases.
Since this is a context where overload resolution is required (tofind the correct conversion function), 12.2.2.7 [over.match.ref]should probably be used (12.2 [over.match] paragraph 2);however, there are some oddities.
(Issue 1)
Consider:
struct A { typedef void functype(); operator functype&&(); }; void (&&x)() = A();
We are looking for a function lvalue; 12.2.2.7 [over.match.ref] (if we take it to be applicable) says the viablefunctions are limited to conversion functions yielding“lvalue reference” when 9.5.4 [dcl.init.ref]requires an lvalue result. The above would then fail to have anycandidate conversion functions and we are left with a non-viableindirect binding to a “function temporary.”
(Issue 2)
Also, since the candidate functions in the case where an rvalue(that is prvalue or xvalue) result is required do not include oneswhich return lvalue references, I do not see what the wordingregarding the second standard conversion sequence having anlvalue-to-rvalue transformation added inissue 1138 is meant to catch.
If the example containing
int&& rri2 = X();
with a comment aboutoperator int&() is a clue,then it seems that 12.2.2.7 [over.match.ref] is beingignored.
(Conclusion)
It would seem that 12.2.2.7 [over.match.ref] should apply (andbe fixed to match the cases fromissue1138), the verbiage about the second standard conversion isredundant, and the explanation in the example is wrong.
In particular, the previous wording for 9.5.4 [dcl.init.ref]did have distinct bullets for converting to an “lvalue”and to an “rvalue;” we now have a bullet which is notexclusively one or the other.
Possible fix
Add reference to [12.2.2.7 [over.match.ref]] in9.5.4 [dcl.init.ref] for direct binding to rvaluereference/const non-volatile via UDC.
Remove redundent sentence referring to second SCS.
Modify example to indicateoperator int&() is nota candidate function.
Clarify that the point from 9.5.4 [dcl.init.ref] below:
has a class type (i.e.,T2 is a class type), whereT1 is not reference-related toT2, and can beimplicitly converted to an xvalue, class prvalue, or functionlvalue of type “cv3T3,” where“cv1T1” is reference-compatible with“cv3T3”...
is an rvalue case for 12.2.2.7 [over.match.ref] fornon-function types and lvalue case for function types.
Fix 12.2.2.7 [over.match.ref] to allow candidate functionsreturn rvalue reference to function type for lvalue cases.
Update 110510:
The example appears to be actually well-formed because the wordingabout the second SCS is not triggered. Falling through to indirectbinding then succeeds.
Rationale (February, 2012):
This issue is a duplicate ofissue 1328.
Rationale (August, 2017):
This issue is a duplicate ofissue 2243.
According to 12.2.4.2.5 [over.ics.ref] paragraph 3,
Except for an implicit object parameter, for which see 12.2.2 [over.match.funcs], a standard conversion sequence cannot be formed if itrequires binding an lvalue reference to non-const to an rvalue orbinding an rvalue reference to an lvalue.
This isn't precisely the restriction placed by 9.5.4 [dcl.init.ref]on binding an lvalue reference to an rvalue; the requirement there is thatthe cv-qualification must be exactlyconst in such cases. Thishas an impact on the interpretation of the following example:
void f(const volatile int&); void f(...); void g() { f(1); }
Becausef(const volatile int&) is considered a viablefunction for the call, it is a better match thanf(...), butthe binding of the argument to the parameter cannot be done, so theprogram is ill-formed. Presumably “lvalue reference tonon-const” should be clarified to exclude theconst volatile case. (Implementations vary on their handlingof this example.)
Rationale (November, 2010):
This is a duplicate ofissue 1152.
The following example is ambiguous according to the Standard:
struct Y { operator int(); operator double(); }; void f(Y y) { double d; d = y; // Ambiguous:Y::operator int() orY::operator double()? }
The reason for the ambiguity is that 12.5 [over.built] paragraph 18says that there are candidate functionsdouble& operator=(double&, int) anddouble& operator=(double&, double) (amongothers). In each case, the second argument is converted by auser-defined conversion sequence (12.2.4.2.3 [over.ics.user])where the initial and final standard conversion sequences are theidentity conversion — i.e., the conversion sequences for thesecond argument are indistinguishable for each of these candidatefunctions, and they are thus ambiguous.
Intuitively one might expect that, because it converts directlyto the target type in the assignment,Y::operator double()would be selected, and in fact, most compilers do select it, but thereis currently no rule to distinguish between these user-definedconversions. Should there be?
Additional note (May, 2008):
Here is another example that is somewhat similar:
enum En { ec }; struct S { operator int(); operator En(); }; void foo () { S() == 0; // ambiguous? }
According to 12.5 [over.built] paragraph 12, the candidatefunctions are
whereR isint andLis every promoted arithmetic type. Overload resolution proceeds intwo steps: first, for each candidate function, determine whichimplicit conversion sequence is used to convert from the argumenttype to the parameter type; then compare the candidate functions onthe basis of the relative costs of those conversion sequences.
In the case ofoperator==(int, int) there is a clearwinner:S::operator int() is chosen because the identityconversionint -> int is better than the promotionEn-> int. For all the other candidates, the conversion for thefirst parameter is ambiguous: bothS::operator int() andS::operator En() require either an integral conversion (forintegralL) or a floating-integral conversion (forfloating pointL) and are thus indistinguishable.
These additional candidates are not removed from the set of viablefunctions, however; because of 12.2.4.2 [over.best.ics] paragraph 10, they are assigned the “ambiguous conversionsequence,” which “is treated as a user-defined sequencethat is indistinguishable from any other user-defined conversionsequence.” As a result, all the viable functions areindistinguishable and the call is ambiguous. Like the earlier example,one might naively think that the exact match withS::operator int()andbool operator==(int, int) would be selected, but that is notthe case.
Rationale (August, 2010):
Duplicate ofissue 260.
John Spicer: The standard does say that a namespace scope templatehas external linkage unless it is a function template declared "static".It doesn't explicitly say that the linkage of the template is also thelinkage of the instantiations, but I believe that is the intent.For example, a storage class is prohibited on an explicit specializationto ensure that a specialization cannot be given a different storage classthan the template on which it is based.
Mike Ball: This makes sense, but I couldn't find much supportin the document. Sounds like yet another interpretation to add tothe list.
John Spicer: The standard does not talk about the linkage ofinstantiations, because only "names" are considered to have linkage, andinstances are not really names. So, from an implementation pointof view, instances have linkage, but from a language point of view, onlythe template from which the instances are generated has linkage.
Mike Ball: Which is why I think it would be cleaner to eliminatestorage class specifiers entirely and rely on the unnamed namespace.There is a statement that specializations go into the namespace of thetemplate. No big deal, it's not something it says, so we live withwhat's there.
John Spicer: That would mean prohibiting static function templates.I doubt those are common, but I don't really see much motivation for gettingrid of them at this point.
"export" is an additional attribute that is separate from linkage, butthat can only be applied to templates with external linkage.
Mike Ball: I can't find that restriction in the standard, thoughthere is one that templates in an unnamed namespace can't be exported.I'm pretty sure that we intended it, though.
John Spicer: I can't find it either. The "inline" caseseems to be addressed, but not static. Surely this is an error as,by definition, a static template can't be used from elsewhere.
Rationale: Duplicate ofCore issue 69.
Currently, 13.4.3 [temp.arg.nontype] paragraph 1 only requires thatan object whose address is used as a non-type template argumenthave external linkage, thus allowing objects of thread storage durationto be used. The requirement should presumably be for an object to havestatic storage duration as well as external linkage.
Rationale (August, 2010):
This is a duplicate ofissue 1154.
The Standard is not clear on the treatment of an example like thefollowing, and there is implementation variance:
template<class ...Types> struct Tuple_ { // _VARIADIC_TEMPLATE template<Types ...T, int> int f() { return sizeof...(Types); } }; int main() { Tuple_<char,int> a; int b = a.f<1, 2, 3>(); }
Rationale (February, 2019):
This issue is covered in more detail inissue 2395.
See alsoissue 2025.
Given the declarations
template<int> using T = int; template<typename U> void h(T<f(U())>); template<typename U> void h(T<g(U())>);
Does this declare one function template or two?
Rationale (November, 2014):
This issue is a duplicate ofissue 1980.
Consider the following example:
template<typename ...T> struct X { void f(); static int n; }; template<typename T, typename U> using A = T; template<typename ...T> void X<A<T, decltype(sizeof(T))>...>::f() {} template<typename ...T> void X<A<T, decltype(sizeof(T))>...>::n = 0; void g() { X<void>().f(); X<void>::n = 1; }
Should this be valid? The best answer would seem to be to producean error during instantiation, and that appears to be consistent withthe current Standard wording, but there is implementation divergence.
See alsoissue 2021.
Rationale (May, 2015):
This issue is a duplicate osissue 1979.
The description of how the partial ordering of template functionsis determined in13.7.7.3 [temp.func.order]paragraphs 3-5 doesnot make any provision for nondeduced template parameters. Forexample, the function call in the following code is ambiguous, eventhough one template is "obviously" more specialized than the other:
template <class T> T f(int); template <class T, class U> T f(U); void g() { f<int>(1); }The reason is that neither function parameter list allows templateparameterT to be deduced; both deductions fail, so neithertemplate is considered more specialized than the other and thefunction call is ambiguous.
One possibility of addressing this situation would be toincorporate explicit template arguments from the call in the argumentdeduction using the transformed function parameter lists. In thiscase, that would result in finding the first template to be morespecialized than the second.
Rationale (04/00):
This issue is covered in a more general context inissue 214.
There appears to be no requirement that a redeclaration of an aliastemplate must be equivalent to the earlier one. Analias-declarationis not a definition (6.2 [basic.def] paragraph 2), so presumablyan alias template declaration is also not a definition and thus the ODRdoes not apply.
Rationale (November, 2014):
This issue is superseded byissue 1896.
Section 13.8 [temp.res] paragraph 4 uses thefollowing example to show thatqualified name lookup described in Section 6.5.5 [basic.lookup.qual]applies even in the presence of "typename":
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 }
This example is confusing because the definition of the template functionitself is not ill formed unless it is instantiated with "A" as the templateparameter. In other words, the example should be modified to somethinglike:
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 -- finds member type B::X. f(a) ; // ill-formed: finds the data member A::X not // the member type A::X. }
Notes from October 2002 meeting:
This is a duplicate ofCore Issue 345.
According to 13.8.3 [temp.dep] paragraph 3,
In the definition of a class or class template, if a base classdepends on atemplate-parameter, the base class scope is notexamined during unqualified name lookup either at the point ofdefinition of the class template or member or during an instantiationof the class template or member.
Note that this is phrased not as “if a base class is adependent type” but as “if a base class depends on atemplate-parameter;” the current instantiation depends onatemplate-parameter but is not a dependent type. Thedifference can be seen in this example:
template<typename T> struct A { typedef int type; struct C; }; template<typename T> struct A<T>::C { void type(); struct B; }; template<typename T> struct A<T>::C::B : A<T> { type x; }; A<int>::C::B b; // #1
If the excluded bases were dependent types, the reference totype at #1 would resolve toA::type; with thecurrent wording, the reference resolves toC::type.
(See alsoissue 1524 for another casein which this distinction makes a difference.)
Rationale (September, 2012):
This issue is a duplicate ofissue 591.
According to 13.9.4 [temp.expl.spec] paragraph 15,
A member or a member template may be nested within manyenclosing class templates. In an explicit specialization forsuch a member, the member declaration shall be preceded by atemplate<> for each enclosing class template that isexplicitly specialized. [Example:
template<class T1> class A { template<class T2> class B { void mf(); }; }; template<> template<> class A<int>::B<double>; template<> template<> void A<char>::B<char>::mf();—end example]
However, in the declaration ofA<int>::B<double>,A<int> is not explicitly instantiated, it is implicitlyinstantiated.
Rationale (November, 2014):
This issue is a duplicate ofissue 529.
It would be useful to be able to deduce an array bound from thenumber of elements in an initializer list.
Rationale (August, 2011):
The implications of array temporaries for the language should beconsidered by the Evolution Working Group in a comprehensive fashionrather than on a case-by-case basis. See also issues1300,1307,and1525.
Rationale (January, 2021):
This issue is a duplicate ofissue 1591.
Consider the following example:
template<typename T, int N> void g(T (* const (&)[N])(T)) { } int f1(int); int f4(int); char f4(char); void f() { g({ &f1, &f4 }); // OK,T deduced toint,N deduced to2? }
There is implementation divergence on the handling of thisexample. According to 13.10.3.2 [temp.deduct.call] paragraph 1,
If removing references and cv-qualifiers fromPgivesstd::initializer_list<P'> orP'[N] forsomeP' andN and the argument is a non-emptyinitializer list (9.5.5 [dcl.init.list]), then deduction isperformed instead for each element of the initializer list,takingP' as a function template parameter type and theinitializer element as its argument, and in theP'[N]case, ifN is a non-type template parameter,Nis deduced from the length of the initializer list.
Deduction fails for the&f4 element fails due toambiguity, so by 13.10.3.6 [temp.deduct.type] bullet 5.5.1 thefunction parameter is a non-deduced context.
It is not clear, however, whether that implies that thefunction parameter is a non-deduced context from the perspectiveof the entire deduction, so we cannot deductT andN, or if it's only a non-deduced context for this sliceof the initializer list deduction and we can still deduce thetemplate parameters from the&f1 element.
See alsoissue 1513.
Rationale, July, 2017
This issue is a duplicate ofissue 2318.
Rationale (August, 2011):
This issue duplicatesissue 455.
14.5 [except.spec] paragraph 1says,
Anexception-specification shall appear only on a functiondeclarator in a function, pointer, reference or pointer to memberdeclaration or definition.This wording forbidsexception specifications in declarations where they might plausiblyoccur (e.g., an array of function pointers). This restriction seemsarbitrary. It's also unclear whether this wording allows declarationssuch as
void (*f())() throw(int); // returns a pointer to a function // that might throw "int"
At the same time, other cases are allowed by the wording inparagraph 1 (e.g., a pointer to a pointer to a function), but nochecking for such cases is specified in paragraph 3. For example,the following appears to be allowed:
void (*p)() throw(int); void (**pp)() throw() = &p;
Rationale (10/99): Duplicate of issues87 and92.
A type used in an exception specification must be complete(14.5 [except.spec] paragraph 2). The resolution ofissue 437 stated that a class typeappearing in an exception specification inside its ownmember-specification is considered to be complete. Shouldthis also apply to exception specifications in class templatesinstantiated because of a reference insidethemember-specification of a class? For example,
template<class T> struct X { void f() throw(T) {} }; struct S { X<S> xs; };
Note, January, 2012:
With the deprecation ofdynamic-exception-specifications,the importance of this issue is reduced even further. The currentspecification is clear, and the suggested resolution is an extension.It has been suggested that the issue be closed as NAD.
Notes from the February, 2012 meeting:
The outcome of this issue will be affected by the resolution ofissue 1330. See alsoissue 287.
This issue is subsumed by the newerissue 1330and should be discussed in that context.
The expected behavior of the following example is not clear:
template<class T> struct Y { typedef typename T::value_type blah; void swap(Y<T> &); }; template<class T> void swap(Y<T>& Left, Y<T>& Right) noexcept(noexcept(Left.swap(Right))) { } template <class T> struct Z { void swap(Z<T> &); }; template<class T> void swap(Z<T>& Left, Z<T>& Right) noexcept(noexcept(Left.swap(Right))) { } Z<int> x00, y00; constexpr bool b00 = noexcept(x00.swap(y00)); // Instantiates theZ<int> overload: template void swap<int>(Z<int>&, Z<int>&) noexcept(b00);
The question is whether the explicit instantiation directivealso instantiates theY<int> overload and thusY<int> (because of the exception specification),which will fail because of the reference toT::value_typewithT=int.
According to 14.5 [except.spec] bullet 13.3, one of thecontexts in which an exception specification is needed (thustriggering its instantiation) is when:
the exception specification is compared to that of anotherdeclaration (e.g., an explicit specialization or anoverriding virtual function);
In this example, the declarations ofswap must becompared in order to determine which function template is beinginstantiated, resulting in the instantiation ofY<int>.There is implementation divergence, however, with some acceptingthe example and some issuing an error for the instantiation ofY<int>.
Rationale (February, 2022): Duplicate ofissue 2417.
The example in 17.6.3.4 [new.delete.placement]reads:
[Example: This can be useful for constructingan object at a known address:This example has potential alignment problems. One way to correct it wouldbe to change the definition ofplace to read:char place[sizeof(Something)]; Something* p = new (place) Something();—end example]
char* place = new char[sizeof(Something)];
Rationale (10/99): This is an issue for the LibraryWorking Group.
Access declarations were removed from C++11 but are not mentioned inC.6 [diff.cpp03].
Rationale (January, 2012):
Issue 1279 already deals with missingdifferences between C++03 and C++11; this specific item has been addedto the list there.
In the example in _N2914_.14.10.1.1 [concept.fct] paragraph 10,
concept EqualityComparable<typename T> { bool operator==(T, T); bool operator!=(T x, T y) { return !(x == y); } }
is the call tooperator== in the default implementationwell-formed, or is another requirement needed to allow the argumentsto be passed by value? If another requirement is needed, should itbe added in this example, or should the rules for implicit requirementsbe changed so that the example is well-formed?
According to _N2914_.14.11.1.1 [temp.req.sat] paragraph 2,a concept requirement is satisfied if a concept map with the samename and template argument list is found by concept map lookup. Thepoint at which the name of a concept map is inserted into its scopeis, according to _N2914_.14.10.2 [concept.map] paragraph 2, immediatelyfollowing itsconcept-id. This enables a requirement on amember of a concept map to be satisfied by the concept map in which itappears, for example:
concept C2<typename T>{} concept D2<typename T> { typename type; requires C2<type>; } template<D2 T> concept_map C2<T>{} concept_map D2<int> { typedef int type; // Okay }
However, these rules might lead to problems with the concept mapsthat the compiler tries but fails to generate for auto concepts.Presumably a compiler might insert the name of the generated conceptmap into the containing scope, so that it can satisfy its ownrequirements, but then if some other requirement cannot be satisfiedand thus the concept map is not defined after all (_N2914_.14.10.2 [concept.map] paragraph 11), the name must then be removed again. Itmight be clearer to make the point of definition for a concept mapafter the closing brace and just have a special case for how theconcept map is handled within its own definition.
On a related note, the current specification seems unclearabout whether a failure to generate a concept map for an autoconcept means that no further attempts will be made to generate it.Consider the following example:
auto concept A<typename X> { /* */ } auto concept B<typename X> : A<X> { void enabler(X); } template <A T> void f(T x); // #1 template <B T> void f(T x); // #2 class C { // a class that satisfies A<C> but not B<C> // because no enabler(X) in scope }; int foo() { C x; f(x); // #3 } void enabler(C); int bar() { C x; f(x); // #4 }
At #3, the concept map forB cannot be generated, so thecall invokes #1. There doesn't appear to be anything currently thatindicates that the reference at #4 should not once again attempt togenerate the concept map forB, which will succeed this timeand make the call invoke #2. It seems preferable that both callsshould invoke #1, but that does not seem to be the effect of thecurrent wording.
Given a concept and an unconstrained template, e.g.,
auto concept HasFoo<typename T> { void foo(T&); } template<typename T> struct SomeThing { void bar(){} };
how can one write a concept map template that adapts allspecializations ofSomeThing to conceptHasFoo?Because a concept map template is a constrained context, referringtoSomeThing violates the prohibition against using aspecialization of an unconstrained template.
Surrounding the entire concept map template withlate_checkwould appear not to work; the location of thelate_check isin the unconstrained context, andlate_check is ignored inunconstrained contexts.
One possibility would be to allowlate_check to appearin theconcept_map syntax.
_N2914_.14.10.2.1 [concept.map.fct] paragraph 3 says,
Construct an expressionE (as defined below) in the scope ofthe concept map.
This is the wrong context for this expression. Requirementmembers are visible to name lookup, and they are obviously not adesirable lookup result; names within the concept map should beinvisible during the evaluation ofE. Presumably thisshould read,
...in the scope in which the concept map is defined.
_N2914_.14.10.2.1 [concept.map.fct] paragraph 5 says,
Each satisfied associated function (or function template) requirementhas a corresponding associated function candidate set. Anassociated function candidate set is a candidate set(_N2914_.14.11.3 [temp.constrained.set]) representing the functions oroperations used to satisfy the requirement. The seed of the associatedfunction candidate set is determined based on the expressionE used to determine that the requirement was satisfied.
If the evaluation ofE involves overloadresolution at the top level, the seed is the function (12.2.2 [over.match.funcs]) selected by the outermost application of overloadresolution (Clause 12 [over]).
Otherwise, ifE is a pseudo destructor call(_N4778_.7.6.1.4 [expr.pseudo]), the seed is apseudo-destructor-name.
Otherwise, the seed is the initialization of an object.
It is not clear that this takes built-in operators into account.For example:
concept C<class T, class U> { typename R; R operator+( T, U ); } concept_map C<int, double> {}
Is the following well-formed?
auto concept HasDestructor<typename T> { T::~T(); } concept_map HasDestructor<int&> { }
According to _N2914_.14.10.2.1 [concept.map.fct] paragraph 4, thedestructor requirement in the concept map results in an expressionx.~X(), whereX is the typeint&.According to _N4778_.7.6.1.4 [expr.pseudo], this expression is ill-formedbecause the object type and thetype-name must be the same type,but the object type cannot be a reference type (references are droppedfrom types used in expressions, Clause 7 [expr] paragraph 5).
It is not clear whether this should be addressed by changing_N4778_.7.6.1.4 [expr.pseudo] or _N2914_.14.10.2.1 [concept.map.fct].
It is possible that under some circumstances an expression createdunder the rules of _N2914_.14.10.2.1 [concept.map.fct] might be syntacticallyambiguous with declarations, in which case they would be interpreted asdeclarations and not expressions. It would be helpful to have anexplicit statement to the effect that the expressions created by theserules shall always be interpreted as expressions and never asdeclarations.
Given the following example:
auto concept C<typename T, typename U> { Returnable U; typename type = T&&; U::U(type); }
_N2914_.14.10.2.2 [concept.map.assoc] paragraph 5 says,
If an associated type or class template (_N2914_.14.10.1.2 [concept.assoc]) has a default value, a concept map membersatisfying the associated type or class template requirement shall beimplicitly defined by substituting the concept maparguments into the default value.
It is not clear what the order of processing should be between thisstep and the formation of the expression in _N2914_.14.10.2.1 [concept.map.fct]. Deduction of the associated type (in _N2914_.14.10.2.2 [concept.map.assoc] paragraph 4) isn't used in this example, but ingeneral requires the expression, but the expression can't be createdwithout the definition of the associated type. Perhaps the approachshould be to attempt to define the expression, fail for want of theassociated type, apply the default, and then try to define theexpression again. Whatever the answer, this needs to be spelled outmore clearly.
The example in _N2914_.14.10.3.2 [concept.refine.maps] paragraph 3 reads:
concept C<typename T> { } concept D<typename T, typename U> : C<T> { } template<typename T> struct A { }; template<typename T> concept_map D<A<T>, T> { } ...
Since all concept maps templates are constrained templates, we knowthat we're in a constrained context at the point of theconcept_map keyword. Then the first argument toDisA<T>, andA is an unconstrained template,so this is ill-formed by _N2914_.14.11 [temp.constrained] paragraph 5:
Within a constrained context, a program shall not require a templatespecialization of an unconstrained template for which the templatearguments of the specialization depend on a template parameter.
Suggestion: makeA a constrained template, e.g.,
template<std::ObjectType T> struct A { };
Additional notes (May, 2009):
There are other examples that exhibit the same problem. Forexample, _N2960_.14.6.8 [temp.concept.map] paragraph 7 has this example:
concept Stack<typename X> { typename value_type; value_type& top(X&); // ... } template<typename T> struct dynarray { T& top(); }; template<> struct dynarray<bool> { bool top(); }; template<typename T> concept_map Stack<dynarray<T>> { typedef T value_type; T& top(dynarray<T>& x) { return x.top(); } }
dynarray needs to be constrained. Similarly, in_N2914_.14.10.2.2 [concept.map.assoc] paragraph 3, in the example
concept Allocator<typename Alloc> { template<class T> class rebind_type; } template<typename T> class my_allocator { template<typename U> class rebind_type; }; template<typename T> concept_map Allocator<my_allocator<T>> { template<class U> using rebind_type = my_allocator<T>::rebind_type; }
my_allocator must be constrained. (Note also the missingtemplate argument in the target of the template alias declaration.)
Trivially copyable type was added in 6.8 [basic.types], so wethink that it is necessary to add a concept for trivially copyable type likeTriviallyCopyableType.
Notes from the March, 2009 meeting:
It is not clear whether this should be supported here or in_N2914_.20.2.9 [concept.copymove], similar toTriviallyCopyConstructible andTriviallyCopyAssignable.
_N2914_.14.11 [temp.constrained] paragraph 5 says,
Within a constrained context, a program shall not require a templatespecialization of an unconstrained template for which the templatearguments of the specialization depend on a template parameter.
This would appear to indicate that an example like the followingis ill-formed:
auto concept C<class T> {}; template<template<class> class T, C U> struct Y { Y() { T<U> x; // Well-formed? } };
becauseT' is not a constrained template archetype. However,this is not the intended outcome. The wording needs to be clarified onthis point (and an example and a note explaining the rationale would behelpful).
It should bepossible to support boolean constant expressions asrequirements without resorting to defining theTrue conceptin the library. Boolean expressions are very likely to beconstraints when dealing with non-type template parametersand variadic templates, and constraints in these casesshould feel just as natural as constraints on the typesystem.
The use of&& as the separator fora list of requirements has shown itself to be a seriousteachability problem. The mental model behind&& treats concepts as simplepredicates, which ignores the role of concepts intype-checking templates. The more programmers read into the&& (and especially try to fake||with&& and!), the harder it is for them tounderstand the role of concept maps. Simply changing theseparator to, would eliminate a significantsource of confusion.
The example in _N2914_.14.11.1.1 [temp.req.sat] paragraph 6 reads,
concept C<typename T> { } concept D<typename T> { } namespace N2 { template<C T> void f(T); // #1 template<C T> requires D<T> void f(T); // #2 template<C T> void g(T x) { f(x); } ...
The callf(x) is ill-formed without a constraintindicating thatx can be passed by value.
Consider the following example:
auto concept A<class T> { int f(T); } auto concept B<class T> { int f(T) template<class U> requires A<U> // brings f(U') into scope auto g(T p, U q) -> decltype(f(p) + f(q)); // Both B<T>::f(T) and A<U>::f(U) needed here // but B<T>::f(T) is hidden by A<U>::f(U) // (declared in the same scope as g's template parameters) }
This is similar to the case that motivated 11.4 [class.mem] paragraph 19:
A constrained member is treated as a constrained template(_N2914_.14.11 [temp.constrained]) whose template requirements include therequirements specified in itsmember-requirement clause and therequirements of each enclosing constrained template.
See also _N2914_.14.10.1.1 [concept.fct] paragraph 10 for a similar rulefor default implementations.
A more general version of this merging of requirements is needed,but it does not appear to exist. _N2914_.14.11.1.2 [temp.req.impl]would seem to be the logical place for such a rule.
The example at the end of _N2914_.14.11.2 [temp.archetype] paragraph 13reads,
auto concept CopyConstructible<typename T> { T::T(const T&); } template<CopyConstructible T> struct vector; auto concept VectorLike<typename X> { typename value_type = typename X::value_type; X::X(); void X::push_back(const value_type&); value_type& X::front(); } template<CopyConstructible T> requires VectorLike<vector<T>> //vector<T> is an archetype (but not an instantiated archetype) void f(const T& value) { vector<T> x; // OK: default constructor inVectorLike<vector<T> > x.push_back(value); // OK:push_back inVectorLike<vector<T> > VectorLike<vector<T>>::value_type& val = x.front(); // OK:front inVectorLike<vector<T> > }
However,x.push_back(value) is, in fact, ill-formed: thereis no relationship betweenVectorLike<vector<T>>::value_type and T in thisexample. The function needs one further requirement, e.g.,std::SameType<VectorLike<vector<T>>::value_type, T>to allow use of the function parametervalue as the argumentof thepush_back call.
Suppose we have
template<std::ObjectType T> T* f(T* p) { return ++p; // Presumably ok }
7.6.2.3 [expr.pre.incr] paragraph 1 requires that “Thetype of the operand shall be an arithmetic type or a pointer to acompletely-defined effective object type.” At++p inthis example, the type archetypeT' is considered to becompletely-defined because
A type archetype is considered to be completely defined when it isestablished
(_N2914_.14.11.2.1 [temp.archetype.assemble] paragraph 1) and13.9.3 [temp.explicit] paragraph 7 says that an archetype becomesestablished when
the archetype is used in a context where a complete type is required
So far, so good. Consider use off(T*) with an incompletetype, for instance:
struct A; // A is not defined yet. A* g(A* p) { return f(p); }
During template argument deduction against the templatef(T*), we find that there is a concept map forstd::ObjectType<A> becausestd::ObjectType isa compiler-supported concept, and becauseA is an object type(6.8 [basic.types]), so the compiler provides the concept mapimplicitly. Type deduction succeeds, but then we get an instantiation-timeerror on++p becauseA is incomplete.
I see two potential solutions:
We can remove built-in operations forptr-to-effective-object-type, so that you would have to explicitlyrequire something likestd::HasPreincrement<T*> beforeusing++ on values of typeT* inf(T*).ThenA's lack of completeness would be indicated when we tryto satisfy those requirements automatically (and not at instantiationtime).
Alternatively, we can introduce the notion of acompiler-supported conceptstd::CompleteType<T>, andamend _N2914_.14.11.2.1 [temp.archetype.assemble] so that a type archetype is onlyconsidered to be completely-defined if it has that requirement. Thiswould imply thatf(T*) above is ill-formed at++pbecauseT would then be an incomplete effective object type;the user could fix this by insertingrequiresstd::CompleteType<T> after thetemplate-parameter-list, and then the callf(p) wouldbe ill-formed becausestd::CompleteType<A> would not besatisfied.
If there is no requirement for a destructor for a type, according to_N2914_.14.11.2.1 [temp.archetype.assemble] paragraph 5 its archetype will have adeleted destructor. As a result, several examples in the currentwording are ill-formed:
_N2914_.14.10.2 [concept.map] paragraph 11: theadd function.
_N2914_.14.10.3.1 [concept.member.lookup] paragraph 3: theffunction. (Also missing the copy constructor requirement.)
_N2914_.14.10.3.1 [concept.member.lookup] paragraph 5: thehfunction. (Also missing the copy constructor requirement.)
_N2914_.14.11.1 [temp.req] paragraph 3: theffunction.
_N2914_.14.11.1.1 [temp.req.sat] paragraph 6: thegfunction. (Also missing the copy constructor requirement.)
_N2914_.14.11.2 [temp.archetype] paragraph 15: thedistancefunction.
_N2914_.14.11.2.1 [temp.archetype.assemble] paragraph 2: thefoofunction.
_N2914_.14.11.2.1 [temp.archetype.assemble] paragraph 3: theffunction.
_N2914_.14.11.4 [temp.constrained.inst] paragraph 4: needed fordifference_type.
One possibility would be to add the destructor requirementdirectly in these examples. Another might be to usestd::CopyConstructible instead of a local concept.Yet another would be to consider an implicit requirement for adestructor forstd::Returnable andstd::VariableType.
The example in _N2914_.14.11.4 [temp.constrained.inst] paragraph 4 isill-formed. The call toadvance(i, 1) infattempts to pass1 to a parameter declared asIter::difference_type, but there is nothing that saysthatint is compatible withIter::difference_type.
According to _N2960_.3.3.9 [basic.scope.req] paragraph 2,
In a constrained context (_N2914_.14.11 [temp.constrained]), the names ofall associated functions inside the concepts named by the conceptrequirements in the template's requirements are declared in thesame scope as the constrained template's template parameters.
This does not appear to cover the case when the requirementappears in a concept definition:
auto concept B<class T> { void f( T ); } auto concept C<class T> { typename U = int; requires B<U>; // Is void f(U) placed in the scope of C? void g(U x) { f(x); // Ok, finds the 'f(U)' implicitly declared as a // result of the associated requirement B<U>. } } void f(int); void g() { C<char>::f(42); // Ok? }
This program should be well-formed, but the current wording doesnot make that clear.
Another question that must be addressed is ifC alsocontained an explicit declaration off(U), either beforeor afterB<U>, and whether it would need a satisfierwithin a concept map toC.
(See alsoissue 866.)
_N2960_.6.9 [stmt.late] paragraph 2 consists of the followingexample:
concept Semigroup<typename T> { T::T(const T&); T operator+(T, T); } concept_map Semigroup<int> { int operator+(int x, int y) { return x * y; } } template<Semigroup T> T add(T x, T y) { T r = x + y; // usesSemigroup<T>::operator+ late_check { r = x + y; // usesoperator+ found at instantiation time (not consideringSemigroup<T>::operator+) } return r; }
The second comment is correct but incomplete, because the assignmentoperator is also found at instantiation time. The assignment would beill-formed outside thelate_check block, because theSemigroup concept has no copy assignment operator. The commentshould be extended accordingly.
According to 6.5.5 [basic.lookup.qual] paragraph 7,
In a constrained context (_N2914_.14.11 [temp.constrained]), a name prefixed by anested-name-specifier that nominates a template type parameterT is looked up as follows: for each template requirementC<args> whose template argument list referencesT, the name is looked up as if thenested-name-specifier referencedC<args> instead ofT(_N2960_.3.4.3.3 [concept.qual]), except that only the names ofassociated types are visible during this lookup. If an associated typeof at least one requirement is found, then each name found shall referto the same type. Otherwise, if the reference to the name occurswithin a constrained context, the name is looked up within the scopeof the archetype associated withT (and no specialrestriction on name visibility is in effect for this lookup).
In an example like,
concept A<typename T> { typename assoc_type; } concept B<typename T> { typename assoc_type; } template<typename T> requires A<T> B<T::assoc_type>::assoc_type f();
it is not clear whether the argumentT::assoc_type ofB “references”T or not.
James Widman: In our mental model (and in our intentionswhile drafting), we still have a (non-archetype) dependent type fortheT in your example, and, even after theSameTyperequirement is seen, we also have a distinct dependent type torepresentA<T>::assoc_type (which itself is distinctfrom the type of the entity namedassoc_type that lives inthe scope of the conceptA). And those two dependent types(A<T>::assoc_type andT) will both alias thesame type archetype when that archetype is established (see theparagraph on establishment in _N2914_.14.11.2 [temp.archetype]).
I think 6.5.5 [basic.lookup.qual] paragraph 6 will read moreeasily if we change the “references a template parameter”verbiage to a generalized “dependent type” verbiage. (Weshied away from that in the past because we wanted to say that there'snothing “dependent” within a constrained context. That'sbecause we wanted to say that all name references are bound tosomething, overload resolution is done, etc. So certainly there areno instances of deferred name lookup or deferred overload resolutionwithin a constrained context. But we still need to be able to saywhen a type, template, value or concept instance depends on a templateparameter.) I propose we change this wording to read,
In a constrained context (_N2914_.14.11 [temp.constrained]), the identifierof anunqualified-id prefixed by anested-name-specifierthat nominates a dependent typeT is looked up as follows:for each template requirementC<args>such that eitherT or an equivalent type (_N2914_.14.11.1 [temp.req]) is a template argument toC, the identifier oftheunqualified-id is looked up as if thenested-name-specifier nominatedC<args> instead ofT(_N2960_.3.4.3.3 [concept.qual]), except that only the names ofassociated types and class templates (_N2914_.14.10.1.2 [concept.assoc])are visible during this lookup. If an associated type or classtemplate of at least one requirement is found, then theunqualified-id shall refer either to the same type or to anequivalent type when its identifier is looked up in each of theconcepts of the other requirements whereT is a templateargument. [Note: no part of the procedure described in thepreceding part of this paragraph results in the establishment of anarchetype (_N2914_.14.11.2 [temp.archetype]). However, in the event thattheunqualified-id is atemplate-id, one of its templatearguments could contain some construct that would force archetypeestablishment. —end note] Otherwise, the name is lookedup within the scope of the archetype aliased byT (and nospecial restriction on name visibility is in effect for this lookup).[Note: this establishes the archetype ofT (if it wasnot established already). —end note]
(It looks like we have a wording nit to fix in the archetypeestablishment paragraph: it talks about a type archetype coming intoexistence “when it is used [in some way].” It seems odd tosay that something is used in a particular way before it exists. Weshould instead say something like “whenanecessarily-dependent type that would alias the archetype is used[in some way].”)
(It might also be nice to have a cleanup in the paragraph thatintroduces the notion ofstd::SameType and “equivalenttypes” (_N2914_.14.11.1 [temp.req] paragraph 3) so that thecongruence relation is part of the normative text rather than a note.)
6.6 [basic.link] does not specify whether concept names havelinkage or not.
Given an example like:
auto concept Conv<typename T, typename U> { U::U(T&&); U::U(const U&); U::~U(); }; template<typename U, typename T> requires Conv<T*, U*> U* f(T* p) { return static_cast<T*&&>(p); }
There is currently no normative wording that makes aT*convertible to aU* in the return statement.
One possible approach would be to take the concept map archetypeas specifying an additional case for the pointer conversions in7.3.12 [conv.ptr].
If we cannotbind references/take address of functions in concept_maps,does that mean we cannot use generic bind in constrainedtemplates? Launch threads with expressions found viaconcept map lookup? Hit problems creating std::functionobjects? Does the problem only occur if we use qualifiedlookup to explicitly name a concept map? Does it only kickin if we rely on the implicit function implementationprovided by a concept_map, so some types will work andothers won't for the same algorithm?!
Additional note, June, 2009:
Here is an example illustrating the question:
auto concept Fooable<T> { void foo(T); } struct test_type { void foo() { cout << "foo test_type\n"; } }; concept_map Fooable<test_type> { void foo(test_type& t) { t.foo(); } } void foo(int x) { cout "foo int\n"; } template<typename T> requires Fooable<T> function<void(T)> callback(T t) { void(*fn)(T) = foo; return fn; } int main() { auto fn1 = factory(test_type{}); auto fn2 = factory(0); fn1(test_type{}); fn2(0); return 0; }
The expansion of the range-basedfor statement is givenin 8.6.5 [stmt.ranged] paragraph 1 as:
{ auto && __range = ( expression ); for ( auto __begin = std::Range<_RangeT>::begin(__range), __end = std::Range<_RangeT>::end(__range); __begin != __end; ++__begin ) { for-range-declaration = *__begin; statement } }
In a non-templated context, the concept map tostd::Rangehas been dropped, so the operators and initialization will bewhatever they would normally be; if the concept map replaced thosewith some customized version (e.g., if the iterator's++were supposed to skip odd-numbered elements), that customizedmeaning would be lost.
What we really want are the operators associated with the conceptmap tostd::Iterator that was used to satisfy the associatedrequirement (std::Iterator<iterator>) withinstd::Range<_RangeT> (in whatever concept map wasused to satisfystd::Range<_RangeT>). That is, ifthe grammar permitted it, we want something like
std::Range<_RangeT>::concept_map ::std::Iterator<std::Range<_RangeT>::iterator>::operator++(_begin)
Another alternative would be, ifissue 856is resolved by injecting the declaration of associated functions intoconcept definitions, something like
std::Range<_RangeT>::operator++(__begin)
Paper N2762 changed 8.9 [stmt.dcl] paragraph 3 from
...unless the variable has trivial type (6.8 [basic.types])...
to
...unless the variable has scalar type, class type with a trivialdefault constructor and a trivial destructor, a cv-qualifiedversion of one of these types, or an array of one of the precedingtypes...
However, this change overrode the colliding change fromN2773 that would have changed it to read
...unless the variable has effective trivial type...
The revised wording needs to be changed to allow forarchetypes with the appropriate requirements.
If we write
concept C<class T> {} template<C T> struct B { B f(); virtual void g() = 0; };
... it seems reasonable to expect a diagnostic aboutB<T>::f() not because it doesn't requirestd::Returnable<B<T>> (which I think should notdraw an error), but becauseg() is a pure virtual function.
Now how about this:
template<C T> struct G { B<T> f() { return B<T>(); } };
Here, I'd like to see an error not because we lack the requirementstd::Returnable<B<T>>, but because, when weinstantiateB<T'> (as the current wording indicates wemust within the definition ofG<T>::f()), it turns outto be an abstract class.
Now, it could be that when we instantiateG, we get adifferent partial specialization ofB, and that partialspecialization could have a pure virtual member. So you might see aninstantiation-time error. But partial specializations present dangerslike this anyway.
I suggest we make the rule aboutReturnable<T> applyonly in the case whereT is not an instantiated archetype.The rationale is that with an instantiated archetype, it's possible tosee at template definition time whether the type is abstract, whereaswith a non-instantiated archetype, the only known attributes comefrom requirements.
I suspect we need similar changes for the declarator section.E.g., for a class templateA, we shouldn't need to explicitlyrequireVariableType<A<T>> if we want to declare avariable of typeA<T>. Instead, we just instantiateA<T'> (as would be naturally required at the point ofdefinition of a variable of typeA<T'>), and issueerrors when appropriate like we do with ordinary classes today.
According to 11.4 [class.mem] paragraph 19,
A non-templatemember-declaration that has amember-requirement (_N2914_.14.11.1 [temp.req]) is aconstrained member and shall occur only in a class template(13.7.2 [temp.class]) or nested class thereof. Themember-declaration for a constrained member shall declare amember function. A constrained member is treated as a constrainedtemplate (_N2914_.14.11 [temp.constrained]) whose template requirementsinclude the requirements specified in itsmember-requirementclause and the requirements of each enclosing constrained template.
Furthermore, 13.7.3 [temp.mem] paragraph 9 says,
A member template of a constrained class template is itself aconstrained template (_N2914_.14.11 [temp.constrained])...
and illustrates this statement with the following example:
concept C<typename T> { void f(const T&); } concept D<typename T> { void g(const T&); } template<C T> class A { requires D<T> void h(const T& x) { f(x); // OK:C<T>::f g(x); // OK:D<T>::g } };
If these passages are taken at face value and a constrained memberfunction is, in fact, “treated as a... template,” thereare negative consequences. For example, according to11.4.5.3 [class.copy.ctor] paragraph 2, a member function template isnever considered to be a copy constructor, so a constrainedconstructor that is in the form of a copy constructor does notsuppress the implicit declaration and definition of a default copyconstructor. Also, according to 13.7.3 [temp.mem] paragraph3, a member function template cannot be virtual, so it is not possibleto specify amember-requirement clause for a virtual function.
Presumably these consequences are unintended, so the wording thatsuggests otherwise should be revised to make that clear.
11.4.8.3 [class.conv.fct] paragraph 1 says,
A conversion function is never used to convert a (possiblycv-qualified) object to the (possibly cv-qualified) same object type(or a reference to it), to a (possibly cv-qualified) base class ofthat type (or a reference to it), or to (possibly cv-qualified) void.
Does this mean that the following example is ill-formed?
auto concept Convertible<typename T, typename U> { operator U(const T&); } template <typename T, typename U> requires Convertible<T, U> U convert(const T& t) { return t; } int main() { convert<int>(42); }
12.5 [over.built] paragraph 11 says,
For every quintuple (C1, C2, T, CV1, CV2), whereC2is a class type,C1 is the same type asC2 or is aderived class ofC2,T is an effective object type or afunction type, andCV1 andCV2 arecv-qualifier-seqs, there exist candidate operator functions ofthe form
CV12 T& operator->*(CV1 C1*, CV2 T C2::*);
whereCV12 is the union ofCV1 andCV2.
C1 andC2 should be effective class types (cf7.6.4 [expr.mptr.oper] paragraph 3.
Also, should the relationship between those two classes beexpressed asstd::SameType orstd::DerivedFromrequirements?
Is it possibleto export a concept map template? The current wordingsuggests it is possible, but it is not entirely clear whatit would mean.
Notes from the March, 2009 meeting:
Export is only useful for non-inline function templates and staticdata members of class templates, so it does not make sense to exporta concept map template.
The grammar forconstrained-template-parameter given in13.2 [temp.param] paragraph 1 is:
Theidentifier naming the parameter is optional in the firsttwo productions but not in the latter two productions. Is there a reasonfor this discrepancy?
There is currently no way to distinguish between templates thatdiffer only by their requirements when naming a specialization. Forexample:
auto concept A<class T> {} auto concept B<class T> {} template<class T> requires A<T> void f(T); // #1 template<class T> requires B<T> void f(T); // #2 template <> void f(int); // Which one?
(See alsoissue 868.)
There is no way to specify a concept map in the name of aspecialization. It would be useful to be able to do something like
void g(int n) { f<int : N::concept_map A<int>>(n); }
(See alsoissue 867.)
The requirements for matching of template template parameters andtemplate template arguments given in 13.4.4 [temp.arg.template] donot mention constraints, leaving questions about whether examples likethe following are well-formed:
auto concept C<class T> {}; template <template <C T> class U, C V> struct A{}; template <class T> struct X {}; A<X,int> ax; // Well-formed? template <template <class T> class U, C V> struct B{}; template <C T> struct Y {}; B<Y,int> by; // Well-formed? template <template <class T> class U> struct D{}; template <C T> struct Z {}; D<Z> dz; // Well-formed?
(See alsoissue 848.)
If the requirements of a constrained special member function arenot satisfied, the result is that the member function is not declared(13.7.2 [temp.class] paragraph 5). This allows the specialmember function to be implicitly declared and defined, which willlikely result in an ill-formed program or one with the wrong semantics.
Although the current wording does specify the outcome, it is notimmediately apparent what the result of an example like the followingshould be:
template<std::ObjectType T> struct S { requires std::CopyConstructible<T> S(const S&) = default; };
The outcome (thatS will have an implicitly-declaredcopy constructor that is defined as deleted in specializations inwhichT is not copy-constructible) would be clearerwith the addition of two notes. First, it would be helpful if13.7.2 [temp.class] paragraph 5, which currently reads,
A constrained member (11.4 [class.mem]) in a class template isdeclared only in class template specializations in which its templaterequirements (_N2914_.14.11.1 [temp.req]) are satisfied(_N2914_.14.11.1.1 [temp.req.sat])...
had a note or footnote to the effect,
When a constrained member of a template is a special member function,and when, in an instantiation, the member is not declared because itsrequirements are not satisfied, the special member is considered notto have been “explicitly declared” (i.e., the member isnot user-declared); therefore a declaration may still be implicitlygenerated as specified in 11.4.4 [special].
The fact that the implicitly-declared copy constructor in thiscase is defined as deleted would be clearer if somewhere inthe second list in 11.4.5.3 [class.copy.ctor] paragraph 5, whichcurrently reads
...An implicitly-declared copy constructor for a classX isdefined as deleted ifX has:
a variant member with a non-trivial copy constructor andX is a union-like class,
a non-static data member of class typeM (or arraythereof) that cannot be copied because overload resolution(12.2 [over.match]), as applied toM's copyconstructor, results in an ambiguity or a function that is deleted orinaccessible from the implicitly-declared copy constructor,or
a direct or virtual base classB that cannot be copiedbecause overload resolution (12.2 [over.match]), as applied toB's copy constructor, results in an ambiguity or a functionthat is deleted or inaccessible from the implicitly-declared copyconstructor.
there were a cross-reference to _N2914_.14.11.2.1 [temp.archetype.assemble],whose third paragraph reads,
If no requirement specifies a copy constructor for a typeT,a copy constructor is implicitly declared (11.4.5.3 [class.copy.ctor])in the archetype ofT with the following signature:T(const T&) = delete;
The relationship of requirements with template aliases is not clearin the current wording. For example, something like
auto concept C{}; template <class T> struct A{}; template <C T> using B = A<T>;
is presumably allowed by the current wording of 13.7.8 [temp.alias]but, unless a good use case is presented, should probably be prohibited.
On the other hand, _N2914_.14.11 [temp.constrained] paragraph 5,
Within a constrained context, a program shall not require a templatespecialization of an unconstrained template for which the templatearguments of the specialization depend on a template parameter.
might be considered to forbid an example like
template <C T> struct X {}; template <class T> using Y = X<T>; template <std::VariableType T> void f(Y<T>); // Error?
although it should probably be allowed. (Note, however, that13.7.8 [temp.alias] paragraph 2,
When atemplate-id refers to the specialization of a templatealias, it is equivalent to the associated type obtained bysubstitution of itstemplate-arguments for thetemplate-parameters in thetype-id of the templatealias.
could be viewed as allowing this example, depending on how theword “equivalent” is understood.)
The text should be amended to clarify the resolution of thesequestions. (See alsoissue 848.)
The current grammar does not allow indicating that a lambdaisnoreturn, because currently attributes in alambda-expression appertain to the type of the conversionfunction/template, per 7.5.6 [expr.prim.lambda] paragraph 5.
Additional note (February, 2022):
This issue was addressed by the adoption of paperP2173R1 at the February, 2022 plenary.
Part ofissue 2486 raised thequestion of whetherstatic_cast should bepermitted to cast anoexcept(false) functiontype to anoexcept function type. Presumablythat would also involve changing 7.6.1.3 [expr.call] paragraph 6to allow a call through the convertedvalue, with undefined behavior resulting only if thecalled function actually exits via an exception.
CWG felt these questions should be addressed by EWG, sothey were spun off into a separate issue.
Although there are implementations that use thunks forpointers to virtual member functions, it appears thatsuch a technique is not permitted by the Standard.Concerns particularly include the requirements for completetypes for parameter and return types at the point at whichthe member function pointer is formed.
Consider:
template<void *P> void f() { if constexpr (P) {} // #1 }
This is ill-formed at #1, because an expression oftypevoid* cannot be converted tobool as acontextually converted constant expression.
[This suggestion was adopted as paper P2156R1 at the June, 2021plenary.]
The standard attributesnoreturn,carries_dependency,anddeprecated all specify that they cannot appear more than oncein anattribute-list, but there is no such prohibition if theyappear in separateattribute-specifiers within a singleattribute-specifier-seq. Since intuitively these cases areequivalent, they should be treated the same, accepting duplicates inboth or neither.
Rationale (June, 2014):
EWG should determine the desired outcome for this question.
The status of an example like the following is unclear:
struct S { template <class T> friend void f(T) { } }; template void f(int); // Well-formed?
A friend is not found by ordinary name lookup until it is explicitlydeclared in the containing namespace, but declaration matching does notuse ordinary name lookup. There is implementation divergence on thehandling of this example.
Notes from the March, 2018 meeting:
CWG did not come to consensus on the desired outcome and feels thatthe question should be addressed by EWG.