Document number:  J16/07-0227 = WG21 N2367
Date:  2007-08-05
Project:  Programming Language C++
Reference:  ISO/IEC IS 14882:2003
Reply to:  William M. Miller
 Edison Design Group, Inc.
 wmm@edg.com


C++ Standard Core Language Defect Reports, Revision 49


This document contains the C++ core language issues that have been categorized as Defect Reports by the Committee (J16 + WG21), that is, issues with status "DR," "WP," and "TC1," along with their proposed resolutions. ONLY RESOLUTIONS FOR ISSUES WITH TC1 STATUS ARE PART OF THE INTERNATIONAL STANDARD FOR C++. The other issues are provided for informational purposes only, as an indication of the intent of the Committee. They should not be considered definitive until or unless they appear in an approved Technical Corrigendum or revised International Standard for C++.

This document is part of a group of related documents that together describe the issues that have been raised regarding the C++ Standard. The other documents in the group are:

For more information, including a description of the meaning of the issue status codes and instructions on reporting new issues, please see the Active Issues List.


Issues with "DR" Status


452. Wording nit on description of this

Section: 9.3.2  class.this     Status: DR     Submitter: Gennaro Prota     Date: 8 Jan 2004

[Voted into WP at July, 2007 meeting.]

9.3.2  class.this paragraph 1, which specifies the meaning of the keyword 'this', seems to limit its usage to the *body* of non-static member functions. However 'this' is also usable in ctor-initializers which, according to the grammar in 8.4  dcl.fct.def par. 1, are not part of the body.

Proposed resolution: Changing the first part of 9.3.2  class.this par. 1 to:

In the body of a nonstatic (9.3) member function or in a ctor-initializer (12.6.2), the keyword this is a non-lvalue expression whose value is the address of the object for which the function is called.

NOTE: I'm talking of constructors as functions that are "called"; there have been discussions on c.l.c++.m as to whether constructors are "functions" and to whether this terminology is correct or not; I think it is both intuitive and in agreement with the standard wording.

Steve Adamczyk: See also issue 397, which is defining a new syntax term for the body of a function including the ctor-initializers.

Notes from the March 2004 meeting:

This will be resolved when issue 397 is resolved.

Proposed resolution (October, 2005):

  1. Change 8.4  dcl.fct.def paragraph 1 as indicated:

  2. Function definitions have the form

    An informal reference to the body of a function should be interpreted as a reference to the nonterminal function-body.

  3. Change the definition of function-try-block in 15  except paragraph 1:

  4. Change 3.3.6  basic.scope.class paragraph 1, point 1, as indicated:

  5. The potential scope of a name declared in a class consists not only of the declarative region following the name's point of declaration, but also of all function bodies, bodies and default arguments, and constructor ctor-initializers in that class (including such things in nested classes).
  6. Change 3.3.6  basic.scope.class paragraph 1, point 5, as indicated:

  7. The potential scope of a declaration that extends to or past the end of a class definition also extends to the regions defined by its member definitions, even if the members are defined lexically outside the class (this includes static data member definitions, nested class definitions, member function definitions (including the member function body and, for constructor functions (12.1  class.ctor), the ctor-initializer (12.6.2  class.base.init)) and any portion of the declarator part of such definitions which follows the identifier, including a parameter-declaration-clause and any default arguments (8.3.6  dcl.fct.default). [Example:...
  8. Change footnote 32 in 3.4.1  basic.lookup.unqual paragraph 8 as indicated:

  9. That is, an unqualified name that occurs, for instance, in a type or default argument expression in the parameter-declaration-clause, parameter-declaration-clause or in the function body, or in an expression of a mem-initializer in a constructor definition.
  10. Change 5.1  expr.prim paragraph 3 as indicated:

  11. ...The keyword this shall be used only inside a non-static class member function body (9.3  class.mfct) or in a constructor mem-initializer (12.6.2  class.base.init)...
  12. Change 9.2  class.mem paragraph 2 as indicated:

  13. ...Within the class member-specification, the class is regarded as complete within function bodies, default arguments, and exception-specifications, and constructor ctor-initializers (including such things in nested classes)...
  14. Change 9.2  class.mem paragraph 9 as indicated:

  15. Each occurrence in an expression of the name of a non-static data member or non-static member function of a class shall be expressed as a class member access (5.2.5  expr.ref), except when it appears in the formation of a pointer to member (5.3.1  expr.unary.op), or or when it appears in the body of a non-static member function of its class or of a class derived from its class (9.3.1  class.mfct.nonstatic), or when it appears in a mem-initializer for a constructor for its class or for a class derived from its class (12.6.2  class.base.init).
  16. Change the note in 9.3  class.mfct paragraph 5 as indicated:

  17. [Note: a name used in a member function definition (that is, in the parameter-declaration-clause including the default arguments (8.3.6  dcl.fct.default), or or in the member function body, or, for a constructor function (12.1  class.ctor), in a mem-initializer expression (12.6.2  class.base.init)) is looked up as described in 3.4  basic.lookup. —end note]
  18. Change 9.3.1  class.mfct.nonstatic paragraph 1 as indicated:

  19. ...A non-static member function may also be called directly using the function call syntax (5.2.2  expr.call, 13.3.1.1  over.match.call) from within the body of a member function of its class or of a class derived from its class.

  20. Change 9.3.1  class.mfct.nonstatic paragraph 3 as indicated:

  21. When an id-expression (5.1  expr.prim) that is not part of a class member access syntax (5.2.5  expr.ref) and not used to form a pointer to member (5.3.1  expr.unary.op) is used in the body of a non-static member function of class X or used in the mem-initializer for a constructor of class X, if name lookup (3.4.1  basic.lookup.unqual) resolves the name in the id-expression to a non-static non-type member of class X or of a base class of X, the id-expression is transformed into a class member access expression (5.2.5  expr.ref) using (*this) (9.3.2  class.this) as the postfix-expression to the left of the . operator...
  22. Change 12.1  class.ctor paragraph 7 as indicated:

  23. ...The implicitly-defined default constructor performs the set of initializations of the class that would be performed by a user-written default constructor for that class with an empty mem-initializer-list no ctor-initializer (12.6.2  class.base.init) and an empty function body compound-statement...
  24. Change 12.6.2  class.base.init paragraph 4 as indicated:

  25. ...After the call to a constructor for class X has completed, if a member of X is neither specified in the constructor’s mem-initializers, nor default-initialized, nor value-initialized, nor given a value during execution of the compound-statement of the body of the constructor, the member has indeterminate value.
  26. Change the last bullet of 12.6.2  class.base.init paragraph 5 as indicated:

  27. Change 15  except paragraph 4 as indicated:

  28. A function-try-block associates a handler-seq with the ctor-initializer, if present, and the function-body compound-statement. An exception thrown during the execution of the initializer expressions in the ctor-initializer or during the execution of the function-body compound-statement transfers control to a handler in a function-try-block in the same way as an exception thrown during the execution of a try-block transfers control to other handlers. [Example:

        int f(int);
        class C {
            int i;
            double d;
        public:
            C(int, double);
        };
    
        C::C(int ii, double id)
        try
            : i(f(ii)), d(id)
        {
            // constructor function body statements
        }
        catch (...)
        {
            // handles exceptions thrown from the ctor-initializer
            // and from the constructor function body statements
        }
    

    end example]

  29. Change 15.2  except.ctor paragraph 2 as indicated:

  30. When an exception is thrown, control is transferred to the nearest handler with a matching type (15.3  except.handle); “nearest” means the handler for which the compound-statement, compound-statement or ctor-initializer, or function-body following the try keyword was most recently entered by the thread of control and not yet exited.





Issues with "WP" Status


357. Definition of signature should include name

Section: 1.3.10  defns.signature     Status: WP     Submitter: Steve Clamage     Date: 26 May 2002

[Voted into WP at April, 2007 meeting.]

Section 1.3.10  defns.signature, definition of "signature" omits the function name as part of the signature. Since the name participates in overload resolution, shouldn't it be included in the definition? I didn't find a definition of signature in the ARM, but I might have missed it.

Fergus Henderson: I think so. In particular, 17.4.3.1.2  lib.global.names reserves certain "function signatures" for use by the implementation, which would be wrong unless the signature includes the name.

-2- Each global function signature declared with external linkage in a header is reserved to the implementation to designate that function signature with external linkage.

-5- Each function signature from the Standard C library declared with external linkage is reserved to the implementation for use as a function signature with both extern "C" and extern "C++" linkage, or as a name of namespace scope in the global namespace.

Other uses of the term "function signature" in the description of the standard library also seem to assume that it includes the name.

James Widman:

Names don't participate in overload resolution; name lookup is separate from overload resolution. However, the word “signature” is not used in clause 13  over. It is used in linkage and declaration matching (e.g., 14.5.5.1  temp.over.link). This suggests that the name and scope of the function should be part of its signature.

Proposed resolution (October, 2006):

  1. Replace 1.3.10  defns.signature with the following:

  2. the name and the parameter-type-list (8.3.5  dcl.fct) of a function, as well as the class or namespace of which it is a member. If a function or function template is a class member its signature additionally includes the cv-qualifiers (if any) on the function or function template itself. The signature of a function template additionally includes its return type and its template parameter list. The signature of a function template specialization includes the signature of the template of which it is a specialization and its template arguments (whether explicitly specified or deduced). [Note: Signatures are used as a basis for name-mangling and linking. —end note]
  3. Delete paragraph 3 and replace the first sentence of 14.5.5.1  temp.over.link as follows:

  4. The signature of a function template specialization consists of the signature of the function template and of the actual template arguments (whether explicitly specified or deduced).

    The signature of a function template consists of its function signature, its return type and its template parameter list is defined in 1.3.10  defns.signature. The names of the template parameters are significant...

(See also issue 537.)




537. Definition of “signature”

Section: 1.3.10  defns.signature     Status: WP     Submitter: Daveed Vandevoorde     Date: 12 October 2005

[Voted into WP at April, 2007 meeting.]

The standard defines “signature” in two places: 1.3.10  defns.signature and 14.5.5.1  temp.over.link paragraphs 3-4. The former seems to be meant as a formal definition (I think it's the only place covering the nontemplate case), yet it lacks some bits mentioned in the latter (specifically, the notion of a “signature of a function template,” which is part of every signature of the associated function template specializations).

Also, I think the 1.3.10  defns.signature words “the information about a function that participates in overload resolution” isn't quite right either. Perhaps, “the information about a function that distinguishes it in a set of overloaded functions?”

Eric Gufford:

In 1.3.10  defns.signature the definition states that “Function signatures do not include return type, because that does not participate in overload resolution,” while 14.5.5.1  temp.over.link paragraph 4 states “The signature of a function template consists of its function signature, its return type and its template parameter list.” This seems inconsistent and potentially confusing. It also seems to imply that two identical function templates with different return types are distinct signatures, which is in direct violation of 13.3  over.match. 14.5.5.1  temp.over.link paragraph 4 should be amended to include verbiage relating to overload resolution.

Either return types are included in function signatures, or they're not, across the board. IMHO, they should be included as they are an integral part of the function declaration/definition irrespective of overloads. Then verbiage should be added about overload resolution to distinguish between signatures and overload rules. This would help clarify things, as it is commonly understood that overload resolution is based on function signature.

In short, the term “function signature” should be made consistent, and removed from its (implicit, explicit or otherwise) linkage to overload resolution as it is commonly understood.

James Widman:

The problem is that (a) if you say the return type is part of the signature of a non-template function, then you have overloading but not overload resolution on return types (i.e., what we have now with function templates). I don't think anyone wants to make the language uglier in that way. And (b) if you say that the return type is not part of the signature of a function template, you will break code. Given those alternatives, it's probably best to maintain the status quo (which the implementors appear to have rendered faithfully).

Proposed resolution (September, 2006):

This issue is resolved by the resolution of issue 357.




513. Non-class “most-derived” objects

Section: 1.8  intro.object     Status: WP     Submitter: Marc Schoolderman     Date: 20 Mar 2005

[Voted into WP at April, 2006 meeting.]

The standard uses “most derived object” in some places (for example, 1.3.3  defns.dynamic.type, 5.3.5  expr.delete) to refer to objects of both class and non-class type. However, 1.8  intro.object only formally defines it for objects of class type.

Possible fix: Change the wording in 1.8  intro.object paragraph 4 from

an object of a most derived class type is called a most derived object

to

an object of a most derived class type, or of non-class type, is called a most derived object

Proposed resolution (October, 2005):

Add the indicated words to 1.8  intro.object paragraph 4:

If a complete object, a data member (9.2  class.mem), or an array element is of class type, its type is considered the most derived class, to distinguish it from the class type of any base class subobject; an object of a most derived class type, or of a non-class type, is called a most derived object.



362. Order of initialization in instantiation units

Section: 2.1  lex.phases     Status: WP     Submitter: Mark Mitchell     Date: 2 July 2002

[Voted into WP at March 2004 meeting.]

Should this program do what its author obviously expects? As far as I can tell, the standard says that the point of instantiation for Fib<n-1>::Value is the same as the point of instantiation as the enclosing specialization, i.e., Fib<n>::Value. What in the standard actually says that these things get initialized in the right order?

  template<int n>
  struct Fib { static int Value; };

  template <>
  int Fib<0>::Value = 0;

  template <>
  int Fib<1>::Value = 1;

  template<int n>
  int Fib<n>::Value = Fib<n-1>::Value + Fib<n-2>::Value;

  int f ()
  {
    return Fib<40>::Value;
  }

John Spicer: My opinion is that the standard does not specify the behavior of this program. I thought there was a core issue related to this, but I could not find it. The issue that I recall proposed tightening up the static initialization rules to make more cases well defined.

Your comment about point of instantiation is correct, but I don't think that really matters. What matters is the order of execution of the initialization code at execution time. Instantiations don't really live in "translation units" according to the standard. They live in "instantiation units", and the handling of instantiation units in initialization is unspecified (which should probably be another core issue). See 2.1  lex.phases paragraph 8.

Notes from October 2002 meeting:

We discussed this and agreed that we really do mean the the order is unspecified. John Spicer will propose wording on handling of instantiation units in initialization.

Proposed resolution (April 2003):

TC1 contains the following text in 3.6.2  basic.start.init paragraph 1:

Objects with static storage duration defined in namespace scope in the same translation unit and dynamically initialized shall be initialized in the order in which their definition appears in the translation unit.

This was revised by issue 270 to read:

Dynamic initialization of an object is either ordered or unordered. Explicit specializations and definitions of class template static data members have ordered initialization. Other class template static data member instances have unordered initialization. Other objects defined in namespace scope have ordered initialization. Objects defined within a single translation unit and with ordered initialization shall be initialized in the order of their definitions in the translation unit. The order of initialization is unspecified for objects with unordered initialization and for objects defined in different translation units.

This addresses this issue but while reviewing this issue some additional changes were suggested for the above wording:

Dynamic initialization of an object is either ordered or unordered. Definitions of explicitly specialized Explicit specializations and definitions of class template static data members have ordered initialization. Other class template static data members (i.e., implicitly or explicitly instantiated specializations) instances have unordered initialization. Other objects defined in namespace scope have ordered initialization. Objects defined within a single translation unit and with ordered initialization shall be initialized in the order of their definitions in the translation unit. The order of initialization is unspecified for objects with unordered initialization and for objects defined in different translation units.



505. Conditionally-supported behavior for unknown character escapes

Section: 2.13.2  lex.ccon     Status: WP     Submitter: Mike Miller     Date: 14 Apr 2005

[Voted into WP at the October, 2006 meeting.]

The current wording of 2.13.2  lex.ccon paragraph 3 states,

If the character following a backslash is not one of those specified, the behavior is undefined.

Paper J16/04-0167=WG21 N1727 suggests that such character escapes be ill-formed. In discussions at the Lillehammer meeting, however, the CWG felt that the newly-approved category of conditionally-supported behavior would be more appropriate.

Proposed resolution (April, 2006):

Change the next-to-last sentence of 2.13.2  lex.ccon paragraph 3 from:

If the character following a backslash is not one of those specified, the behavior is undefined.

to:

Escape sequences in which the character following the backslash is not listed in Table 6 are conditionally-supported, with implementation-defined semantics.



261. When is a deallocation function "used?"

Section: 3.2  basic.def.odr     Status: WP     Submitter: Mike Miller     Date: 7 Nov 2000

[Moved to DR at October 2002 meeting.]

3.2  basic.def.odr paragraph 2 says that a deallocation function is "used" by a new-expression or delete-expression appearing in a potentially-evaluated expression. 3.2  basic.def.odr paragraph 3 requires only that "used" functions be defined.

This wording runs afoul of the typical implementation technique for polymorphic delete-expressions in which the deallocation function is invoked from the virtual destructor of the most-derived class. The problem is that the destructor must be defined, because it's virtual, and if it contains an implicit reference to the deallocation function, the deallocation function must also be defined, even if there are no relevant new-expressions or delete-expressions in the program.

For example:

        struct B { virtual ~B() { } };

        struct D: B {
            void operator delete(void*);
            ~D() { }
        };

Is it required that D::operator delete(void*) be defined, even if no B or D objects are ever created or deleted?

Suggested resolution: Add the words "or if it is found by the lookup at the point of definition of a virtual destructor (12.4  class.dtor)" to the specification in 3.2  basic.def.odr paragraph 2.

Notes from 04/01 meeting:

The consensus was in favor of requiring that any declared non-placement operator delete member function be defined if the destructor for the class is defined (whether virtual or not), and similarly for a non-placement operator new if a constructor is defined.

Proposed resolution (10/01):

In 3.2  basic.def.odr paragraph 2, add the indicated text:

An allocation or deallocation function for a class is used by a new expression appearing in a potentially-evaluated expression as specified in 5.3.4  expr.new and 12.5  class.free. A deallocation function for a class is used by a delete expression appearing in a potentially-evaluated expression as specified in 5.3.5  expr.delete and 12.5  class.free. A non-placement allocation or deallocation function for a class is used by the definition of a constructor of that class. A non-placement deallocation function for a class is used by the definition of the destructor of that class, or by being selected by the lookup at the point of definition of a virtual destructor (12.4  class.dtor). [Footnote: An implementation is not required to call allocation and deallocation functions from constructors or destructors; however, this is a permissible implementation technique.]




289. Incomplete list of contexts requiring a complete type

Section: 3.2  basic.def.odr     Status: WP     Submitter: Mike Miller     Date: 25 May 2001

[Moved to DR at October 2002 meeting.]

3.2  basic.def.odr paragraph 4 has a note listing the contexts that require a class type to be complete. It does not list use as a base class as being one of those contexts.

Proposed resolution (10/01):

In 3.2  basic.def.odr paragraph 4 add a new bullet at the end of the note as the next-to-last bullet:




433. Do elaborated type specifiers in templates inject into enclosing namespace scope?

Section: 3.3.1  basic.scope.pdecl     Status: WP     Submitter: Daveed Vandevoorde     Date: 2 September 2003

[Voted into WP at March 2004 meeting.]

Consider the following translation unit:

  template<class T> struct S {
    void f(union U*);  // (1)
  };
  template<class T> void S<T>::f(union U*) {}  // (2)
  U *p;  // (3)

Does (1) introduce U as a visible name in the surrounding namespace scope?

If not, then (2) could presumably be an error since the "union U" in that definition does not find the same type as the declaration (1).

If yes, then (3) is OK too. However, we have gone through much trouble to allow template implementations that do not pre-parse the template definitions, but requiring (1) to be visible would change that.

A slightly different case is the following:

  template<typename> void f() { union U *p; }
  U *q;  // Should this be valid?

Notes from October 2003 meeting:

There was consensus that example 1 should be allowed. (Compilers already parse declarations in templates; even MSVC++ 6.0 accepts this case.) The vote was 7-2.

Example 2, on the other hand, is wrong; the union name goes into a block scope anyway.

Proposed resolution:

In 3.3.1  basic.scope.pdecl change the second bullet of paragraph 5 as follows:

for an elaborated-type-specifier of the form
   class-key identifier
if the elaborated-type-specifier is used in the decl-specifier-seq or parameter-declaration-clause of a function defined in namespace scope, the identifier is declared as a class-name in the namespace that contains the declaration; otherwise, except as a friend declaration, the identifier is declared in the smallest non-class, non-function-prototype scope that contains the declaration. [Note: These rules also apply within templates.] [Note: ...]



432. Is injected class name visible in base class specifier list?

Section: 3.3.6  basic.scope.class     Status: WP     Submitter: Daveed Vandevoorde     Date: 29 August 2003

[Voted into WP at March 2004 meeting.]

Consider the following example (inspired by a question from comp.lang.c++.moderated):

  template<typename> struct B {};
  template<typename T> struct D: B<D> {};

Most (all?) compilers reject this code because D is handled as a template name rather than as the injected class name.

class/2 says that the injected class name is "inserted into the scope of the class."

3.3.6  basic.scope.class/1 seems to be the text intended to describe what "scope of a class" means, but it assumes that every name in that scope was introduced using a "declarator". For an implicit declaration such as the injected-class name it is not clear what that means.

So my questions:

  1. Should the injected class name be available in the base class specifiers?
    John Spicer: I do not believe the injected class name should be available in the base specifier. I think the semantics of injected class names should be as if a magic declaration were inserted after the opening "{" of the class definition. The injected class name is a member of the class and members don't exist at the point where the base specifiers are scanned.
  2. Do you agree the wording should be clarified whatever the answer to the first question?
    John Spicer: I believe the 3.3.6  basic.scope.class wording should be updated to reflect the fact that not all names come from declarators.

Notes from October 2003 meeting:

We agree with John Spicer's suggested answers above.

Proposed Resolution (October 2003):

The answer to question 1 above is No and no change is required.

For question 1, change 3.3.6  basic.scope.class paragraph 1 rule 1 to:

1) The potential scope of a name declared in a class consists not only of the declarative region following the name's point of declaration declarator, but also of all function bodies, default arguments, and constructor ctor-initializers in that class (including such things in nested classes). The point of declaration of an injected-class-name (clause 9  class) is immediately following the opening brace of the class definition.

(Note that this change overlaps a change in issue 417.)

Also change 3.3.1  basic.scope.pdecl by adding a new paragraph 8 for the injected-class-name case:

The point of declaration for an injected-class-name (clause 9  class) is immediately following the opening brace of the class definition.

Alternatively this paragraph could be added after paragraph 5 and before the two note paragraphs (i.e. it would become paragraph 5a).




139. Error in friend lookup example

Section: 3.4.1  basic.lookup.unqual     Status: WP     Submitter: Mike Miller     Date: 14 Jul 1999

[Moved to DR at 10/01 meeting.]

The example in 3.4.1  basic.lookup.unqual paragraph 3 is incorrect:

    typedef int f;
    struct A {
        friend void f(A &);
        operator int();
        void g(A a) {
            f(a);
        }
    };
Regardless of the resolution of other issues concerning the lookup of names in friend declarations, this example is ill-formed (the function and the typedef cannot exist in the same scope).

One possible repair of the example would be to make f a class with a constructor taking either A or int as its parameter.

(See also issues 95, 136, 138, 143, 165, and 166.)

Proposed resolution (04/01):

  1. Change the example in 3.4.1  basic.lookup.unqual paragraph 3 to read:

        typedef int f;
        namespace N {
            struct A {
                friend int f(A &);
                operator int();
                void g(A a) {
                    int i = f(a);
                          // f is the typedef, not the friend function:
                          // equivalent to int(a)
                }
            };
        }
    
  2. Delete the sentence immediately following the example:

    The expression f(a) is a cast-expression equivalent to int(a).



514. Is the initializer for a namespace member in the scope of the namespace?

Section: 3.4.1  basic.lookup.unqual     Status: WP     Submitter: Mike Miller     Date: 24 Mar 2005

[Voted into WP at the October, 2006 meeting.]

Is the following code well-formed?

    namespace N {
      int i;
      extern int j;
    }
    int N::j = i;

The question here is whether the lookup for i in the initializer of N::j finds the declaration in namespace N or not. Implementations differ on this question.

If N::j were a static data member of a class, the answer would be clear: both 3.4.1  basic.lookup.unqual paragraph 12 and 8.5  dcl.init paragraph 11 say that the initializer “is in the scope of the member's class.” There is no such provision for namespace members defined outside the namespace, however.

The reasoning given in 3.4.1  basic.lookup.unqual may be instructive:

A name used in the definition of a static data member of class X (9.4.2  class.static.data) (after the qualified-id of the static member) is looked up as if the name was used in a member function of X.

It is certainly the case that a name used in a function that is a member of a namespace is looked up in that namespace (3.4.1  basic.lookup.unqual paragraph 6), regardless of whether the definition is inside or outside that namespace. Initializers for namespace members should probably be looked up the same way.

Proposed resolution (April, 2006):

Add a new paragraph following 3.4.1  basic.lookup.unqual paragraph 12:

If a variable member of a namespace is defined outside of the scope of its namespace then any name used in the definition of the variable member (after the declarator-id) is looked up as if the definition of the variable member occurred in its namespace. [Example:

    namespace N {
      int i = 4;
      extern int j;
    }

    int i = 2;

    int N::j = i;	// N::j == 4

end example]




143. Friends and Koenig lookup

Section: 3.4.2  basic.lookup.argdep     Status: WP     Submitter: Mike Miller     Date: 21 Jul 1999

[Moved to DR at 4/02 meeting.]

Paragraphs 1 and 2 of 3.4.2  basic.lookup.argdep say, in part,

When an unqualified name is used as the postfix-expression in a function call (5.2.2  expr.call )... namespace-scope friend function declarations (11.4  class.friend ) not otherwise visible may be found... the set of declarations found by the lookup of the function name [includes] the set of declarations found in the... classes associated with the argument types.
The most straightforward reading of this wording is that if a function of namespace scope (as opposed to a class member function) is declared as a friend in a class, and that class is an associated class in a function call, the friend function will be part of the overload set, even if it is not visible to normal lookup.

Consider the following example:

    namespace A {
	class S;
    };
    namespace B {
	void f(A::S);
    };
    namespace A {
	class S {
	    int i;
	    friend void B::f(S);
	};
    }
    void g() {
	A::S s;
	f(s); // should find B::f(A::S)
    }
This example would seem to satisfy the criteria from 3.4.2  basic.lookup.argdep : A::S is an associated class of the argument, and A::S has a friend declaration of the namespace-scope function B::f(A::S), so Koenig lookup should include B::f(A::S) as part of the overload set in the call.

Another interpretation is that, instead of finding the friend declarations in associated classes, one only looks for namespace-scope functions, visible or invisible, in the namespaces of which the the associated classes are members; the only use of the friend declarations in the associated classes is to validate whether an invisible function declaration came from an associated class or not and thus whether it should be included in the overload set or not. By this interpretation, the call f(s) in the example will fail, because B::f(A::S) is not a member of namespace A and thus is not found by the lookup.

Notes from 10/99 meeting: The second interpretation is correct. The wording should be revised to make clear that Koenig lookup works by finding "invisible" declarations in namespace scope and not by finding friend declarations in associated classes.

Proposed resolution (04/01): The "associated classes" are handled adequately under this interpretation by 3.4.2  basic.lookup.argdep paragraph 3, which describes the lookup in the associated namespaces as including the friend declarations from the associated classes. Other mentions of the associated classes should be removed or qualified to avoid the impression that there is a lookup in those classes:

  1. In 3.4.2  basic.lookup.argdep, change

    When an unqualified name is used as the postfix-expression in a function call (5.2.2  expr.call), other namespaces not considered during the usual unqualified lookup (3.4.1  basic.lookup.unqual) may be searched, and namespace-scope friend function declarations (11.4  class.friend) not otherwise visible may be found.

    to

    When an unqualified name is used as the postfix-expression in a function call (5.2.2  expr.call), other namespaces not considered during the usual unqualified lookup (3.4.1  basic.lookup.unqual) may be searched, and in those namespaces, namespace-scope friend function declarations (11.4  class.friend) not otherwise visible may be found.
  2. In 3.4.2  basic.lookup.argdep paragraph 2, delete the words and classes in the following two sentences:

    If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated namespaces and classes are not considered. Otherwise the set of declarations found by the lookup of the function name is the union of the set of declarations found using ordinary unqualified lookup and the set of declarations found in the namespaces and classes associated with the argument types.

(See also issues 95, 136, 138, 139, 165, 166, and 218.)




218. Specification of Koenig lookup

Section: 3.4.2  basic.lookup.argdep     Status: WP     Submitter: Hyman Rosen     Date: 28 Mar 2000

[Voted into WP at April, 2007 meeting.]

The original intent of the Committee when Koenig lookup was added to the language was apparently something like the following:

  1. The name in the function call expression is looked up like any other unqualified name.
  2. If the ordinary unqualified lookup finds nothing or finds the declaration of a (non-member) function, function template, or overload set, argument-dependent lookup is done and any functions found in associated namespaces are added to the result of the ordinary lookup.

This approach is not reflected in the current wording of the Standard. Instead, the following appears to be the status quo:

  1. Lookup of an unqualified name used as the postfix-expression in the function call syntax always performs Koenig lookup (3.4.1  basic.lookup.unqual paragraph 3).
  2. Unless ordinary lookup finds a class member function, the result of Koenig lookup always includes the declarations found in associated namespaces (3.4.2  basic.lookup.argdep paragraph 2), regardless of whether ordinary lookup finds a declaration and, if so, what kind of entity is found.
  3. The declarations from associated namespaces are not limited to functions and template functions by anything in 3.4.2  basic.lookup.argdep. However, if Koenig lookup results in more than one declaration and at least one of the declarations is a non-function, the program is ill-formed (7.3.4  namespace.udir, paragraph 4; although this restriction is in the description of the using-directive, the wording applies to any lookup that spans namespaces).

John Spicer: Argument-dependent lookup was created to solve the problem of looking up function names within templates where you don't know which namespace to use because it may depend on the template argument types (and was then expanded to permit use in nontemplates). The original intent only concerned functions. The safest and simplest change is to simply clarify the existing wording to that effect.

Bill Gibbons: I see no reason why non-function declarations should not be found. It would take a special rule to exclude "function objects", as well as pointers to functions, from consideration. There is no such rule in the standard and I see no need for one.

There is also a problem with the wording in 3.4.2  basic.lookup.argdep paragraph 2:

If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated namespaces and classes are not considered.

This implies that if the ordinary lookup of the name finds the declaration of a data member which is a pointer to function or function object, argument-dependent lookup is still done.

My guess is that this is a mistake based on the incorrect assumption that finding any member other than a member function would be an error. I would just change "class member function" to "class member" in the quoted sentence.

Mike Miller: In light of the issue of "short-circuiting" Koenig lookup when normal lookup finds a non-function, perhaps it should be written as "...finds the declaration of a class member, an object, or a reference, the associated namespaces..."?

Andy Koenig: I think I have to weigh in on the side of extending argument-dependent lookup to include function objects and pointers to functions. I am particularly concerned about [function objects], because I think that programmers should be able to replace functions by function objects without changing the behavior of their programs in fundamental ways.

Bjarne Stroustrup: I don't think we could seriously argue from first principles that [argument-dependent lookup should find only function declarations]. In general, C++ name lookup is designed to be independent of type: First we find the name(s), then, we consider its(their) meaning. 3.4  basic.lookup states "The name lookup rules apply uniformly to all names ..." That is an important principle.

Thus, I consider text that speaks of "function call" instead of plain "call" or "application of ()" in the context of koenig lookup an accident of history. I find it hard to understand how 5.2.2  expr.call doesn't either disallow all occurrences of x(y) where x is a class object (that's clearly not intended) or requires koenig lookup for x independently of its type (by reference from 3.4  basic.lookup). I suspect that a clarification of 5.2.2  expr.call to mention function objects is in order. If the left-hand operand of () is a name, it should be looked up using koenig lookup.

John Spicer: This approach causes otherwise well-formed programs to be ill-formed, and it does so by making names visible that might be completely unknown to the author of the program. Using-directives already do this, but argument-dependent lookup is different. You only get names from using-directives if you actually use using-directives. You get names from argument-dependent lookup whether you want them or not.

This basically breaks an important reason for having namespaces. You are not supposed to need any knowledge of the names used by a namespace.

But this example breaks if argument-dependent lookup finds non-functions and if the translation unit includes the <list> header somewhere.

    namespace my_ns {
        struct A {};
        void list(std::ostream&, A&);

        void f() {
            my_ns::A a;
            list(cout, a);
        }
    }

This really makes namespaces of questionable value if you still need to avoid using the same name as an entity in another namespace to avoid problems like this.

Erwin Unruh: Before we really decide on this topic, we should have more analysis on the impact on programs. I would also like to see a paper on the possibility to overload functions with function surrogates (no, I won't write one). Since such an extension is bound to wait until the next official update, we should not preclude any outcome of the discussion.

I would like to have a change right now, which leaves open several outcomes later. I would like to say that:

Koenig lookup will find non-functions as well. If it finds a variable, the program is ill-formed. If the primary lookup finds a variable, Koenig lookup is done. If the result contains both functions and variables, the program is ill-formed. [Note: A future standard will assign semantics to such a program.]

I myself are not comfortable with this as a long-time result, but it prepares the ground for any of the following long term solutions:

The note is there to prevent compiler vendors to put their own extensions in here.

(See also issues 113 and 143.)

Notes from 04/00 meeting:

Although many agreed that there were valid concerns motivating a desire for Koenig lookup to find non-function declarations, there was also concern that supporting this capability would be more dangerous than helpful in the absence of overload resolution for mixed function and non-function declarations.

A straw poll of the group revealed 8 in favor of Koenig lookup finding functions and function templates only, while 3 supported the broader result.

Notes from the 10/01 meeting:

There was unanimous agreement on one less controversial point: if the normal lookup of the identifier finds a non-function, argument-dependent lookup should not be done.

On the larger issue, the primary point of consensus is that making this change is an extension, and therefore it should wait until the point at which we are considering extensions (which could be very soon). There was also consensus on the fact that the standard as it stands is not clear: some introductory text suggests that argument-dependent lookup finds only functions, but the more detailed text that describes the lookup does not have any such restriction.

It was also noted that some existing implementations (e.g., g++) do find some non-functions in some cases.

The issue at this point is whether we should (1) make a small change to make the standard clear (presumably in the direction of not finding the non-functions in the lookup), and revisit the issue later as an extension, or (2) leave the standard alone for now and make any changes only as part of considering the extension. A straw vote favored option (1) by a strong majority.

Additional Notes (September, 2006):

Recent discussion of this issue has emphasized the following points:

  1. The concept of finding function pointers and function objects as part of argument-dependent lookup is not currently under active discussion in the Evolution Working Group.

  2. The major area of concern with argument-dependent lookup is finding functions in unintended namespaces. There are current proposals to deal with this concern either by changing the definition of “associated namespace” so that fewer namespaces are considered or to provide a mechanism for enabling or disabling ADL altogether. Although this concern is conceptually distinct from the question of whether ADL finds function pointers and function objects, it is related in the sense that the current rules are perceived as finding too many functions (because of searching too many namespaces), and allowing function pointers and function objects would also increase the number of entities found by ADL.

  3. Any expansion of ADL to include function pointers and function objects must necessarily update the overloading rules to specify how they interact with functions and function templates in the overload set. Current implementation experience (g++) is not helpful in making this decision because, although it performs a uniform lookup and finds non-function entities, it diagnoses an error in overload resolution if non-function entities are in the overload set.

  4. There is a possible problem if types are found by ADL: it is not clear that overloading between callable entities (functions, function templates, function pointers, and function objects) and types (where the postfix syntax means a cast or construction of a temporary) is reasonable or useful.

James Widman:

There is a larger debate here about whether ADL should find object names; the proposed wording below is only intended to answer the request for wording to clarify the status quo (option 1 above) and not to suggest the outcome of the larger debate.

Proposed Resolution (October, 2006):

  1. Replace the normative text in 3.4.2  basic.lookup.argdep paragraph 3 with the following (leaving the text of the note and example unchanged):

    Let X be the lookup set produced by unqualified lookup (3.4.1  basic.lookup.unqual) and let Y be the lookup set produced by argument dependent lookup (defined as follows). If X contains

    • a declaration of a class member, or
    • a block-scope function declaration that is not a using-declaration, or
    • a declaration that is neither a function nor a function template

    then Y is empty. Otherwise Y is the set of declarations found in the namespaces associated with the argument types as described below. The set of declarations found by the lookup of the name is the union of X and Y.

  2. Change 3.4.1  basic.lookup.unqual paragraph 4 as indicated:

    When considering an associated namespace, the lookup is the same as the lookup performed when the associated namespace is used as a qualifier (3.4.3.2  namespace.qual) except that:

    • Any using-directives in the associated namespace are ignored.
    • Any namespace-scope friend functions or friend function templates declared in associated classes are visible within their respective namespaces even if they are not visible during an ordinary lookup (11.4  class.friend).
    • All names except those of (possibly overloaded) functions and function templates are ignored.




403. Reference to a type as a template-id

Section: 3.4.2  basic.lookup.argdep     Status: WP     Submitter: John Spicer     Date: 18 Sep 2003

[Voted into WP at March 2004 meeting.]

Spun off from issue 384.

3.4.2  basic.lookup.argdep says:

If T is a template-id, its associated namespaces and classes are the namespace in which the template is defined; for member templates, the member template's class; the namespaces and classes associated with the types of the template arguments provided for template type parameters (excluding template template parameters); the namespaces in which any template template arguments are defined; and the classes in which any member templates used as template template arguments are defined. [Note: non-type template arguments do not contribute to the set of associated namespaces. ]
There is a problem with the term "is a template-id". template-id is a syntactic construct and you can't really talk about a type being a template-id. Presumably, this is intended to mean "If T is the type of a class template specialization ...".

Proposed Resolution (October 2003):

In 3.4.2  basic.lookup.argdep, paragraph 2, bullet 8, replace

If T is a template-id ...
with
If T is a class template specialization ...




557. Does argument-dependent lookup cause template instantiation?

Section: 3.4.2  basic.lookup.argdep     Status: WP     Submitter: Mike Miller     Date: 8 February 2006

[Voted into WP at the October, 2006 meeting.]

One might assume from 14.7.1  temp.inst paragraph 1 that argument-dependent lookup would require instantiation of any class template specializations used in argument types:

Unless a class template specialization has been explicitly instantiated (14.7.2  temp.explicit) or explicitly specialized (14.7.3  temp.expl.spec), the class template specialization is implicitly instantiated when the specialization is referenced in a context that requires a completely-defined object type or when the completeness of the class type affects the semantics of the program.

A complete class type is required to determine the associated classes and namespaces for the argument type (to determine the class's bases) and to determine the friend functions declared by the class, so the completeness of the class type certainly “affects the semantics of the program.”

This conclusion is reinforced by the second bullet of 3.4.2  basic.lookup.argdep paragraph 2:

A class template specialization is a class type, so the second bullet would appear to apply, requiring the specialization to be instantiated in order to determine its base classes.

However, bullet 8 of that paragraph deals explicitly with class template specializations:

Note that the class template specialization itself is not listed as an associated class, unlike other class types, and there is no mention of base classes. If bullet 8 were intended as a supplement to the treatment of class types in bullet 2, one would expect phrasing along the lines of, “In addition to the associated namespaces and classes for all class types...” or some such; instead, bullet 8 reads like a self-contained and complete specification.

If argument-dependent lookup does not cause implicit instantiation, however, examples like the following fail:

    template <typename T> class C {
        friend void f(C<T>*) { }
    };
    void g(C<int>* p) {
        f(p);    // found by ADL??
    }

Implementations differ in whether this example works or not.

Proposed resolution (April, 2006):

  1. Change bullet 2 of 3.4.2  basic.lookup.argdep paragraph 2 as indicated:

  2. Delete bullet 8 of 3.4.2  basic.lookup.argdep paragraph 2:




298. T::x when T is cv-qualified

Section: 3.4.3.1  class.qual     Status: WP     Submitter: Steve Adamczyk     Date: 7 Jul 2001

[Voted into WP at April 2003 meeting.]

Can a typedef T to a cv-qualified class type be used in a qualified name T::x?

    struct A { static int i; };
    typedef const A CA;
    int main () {
      CA::i = 0;  // Okay?
    }

Suggested answer: Yes. All the compilers I tried accept the test case.

Proposed resolution (10/01):

In 3.4.3.1  class.qual paragraph 1 add the indicated text:

If the nested-name-specifier of a qualified-id nominates a class, the name specified after the nested-name-specifier is looked up in the scope of the class (10.2  class.member.lookup), except for the cases listed below. The name shall represent one or more members of that class or of one of its base classes (clause 10  class.derived). If the class-or-namespace-name of the nested-name-specifier names a cv-qualified class type, it nominates the underlying class (the cv-qualifiers are ignored).

Notes from 4/02 meeting:

There is a problem in that class-or-namespace-name does not include typedef names for cv-qualified class types. See 7.1.3  dcl.typedef paragraph 4:

Argument and text removed from proposed resolution (October 2002):

7.1.3  dcl.typedef paragraph 5:

Here's a good question: in this example, should X be used as a name-for-linkage-purposes (FLP name)?

  typedef class { } const X;

Because a type-qualifier is parsed as a decl-specifier, it isn't possible to declare cv-qualified and cv-unqualified typedefs for a type in a single declaration. Also, of course, there's no way to declare a typedef for the cv-unqualified version of a type for which only a cv-qualified version has a name. So, in the above example, if X isn't used as the FLP name, then there can be no FLP name. Also note that a FLP name usually represents a parameter type, where top-level cv-qualifiers are usually irrelevant anyway.

Data points: for the above example, Microsoft uses X as the FLP name; GNU and EDG do not.

My recommendation: for consistency with the direction we're going on this issue, for simplicity of description (e.g., "the first class-name declared by the declaration"), and for (very slightly) increased utility, I think Microsoft has this right.

If the typedef declaration defines an unnamed class type (or enum type), the first typedef-name declared by the declaration to be have that class type (or enum type) or a cv-qualified version thereof is used to denote the class type (or enum type) for linkage purposes only (3.5  basic.link). [Example: ...

Proposed resolution (October 2002):

3.4.4  basic.lookup.elab paragraphs 2 and 3:

This sentence is deleted twice:

... If this name lookup finds a typedef-name, the elaborated-type-specifier is ill-formed. ...

Note that the above changes are included in N1376 as part of the resolution of issue 245.

5.1  expr.prim paragraph 7:

This is only a note, and it is at least incomplete (and quite possibly inaccurate), despite (or because of) its complexity. I propose to delete it.

... [Note: a typedef-name that names a class is a class-name (9.1  class.name). Except as the identifier in the declarator for a constructor or destructor definition outside of a class member-specification (12.1  class.ctor, 12.4  class.dtor), a typedef-name that names a class may be used in a qualified-id to refer to a constructor or destructor. ]

7.1.3  dcl.typedef paragraph 4:

My first choice would have been to make this the primary statement about the equivalence of typedef-name and class-name, since the equivalence comes about as a result of a typedef declaration. Unfortunately, references to class-name point to 9.1  class.name, so it would seem that the primary statement should be there instead. To avoid the possiblity of conflicts in the future, I propose to make this a note.

[Note: A typedef-name that names a class type, or a cv-qualified version thereof, is also a class-name (9.1  class.name). If a typedef-name is used following the class-key in an elaborated-type-specifier (7.1.5.3  dcl.type.elab), or in the class-head of a class declaration (9  class), or is used as the identifier in the declarator for a constructor or destructor declaration (12.1  class.ctor, 12.4  class.dtor), to identify the subject of an elaborated-type-specifier (7.1.5.3  dcl.type.elab), class declaration (clause 9  class), constructor declaration (12.1  class.ctor), or destructor declaration (12.4  class.dtor), the program is ill-formed. ] [Example: ...

7.1.5.3  dcl.type.elab paragraph 2:

This is the only remaining (normative) statement that a typedef-name can't be used in an elaborated-type-specifier. The reference to template type-parameter is deleted by the resolution of issue 283.

... If the identifier resolves to a typedef-name or a template type-parameter, the elaborated-type-specifier is ill-formed. [Note: ...

dcl.decl grammar rule declarator-id:

When I looked carefully into the statement of the rule prohibiting a typedef-name in a constructor declaration, it appeared to me that this grammar rule (inadvertently?) allows something that's always forbidden semantically.

9.1  class.name paragraph 5:

Unlike the prohibitions against appearing in an elaborated-type-specifier or constructor or destructor declarator, each of which was expressed more than once, the prohibition against a typedef-name appearing in a class-head was previously stated only in 7.1.3  dcl.typedef. It seems to me that that prohibition belongs here instead. Also, it seems to me important to clarify that a typedef-name that is a class-name is still a typedef-name. Otherwise, the various prohibitions can be argued around easily, if perversely ("But that isn't a typedef-name, it's a class-name; it says so right there in 9.1  class.name.")

A typedef-name (7.1.3  dcl.typedef) that names a class type or a cv-qualified version thereof is also a class-name, but shall not be used in an elaborated-type-specifier; see also 7.1.3  dcl.typedef. as the identifier in a class-head.

12.1  class.ctor paragraph 3:

The new nonterminal references are needed to really nail down what we're talking about here. Otherwise, I'm just eliminating redundancy. (A typedef-name that doesn't name a class type is no more valid here than one that does.)

A typedef-name that names a class is a class-name (7.1.3  dcl.typedef); however, a A typedef-name that names a class shall not be used as the identifier class-name in the declarator declarator-id for a constructor declaration.

12.4  class.dtor paragraph 1:

The same comments apply here as to 12.1  class.ctor.

... A typedef-name that names a class is a class-name (7.1.3); however, a A typedef-name that names a class shall not be used as the identifier class-name following the ~ in the declarator for a destructor declaration.



318. struct A::A should not name the constructor of A

Section: 3.4.3.1  class.qual     Status: WP     Submitter: John Spicer     Date: 18 Oct 2001

[Voted into WP at April 2003 meeting.]

A use of an injected-class-name in an elaborated-type-specifier should not name the constructor of the class, but rather the class itself, because in that context we know that we're looking for a type. See issue 147.

Proposed Resolution (revised October 2002):

This clarifies the changes made in the TC for issue 147.

In 3.4.3.1  class.qual paragraph 1a replace:

If the nested-name-specifier nominates a class C, and the name specified after the nested-name-specifier, when looked up in C, is the injected class name of C (clause 9  class), the name is instead considered to name the constructor of class C.

with

In a lookup in which the constructor is an acceptable lookup result, if the nested-name-specifier nominates a class C and the name specified after the nested-name-specifier, when looked up in C, is the injected class name of C (clause 9  class), the name is instead considered to name the constructor of class C. [Note: For example, the constructor is not an acceptable lookup result in an elaborated type specifier so the constructor would not be used in place of the injected class name.]

Note that issue 263 updates a part of the same paragraph.

Append to the example:

  struct A::A a2;  // object of type A



400. Using-declarations and the "struct hack"

Section: 3.4.3.2  namespace.qual     Status: WP     Submitter: Mark Mitchell     Date: 22 Jan 2003

[Voted into WP at March 2004 meeting.]

Consider this code:

  struct A { int i; struct i {}; };
  struct B { int i; struct i {}; };
  struct D : public A, public B { using A::i; void f (); };
  void D::f () { struct i x; }

I can't find anything in the standard that says definitively what this means. 7.3.3  namespace.udecl says that a using-declaration shall name "a member of a base class" -- but here we have two members, the data member A::i and the class A::i.

Personally, I'd find it more attractive if this code did not work. I'd like "using A::i" to mean "lookup A::i in the usual way and bind B::i to that", which would mean that while "i = 3" would be valid in D::f, "struct i x" would not be. However, if there were no A::i data member, then "A::i" would find the struct and the code in D::f would be valid.

John Spicer: I agree with you, but unfortunately the standard committee did not.

I remembered that this was discussed by the committee and that a resolution was adopted that was different than what I hoped for, but I had a hard time finding definitive wording in the standard.

I went back though my records and found the paper that proposed a resolution and the associated committee motion that adopted the proposed resolution The paper is N0905, and "option 1" from that paper was adopted at the Stockholm meeting in July of 1996. The resolution is that "using A::i" brings in everything named i from A.

3.4.3.2  namespace.qual paragraph 2 was modified to implement this resolution, but interestingly that only covers the namespace case and not the class case. I think the class case was overlooked when the wording was drafted. A core issue should be opened to make sure the class case is handled properly.

Notes from April 2003 meeting:

This is related to issue 11. 7.3.3  namespace.udecl paragraph 10 has an example for namespaces.

Proposed resolution (October 2003):

Add a bullet to the end of 3.4.3.1  class.qual paragraph 1:

Change the beginning of 7.3.3  namespace.udecl paragraph 4 from

A using-declaration used as a member-declaration shall refer to a member of a base class of the class being defined, shall refer to a member of an anonymous union that is a member of a base class of the class being defined, or shall refer to an enumerator for an enumeration type that is a member of a base class of the class being defined.

to

In a using-declaration used as a member-declaration, the nested-name-specifier shall name a base class of the class being defined. Such a using-declaration introduces the set of declarations found by member name lookup (10.2  class.member.lookup, 3.4.3.1  class.qual).



245. Name lookup in elaborated-type-specifiers

Section: 3.4.4  basic.lookup.elab     Status: WP     Submitter: Jack Rouse     Date: 14 Sep 2000

[Voted into WP at April 2003 meeting.]

I have some concerns with the description of name lookup for elaborated type specifiers in 3.4.4  basic.lookup.elab:

  1. Paragraph 2 has some parodoxical statements concerning looking up names that are simple identifers:

    If the elaborated-type-specifier refers to an enum-name and this lookup does not find a previously declared enum-name, the elaborated-type-specifier is ill-formed. If the elaborated-type-specifier refers to an [sic] class-name and this lookup does not find a previously declared class-name... the elaborated-type-specifier is a declaration that introduces the class-name as described in 3.3.1  basic.scope.pdecl."

    It is not clear how an elaborated-type-specifier can refer to an enum-name or class-name given that the lookup does not find such a name and that class-name and enum-name are not part of the syntax of an elaborated-type-specifier.

  2. The second sentence quoted above seems to suggest that the name found will not be used if it is not a class name. typedef-name names are ill-formed due to the sentence preceding the quote. If lookup finds, for instance, an enum-name then a new declaration will be created. This differs from C, and from the enum case, and can have surprising effects:

        struct S {
           enum E {
               one = 1
           };
           class E* p;     // declares a global class E?
        };
    

    Was this really the intent? If this is the case then some more work is needed on 3.4.4  basic.lookup.elab. Note that the section does not make finding a type template formal ill-formed, as is done in 7.1.5.3  dcl.type.elab. I don't see anything that makes a type template formal name a class-name. So the example in 7.1.5.3  dcl.type.elab of friend class T; where T is a template type formal would no longer be ill-formed with this interpretation because it would declare a new class T.

(See also issue 254.)

Notes from the 4/02 meeting:

This will be consolidated with the changes for issue 254. See also issue 298.

Proposed resolution (October 2002):

As given in N1376=02-0034. Note that the inserts and strikeouts in that document do not display correctly in all browsers; <del> --> <strike> and <ins> --> <b>, and the similar changes for the closing delimiters, seem to do the trick.




254. Definitional problems with elaborated-type-specifiers

Section: 3.4.4  basic.lookup.elab     Status: WP     Submitter: Clark Nelson     Date: 26 Oct 2000

[Voted into WP at April 2003 meeting.]

  1. The text in 3.4.4  basic.lookup.elab paragraph 2 twice refers to the possibility that an elaborated-type-specifier might have the form

            class-key identifier ;
    

    However, the grammar for elaborated-type-specifier does not include a semicolon.

  2. In both 3.4.4  basic.lookup.elab and 7.1.5.3  dcl.type.elab, the text asserts that an elaborated-type-specifier that refers to a typedef-name is ill-formed. However, it is permissible for the form of elaborated-type-specifier that begins with typename to refer to a typedef-name.

    This problem is the result of adding the typename form to the elaborated-type-name grammar without changing the verbiage correspondingly. It could be fixed either by updating the verbiage or by moving the typename syntax into its own production and referring to both nonterminals when needed.

(See also issue 180. If this issue is resolved in favor of a separate nonterminal in the grammar for the typename forms, the wording in that issue's resolution must be changed accordingly.)

Notes from 04/01 meeting:

The consensus was in favor of moving the typename forms out of the elaborated-type-specifier grammar.

Notes from the 4/02 meeting:

This will be consolidated with the changes for issue 245.

Proposed resolution (October 2002):

As given in N1376=02-0034.




305. Name lookup in destructor call

Section: 3.4.5  basic.lookup.classref     Status: WP     Submitter: Mark Mitchell     Date: 19 May 2001

[Voted into WP at the October, 2006 meeting.]

I believe this program is invalid:

    struct A {
    };

    struct C {
      struct A {};
      void f ();
    };

    void C::f () {
      ::A *a;
      a->~A ();
    }
The problem is that 3.4.5  basic.lookup.classref says that you have to look up A in both the context of the pointed-to-type (i.e., ::A), and in the context of the postfix-expression (i.e., the body of C::f), and that if the name is found in both places it must name the same type in both places.

The EDG front end does not issue an error about this program, though.

Am I reading the standardese incorrectly?

John Spicer: I think you are reading it correctly. I think I've been hoping that this would get changed. Unlike other dual lookup contexts, this is one in which the compiler already knows the right answer (the type must match that of the left hand of the -> operator). So I think that if either of the types found matches the one required, it should be sufficient. You can't say a->~::A(), which means you are forced to say a->::A::~A(), which disables the virtual mechanism. So you would have to do something like create a local typedef for the desired type.

See also issues 244, 399, and 466.

Proposed resolution (April, 2006):

  1. Remove the indicated text from 3.4.5  basic.lookup.classref paragraph 2:

    If the id-expression in a class member access (5.2.5  expr.ref) is an unqualified-id, and the type of the object expression is of a class type C (or of pointer to a class type C), the unqualified-id is looked up in the scope of class C...
  2. Change 3.4.5  basic.lookup.classref paragraph 3 as indicated:

    If the unqualified-id is ~type-name, the type-name is looked up in the context of the entire postfix-expression. and If the type T of the object expression is of a class type C (or of pointer to a class type C), the type-name is also looked up in the context of the entire postfix-expression and in the scope of class C. The type-name shall refer to a class-name. If type-name is found in both contexts, the name shall refer to the same class type. If the type of the object expression is of scalar type, the type-name is looked up in the scope of the complete postfix-expression. At least one of the lookups shall find a name that refers to (possibly cv-qualified) T. [Example:
        struct A { };
    
        struct B {
          struct A { };
          void f(::A* a);
        };
    
        void B::f(::A* a) {
          a->~A();  // OK, lookup in *a finds the injected-class-name
        }
    
    end example]

[Note: this change also resolves issue 414.]




381. Incorrect example of base class member lookup

Section: 3.4.5  basic.lookup.classref     Status: WP     Submitter: Steve Adamczyk     Date: 8 Nov 2002

[Voted into WP at October 2004 meeting.]

The example in 3.4.5  basic.lookup.classref paragraph 4 is wrong (see 11.2  class.access.base paragraph 5; the cast to the naming class can't be done) and needs to be corrected. This was noted when the final version of the algorithm for issue 39 was checked against it.

Proposed Resolution (October 2003):

Remove the entire note at the end of 3.4.5  basic.lookup.classref paragraph 4, including the entire example.




414. Multiple types found on destructor lookup

Section: 3.4.5  basic.lookup.classref     Status: WP     Submitter: John Spicer     Date: 1 May 2003

[Voted into WP at the October, 2006 meeting.]

By 3.4.5  basic.lookup.classref paragraph 3, the following is ill-formed because the two lookups of the destructor name (in the scope of the class of the object and in the surrounding context) find different Xs:

  struct X {};
  int main() {
    X x;
    struct X {};
    x.~X();  // Error?
  }

This is silly, because the compiler knows what the type has to be, and one of the things found matches that. The lookup should require only that one of the lookups finds the required class type.

Proposed resolution (April, 2005):

This issue is resolved by the resolution of issue 305.




216. Linkage of nameless class-scope enumeration types

Section: 3.5  basic.link     Status: WP     Submitter: Daveed Vandevoorde     Date: 13 Mar 2000

[Moved to DR at 10/01 meeting.]

3.5  basic.link paragraph 4 says (among other things):
A name having namespace scope has external linkage if it is the name of
That prohibits for example:
    typedef enum { e1 } *PE;
    void f(PE) {}  // Cannot declare a function (with linkage) using a 
		   // type with no linkage.

However, the same prohibition was not made for class scope types. Indeed, 3.5  basic.link paragraph 5 says:

In addition, a member function, static data member, class or enumeration of class scope has external linkage if the name of the class has external linkage.

That allows for:

    struct S {
       typedef enum { e1 } *MPE;
       void mf(MPE) {}
    };

My guess is that this is an unintentional consequence of 3.5  basic.link paragraph 5, but I would like confirmation on that.

Proposed resolution:

Change text in 3.5  basic.link paragraph 5 from:

In addition, a member function, static data member, class or enumeration of class scope has external linkage if the name of the class has external linkage.
to:
In addition, a member function, a static data member, a named class or enumeration of class scope, or an unnamed class or enumeration defined in a class-scope typedef declaration such that the class or enumeration has the typedef name for linkage purposes (7.1.3  dcl.typedef), has external linkage if the name of the class has external linkage.



319. Use of names without linkage in declaring entities with linkage

Section: 3.5  basic.link     Status: WP     Submitter: Clark Nelson     Date: 29 Oct 2001

[Voted into WP at October 2004 meeting.]

According to 3.5  basic.link paragraph 8, "A name with no linkage ... shall not be used to declare an entity with linkage." This would appear to rule out code such as:

  typedef struct {
    int i;
  } *PT;
  extern "C" void f(PT);
[likewise]
  static enum { a } e;
which seems rather harmless to me.

See issue 132, which dealt with a closely related issue.

Andrei Iltchenko submitted the same issue via comp.std.c++ on 17 Dec 2001:

Paragraph 8 of Section 3.5  basic.link contains the following sentences: "A name with no linkage shall not be used to declare an entity with linkage. If a declaration uses a typedef name, it is the linkage of the type name to which the typedef refers that is considered."

The problem with this wording is that it doesn't cover cases where the type to which a typedef-name refers has no name. As a result it's not clear whether, for example, the following program is well-formed:

#include <vector>

int  main()
{
   enum  {   sz = 6u   };
   typedef int  (* aptr_type)[sz];
   typedef struct  data  {
      int   i,  j;
   }  * elem_type;
   std::vector<aptr_type>   vec1;
   std::vector<elem_type>   vec2;
}

Suggested resolution:

My feeling is that the rules for whether or not a typedef-name used in a declaration shall be treated as having or not having linkage ought to be modelled after those for dependent types, which are explained in 14.6.2.1  temp.dep.type.

Add the following text at the end of Paragraph 8 of Section 3.5  basic.link and replace the following example:

In case of the type referred to by a typedef declaration not having a name, the newly declared typedef-name has linkage if and only if its referred type comprises no names of no linkage excluding local names that are eligible for appearance in an integral constant-expression (5.19  expr.const). [Note: if the referred type contains a typedef-name that does not denote an unnamed class, the linkage of that name is established by the recursive application of this rule for the purposes of using typedef names in declarations.] [Example:
  void f()
  {
     struct A { int x; };        // no linkage
     extern A a;                 // ill-formed
     typedef A Bl
     extern B b;                 // ill-formed

     enum  {   sz = 6u   };
     typedef int  (* C)[sz];     // C has linkage because sz can
                                 // appear in a constant expression
  }
--end example.]

Additional issue (13 Jan 2002, from Andrei Iltchenko):

Paragraph 2 of Section 14.3.1  temp.arg.type is inaccurate and unnecessarily prohibits a few important cases; it says "A local type, a type with no linkage, an unnamed type or a type compounded from any of these types shall not be used as a template-argument for a template-parameter." The inaccuracy stems from the fact that it is not a type but its name that can have a linkage.

For example based on the current wording of 14.3.1  temp.arg.type, the following example is ill-formed.

  #include <vector>
  struct  data  {
    int   i,  j;
  };
  int  main()
  {
    enum  {   sz = 6u   };
    std::vector<int(*)[sz]>   vec1; // The types 'int(*)[sz]' and 'data*'
    std::vector<data*>        vec2; // have no names and are thus illegal
                                    // as template type arguments.
  }

Suggested resolution:

Replace the whole second paragraph of Section 14.3.1  temp.arg.type with the following wording:

A type whose name does not have a linkage or a type compounded from any such type shall not be used as a template-argument for a template-parameter. In case of a type T used as a template type argument not having a name, T constitutes a valid template type argument if and only if the name of an invented typedef declaration referring to T would have linkage; see 3.5. [Example:
  template <class T> class X { /* ... */ };
  void f()
  {
    struct S { /* ... */ };
    enum  {   sz = 6u   };

    X<S> x3;                     // error: a type name with no linkage
                                 // used as template-argument
    X<S*> x4;                    // error: pointer to a type name with
                                 // no linkage used as template-argument
    X<int(*)[sz]> x5;            // OK: since the name of typedef int
                                 // (*pname)[sz] would have linkage
  }
--end example] [Note: a template type argument may be an incomplete type (3.9  basic.types).]

Proposed resolution:

This is resolved by the changes for issue 389. The present issue was moved back to Review status in February 2004 because 389 was moved back to Review.




389. Unnamed types in entities with linkage

Section: 3.5  basic.link     Status: WP     Submitter: Daveed Vandevoorde     Date: 31 Oct 2002

[Voted into WP at October 2004 meeting.]

3.5  basic.link paragraph 8 says (among other things):

A name with no linkage (notably, the name of a class or enumeration declared in a local scope (3.3.2  basic.scope.local)) shall not be used to declare an entity with linkage. If a declaration uses a typedef name, it is the linkage of the type name to which the typedef refers that is considered.

I would expect this to catch situations such as the following:

  // File 1:
  typedef struct {} *UP;
  void f(UP) {}

  // File 2:
  typedef struct {} *UP; // Or: typedef struct {} U, *UP;
  void f(UP);

The problem here is that most implementations must generate the same mangled name for "f" in two translation units. The quote from the standard above isn't quite clear, unfortunately: There is no type name to which the typedef refers.

A related situation is the following:

  enum { no, yes } answer;
The variable "answer" is declared as having external linkage, but it is declared with an unnamed type. Section 3.5  basic.link talks about the linkage of names, however, and does therefore not prohibit this. There is no implementation issue for most compilers because they do not ordinarily mangle variable names, but I believe the intent was to allow that implementation technique.

Finally, these problems are much less relevant when declaring names with internal linkage. For example, I would expect there to be few problems with:

  typedef struct {} *UP;
  static void g(UP);

I recently tried to interpret 3.5  basic.link paragraph 8 with the assumption that types with no names have no linkage. Surprisingly, this resulted in many diagnostics on variable declarations (mostly like "answer" above).

I'm pretty sure the standard needs clarifying words in this matter, but which way should it go?

See also issue 319.

Notes from April 2003 meeting:

There was agreement that this check is not needed for variables and functions with extern "C" linkage, and a change there is desirable to allow use of legacy C headers. The check is also not needed for entities with internal linkage, but there was no strong sentiment for changing that case.

We also considered relaxing this requirement for extern "C++" variables but decided that we did not want to change that case.

We noted that if extern "C" functions are allowed an additional check is needed when such functions are used as arguments in calls of function templates. Deduction will put the type of the extern "C" function into the type of the template instance, i.e., there would be a need to mangle the name of an unnamed type. To plug that hole we need an additional requirement on the template created in such a case.

Proposed resolution (April 2003, revised slightly October 2003 and March 2004):

In 3.5  basic.link paragraph 8, change

A name with no linkage (notably, the name of a class or enumeration declared in a local scope (3.3.2  basic.scope.local)) shall not be used to declare an entity with linkage. If a declaration uses a typedef name, it is the linkage of the type name to which the typedef refers that is considered.

to

A type is said to have linkage if and only if A type without linkage shall not be used as the type of a variable or function with linkage, unless the variable or function has extern "C" linkage (7.5  dcl.link). [Note: in other words, a type without linkage contains a class or enumeration that cannot be named outside of its translation unit. An entity with external linkage declared using such a type could not correspond to any other entity in another translation unit of the program and is thus not permitted. Also note that classes with linkage may contain members whose types do not have linkage, and that typedef names are ignored in the determination of whether a type has linkage.]

Change 14.3.1  temp.arg.type paragraph 2 from (note: this is the wording as updated by issue 62)

The following types shall not be used as a template-argument for a template type-parameter:

to

A type without linkage (3.5  basic.link) shall not be used as a template-argument for a template type-parameter.

Once this issue is ready, issue 319 should be moved back to ready as well.




474. Block-scope extern declarations in namespace members

Section: 3.5  basic.link     Status: WP     Submitter: Daveed Vandevoorde     Date: 23 Jul 2004

[Voted into WP at October 2005 meeting.]

Consider the following bit of code:

    namespace N {
      struct S {
        void f();
      };
    }
    using namespace N;
    void S::f() {
      extern void g();  // ::g or N::g?
    }

In 3.5  basic.link paragraph 7 the Standard says (among other things),

When a block scope declaration of an entity with linkage is not found to refer to some other declaration, then that entity is a member of the innermost enclosing namespace.

The question then is whether N is an “enclosing namespace” for the local declaration of g()?

Proposed resolution (October 2004):

Add the following text as a new paragraph at the end of 7.3.1  namespace.def:

The enclosing namespaces of a declaration are those namespaces in which the declaration lexically appears, except for a redeclaration of a namespace member outside its original namespace (e.g., a definition as specified in 7.3.1.2  namespace.memdef). Such a redeclaration has the same enclosing namespaces as the original declaration. [Example:
  namespace Q {
    namespace V {
      void f(); // enclosing namespaces are the global namespace, Q, and Q::V
      class C { void m(); };
    }
    void V::f() { // enclosing namespaces are the global namespace, Q, and Q::V
      extern void h(); // ... so this declares Q::V::h
    }
    void V::C::m() { // enclosing namespaces are the global namespace, Q, and Q::V
    }
  }

end example]




270. Order of initialization of static data members of class templates

Section: 3.6.2  basic.start.init     Status: WP     Submitter: Jonathan H. Lundquist     Date: 9 Feb 2001

[Moved to DR at 4/02 meeting.]

The Standard does not appear to address how the rules for order of initialization apply to static data members of class templates.

Suggested resolution: Add the following verbiage to either 3.6.2  basic.start.init or 9.4.2  class.static.data:

Initialization of static data members of class templates shall be performed during the initialization of static data members for the first translation unit to have static initialization performed for which the template member has been instantiated. This requirement shall apply to both the static and dynamic phases of initialization.

Notes from 04/01 meeting:

Enforcing an order of initialization on static data members of class templates will result in substantial overhead on access to such variables. The problem is that the initialization be required as the result of instantiation in a function used in the initialization of a variable in another translation unit. In current systems, the order of initialization of static data data members of class templates is not predictable. The proposed resolution is to state that the order of initialization is undefined.

Proposed resolution (04/01, updated slightly 10/01):

Replace the following sentence in 3.6.2  basic.start.init paragraph 1:

Objects with static storage duration defined in namespace scope in the same translation unit and dynamically initialized shall be initialized in the order in which their definition appears in the translation unit.

with

Dynamic initialization of an object is either ordered or unordered. Explicit specializations and definitions of class template static data members have ordered initialization. Other class template static data member instances have unordered initialization. Other objects defined in namespace scope have ordered initialization. Objects defined within a single translation unit and with ordered initialization shall be initialized in the order of their definitions in the translation unit. The order of initialization is unspecified for objects with unordered initialization and for objects defined in different translation units.

Note that this wording is further updated by issue 362.

Note (07/01):

Brian McNamara argues against the proposed resolution. The following excerpt captures the central point of a long message on comp.std.c++:

I have a class for representing linked lists which looks something like
    template <class T>
    class List {
       ...  static List<T>* sentinel; ...
    };
 
    template <class T>
    List<T>* List<T>::sentinel( new List<T> ); // static member definition

The sentinel list node is used to represent "nil" (the null pointer cannot be used with my implementation, for reasons which are immaterial to this discussion). All of the List's non-static member functions and constructors depend upon the value of the sentinel. Under the proposed resolution for issue #270, Lists cannot be safely instantiated before main() begins, as the sentinel's initialization is "unordered".

(Some readers may propose that I should use the "singleton pattern" in the List class. This is undesirable, for reasons I shall describe at the end of this post at the location marked "[*]". For the moment, indulge me by assuming that "singleton" is not an adequate solution.)

Though this is a particular example from my own experience, I believe it is representative of a general class of examples. It is common to use static data members of a class to represent the "distinguished values" which are important to instances of that class. It is imperative that these values be initialized before any instances of the class are created, as the instances depend on the values.

In a comp.std.c++ posting on 28 Jul 2001, Brian McNamara proposes the following alternative resolution:

Replace the following sentence in 3.6.2  basic.start.init paragraph 1:

Objects with static storage duration defined in namespace scope in the same translation unit and dynamically initialized shall be initialized in the order in which their definition appears in the translation unit.
with
Objects with static storage duration defined in namespace scope shall be initialized in the order described below.
and then after paragraph 1, add this text:
Dynamic initialization is either ordered or quasi-ordered. Explicit specializations of class template static data members have ordered initialization. Other class template static data member instances have quasi-ordered initialization. All other objects defined in namespace scope have ordered initialization. The order of initialization is specified as follows:
along with a non-normative note along the lines of
[ Note: The intention is that translation units can each be compiled separately with no knowledge of what objects may be re-defined in other translation units. Each translation unit can contain a method which initializes all objects (both quasi-ordered and ordered) as though they were ordered. When these translation units are linked together to create an executable program, all of these objects can be initialized by simply calling the initialization methods (one from each translation unit) in any order. Quasi-ordered objects require some kind of guard to ensure that they are not initialized more than once (the first attempt to initialize such an object should succeed; any subsequent attempts should simply be ignored). ]

Erwin Unruh replies: There is a point which is not mentioned with this posting. It is the cost for implementing the scheme. It requires that each static template variable is instantiated in ALL translation units where it is used. There has to be a flag for each of these variables and this flag has to be checked in each TU where the instantiation took place.

I would reject this idea and stand with the proposed resolution of issue 270.

There just is no portable way to ensure the "right" ordering of construction.

Notes from 10/01 meeting:

The Core Working Group reaffirmed its previous decision.




441. Ordering of static reference initialization

Section: 3.6.2  basic.start.init     Status: WP     Submitter: Mike Miller     Date: 1 Dec 2003

[Voted into WP at April 2005 meeting.]

I have a couple of questions about 3.6.2  basic.start.init, "Initialization of non-local objects." I believe I recall some discussion of related topics, but I can't find anything relevant in the issues list.

The first question arose when I discovered that different implementations treat reference initialization differently. Consider, for example, the following (namespace-scope) code:

  int i;
  int& ir = i;
  int* ip = &i;
Both initializers, "i" and "&i", are constant expressions, per 5.19  expr.const paragraph 4-5 (a reference constant expression and an address constant expression, respectively). Thus, both initializations are categorized as static initialization, according to 3.6.2  basic.start.init paragraph 1:
Zero-initialization and initialization with a constant expression are collectively called static initialization; all other initialization is dynamic initialization.

However, that does not mean that both ir and ip must be initialized at the same time:

Objects of POD types (3.9) with static storage duration initialized with constant expressions (5.19) shall be initialized before any dynamic initialization takes place.

Because "int&" is not a POD type, there is no requirement that it be initialized before dynamic initialization is performed, and implementations differ in this regard. Using a function called during dynamic initialization to print the values of "ip" and "&ir", I found that g++, Sun, HP, and Intel compilers initialize ir before dynamic initialization and the Microsoft compiler does not. All initialize ip before dynamic initialization. I believe this is conforming (albeit inconvenient :-) behavior.

So, my first question is whether it is intentional that a reference of static duration, initialized with a reference constant expression, need not be initialized before dynamic initialization takes place, and if so, why?

The second question is somewhat broader. As 3.6.2  basic.start.init is currently worded, it appears that there are no requirements on when ir is initialized. In fact, there is a whole category of objects -- non-POD objects initialized with a constant expression -- for which no ordering is specified. Because they are categorized as part of "static initialization," they are not subject to the requirement that they "shall be initialized in the order in which their definition appears in the translation unit." Because they are not POD types, they are not required to be initialized before dynamic initialization occurs. Am I reading this right?

My preference would be to change 3.6.2  basic.start.init paragraph 1 so that 1) references are treated like POD objects with respect to initialization, and 2) "static initialization" applies only to POD objects and references. Here's some sample wording to illustrate:

Suggested resolution:

Objects with static storage duration (3.7.1) shall be zero-initialized (8.5) before any other initialization takes place. Initializing a reference, or an object of POD type, of static storage duration with a constant expression (5.19) is called constant initialization. Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place. [Remainder unchanged.]

Proposed Resolution:

Change 3.6.2  basic.start.init paragraph 1 as follows:

Objects with static storage duration (3.7.1) shall be zero-initialized (8.5) before any other initialization takes place. Initializing a reference, or an object of POD type, of static storage duration with a constant expression (5.19) is called constant initialization. Together, zero-initialization and constant initialization are Zero-initialization and initialization with a constant expression are collectively called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed Objects of POD types (3.9) with static storage duration initialized with constant expressions (5.19) shall be initialized before any dynamic initialization takes place.



521. Requirements for exceptions thrown by allocation functions

Section: 3.7.3.1  basic.stc.dynamic.allocation     Status: WP     Submitter: Alisdair Meredith     Date: 22 May 2005

[Voted into WP at the October, 2006 meeting.]

According to 3.7.3.1  basic.stc.dynamic.allocation paragraph 3,

Any other allocation function that fails to allocate storage shall only indicate failure by throwing an exception of class std::bad_alloc (18.4.2.1  lib.bad.alloc) or a class derived from std::bad_alloc.

Shouldn't this statement have the usual requirements for an unambiguous and accessible base class?

Proposed resolution (April, 2006):

Change the last sentence of 3.7.3.1  basic.stc.dynamic.allocation paragraph 3 as indicated:

Any other allocation function that fails to allocate storage shall only indicate failure only by throwing an exception of class std::bad_alloc (18.4.2.1  lib.bad.alloc) or a class derived from std::bad_alloc a type that would match a handler (15.3  except.handle) of type std::bad_alloc (18.4.2.1  lib.bad.alloc).



348. delete and user-written deallocation functions

Section: 3.7.3.2  basic.stc.dynamic.deallocation     Status: WP     Submitter: Ruslan Abdikeev     Date: 1 April 2002

[Voted into WP at October 2005 meeting.]

Standard is clear on behaviour of default allocation/deallocation functions. However, it is surpisingly vague on requirements to the behaviour of user-defined deallocation function and an interaction between delete-expression and deallocation function. This caused a heated argument on fido7.su.c-cpp newsgroup.

Resume:

It is not clear if user-supplied deallocation function is called from delete-expr when the operand of delete-expr is the null pointer (5.3.5  expr.delete). If it is, standard does not specify what user-supplied deallocation function shall do with the null pointer operand (18.4.1  lib.new.delete). Instead, Standard uses the term "has no effect", which meaning is too vague in context given (5.3.5  expr.delete).

Description:

Consider statements

   char* p= 0; //result of failed non-throwing ::new char[]
   ::delete[] p;
Argument passed to delete-expression is valid - it is the result of a call to the non-throwing version of ::new, which has been failed. 5.3.5  expr.delete paragraph 1 explicitly prohibit us to pass 0 without having the ::new failure.

Standard does NOT specify whether user-defined deallocation function should be called in this case, or not.

Specifically, standard says in 5.3.5  expr.delete paragraph 2:

...if the value of the operand of delete is the null pointer the operation has no effect.
Standard doesn't specify term "has no effect". It is not clear from this context, whether the called deallocation function is required to have no effect, or delete-expression shall not call the deallocation function.

Furthermore, in para 4 standard says on default deallocation function:

If the delete-expression calls the implementation deallocation function (3.7.3.2  basic.stc.dynamic.deallocation), if the operand of the delete expression is not the null pointer constant, ...
Why it is so specific on interaction of default deallocation function and delete-expr?

If "has no effect" is a requirement to the deallocation function, then it should be stated in 3.7.3.2  basic.stc.dynamic.deallocation, or in 18.4.1.1  lib.new.delete.single and 18.4.1.2  lib.new.delete.array, and it should be stated explicitly.

Furthermore, standard does NOT specify what actions shall be performed by user-supplied deallocation function if NULL is given (18.4.1.1  lib.new.delete.single paragraph 12):

Required behaviour: accept a value of ptr that is null or that was returned by an earlier call to the default operator new(std::size_t) or operator new(std::size_t, const std::nothrow_t&).

The same corresponds to ::delete[] case.

Expected solution:

  1. Make it clear that delete-expr will not call deallocation function if null pointer is given (in 5.3.5  expr.delete).
  2. Specify what user deallocation function shall do when null is given (either in 3.7.3.2  basic.stc.dynamic.deallocation, or in 18.4.1.1  lib.new.delete.single, and 18.4.1.2  lib.new.delete.array).

Notes from October 2002 meeting:

We believe that study of 18.4.1.1  lib.new.delete.single paragraphs 12 and 13, 18.4.1.2  lib.new.delete.array paragraphs 11 and 12, and 3.7.3.2  basic.stc.dynamic.deallocation paragraph 3 shows that the system-provided operator delete functions must accept a null pointer and ignore it. Those sections also show that a user-written replacement for the system-provided operator delete functions must accept a null pointer. There is no requirement that such functions ignore a null pointer, which is okay -- perhaps the reason for replacing the system-provided functions is to do something special with null pointer values (e.g., log such calls and return).

We believe that the standard should not require an implementation to call a delete function with a null pointer, but it must allow that. For the system-provided delete functions or replacements thereof, the standard already makes it clear that the delete function must accept a null pointer. For class-specific delete functions, we believe the standard should require that such functions accept a null pointer, though it should not mandate what they do with null pointers.

5.3.5  expr.delete needs to be updated to say that it is unspecified whether or not the operator delete function is called with a null pointer, and 3.7.3.2  basic.stc.dynamic.deallocation needs to be updated to say that any deallocation function must accept a null pointer.

Proposed resolution (October, 2004):

  1. Change 5.3.5  expr.delete paragraph 2 as indicated:

    If the operand has a class type, the operand is converted to a pointer type by calling the above-mentioned conversion function, and the converted operand is used in place of the original operand for the remainder of this section. In either alternative, if the value of the operand of delete is the null pointer the operation has no effect may be a null pointer value. If it is not a null pointer value, in In the first alternative (delete object), the value of the operand of delete shall be a pointer to a non-array object or a pointer to a sub-object (1.8  intro.object) representing a base class of such an object (clause 10  class.derived)...
  2. Change 5.3.5  expr.delete paragraph 4 as follows (note that the old wording reflects the changes proposed by issue 442:

    The cast-expression in a delete-expression shall be evaluated exactly once. If the delete-expression calls the implementation deallocation function (3.7.3.2  basic.stc.dynamic.deallocation), and if the value of the operand of the delete expression is not a null pointer, the deallocation function will deallocate the storage referenced by the pointer thus rendering the pointer invalid. [Note: the value of a pointer that refers to deallocated storage is indeterminate. —end note]

  3. Change 5.3.5  expr.delete paragraphs 6-7 as follows:

    The If the value of the operand of the delete-expression is not a null pointer value, the delete-expression will invoke the destructor (if any) for the object or the elements of the array being deleted. In the case of an array, the elements will be destroyed in order of decreasing address (that is, in reverse order of the completion of their constructor; see 12.6.2  class.base.init).

    The If the value of the operand of the delete-expression is not a null pointer value, the delete-expression will call a deallocation function (3.7.3.2  basic.stc.dynamic.deallocation). Otherwise, it is unspecified whether the deallocation function will be called. [Note: The deallocation function is called regardless of whether the destructor for the object or some element of the array throws an exception. —end note]

  4. Change 3.7.3.2  basic.stc.dynamic.deallocation paragraph 3 as indicated:

    The value of the first argument supplied to one of the a deallocation functions provided in the standard library may be a null pointer value; if so, and if the deallocation function is one supplied in the standard library, the call to the deallocation function has no effect. Otherwise, the value supplied to operator delete(void*) in the standard library shall be one of the values returned by a previous invocation of either operator new(std::size_t) or operator new(std::size_t, const std::nothrow_t&) in the standard library, and the value supplied to operator delete[](void*) in the standard library shall be one of the values returned by a previous invocation of either operator new[](std::size_t) or operator new[](std::size_t, const std::nothrow_t&) in the standard library.

[Note: this resolution also resolves issue 442.]




119. Object lifetime and aggregate initialization

Section: 3.8  basic.life     Status: WP     Submitter: Jack Rouse     Date: 20 May 1999

[Moved to DR at 4/02 meeting.]

Jack Rouse: 3.8  basic.life paragraph 1 includes:

The lifetime of an object is a runtime property of the object. The lifetime of an object of type T begins when:
Consider the code:
    struct B {
        B( int = 0 );
        ~B();
    };

    struct S {
        B b1;
    };

    int main()
    {
        S s = { 1 };
        return 0;
    }
In the code above, class S does have a non-trivial constructor, the default constructor generated by the compiler. According the text above, the lifetime of the auto s would never begin because a constructor for S is never called. I think the second case in the text needs to include aggregate initialization.

Mike Miller: I see a couple of ways of fixing the problem. One way would be to change "the constructor call has completed" to "the object's initialization is complete."

Another would be to add following "a class type with a non-trivial constructor" the phrase "that is not initialized with the brace notation (8.5.1  dcl.init.aggr )."

The first formulation treats aggregate initialization like a constructor call; even POD-type members of an aggregate could not be accessed before the aggregate initialization completed. The second is less restrictive; the POD-type members of the aggregate would be usable before the initialization, and the members with non-trivial constructors (the only way an aggregate can acquire a non-trivial constructor) would be protected by recursive application of the lifetime rule.

Proposed resolution (04/01):

In 3.8  basic.life paragraph 1, change

If T is a class type with a non-trivial constructor (12.1  class.ctor), the constructor call has completed.

to

If T is a class type with a non-trivial constructor (12.1  class.ctor), the initialization is complete. [Note: the initialization can be performed by a constructor call or, in the case of an aggregate with an implicitly-declared non-trivial default constructor, an aggregate initialization (8.5.1  dcl.init.aggr).]



274. Cv-qualification and char-alias access to out-of-lifetime objects

Section: 3.8  basic.life     Status: WP     Submitter: Mike Miller     Date: 14 Mar 2001

[Voted into WP at April 2003 meeting.]

The wording in 3.8  basic.life paragraph 6 allows an lvalue designating an out-of-lifetime object to be used as the operand of a static_cast only if the conversion is ultimately to "char&" or "unsigned char&". This description excludes the possibility of using a cv-qualified version of these types for no apparent reason.

Notes on 04/01 meeting:

The wording should be changed to allow cv-qualified char types.

Proposed resolution (04/01):

In 3.8  basic.life paragraph 6 change the third bullet:

to read:




404. Unclear reference to construction with non-trivial constructor

Section: 3.8  basic.life     Status: WP     Submitter: Mike Miller     Date: 8 Apr 2003

[Voted into WP at March 2004 meeting.]

3.8  basic.life paragraph 1 second bullet says:

if T is a class type with a non-trivial constructor (12.1), the constructor call has completed.

This is confusing; what was intended is probably something like

if T is a class type and the constructor invoked to create the object is non-trivial (12.1), the constructor call has completed.

Proposed Resolution (October 2003):

As given above.




158. Aliasing and qualification conversions

Section: 3.10  basic.lval     Status: WP     Submitter: Mike Stump     Date: 20 Aug 1999

[Moved to DR at 4/02 meeting.]

3.10  basic.lval paragraph 15 lists the types via which an lvalue can be used to access the stored value of an object; using an lvalue type that is not listed results in undefined behavior. It is permitted to add cv-qualification to the actual type of the object in this access, but only at the top level of the type ("a cv-qualified version of the dynamic type of the object").

However, 4.4  conv.qual paragraph 4 permits a "conversion [to] add cv-qualifiers at levels other than the first in multi-level pointers." The combination of these two rules allows creation of pointers that cannot be dereferenced without causing undefined behavior. For instance:

    int* jp;
    const int * const * p1 = &jp;
    *p1;    // undefined behavior!

The reason that *p1 results in undefined behavior is that the type of the lvalue is const int * const", which is not "a cv-qualified version of" int*.

Since the conversion is permitted, we must give it defined semantics, hence we need to fix the wording in 3.10  basic.lval to include all possible conversions of the type via 4.4  conv.qual.

Proposed resolution (04/01):

Add a new bullet to 3.10  basic.lval paragraph 15, following "a cv-qualified version of the dynamic type of the object:"




519. Null pointer preservation in void* conversions

Section: 4.10  conv.ptr     Status: WP     Submitter: comp.std.c++     Date: 19 May 2005

[Voted into WP at April, 2006 meeting.]

The C standard says in 6.3.2.3, paragraph 4:

Conversion of a null pointer to another pointer type yields a null pointer of that type. Any two null pointers shall compare equal.

C++ appears to be incompatible with the first sentence in only two areas:

    A *a = 0;
    void *v = a;

C++ (4.10  conv.ptr paragraph 2) says nothing about the value of v.

    void *v = 0;
    A *b = (A*)v; // aka static_cast<A*>(v)

C++ (5.2.9  expr.static.cast paragraph 10) says nothing about the value of b.

Suggested changes:

  1. Add the following sentence to 4.10  conv.ptr paragraph 2:

  2. The null pointer value is converted to the null pointer value of the destination type.
  3. Add the following sentence to 5.2.9  expr.static.cast paragraph 10:

  4. The null pointer value (4.10  conv.ptr) is converted to the null pointer value of the destination type.

Proposed resolution (October, 2005):

  1. Add the indicated words to 4.10  conv.ptr paragraph 2:

  2. An rvalue of type “pointer to cv T,” where T is an object type, can be converted to an rvalue of type “pointer to cv void”. The result of converting a “pointer to cv T” to a “pointer to cv void” points to the start of the storage location where the object of type T resides, as if the object is a most derived object (1.8  intro.object) of type T (that is, not a base class subobject). The null pointer value is converted to the null pointer value of the destination type.
  3. Add the indicated words to 5.2.9  expr.static.cast paragraph 11:

  4. An rvalue of type “pointer to cv1 void” can be converted to an rvalue of type “pointer to cv2 T,” where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. The null pointer value is converted to the null pointer value of the destination type. A value of type pointer to object converted to “pointer to cv void” and back, possibly with different cv-qualification, shall have its original value...



480. Is a base of a virtual base also virtual?

Section: 4.11  conv.mem     Status: WP     Submitter: Mark Mitchell     Date: 18 Oct 2004

[Voted into WP at the October, 2006 meeting.]

When the Standard refers to a virtual base class, it should be understood to include base classes of virtual bases. However, the Standard doesn't actually say this anywhere, so when 4.11  conv.mem (for example) forbids casting to a derived class member pointer from a virtual base class member pointer, it could be read as meaning:

  struct B {};
  struct D : public B {};
  struct D2 : virtual public D {};

  int B::*p;
  int D::*q;

  void f() {
    static_cast<int D2::*>(p);  // permitted
    static_cast<int D2::*>(q);  // forbidden
  }

Proposed resolution (October, 2005):

  1. Change 4.11  conv.mem paragraph 2 as indicated:

  2. ...If B is an inaccessible (clause 11  class.access), ambiguous (10.2  class.member.lookup) or virtual (10.1  class.mi) base class of D, or a base class of a virtual base class of D, a program that necessitates this conversion is ill-formed...
  3. Change 5.2.9  expr.static.cast paragraph 2 as indicated:

  4. ...and B is not neither a virtual base class of D nor a base class of a virtual base class of D...
  5. Change 5.2.9  expr.static.cast paragraph 9 as indicated:

  6. ...and B is not neither a virtual base class of D nor a base class of a virtual base class of D...



351. Sequence point error: unspecified or undefined?

Section: expr     Status: WP     Submitter: Andrew Koenig     Date: 23 April 2002

[Voted into WP at March 2004 meeting.]

I have found what looks like a bug in clause 5  expr, paragraph 4:

Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored. The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full expression; otherwise the behavior is undefined. Example:
        i = v[i++];                     // the behavior is unspecified
        i = 7, i++, i++;                // i becomes 9

        i = ++i + 1;                    // the behavior is unspecified
        i = i + 1;                      // the value of i is incremented
--end example]

So which is it, unspecified or undefined?

Notes from October 2002 meeting:

We should find out what C99 says and do the same thing.

Proposed resolution (April 2003):

Change the example in clause 5  expr, paragraph 4 from

[Example:
i = v[i++];                     //  the behavior is unspecified
i = 7, i++, i++;                //   i  becomes  9

i = ++i + 1;                    //  the behavior is unspecified
i = i + 1;                      //  the value of  i  is incremented
--- end example]

to (changing "unspecified" to "undefined" twice)

[Example:
i = v[i++];                     //  the behavior is undefined
i = 7, i++, i++;                //   i  becomes  9

i = ++i + 1;                    //  the behavior is undefined
i = i + 1;                      //  the value of  i  is incremented
--- end example]



451. Expressions with invalid results and ill-formedness

Section: expr     Status: WP     Submitter: Gennaro Prota     Date: 19 Jan 2004

[Voted into WP at October 2005 meeting.]

Clause 5  expr par. 5 of the standard says:

If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined, unless such an expression is a constant expression (5.19), in which case the program is ill-formed.

Well, we do know that except in some contexts (e.g. controlling expression of a #if, array bounds), a compiler is not required to evaluate constant-expressions in compile time, right?

Now, let us consider, the following simple snippet:

  if (a && 1/0)
      ...
with a, to fix our attention, being *not* a constant expression. The quote above seems to say that since 1/0 is a constant (sub-)expression, the program is ill-formed. So, is it the intent that such ill-formedness is diagnosable at run-time? Or is it the intent that the above gives undefined behavior (if 1/0 is evaluated) and is not ill-formed?

I think the intent is actually the latter, so I propose the following rewording of the quoted section:

If an expression is evaluated but its result is not mathematically defined or not in the range of representable values for its type the behavior is undefined, unless such an expression is a constant expression (5.19) that shall be evaluated during program translation, in which case the program is ill-formed.

Rationale (March, 2004):

We feel the standard is clear enough. The quoted sentence does begin "If during the evaluation of an expression, ..." so the rest of the sentence does not apply to an expression that is not evaluated.

Note (September, 2004):

Gennaro Prota feels that the CWG missed the point of his original comment: unless a constant expression appears in a context that requires a constant expression, an implementation is permitted to defer its evaluation to runtime. An evaluation that fails at runtime cannot affect the well-formedness of the program; only expressions that are evaluated at compile time can make a program ill-formed.

The status has been reset to “open” to allow further discussion.

Proposed resolution (October, 2004):

Change paragraph 5 of 5  expr as indicated:

If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined, unless such an expression is a constant expression appears where an integral constant expression is required (5.19  expr.const), in which case the program is ill-formed.



122. template-ids as unqualified-ids

Section: 5.1  expr.prim     Status: WP     Submitter: Mike Miller     Date: 3 June 1999

[Moved to DR at 10/01 meeting.]

5.1  expr.prim paragraph 11 reads,

A template-id shall be used as an unqualified-id only as specified in 14.7.2  temp.explicit , 14.7  temp.spec , and 14.5.4  temp.class.spec .

What uses of template-ids as unqualified-ids is this supposed to prevent? And is the list of referenced sections correct/complete? For instance, what about 14.8.1  temp.arg.explicit, "Explicit template argument specification?" Does its absence from the list in 5.1  expr.prim paragraph 11 mean that "f<int>()" is ill-formed?

This is even more confusing when you recall that unqualified-ids are contained in qualified-ids:

qualified-id: ::opt nested-name-specifier templateopt unqualified-id

Is the wording intending to say "used as an unqualified-id that is not part of a qualified-id?" Or something else?

Proposed resolution (10/00):

Remove the referenced sentence altogether.




125. Ambiguity in friend declaration syntax

Section: 5.1  expr.prim     Status: WP     Submitter: Martin von Loewis     Date: 7 June 1999

[Voted into WP at March 2004 meeting.]

The example below is ambiguous.

    struct A{
      struct B{};
    };

    A::B C();

    namespace B{
      A C();
    }

    struct Test {
      friend A::B ::C();
    };
Here, it is not clear whether the friend declaration denotes A B::C() or A::B C(), yet the standard does not resolve this ambiguity.

The ambiguity arises since both the simple-type-specifier (7.1.5.2  dcl.type.simple paragra 1) and an init-declararator (8  dcl.decl paragraph 1) contain an optional :: and an optional nested-name-specifier (5.1  expr.prim paragraph 1). Therefore, two different ways to analyse this code are possible:

simple-type-specifier = A::B
init-declarator = ::C()
simple-declaration = friend A::B ::C();
or
simple-type-specifier = A
init-declarator = ::B::C()
simple-declaration = friend A ::B::C();
Since it is a friend declaration, the init-declarator may be qualified, and start with a global scope.

Suggested Resolution: In the definition of nested-name-specifier, add a sentence saying that a :: token immediately following a nested-name-specifier is always considered as part of the nested-name-specifier. Under this interpretation, the example is ill-formed, and should be corrected as either

    friend A (::B::C)();   //or
    friend A::B (::C)();

An alternate suggestion — changing 7.1  dcl.spec to say that

The longest sequence of tokens that could possibly be a type name is taken as the decl-specifier-seq of a declaration.

— is undesirable because it would make the example well-formed rather than requiring the user to disambiguate the declaration explicitly.

Proposed resolution (04/01):

(See below for problem with this, from 10/01 meeting.)

In 5.1  expr.prim paragraph 7,

  1. Before the grammar for qualified-id, start a new paragraph 7a with the text

    A qualified-id is an id-expression that contains the scope resolution operator ::.
  2. Following the grammar fragment, insert the following:

    The longest sequence of tokens that could form a qualified-id constitutes a single qualified-id. [Example:

        // classes C, D; functions F, G, namespace N; non-class type T
        friend C ::D::F();   // ill-formed, means friend (C::D::F)();
        friend C (::D::F)(); // well-formed
        friend N::T ::G();   // ill-formed, means friend (N::T::G)();
        friend N::T (::G)(); // well-formed
    

    end example]

  3. Start a new paragraph 7b following the example.

(This resolution depends on that of issue 215.)

Notes from 10/01 meeting:

It was pointed out that the proposed resolution does not deal with cases like X::Y where X is a type but not a class type. The working group reaffirmed its decision that the disambiguation should be syntactic only, i.e., it should depend only on whether or not the name is a type.

Jason Merrill :

At the Seattle meeting, I suggested that a solution might be to change the class-or-namespace-name in the nested-name-specifier rule to just be "identifier"; there was some resistance to this idea. FWIW, I've tried this in g++. I had to revise the idea so that only the second and subsequent names were open to being any identifier, but that seems to work just fine.

So, instead of

it would be

Or some equivalent but right-associative formulation, if people feel that's important, but it seems irrelevant to me.

Clark Nelson :

Personally, I prefer the left-associative rule. I think it makes it easier to understand. I was thinking about this production a lot at the meeting, considering also some issues related to 301. My formulation was getting kind of ugly, but with a left-associative rule, it gets a lot nicer.

Your proposal isn't complete, however, as it doesn't allow template arguments without an explicit template keyword. You probably want to add an alternative for:

There is admittedly overlap between this alternative and

but I think they're both necessary.

Notes from the 4/02 meeting:

The changes look good. Clark Nelson will merge the two proposals to produce a single proposed resolution.

Proposed resolution (April 2003):

nested-name-specifier is currently defined in 5.1  expr.prim paragraph 7 as:

The proposed definition is instead:

Issue 215 is addressed by using type-name instead of class-name in the first alternative. Issue 125 (this issue) is addressed by using identifier instead of anything more specific in the third alternative. Using left association instead of right association helps eliminate the need for class-or-namespace-name (or type-or-namespace-name, as suggested for issue 215).

It should be noted that this formulation also rules out the possibility of A::template B::, i.e. using the template keyword without any template arguments. I think this is according to the purpose of the template keyword, and that the former rule allowed such a construct only because of the difficulty of formulation of a right-associative rule that would disallow it. But I wanted to be sure to point out this implication.

Notes from April 2003 meeting:

See also issue 96.

The proposed change resolves only part of issue 215.




113. Visibility of called function

Section: 5.2.2  expr.call     Status: WP     Submitter: Christophe de Dinechin     Date: 5 May 1999

[Moved to DR at 10/01 meeting.]

Christophe de Dinechin: In 5.2.2  expr.call , paragraph 2 reads:

If no declaration of the called function is visible from the scope of the call the program is ill-formed.
I think nothing there or in the previous paragraph indicates that this does not apply to calls through pointer or virtual calls.

Mike Miller: "The called function" is unfortunate phraseology; it makes it sound as if it's referring to the function actually called, as opposed to the identifier in the postfix expression. It's wrong with respect to Koenig lookup, too (the declaration need not be visible if it can be found in a class or namespace associated with one or more of the arguments).

In fact, this paragraph should be a note. There's a general rule that says you have to find an unambiguous declaration of any name that is used (3.4  basic.lookup paragraph 1); the only reason this paragraph is here is to contrast with C's implicit declaration of called functions.

Proposed resolution:

Change section 5.2.2  expr.call paragraph 2 from:
If no declaration of the called function is visible from the scope of the call the program is ill-formed.
to:
[Note: if a function or member function name is used, and name lookup (3.4  basic.lookup) does not find a declaration of that name, the program is ill-formed. No function is implicitly declared by such a call. ]

(See also issue 218.)




506. Conditionally-supported behavior for non-POD objects passed to ellipsis

Section: 5.2.2  expr.call     Status: WP     Submitter: Mike Miller     Date: 14 Apr 2005

[Voted into WP at the October, 2006 meeting.]

The current wording of 5.2.2  expr.call paragraph 7 states:

When there is no parameter for a given argument, the argument is passed in such a way that the receiving function can obtain the value of the argument by invoking va_arg (18.7  lib.support.runtime). The lvalue-to-rvalue (4.1  conv.lval), array-to-pointer (4.2  conv.array), and function-to-pointer (4.3  conv.func) standard conversions are performed on the argument expression. After these conversions, if the argument does not have arithmetic, enumeration, pointer, pointer to member, or class type, the program is ill-formed. If the argument has a non-POD class type (clause 9  class), the behavior is undefined.

Paper J16/04-0167=WG21 N1727 suggests that passing a non-POD object to ellipsis be ill-formed. In discussions at the Lillehammer meeting, however, the CWG felt that the newly-approved category of conditionally-supported behavior would be more appropriate.

Proposed resolution (October, 2005):

Change 5.2.2  expr.call paragraph 7 as indicated:

...After these conversions, if the argument does not have arithmetic, enumeration, pointer, pointer to member, or class type, the program is ill-formed. If the argument has a non-POD class type (clause 9), the behavior is undefined. Passing an argument of non-POD class type (clause 9) with no corresponding parameter is conditionally-supported, with implementation-defined semantics.



466. cv-qualifiers on pseudo-destructor type

Section: 5.2.4  expr.pseudo     Status: WP     Submitter: Mark Mitchell     Date: 18 Mar 2004

[Voted into WP at April, 2006 meeting.]

5.2.4  expr.pseudo paragraph 2 says both:

The type designated by the pseudo-destructor-name shall be the same as the object type.
and also:
The cv-unqualified versions of the object type and of the type designated by the pseudo-destructor-name shall be the same type.
Which is it? "The same" or "the same up to cv-qualifiers"? The second sentence is more generous than the first. Most compilers seem to implement the less restrictive form, so I guess that's what I think we should do.

See also issues 305 and 399.

Proposed resolution (October, 2005):

Change 5.2.4  expr.pseudo paragraph 2 as follows:

The left-hand side of the dot operator shall be of scalar type. The left-hand side of the arrow operator shall be of pointer to scalar type. This scalar type is the object type. The type designated by the pseudo-destructor-name shall be the same as the object type. The cv-unqualified versions of the object type and of the type designated by the pseudo-destructor-name shall be the same type. Furthermore, the two type-names in a pseudo-destructor-name of the form shall designate the same scalar type. The cv-unqualified versions of the object type and of the type designated by the pseudo-destructor-name shall be the same type.



421. Is rvalue.field an rvalue?

Section: 5.2.5  expr.ref     Status: WP     Submitter: Gabriel Dos Reis     Date: 15 June 2003

[Voted into WP at March 2004 meeting.]

Consider

  typedef
    struct {
      int a;
    } A;

  A f(void)
  {
    A a;
    return a;
  }

  int main(void)
  {
    int* p = &f().a;   // #1
  }

Should #1 be rejected? The standard is currently silent.

Mike Miller: I don't believe the Standard is silent on this. I will agree that the wording of 5.2.5  expr.ref paragraph 4 bullet 2 is unfortunate, as it is subject to misinterpretation. It reads,

If E1 is an lvalue, then E1.E2 is an lvalue.
The intent is, "and not otherwise."

Notes from October 2003 meeting:

We agree the reference should be an rvalue, and a change along the lines of that recommended by Mike Miller is reasonable.

Proposed Resolution (October 2003):

Change the second bullet of 5.2.5  expr.ref paragraph 4 to read:

If E1 is an lvalue, then E1.E2 is an lvalue; otherwise, it is an rvalue.



492. typeid constness inconsistent with example

Section: 5.2.8  expr.typeid     Status: WP     Submitter: Ron Natalie     Date: 15 Dec 2004

[Voted into WP at April, 2006 meeting.]

There is an inconsistency between the normative text in section 5.2.8  expr.typeid and the example that follows.

Here is the relevant passage (starting with paragraph 4):

When typeid is applied to a type-id, the result refers to a std::type_info object representing the type of the type-id. If the type of the type-id is a reference type, the result of the typeid expression refers to a std::type_info object representing the referenced type.

The top-level cv-qualifiers of the lvalue expression or the type-id that is the operand of typeid are always ignored.

and the example:

    typeid(D) == typeid(const D&); // yields true

The second paragraph above says the “type-id that is the operand”. This would be const D&. In this case, the const is not at the top-level (i.e., applied to the operand itself).

By a strict reading, the above should yield false.

My proposal is that the strict reading of the normative test is correct. The example is wrong. Different compilers here give different answers.

Proposed resolution (April, 2005):

Change the second sentence of 5.2.8  expr.typeid paragraph 4 as follows:

If the type of the type-id is a reference to a possibly cv-qualified type, the result of the typeid expression refers to a std::type_info object representing the cv-unqualified referenced type.



54. Static_cast from private base to derived class

Section: 5.2.9  expr.static.cast     Status: WP     Submitter: Steve Adamczyk     Date: 13 Oct 1998

[Voted into WP at October 2004 meeting.]

Is it okay to use a static_cast to cast from a private base class to a derived class? That depends on what the words "valid standard conversion" in paragraph 8 mean — do they mean the conversion exists, or that it would not get an error if it were done? I think the former was intended — and therefore a static_cast from a private base to a derived class would be allowed.

Rationale (04/99): A static_cast from a private base to a derived class is not allowed outside a member from the derived class, because 4.10  conv.ptr paragraph 3 implies that the conversion is not valid. (Classic style casts work.)

Reopened September 2003:

Steve Adamczyk: It makes some sense to disallow the inverse conversion that is pointer-to-member of derived to pointer-to-member of private base. There's less justification for the pointer-to-private-base to pointer-to-derived case. EDG, g++ 3.2, and MSVC++ 7.1 allow the pointer case and disallow the pointer-to-member case. Sun disallows the pointer case as well.

  struct B {};
  struct D : private B {};
  int main() {
    B *p = 0;
    static_cast<D *>(p);  // Pointer case: should be allowed
    int D::*pm = 0;
    static_cast<int B::*>(pm);  // Pointer-to-member case: should get error
  }

There's a tricky case with old-style casts: because the static_cast interpretation is tried first, you want a case like the above to be considered a static_cast, but then issue an error, not be rejected as not a static cast; if you did the latter, you would then try the cast as a reinterpret_cast.

Ambiguity and casting to a virtual base should likewise be errors after the static_cast interpretation is selected.

Notes from the October 2003 meeting:

There was lots of sentiment for making things symmetrical: the pointer case should be the same as the pointer-to-member case. g++ 3.3 now issues errors on both cases.

We decided an error should be issued on both cases. The access part of the check should be done later; by some definition of the word the static_cast is valid, and then later an access error is issued. This is similar to the way standard conversions work.

Proposed Resolution (October 2003):

Replace paragraph 5.2.9  expr.static.cast/6:

The inverse of any standard conversion sequence (clause 4  conv), other than the lvalue-to-rvalue (4.1  conv.lval), array-to-pointer (4.2  conv.array), function-to-pointer (4.3  conv.func), and boolean (4.12  conv.bool) conversions, can be performed explicitly using static_cast. The lvalue-to-rvalue (4.1  conv.lval), array-to-pointer (4.2  conv.array), and function-to-pointer (4.3  conv.func) conversions are applied to the operand. Such a static_cast is subject to the restriction that the explicit conversion does not cast away constness (5.2.11  expr.const.cast), and the following additional rules for specific cases:

with two paragraphs:

The inverse of any standard conversion sequence (clause 4  conv), other than the lvalue-to-rvalue (4.1  conv.lval), array-to-pointer (4.2  conv.array), function-to-pointer (4.3  conv.func), and boolean (4.12  conv.bool) conversions, can be performed explicitly using static_cast. A program is ill-formed if it uses static_cast to perform the inverse of an ill-formed standard conversion sequence.[Example:
struct B {};
struct D : private B {};
void f() {
  static_cast<D*>((B*)0); // Error: B is a private base of D.
  static_cast<int B::*>((int D::*)0); // Error: B is a private base of D.
}
--- end example]

The lvalue-to-rvalue (4.1  conv.lval), array-to-pointer (4.2  conv.array), and function-to-pointer (4.3  conv.func) conversions are applied to the operand. Such a static_cast is subject to the restriction that the explicit conversion does not cast away constness (5.2.11  expr.const.cast), and the following additional rules for specific cases:

In addition, modify the second sentence of 5.4  expr.cast/5. The first two sentences of 5.4  expr.cast/5 presently read:

The conversions performed by can be performed using the cast notation of explicit type conversion. The same semantic restrictions and behaviors apply.

Change the second sentence to read:

The same semantic restrictions and behaviors apply, with the exception that in performing a static_cast in the following situations the conversion is valid even if the base class is inaccessible:

Remove paragraph 5.4  expr.cast/7, which presently reads:

In addition to those conversions, the following static_cast and reinterpret_cast operations (optionally followed by a const_cast operation) may be performed using the cast notation of explicit type conversion, even if the base class type is not accessible:



427. static_cast ambiguity: conversion versus cast to derived

Section: 5.2.9  expr.static.cast     Status: WP     Submitter: Mark Mitchell     Date: 5 July 2003

[Voted into WP at October 2004 meeting.]

Consider this code:

  struct B {};

  struct D : public B { 
    D(const B&);
  };

  extern B& b;

  void f() {
    static_cast<const D&>(b);
  }

The rules for static_cast permit the conversion to "const D&" in two ways:

  1. D is derived from B, and b is an lvalue, so a cast to D& is OK.
  2. const D& t = b is valid, using the constructor for D. [Ed. note: actually, this should be parenthesized initialization.]

The first alternative is 5.2.9  expr.static.cast/5; the second is 5.2.9  expr.static.cast/2.

Presumably the first alternative is better -- it's the "simpler" conversion. The standard does not seem to make that clear.

Steve Adamczyk: I take the "Otherwise" at the beginning of 5.2.9  expr.static.cast/3 as meaning that the paragraph 2 interpretation is used if available, which means in your example above interpretation 2 would be used. However, that's not what EDG's compiler does, and I agree that it's not the "simpler" conversion.

Proposed Resolution (October 2003):

Move paragraph 5.2.9/5:

An lvalue of type ``cv1 B'', where B is a class type, can be cast to type ``reference to cv2 D'', where D is a class derived (clause 10  class.derived) from B, if a valid standard conversion from ``pointer to D'' to ``pointer to B'' exists (4.10  conv.ptr), cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and B is not a virtual base class of D. The result is an lvalue of type ``cv2 D.'' If the lvalue of type ``cv1 B'' is actually a sub-object of an object of type D, the lvalue refers to the enclosing object of type D. Otherwise, the result of the cast is undefined. [Example:

  struct B {};
  struct D : public B {};
  D d;
  B &br = d;

  static_cast<D&>(br);            //  produces lvalue to the original  d  object
--- end example]

before paragraph 5.2.9  expr.static.cast/2.

Insert Otherwise, before the text of paragraph 5.2.9  expr.static.cast/2 (which will become 5.2.9  expr.static.cast/3 after the above insertion), so that it reads:

Otherwise, an expression e can be explicitly converted to a type T using a static_cast of the form static_cast<T>(e) if the declaration "T t(e);" is well-formed, for some invented temporary variable t (8.5  dcl.init). The effect of such an explicit conversion is the same as performing the declaration and initialization and then using the temporary variable as the result of the conversion. The result is an lvalue if T is a reference type (8.3.2  dcl.ref), and an rvalue otherwise. The expression e is used as an lvalue if and only if the initialization uses it as an lvalue.




439. Guarantees on casting pointer back to cv-qualified version of original type

Section: 5.2.9  expr.static.cast     Status: WP     Submitter: Mark Mitchell     Date: 30 Oct 2003

[Voted into WP at April 2005 meeting.]

Paragraph 5.2.9  expr.static.cast paragraph 10 says that:

A value of type pointer to object converted to "pointer to cv void" and back to the original pointer type will have its original value.

That guarantee should be stronger. In particular, given:

  T* p1 = new T;
  const T* p2 = static_cast<const T*>(static_cast<void *>(p1));
  if (p1 != p2)
    abort ();
there should be no call to "abort". The last sentence of Paragraph 5.2.9  expr.static.cast paragraph 10 should be changed to read:

A value of type pointer to object converted to "pointer to cv void" and back to the original pointer type (or a variant of the original pointer type that differs only in the cv-qualifiers applied to the object type) will have its original value. [Example:
T* p1 = new T;
const T* p2 = static_cast<const T*>(static_cast<void *>(p1));
bool b = p1 == p2; // b will have the value true.
---end example.]

Proposed resolution:

Change 5.2.9  expr.static.cast paragraph 10 as indicated:

A value of type pointer to object converted to "pointer to cv void" and back to the original pointer type, possibly with different cv-qualification, will have its original value. [Example:

  T* p1 = new T;
  const T* p2 = static_cast<const T*>(static_cast<void *>(p1));
  bool b = p1 == p2; // b will have the value true.

---end example]

Rationale: The wording "possibly with different cv-qualification" was chosen over the suggested wording to allow for changes in cv-qualification at different levels in a multi-level pointer, rather than only at the object type level.




195. Converting between function and object pointers

Section: 5.2.10  expr.reinterpret.cast     Status: WP     Submitter: Steve Clamage     Date: 12 Jan 2000

[Voted into WP at April 2005 meeting.]

It is currently not permitted to cast directly between a pointer to function type and a pointer to object type. This conversion is not listed in 5.2.9  expr.static.cast and 5.2.10  expr.reinterpret.cast and thus requires a diagnostic to be issued. However, if a sufficiently long integral type exists (as is the case in many implementations), it is permitted to cast between pointer to function types and pointer to object types using that integral type as an intermediary.

In C the cast results in undefined behavior and thus does not require a diagnostic, and Unix C compilers generally do not issue one. This fact is used in the definition of the standard Unix function dlsym, which is declared to return void* but in fact may return either a pointer to a function or a pointer to an object. The fact that C++ compilers are required to issue a diagnostic is viewed as a "competitive disadvantage" for the language.

Suggested resolution: Add wording to 5.2.10  expr.reinterpret.cast allowing conversions between pointer to function and pointer to object types, if the implementation has an integral data type that can be used as an intermediary.

Several points were raised in opposition to this suggestion:


  1. Early C++ supported this conversion and it was deliberately removed during the standardization process.
  2. The existence of an appropriate integral type is irrelevant to whether the conversion is "safe." The condition should be on whether information is lost in the conversion or not.
  3. There are numerous ways to address the problem at an implementation level rather than changing the language. For example, the compiler could recognize the specific case of dlsym and omit the diagnostic, or the C++ binding to dlsym could be changed (using templates, for instance) to circumvent the violation.
  4. The conversion is, in fact, not supported by C; the dlsym function is simply relying on non-mandated characteristics of C implementations, and we would be going beyond the requirements of C compatibility in requiring (some) implementations to support the conversion.
  5. This issue is in fact not a defect (omitted or self-contradictory requirements) in the current Standard; the proposed change would actually be an extension and should not be considered until the full review of the IS.
  6. dlsym appears not to be used very widely, and the declaration in the header file is not problematic, only calls to it. Since C code generally requires some porting to be valid C++ anyway, this should be considered one of those items that requires porting.

Martin O'Riordan suggested an alternative approach:


The advantage of this approach is that it would permit writing portable, well-defined programs involving such conversions. However, it breaks the current degree of compatibility between old and new casts, and it adds functionality to dynamic_cast which is not obviously related to its current meaning.

Notes from 04/00 meeting:

Andrew Koenig suggested yet another approach: specify that "no diagnostic is required" if the implementation supports the conversion.

Later note:

It was observed that conversion between function and data pointers is listed as a "common extension" in C99.

Notes on the 10/01 meeting:

It was decided that we want the conversion defined in such a way that it always exists but is always undefined (as opposed to existing only when the size relationship is appropriate, and being implementation-defined in that case). This would allow an implementation to issue an error at compile time if the conversion does not make sense.

Bill Gibbons notes that the definitions of the other similar casts are inconsistent in this regard. Perhaps they should be updated as well.

Proposed resolution (April 2003):

After 5.2.10  expr.reinterpret.cast paragraph 6, insert:

A pointer to a function can be explicitly converted to a pointer to a function of a different type. The effect of calling a function through a pointer to a function type (8.3.5  dcl.fct) that is not the same as the type used in the definition of the function is undefined. Except that converting an rvalue of type ``pointer to T1'' to the type ``pointer to T2'' (where T1 and T2 are function types) and back to its original type yields the original pointer value, the result of such a pointer conversion is unspecified. [Note: see also 4.10  conv.ptr for more details of pointer conversions. ] It is implementation defined whether a conversion from pointer to object to pointer to function and/or a conversion from pointer to function to pointer to object exist.
and in paragraph 10:
An lvalue expression of type T1 can be cast to the type ``reference to T2'' if T1 and T2 are object types and an expression of type ``pointer to T1'' can be explicitly converted to the type ``pointer to T2'' using a reinterpret_cast. That is, a reference cast reinterpret_cast< T& >(x) has the same effect as the conversion *reinterpret_cast< T* >(&x) with the built-in & and * operators. The result is an lvalue that refers to the same object as the source lvalue, but with a different type. No temporary is created, no copy is made, and constructors (12.1  class.ctor) or conversion functions (12.3  class.conv) are not called.

Drafting Note:

If either conversion exists, the implementation already has to define the behavior (paragraph 3).

Notes from April 2003 meeting:

The new consensus is that if the implementation allows this cast, pointer-to-function converted to pointer-to-object converted back to the original pointer-to-function should work; anything else is undefined behavior. If the implementation does not allow the cast, it should be ill-formed.

Tom Plum is investigating a new concept, that of a "conditionally-defined" feature, which may be applicable here.

Proposed Resolution (October, 2004):

(See paper J16/04-0067 = WG21 N1627 for background material and rationale for this resolution. The resolution proposed here differs only editorially from the one in the paper.)

  1. Insert the following as 1.3.2 and renumber all following definitions accordingly:

    1.3.2  conditionally-supported behavior

    behavior evoked by a program construct that is not a mandatory requirement of this International Standard. If a given implementation supports the construct, the behavior shall be as described herein; otherwise, the implementation shall document that the construct is not supported and shall treat a program containing an occurrence of the construct as ill-formed (1.3.4  defns.ill.formed).

  2. Add the indicated words to 1.4  intro.compliance paragraph 2, bullet 2:

  3. Insert the following as a new paragraph following 5.2.10  expr.reinterpret.cast paragraph 7:

    Converting a pointer to a function to a pointer to an object type or vice versa evokes conditionally-supported behavior. In any such conversion supported by an implementation, converting from an rvalue of one type to the other and back (possibly with different cv-qualification) shall yield the original pointer value; mappings between pointers to functions and pointers to objects are otherwise implementation-defined.
  4. Change 7.4  dcl.asm paragraph 1 as indicated:

    The meaning of an An asm declaration evokes conditionally-supported behavior. If supported, its meaning is implementation-defined.
  5. Change 7.5  dcl.link paragraph 2 as indicated:

    The string-literal indicates the required language linkage. The meaning of the string-literal is implementation-defined. A linkage-specification with a string that is unknown to the implementation is ill-formed. This International Standard specifies the semantics of C and C++ language linkage. Other values of the string-literal evoke conditionally-supported behavior, with implementation-defined semantics. [Note: Therefore, a linkage-specification with a string-literal that is unknown to the implementation requires a diagnostic. When the string-literal in a linkage-specification names a programming language, the spelling of the programming language's name is implementation-defined. [Note: It is recommended that the spelling be taken from the document defining that language, for example Ada (not ADA) and Fortran or FORTRAN (depending on the vintage). The semantics of a language linkage other than C++ or C are implementation-defined. ]
  6. Change 14  temp paragraph 4 as indicated:

    A template, a template explicit specialization (14.7.3  temp.expl.spec), or a class template partial specialization shall not have C linkage. If the linkage of one of these is something other than C or C++, the behavior is implementation-defined result is conditionally-supported behavior, with implementation-defined semantics.



463. reinterpret_cast<T*>(0)

Section: 5.2.10  expr.reinterpret.cast     Status: WP     Submitter: Gennaro Prota     Date: 14 Feb 2004

[Voted into WP at April, 2006 meeting.]

Is reinterpret_cast<T*>(null_pointer_constant) guaranteed to yield the null pointer value of type T*?

I think a committee clarification is needed. Here's why: 5.2.10  expr.reinterpret.cast par. 8 talks of "null pointer value", not "null pointer constant", so it would seem that

  reinterpret_cast<T*>(0)
is a normal int->T* conversion, with an implementation-defined result.

However a little note to 5.2.10  expr.reinterpret.cast par. 5 says:

Converting an integral constant expression (5.19) with value zero always yields a null pointer (4.10), but converting other expressions that happen to have value zero need not yield a null pointer.
Where is this supported in normative text? It seems that either the footnote or paragraph 8 doesn't reflect the intent.

SUGGESTED RESOLUTION: I think it would be better to drop the footnote #64 (and thus the special case for ICEs), for two reasons:

a) it's not normative anyway; so I doubt anyone is relying on the guarantee it hints at, unless that guarantee is given elsewhere in a normative part

b) users expect reinterpret_casts to be almost always implementation dependent, so this special case is a surprise. After all, if one wants a null pointer there's static_cast. And if one wants reinterpret_cast semantics the special case requires doing some explicit cheat, such as using a non-const variable as intermediary:

   int v = 0;
   reinterpret_cast<T*>(v); // implementation defined

   reinterpret_cast<T*>(0); // null pointer value of type T*
   const int w = 0;
   reinterpret_cast<T*>(w); // null pointer value of type T*

It seems that not only that's providing a duplicate functionality, but also at the cost to hide what seems the more natural one.

Notes from October 2004 meeting:

This footnote was added in 1996, after the invention of reinterpret_cast, so the presumption must be that it was intentional. At this time, however, the CWG feels that there is no reason to require that reinterpret_cast<T*>(0) produce a null pointer value as its result.

Proposed resolution (April, 2005):

  1. Delete the footnote in 5.2.10  expr.reinterpret.cast paragraph 5 reading,

    Converting an integral constant expression (5.19  expr.const) with value zero always yields a null pointer (4.10  conv.ptr), but converting other expressions that happen to have value zero need not yield a null pointer.
  2. Add the indicated note to 5.2.10  expr.reinterpret.cast paragraph 8:

    The null pointer value (4.10  conv.ptr) is converted to the null pointer value of the destination type. [Note: A null pointer constant, which has integral type, is not necessarily converted to a null pointer value. —end note]



324. Can "&" be applied to assignment to bit-field?

Section: 5.3.1  expr.unary.op     Status: WP     Submitter: Alasdair Grant     Date: 27 Nov 2001

[Voted into WP at October 2003 meeting.]

An assignment returns an lvalue for its left operand. If that operand refers to a bit field, can the "&" operator be applied to the assignment? Can a reference be bound to it?

  struct S { int a:3; int b:3; int c:3; };

  void f()
  {
    struct S s;
    const int *p = &(s.b = 0);     // (a)
    const int &r = (s.b = 0);      // (b)
          int &r2 = (s.b = 0);     // (c)
  }

Notes from the 4/02 meeting:

The working group agreed that this should be an error.

Proposed resolution (October 2002):

In 5.3.2  expr.pre.incr paragraph 1 (prefix "++" and "--" operators), change

The value is the new value of the operand; it is an lvalue.
to
The result is the updated operand; it is an lvalue, and it is a bit-field if the operand is a bit-field.

In 5.16  expr.cond paragraph 4 ("?" operator), add the indicated text:

If the second and third operands are lvalues and have the same type, the result is of that type and is an lvalue and it is a bit-field if the second or the third operand is a bit-field, or if both are bit-fields.

In 5.17  expr.ass paragraph 1 (assignment operators) add the indicated text (the original text is as updated by issue 221, which is DR but not in TC1):

The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue with the type and value of the left operand after the assignment has taken place. The result in all cases is a bit-field if the left operand is a bit-field.

Note that issue 222 adds (non-conflicting) text at the end of this same paragraph (5.17  expr.ass paragraph 1).

In 5.18  expr.comma paragraph 1 (comma operator), change:

The type and value of the result are the type and value of the right operand; the result is an lvalue if its right operand is.
to
The type and value of the result are the type and value of the right operand; the result is an lvalue if the right operand is an lvalue, and is a bit-field if the right operand is an lvalue and a bit-field.

Relevant related text (no changes required):

5.3.1  expr.unary.op paragraph 4:

The operand of & shall not be a bit-field.

8.5.3  dcl.init.ref paragraph 5, bullet 1, sub-bullet 1 (regarding binding a reference to an lvalue):

... is an lvalue (but is not a bit-field) ...




299. Conversion on array bound expression in new

Section: 5.3.4  expr.new     Status: WP     Submitter: Mark Mitchell     Date: 19 Jul 2001

[Voted into WP at October 2005 meeting.]

In 5.3.4  expr.new, the standard says that the expression in an array-new has to have integral type. There's already a DR (issue 74) that says it should also be allowed to have enumeration type. But, it should probably also say that it can have a class type with a single conversion to integral type; in other words the same thing as in 6.4.2  stmt.switch paragraph 2.

Suggested resolution:

In 5.3.4  expr.new paragraph 6, replace "integral or enumeration type (3.9.1  basic.fundamental)" with "integral or enumeration type (3.9.1  basic.fundamental), or a class type for which a single conversion function to integral or enumeration type exists".

Proposed resolution (October, 2004):

Change 5.3.4  expr.new paragraph 6 as follows:

Every constant-expression in a direct-new-declarator shall be an integral constant expression (5.19  expr.const) and evaluate to a strictly positive value. The expression in a direct-new-declarator shall have be of integral type, or enumeration type (3.9.1), or a class type for which a single conversion function to integral or enumeration type exists (12.3  class.conv). If the expression is of class type, the expression is converted by calling the conversion function, and the result of the conversion is used in place of the original expression. The value of the expression shall bewith a non-negative value. [Example: ...

Proposed resolution (April, 2005):

Change 5.3.4  expr.new paragraph 6 as follows:

Every constant-expression in a direct-new-declarator shall be an integral constant expression (5.19  expr.const) and evaluate to a strictly positive value. The expression in a direct-new-declarator shall have integral or enumeration type (3.9.1  basic.fundamental) with a non-negative value be of integral type, enumeration type, or a class type for which a single conversion function to integral or enumeration type exists (12.3  class.conv). If the expression is of class type, the expression is converted by calling that conversion function, and the result of the conversion is used in place of the original expression. If the value of the expression is negative, the behavior is undefined. [Example: ...



429. Matching deallocation function chosen based on syntax or signature?

Section: 5.3.4  expr.new     Status: WP     Submitter: John Wilkinson     Date: 18 July 2003

[Voted into WP at October 2004 meeting.]

What does this example do?

  #include <stdio.h>
  #include <stdlib.h>

  struct A {
        void* operator new(size_t alloc_size, size_t dummy=0) {
                printf("A::operator new()\n");
                return malloc(alloc_size);
        };

        void operator delete(void* p, size_t s) {
                printf("A::delete %d\n", s);
        };


        A()  {printf("A constructing\n"); throw 2;};

  };

  int
  main() {
    try {
        A* ap = new A;
        delete ap;
    }
    catch(int) {printf("caught\n"); return 1;}
  }  

The fundamental issue here is whether the deletion-on-throw is driven by the syntax of the new (placement or non-placement) or by signature matching. If the former, the operator delete would be called with the second argument equal to the size of the class. If the latter, it would be called with the second argument 0.

Core issue 127 (in TC1) dealt with this topic. It removed some wording in 15.2  except.ctor paragraph 2 that implied a syntax-based interpretation, leaving wording in 5.3.4  expr.new paragraph 19 that is signature-based. But there is no accompanying rationale to confirm an explicit choice of the signature-based approach.

EDG and g++ get 0 for the second argument, matching the presumed core issue 127 resolution. But maybe this should be revisited.

Notes from October 2003 meeting:

There was widespread agreement that the compiler shouldn't just silently call the delete with either of the possible values. In the end, we decided it's smarter to issue an error on this case and force the programmer to say what he means.

Mike Miller's analysis of the status quo: 3.7.3.2  basic.stc.dynamic.deallocation paragraph 2 says that "operator delete(void*, std::size_t)" is a "usual (non-placement) deallocation function" if the class does not declare "operator delete(void*)." 3.7.3.1  basic.stc.dynamic.allocation does not use the same terminology for allocation functions, but the most reasonable way to understand the uses of the term "placement allocation function" in the Standard is as an allocation function that has more than one parameter and thus can (but need not) be called using the "new-placement" syntax described in 5.3.4  expr.new. (In considering issue 127, the core group discussed and endorsed the position that, "If a placement allocation function has default arguments for all its parameters except the first, it can be called using non-placement syntax.")

5.3.4  expr.new paragraph 19 says that any non-placement deallocation function matches a non-placement allocation function, and that a placement deallocation function matches a placement allocation function with the same parameter types after the first -- i.e., a non-placement deallocation function cannot match a placement allocation function. This makes sense, because non-placement ("usual") deallocation functions expect to free memory obtained from the system heap, which might not be the case for storage resulting from calling a placement allocation function.

According to this analysis, the example shows a placement allocation function and a non-placement deallocation function, so the deallocation function should not be invoked at all, and the memory will just leak.

Proposed Resolution (October 2003):

Add the following text at the end of 5.3.4  expr.new paragraph 19:

If the lookup finds the two-parameter form of a usual deallocation function (3.7.3.2  basic.stc.dynamic.deallocation), and that function, considered as a placement deallocation function, would have been selected as a match for the allocation function, the program is ill-formed. [Example:
struct S { 
  // Placement allocation function:
  static void* operator new(std::size_t, std::size_t); 

  // Usual (non-placement) deallocation function:
  static void operator delete(void*, std::size_t); 
}; 

S* p = new (0) S; // ill-formed: non-placement deallocation function matches 
                  // placement allocation function 
--- end example]



353. Is deallocation routine called if destructor throws exception in delete?

Section: 5.3.5  expr.delete     Status: WP     Submitter: Duane Smith     Date: 30 April 2002

[Voted into WP at April 2003 meeting.]

In a couple of comp.std.c++ threads, people have asked whether the Standard guarantees that the deallocation function will be called in a delete-expression if the destructor throws an exception. Most/all people have expressed the opinion that the deallocation function must be called in this case, although no one has been able to cite wording in the Standard supporting that view.

#include <new.h>
#include <stdio.h>
#include <stdlib.h>

static int flag = 0;

inline 
void operator delete(void* p) throw() 
{
   if (flag)
        printf("in deallocation function\n");
   free(p);
}

struct S {
    ~S() { throw 0; }
};

void f() {
    try {
        delete new S;
    }
    catch(...) { }
}

int main() {
       flag=1;
       f();
       flag=0;
       return 0;
}

Proposed resolution (October 2002):

Add to 5.3.5  expr.delete paragraph 7 the highlighted text:

The delete-expression will call a deallocation function (3.7.3.2  basic.stc.dynamic.deallocation) [Note: The deallocation function is called regardless of whether the destructor for the object or some element of the array throws an exception. ]



442. Incorrect use of null pointer constant in description of delete operator

Section: 5.3.5  expr.delete     Status: WP     Submitter: Matthias Hofmann     Date: 2 Dec 2003

[Voted into WP at October 2005 meeting.]

After some discussion in comp.lang.c++.moderated we came to the conclusion that there seems to be a defect in 5.3.5  expr.delete/4, which says:

The cast-expression in a delete-expression shall be evaluated exactly once. If the delete-expression calls the implementation deallocation function (3.7.3.2), and if the operand of the delete expression is not the null pointer constant, the deallocation function will deallocate the storage referenced by the pointer thus rendering the pointer invalid. [Note: the value of a pointer that refers to deallocated storage is indeterminate. ]

In the second sentence, the term "null pointer constant" should be changed to "null pointer". In its present form, the passage claims that the deallocation function will deallocate the storage refered to by a null pointer that did not come from a null pointer constant in the delete expression. Besides, how can the null pointer constant be the operand of a delete expression, as "delete 0" is an error because delete requires a pointer type or a class type having a single conversion function to a pointer type?

See also issue 348.

Proposed resolution:

Change the indicated sentence of 5.3.5  expr.delete paragraph 4 as follows:

If the delete-expression calls the implementation deallocation function (3.7.3.2  basic.stc.dynamic.deallocation), and if the value of the operand of the delete expression is not the a null pointer constant, the deallocation function will deallocate the storage referenced by the pointer thus rendering the pointer invalid.

Notes from October 2004 meeting:

This wording is superseded by, and this issue will be resolved by, the resolution of issue 348.

Proposed resolution (April, 2005):

This issue is resolved by the resolution of issue 348.




520. Old-style casts between incomplete class types

Section: 5.4  expr.cast     Status: WP     Submitter: comp.std.c++     Date: 19 May 2005

[Voted into WP at April, 2007 meeting.]

5.4  expr.cast paragraph 6 says,

The operand of a cast using the cast notation can be an rvalue of type “pointer to incomplete class type”. The destination type of a cast using the cast notation can be “pointer to incomplete class type”. In such cases, even if there is a inheritance relationship between the source and destination classes, whether the static_cast or reinterpret_cast interpretation is used is unspecified.

The wording seems to allow the following:

  1. casting from void pointer to incomplete type

  2.     struct A;
        struct B;
    
        void *v;
        A *a = (A*)v; // allowed to choose reinterpret_cast
    
  3. variant application of static or reinterpret casting

  4.     B *b = (B*)a;    // compiler can choose static_cast here
        A *aa = (A*)b;   // compiler can choose reinterpret_cast here
        assert(aa == a); // might not hold
    
  5. ability to somehow choose static_cast

  6. It's not entirely clear how a compiler can choose static_cast as 5.4  expr.cast paragraph 6 seems to allow. I believe the intent of 5.4  expr.cast paragraph 6 is to force the use of reinterpret_cast when either are incomplete class types and static_cast iff the compiler knows both types and there is a non-ambiguous hierarchy-traversal between that cast (or maybe not, core issue 242 talks about this). I cannot see any other interpretation because it isn't intuitive, every compiler I've tried agrees with me, and neither standard pointer conversions (4.10  conv.ptr paragraph 3) nor static_cast (5.2.9  expr.static.cast paragraph 5) talk about incomplete class types. If the committee agrees with me, I would like to see 4.10  conv.ptr paragraph 3 and 5.2.9  expr.static.cast paragraph 5 explicitly disallow incomplete class types and the wording of 5.4  expr.cast paragraph 6 changed to not allow any other interpretation.

Proposed resolution (April, 2006):

Change 5.4  expr.cast paragraph 6 as indicated:

The operand of a cast using the cast notation can be an rvalue of type “pointer to incomplete class type.” The destination type of a cast using the cast notation can be “pointer to incomplete class type.” In such cases, even if there is a inheritance relationship between the source and destination classes, whether the static_cast or reinterpret_cast interpretation is used is unspecified. If both the operand and destination types are class types and one or both are incomplete, it is unspecified whether the static_cast or the reinterpret_cast interpretation is used, even if there is an inheritance relationship between the two classes. [Note: For example, if the classes were defined later in the translation unit, a multi-pass compiler would be permitted to interpret a cast between pointers to the classes as if the class types were complete at that point. —end note]



497. Missing required initialization in example

Section: 5.5  expr.mptr.oper     Status: WP     Submitter: Giovanni Bajo     Date: 03 Jan 2005

[Voted into WP at October 2005 meeting.]

5.5  expr.mptr.oper paragraph 5 contains the following example:

    struct S {
        mutable int i;
    };
    const S cs;
    int S::* pm = &S::i;   // pm refers to mutable member S::i
    cs.*pm = 88;           // ill-formed: cs is a const object

The const object cs is not explicitly initialized, and class S does not have a user-declared default constructor. This makes the code ill-formed as per 8.5  dcl.init paragraph 9.

Proposed resolution (April, 2005):

Change the example in 5.5  expr.mptr.oper paragraph 5 to read as follows:

    struct S {
        S() : i(0) { }
        mutable int i;
    };
    void f()
    {
        const S cs;
        int S::* pm = &S::i;   // pm refers to mutable member S::i
        cs.*pm = 88;           // ill-formed: cs is a const object
    }



446. Does an lvalue-to-rvalue conversion on the "?" operator produce a temporary?

Section: 5.16  expr.cond     Status: WP     Submitter: John Potter     Date: 31 Dec 2003

[Voted into WP at October 2005 meeting.]

The problem occurs when the value of the operator is determined to be an rvalue, the selected argument is an lvalue, the type is a class type and a non-const member is invoked on the modifiable rvalue result.

    struct B {
        int v;
        B (int v) : v(v) { }
        void inc () { ++ v; }
        };
    struct D : B {
        D (int v) : B(v) { }
        };

    B b1(42);
    (0 ? B(13) : b1).inc();
    assert(b1.v == 42);

The types of the second and third operands are the same and one is an rvalue. Nothing changes until p6 where an lvalue to rvalue conversion is performed on the third operand. 12.2  class.temporary states that an lvalue to rvalue conversion produces a temporary and there is nothing to remove it. It seems clear that the assertion must pass, yet most implementations fail.

There seems to be a defect in p3 b2 b1. First, the conditions to get here and pass the test.

If E1 and E2 have class type, and the underlying class types are the same or one is a base class of the other: E1 can be converted to match E2 if the class of T2 is the same type as, or a base class of, the class of T1, and the cv-qualification of T2 is the same cv-qualification as, or a greater cv-qualification than, the cv-qualification of T1.

If both E1 and E2 are lvalues, passing the conditions here also passes the conditions for p3 b1. Thus, at least one is an rvalue. The case of two rvalues is not interesting and the action is covered by the case when E1 is an rvalue.

    (0 ? D(13) : b1).inc();
    assert(b1.v == 42);
E1 is changed to an rvalue of type T2 that still refers to the original source class object (or the appropriate subobject thereof). [Note: that is, no copy is made. ]

Having changed the rvalue to base type, we are back to the above case where an lvalue to rvalue conversion is required on the third operand at p6. Again, most implementations fail.

The remaining case, E1 an lvalue and E2 an rvalue, is the defect.

    D d1(42);
    (0 ? B(13) : d1).inc();
    assert(d1.v == 42);

The above quote states that an lvalue of type T1 is changed to an rvalue of type T2 without creating a temporary. This is in contradiction to everything else in the standard about lvalue to rvalue conversions. Most implementations pass in spite of the defect.

The usual accessible and unambiguous is missing from the base class.

There seems to be two possible solutions. Following other temporary creations would produce a temporary rvalue of type T1 and change it to an rvalue of type T2. Keeping the no copy aspect of this bullet intact would change the lvalue of type T1 to an lvalue of type T2. In this case the lvalue to rvalue conversion would happen in p6 as usual.

Suggested wording for p3 b2 b1

The base part:

If E1 and E2 have class type, and the underlying class types are the same or one is a base class of the other: E1 can be converted to match E2 if the class of T2 is the same type as, or an accessible and unambiguous base class of, the class of T1, and the cv-qualification of T2 is the same cv-qualification as, or a greater cv-qualification than, the cv-qualification of T1. If the conversion is applied:

The same type temporary version:

If E1 is an lvalue, an lvalue to rvalue conversion is applied. The resulting or original rvalue is changed to an rvalue of type T2 that refers to the same class object (or the appropriate subobject thereof). [Note: that is, no copy is made in changing the type of the rvalue. ]

The never copy version:

The lvalue(rvalue) E1 is changed to an lvalue(rvalue) of type T2 that refers to the original class object (or the appropriate subobject thereof). [Note: that is, no copy is made. ]

The test case was posted to clc++m and results for implementations were reported.

#include <cassert>
struct B {
    int v;
    B (int v) : v(v) { }
    void inc () { ++ v; }
    };
struct D : B {
    D (int v) : B(v) { }
    };
int main () {
    B b1(42);
    D d1(42);
    (0 ? B(13) : b1).inc();
    assert(b1.v == 42);
    (0 ? D(13) : b1).inc();
    assert(b1.v == 42);
    (0 ? B(13) : d1).inc();
    assert(d1.v == 42);
    }

// CbuilderX(EDG301) FFF  Rob Williscroft
// ICC-8.0           FFF  Alexander Stippler
// COMO-4.301        FFF  Alexander Stippler

// BCC-5.4           FFP  Rob Williscroft
// BCC32-5.5         FFP  John Potter
// BCC32-5.65        FFP  Rob Williscroft
// VC-6.0            FFP  Stephen Howe
// VC-7.0            FFP  Ben Hutchings
// VC-7.1            FFP  Stephen Howe
// OpenWatcom-1.1    FFP  Stephen Howe

// Sun C++-6.2       PFF  Ron Natalie

// GCC-3.2           PFP  John Potter
// GCC-3.3           PFP  Alexander Stippler

// GCC-2.95          PPP  Ben Hutchings
// GCC-3.4           PPP  Florian Weimer

I see no defect with regards to lvalue to rvalue conversions; however, there seems to be disagreement about what it means by implementers. It may not be surprising because 5.16 and passing a POD struct to an ellipsis are the only places where an lvalue to rvalue conversion applies to a class type. Most lvalue to rvalue conversions are on basic types as operands of builtin operators.

Notes from the March 2004 meeting:

We decided all "?" operators that return a class rvalue should copy the second or third operand to a temporary. See issue 86.

Proposed resolution (October 2004):

  1. Change 5.16  expr.cond paragraph 3 bullet 2 sub-bullet 1 as follows:

    if E1 and E2 have class type, and the underlying class types are the same or one is a base class of the other: E1 can be converted to match E2 if the class of T2 is the same type as, or a base class of, the class of T1, and the cv-qualification of T2 is the same cv-qualification as, or a greater cv-qualification than, the cv-qualification of T1. If the conversion is applied, E1 is changed to an rvalue of type T2 that still refers to the original source class object (or the appropriate subobject thereof). [Note: that is, no copy is made. —end note] by copy-initializing a temporary of type T2 from E1 and using that temporary as the converted operand.
  2. Change 5.16  expr.cond paragraph 6 bullet 1 as follows:

    The second and third operands have the same type; the result is of that type. If the operands have class type, the result is an rvalue temporary of the result type, which is copy-initialized from either the second operand or the third operand depending on the value of the first operand.
  3. Change 4.1  conv.lval paragraph 2 as follows:

    The value contained in the object indicated by the lvalue is the rvalue result. When an lvalue-to-rvalue conversion occurs within the operand of sizeof (5.3.3  expr.sizeof) the value contained in the referenced object is not accessed, since that operator does not evaluate its operand. Otherwise, if the lvalue has a class type, the conversion copy-initializes a temporary of type T from the lvalue and the result of the conversion is an rvalue for the temporary. Otherwise, the value contained in the object indicated by the lvalue is the rvalue result.

[Note: this wording partially resolves issue 86. See also issue 462.]




366. String literal allowed in integral constant expression?

Section: 5.19  expr.const     Status: WP     Submitter: Martin v. Loewis     Date: 29 July 2002

[Voted into WP at October 2003 meeting.]

According to 16.1  cpp.cond paragraph 1, the if-group

#if "Hello, world"

is well-formed, since it is an integral constant expression. Since that may not be obvious, here is why:

5.19  expr.const paragraph 1 says that an integral constant expression may involve literals (2.13  lex.literal); "Hello, world" is a literal. It restricts operators to not use certain type conversions; this expression does not use type conversions. It further disallows functions, class objects, pointers, ... - this expression is none of those, since it is an array.

However, 16.1  cpp.cond paragraph 6 does not explain what to do with this if-group, since the expression evaluates neither to false(zero) nor true(non-zero).

Proposed resolution (October 2002):

Change the beginning of the second sentence of 5.19  expr.const paragraph 1 which currently reads

An integral constant-expression can involve only literals (2.13  lex.literal), ...
to say
An integral constant-expression can involve only literals of arithmetic types (2.13  lex.literal, 3.9.1  basic.fundamental), ...




367. throw operator allowed in constant expression?

Section: 5.19  expr.const     Status: WP     Submitter: Martin v. Loewis     Date: 29 July 2002

[Voted into WP at the October, 2006 meeting.]

The following translation unit appears to be well-formed.

int x[true?throw 4:5];

According to 5.19  expr.const, this appears to be an integral constant expression: it is a conditional expression, involves only literals, and no assignment, increment, decrement, function-call, or comma operators. However, if this is well-formed, the standard gives no meaning to this declaration, since the array bound (8.3.4  dcl.array paragraph 1) cannot be computed.

I believe the defect is that throw expressions should also be banned from constant expressions.

Notes from October 2002 meeting:

We should also check on new and delete.

Notes from the April, 2005 meeting:

Although it could be argued that all three of these operators potentially involve function calls — throw to std::terminate, new and delete to the corresponding allocation and deallocation functions — and thus would already be excluded from constant expressions, this reasoning was considered to be too subtle to allow closing the issue with no change. A modification that explicitly clarifies the status of these operators will be drafted.

Proposed resolution (October, 2005):

Change the last sentence of 5.19  expr.const as indicated:

In particular, except in sizeof expressions, functions, class objects, pointers, or references shall not be used, and assignment, increment, decrement, function-call function call (including new-expressions and delete-expressions), or comma operators, or throw-expressions shall not be used.

Note: this sentence is also changed by the resolution of issue 530.




457. Wording nit on use of const variables in constant expressions

Section: 5.19  expr.const     Status: WP     Submitter: Mark Mitchell     Date: 03 Feb 2004

[Voted into WP at April 2005 meeting.]

I'm looking at 5.19  expr.const. I see:

An integral constant-expression can involve only ... const variables or static data members of integral or enumeration types initialized with constant expressions ...

Shouldn't that be "const non-volatile"?

It seems weird to say that:

  const volatile int i = 3;
  int j[i];
is valid.

Steve Adamczyk: See issue 76, which made the similar change to 7.1.5.1  dcl.type.cv paragraph 2, and probably should have changed this one as well.

Proposed resolution (October, 2004):

Change the first sentence in the second part of 5.19  expr.const paragraph 1 as follows:

An integral constant-expression can involve only literals of arithmetic types (2.13  lex.literal, 3.9.1  basic.fundamental), enumerators, non-volatile const variables or static data members of integral or enumeration types initialized with constant expressions (8.5  dcl.init), non-type template parameters of integral or enumeration types, and sizeof expressions.



281. inline specifier in friend declarations

Section: 7.1.2  dcl.fct.spec     Status: WP     Submitter: John Spicer     Date: 24 Apr 2001

[Moved to DR at October 2002 meeting.]

There is currently no restriction on the use of the inline specifier in friend declarations. That would mean that the following usage is permitted:

    struct A {
        void f();
    };

    struct B {
        friend inline void A::f();
    };

    void A::f(){}

I believe this should be disallowed because a friend declaration in one class should not be able to change attributes of a member function of another class.

More generally, I think that the inline attribute should only be permitted in friend declarations that are definitions.

Notes from the 04/01 meeting:

The consensus agreed with the suggested resolution. This outcome would be similar to the resolution of issue 136.

Proposed resolution (10/01):

Add to the end of 7.1.2  dcl.fct.spec paragraph 3:

If the inline specifier is used in a friend declaration, that declaration shall be a definition or the function shall have previously been declared inline.



317. Can a function be declared inline after it has been called?

Section: 7.1.2  dcl.fct.spec     Status: WP     Submitter: Steve Clamage     Date: 14 Oct 2001

[Voted into WP at October 2005 meeting.]

Steve Clamage: Consider this sequence of declarations:

  void foo() { ... }
  inline void foo();
The non-inline definition of foo precedes the inline declaration. It seems to me this code should be ill-formed, but I could not find anything in the standard to cover the situation.

Bjarne Stroustrup: Neither could I, so I looked in the ARM, which addressed this case (apparently for member function only) in some detail in 7.1.2 (pp103-104).

The ARM allows declaring a function inline after its initial declaration, as long as it has not been called.

Steve Clamage: If the above code is valid, how about this:

  void foo() { ... }    // define foo
  void bar() { foo(); } // use foo
  inline void foo();    // declare foo inline

Bjarne Stroustrup: ... and [the ARM] disallows declaring a function inline after it has been called.

This may still be a good resolution.

Steve Clamage: But the situation in the ARM is the reverse: Declare a function inline, and define it later (with no intervening call). That's a long-standing rule in C++, and allows you to write member function definitions outside the class.

In my example, the compiler could reasonably process the entire function as out-of-line, and not discover the inline declaration until it was too late to save the information necessary for inline generation. The equivalent of another compiler pass would be needed to handle this situation.

Bjarne Stroustrup: I see, and I think your argument it conclusive.

Steve Clamage: I'd like to open a core issue on this point, and I recommend wording along the lines of: "A function defined without an inline specifier shall not be followed by a declaration having an inline specifier."

I'd still like to allow the common idiom

  class T {
    int f();
  };
  inline int T::f() { ... }

Martin Sebor: Since the inline keyword is just a hint to the compiler, I don't see any harm in allowing the construct. Your hypothetical compiler can simply ignore the inline on the second declaration. On the other hand, I feel that adding another special rule will unnecessarily complicate the language.

Steve Clamage: The inline specifier is more than a hint. You can have multiple definitions of inline functions, but only one definition of a function not declared inline. In particular, suppose the above example were in a header file, and included multiple times in a program.

Proposed resolution (October, 2004):

Add the indicated words to 7.1.2  dcl.fct.spec paragraph 4:

An inline function shall be defined in every translation unit in which it is used and shall have exactly the same definition in every case (3.2  basic.def.odr). [Note: a call to the inline function may be encountered before its definition appears in the translation unit. —end note] If the definition of a function appears in a translation unit before its first declaration as inline, the program is ill-formed. If a function with external linkage is declared inline in one translation unit...



396. Misleading note regarding use of auto for disambiguation

Section: 7.1.2  dcl.fct.spec     Status: WP     Submitter: Herb Sutter     Date: 3 Jan 2003

[Voted into WP at March 2004 meeting.]

BTW, I noticed that the following note in 7.1.1  dcl.stc paragraph 2 doesn't seem to have made it onto the issues list or into the TR:

[Note: hence, the auto specifier is almost always redundant and not often used; one use of auto is to distinguish a declaration-statement from an expression-statement (stmt.ambig) explicitly. --- end note]

I thought that this was well known to be incorrect, because using auto does not disambiguate this. Writing:

  auto int f();
is still a declaration of a function f, just now with an error since the function's return type may not use an auto storage class specifier. I suppose an error is an improvement over a silent ambiguity going the wrong way, but it's still not a solution for the user who wants to express the other in a compilable way.

Proposed resolution: Replace that note with the following note:

[Note: hence, the auto specifier is always redundant and not often used. --- end note]

John Spicer: I support the proposed change, but I think the disambiguation case is not the one that you describe. An example of the supposed disambiguation is:

  int i;
  int j;
  int main()
  {
    int(i);  // declares i, not reference to ::i
    auto int(j);  // declares j, not reference to ::j
  }

cfront would take "int(i)" as a cast of ::i, so the auto would force what it would otherwise treat as a statement to be considered a declaration (cfront 3.0 warned that this would change in the future).

In a conforming compiler the auto is always redundant (as you say) because anything that could be considered a valid declaration should be treated as one.

Proposed resolution (April 2003):

Replace 7.1.1  dcl.stc paragraph 2

[Note: hence, the auto specifier is almost always redundant and not often used; one use of auto is to distinguish a declaration-statement from an expression-statement (6.8  stmt.ambig) explicitly. --- end note]
with
[Note: hence, the auto specifier is always redundant and not often used. One use of auto is to distinguish a declaration-statement from an expression-statement explicitly rather than relying on the disambiguation rules (6.8  stmt.ambig), which may aid readers. --- end note]




397. Same address for string literals from default arguments in inline functions?

Section: 7.1.2  dcl.fct.spec     Status: WP     Submitter: Mark Mitchell     Date: 13 Jan 2003

[Voted into WP at April, 2007 meeting.]

Are string literals from default arguments used in extern inlines supposed to have the same addresses across all translation units?

  void f(const char* = "s")
  inline g() {
    f();
  }

Must the "s" strings be the same in all copies of the inline function?

Steve Adamczyk: The totality of the standard's wisdom on this topic is (7.1.2  dcl.fct.spec paragraph 4):

A string literal in an extern inline function is the same object in different translation units.

I'd hazard a guess that a literal in a default argument expression is not "in" the extern inline function (it doesn't appear in the tokens of the function), and therefore it need not be the same in different translation units.

I don't know that users would expect such strings to have the same address, and an equally valid (and incompatible) expectation would be that the same string literal would be used for every expansion of a given default argument in a single translation unit.

Notes from April 2003 meeting:

The core working group feels that the address of a string literal should be guaranteed to be the same only if it actually appears textually within the body of the inline function. So a string in a default argument expression in a block extern declaration inside the body of a function would be the same in all instances of the function. On the other hand, a string in a default argument expression in the header of the function (i.e., outside of the body) would not be the same.

Proposed resolution (April 2003):

Change the last sentence and add the note to the end of 7.1.2  dcl.fct.spec paragraph 4:

A string literal in the body of an extern inline function is the same object in different translation units. [Note: A string literal that is encountered only in the context of a function call (in the default argument expression of the called function), is not “in” the extern inline function.]

Notes from October 2003 meeting:

We discussed ctor-initializer lists and decided that they are also part of the body. We've asked Clark Nelson to work on syntax changes to give us a syntax term for the body of a function so we can refer to it here. See also issue 452, which could use this term.

(October, 2005: moved to “review” status in concert with issue 452. With that resolution, the wording above needs no further changes.)

Proposed resolution (April, 2006):

Change the last sentence and add the note to the end of 7.1.2  dcl.fct.spec paragraph 4:

A string literal in the body of an extern inline function is the same object in different translation units. [Note: A string literal appearing in a default argument expression is not considered to be “in the body” of an inline function merely by virtue of the expression’s use in a function call from that inline function. —end note]



477. Can virtual appear in a friend declaration?

Section: 7.1.2  dcl.fct.spec     Status: WP     Submitter: Daveed Vandevoorde     Date: 23 Sep 2004

[Voted into WP at the October, 2006 meeting.]

I couldn't find wording that makes it invalid to say friend virtual... The closest seems to be 7.1.2  dcl.fct.spec paragraph 5, which says:

The virtual specifier shall only be used in declarations of nonstatic class member functions that appear within a member-specification of a class definition; see 10.3  class.virtual.

I don't think that excludes a friend declaration (which is a valid member-specification by 9.2  class.mem).

John Spicer: I agree that virtual should not be allowed on friend declarations. I think the wording in 7.1.2  dcl.fct.spec is intended to be the declaration of a function within its class, although I think the wording should be improved to make it clearer.

Proposed resolution (October, 2005):

Change 7.1.2  dcl.fct.spec paragraphs 5-6 as indicated:

The virtual specifier shall only be used only in declarations the initial declaration of a non-static class member functions that appear within a member-specification of a class definition function; see 10.3  class.virtual.

The explicit specifier shall be used only in declarations the declaration of constructors a constructor within a its class definition; see 12.3.1  class.conv.ctor.




424. Wording problem with issue 56 resolution on redeclaring typedefs in class scope

Section: 7.1.3  dcl.typedef     Status: WP     Submitter: Daveed Vandevoorde     Date: 25 June 2003

[Voted into WP at March 2004 meeting.]

I wonder if perhaps the core issue 56 change in 7.1.3  dcl.typedef paragraph 2 wasn't quite careful enough. The intent was to remove the allowance for:

  struct S {
    typedef int I;
    typedef int I;
  };

but I think it also disallows the following:

  class B {
    typedef struct A {} A;
    void f(struct B::A*p);
  };

See also issue 407.

Proposed resolution (October 2003):

At the end of 7.1.3  dcl.typedef paragraph 2, add the following:

In a given class scope, a typedef specifier can be used to redefine any class-name declared in that scope that is not also a typedef-name to refer to the type to which it already refers. [Example:
  struct S {
    typedef struct A {} A;  // OK
    typedef struct B B;     // OK
    typedef A A;            // error
  };
]



283. Template type-parameters are not syntactically type-names

Section: 7.1.5.2  dcl.type.simple     Status: WP     Submitter: Clark Nelson     Date: 01 May 2001

[Voted into WP at April 2003 meeting.]

Although 14.1  temp.param paragraph 3 contains an assertion that

A type-parameter defines its identifier to be a type-name (if declared with class or typename)

the grammar in 7.1.5.2  dcl.type.simple paragraph 1 says that a type-name is either a class-name, an enum-name, or a typedef-name. The identifier in a template type-parameter is none of those. One possibility might be to equate the identifier with a typedef-name instead of directly with a type-name, which would have the advantage of not requiring parallel treatment of the two in situations where they are treated the same (e.g., in elaborated-type-specifiers, see issue 245). See also issue 215.

Proposed resolution (Clark Nelson, March 2002):

In 14.1  temp.param paragraph 3, change "A type-parameter defines its identifier to be a type-name" to "A type-parameter defines its identifier to be a typedef-name"

In 7.1.5.3  dcl.type.elab paragraph 2, change "If the identifier resolves to a typedef-name or a template type-parameter" to "If the identifier resolves to a typedef-name".

This has been consolidated with the edits for some other issues. See N1376=02-0034.




516. Use of signed in bit-field declarations

Section: 7.1.5.2  dcl.type.simple     Status: WP     Submitter: comp.std.c++     Date: 25 Apr 2005

[Voted into WP at the October, 2006 meeting.]

7.1.5.2  dcl.type.simple paragraph 3 reads,

It is implementation-defined whether bit-fields and objects of char type are represented as signed or unsigned quantities. The signed specifier forces char objects and bit-fields to be signed; it is redundant with other integral types.

The last sentence in that quote is misleading w.r.t. bit-fields. The first sentence in that quote is correct but incomplete.

Proposed fix: change the two sentences to read:

It is implementation-defined whether objects of char type are represented as signed or unsigned quantities. The signed specifier forces char objects signed; it is redundant with other integral types except when declaring bit-fields (9.6  class.bit).

Proposed resolution (October, 2005):

Change 7.1.5.2  dcl.type.simple paragraph 3 as indicated:

When multiple simple-type-specifiers are allowed, they can be freely intermixed with other decl-specifiers in any order. [Note: It is implementation-defined whether bit-fields and objects of char type and certain bit-fields (9.6  class.bit) are represented as signed or unsigned quantities. The signed specifier forces bit-fields and char objects and bit-fields to be signed; it is redundant with other integral types in other contexts. end note]



172. Unsigned int as underlying type of enum

Section: 7.2  dcl.enum     Status: WP     Submitter: Bjarne Stroustrup     Date: 26 Sep 1999

[Moved to DR at October 2002 meeting.]

According to 7.2  dcl.enum paragraph 5, the underlying type of an enum is an unspecified integral type, which could potentially be unsigned int. The promotion rules in 4.5  conv.prom paragraph 2 say that such an enumeration value used in an expression will be promoted to unsigned int. This means that a conforming implementation could give the value false for the following code:

    enum { zero };
    -1 < zero;       // might be false
This is counterintuitive. Perhaps the description of the underlying type of an enumeration should say that an unsigned underlying type can be used only if the values of the enumerators cannot be represented in the corresponding signed type. This approach would be consistent with the treatment of integral promotion of bitfields (4.5  conv.prom paragraph 3).

On a related note, 7.2  dcl.enum paragraph 5 says,

the underlying type shall not be larger than int unless the value of an enumerator cannot fit in an int or unsigned int.

This specification does not allow for an enumeration like

    enum { a = -1, b = UINT_MAX };

Since each enumerator can fit in an int or unsigned int, the underlying type is required to be no larger than int, even though there is no such type that can represent all the enumerators.

Proposed resolution (04/01; obsolete, see below):

Change 7.2  dcl.enum paragraph 5 as follows:

It is implementation-defined which integral type is used as the underlying type for an enumeration except that the underlying type shall not be larger than int unless the value of an enumerator cannot fit in an int or unsigned int neither int nor unsigned int can represent all the enumerator values. Furthermore, the underlying type shall not be an unsigned type if the corresponding signed type can represent all the enumerator values.

See also issue 58.

Notes from 04/01 meeting:

It was noted that 4.5  conv.prom promotes unsigned types smaller than int to (signed) int. The signedness chosen by an implementation for small underlying types is therefore unobservable, so the last sentence of the proposed resolution above should apply only to int and larger types. This observation also prompted discussion of an alternative approach to resolving the issue, in which the bmin and bmax of the enumeration would determine the promoted type rather than the underlying type.

Proposed resolution (10/01):

Change 4.5  conv.prom paragraph 2 from

An rvalue of type wchar_t (3.9.1  basic.fundamental) or an enumeration type (7.2  dcl.enum) can be converted to an rvalue of the first of the following types that can represent all the values of its underlying type: int, unsigned int, long, or unsigned long.
to
An rvalue of type wchar_t (3.9.1  basic.fundamental) can be converted to an rvalue of the first of the following types that can represent all the values of its underlying type: int, unsigned int, long, or unsigned long. An rvalue of an enumeration type (7.2  dcl.enum) can be converted to an rvalue of the first of the following types that can represent all the values of the enumeration (i.e., the values in the range bmin to bmax as described in 7.2  dcl.enum): int, unsigned int, long, or unsigned long.




377. Enum whose enumerators will not fit in any integral type

Section: 7.2  dcl.enum     Status: WP     Submitter: Mark Mitchell     Date: 30 August 2002

[Voted into WP at April 2003 meeting.]

7.2  dcl.enum defines the underlying type of an enumeration as an integral type "that can represent all the enumerator values defined in the enumeration".

What does the standard say about this code:

  enum E { a = LONG_MIN, b = ULONG_MAX };

?

I think this should be ill-formed.

Proposed resolution:

In 7.2  dcl.enum paragraph 5 after

The underlying type of an enumeration is an integral type that can represent all the enumerator values defined in the enumeration.
insert
If no integral type can represent all the enumerator values, the enumeration is ill-formed.




518. Trailing comma following enumerator-list

Section: 7.2  dcl.enum     Status: WP     Submitter: Charles Bryant     Date: 10 May 2005

[Voted into WP at April, 2006 meeting.]

The C language (since C99), and some C++ compilers, accept:

    enum { FOO, };

as syntactically valid. It would be useful

This proposed change is to permit a trailing comma in enum by adding:

enum identifieropt { enumerator-list , }

as an alternative definition for the enum-specifier nonterminal in 7.2  dcl.enum paragraph 1.

Proposed resolution (October, 2005):

Change the grammar in 7.2  dcl.enum paragraph 1 as indicated:

enum-specifier:



540. Propagation of cv-qualifiers in reference-to-reference collapse

Section: 7.3.1  namespace.def     Status: WP     Submitter: Russell Yanofsky     Date: 24 September 2005

[Voted into the WP at the October, 2006 meeting as part of paper J16/06-0188 = WG21 N2118.]

The resolution of issue 106 specifies that an attempt to create a type “reference to cv1 T,” where T is a typedef or template parameter of the type “reference to cv2 S,” actually creates the type “reference to cv12 S,” where cv12 is the union of the two sets of cv-qualifiers.

One objection that has been raised to this resolution is that it is inconsistent with the treatment of cv-qualification and references specified in 8.3.2  dcl.ref paragraph 1, which says that cv-qualifiers applied to a typedef or template argument that is a reference type are ignored. For example:

    typedef int& intref;
    const intref r1;       // reference to int
    const intref& r2;      // reference to const int

In fact, however, these two declarations are quite different. In the declaration of r1, const applies to a “top-level” reference, while in the declaration of t2, it occurs under a reference. In general, cv-qualifiers that appear under a reference are preserved, even if the type appears in a context in which top-level cv-qualification is removed, for example, in determining the type of a function from parameter types (8.3.5  dcl.fct paragraph 3) and in template argument deduction (14.8.2.1  temp.deduct.call paragraph 2).

Another objection to the resolution is that type composition gives different results in a single declaration than it does when separated into two declarations. For example:

    template <class T>
    struct X {
       typedef T const T_const;
       typedef T_const& type1;
       typedef T const& type2;
    };

    X<int&>::type1 t1;    // int&
    X<int&>::type2 t2;    // int const&

The initial motivation for the propagation of cv-qualification during reference-to-reference collapse was to prevent inadvertent loss of cv-qualifiers in contexts in which it could make a difference. For example, if the resolution were changed to discard, rather than propagate, embedded cv-qualification, overload resolution could surprisingly select a non-const version of a member function:

   struct X {
       void g();
       void g() const;
   };

   template <typename T> struct S {
       static void f(const T& t) {
           t.g();    // const or non-const???
       }
   };

   X x;

   void q() {
       S<X>::f(x);    // calls X::g() const
       S<X&>::f(x);   // calls X::g()
   }

Another potentially-surprising outcome of dropping embedded cv-qualifiers would be:

   template <typename T> struct A {
       void f(T&);          // mutating version
       void f(const T&);    // non-mutating version
   };

   A<int&> ai;    // Ill-formed: A<int&> declares f(int&) twice

On the other hand, those who would like to see the resolution changed to discard embedded cv-qualifiers observe that these examples are too simple to be representative of real-world code. In general, it is unrealistic to expect that a template written with non-reference type parameters in mind will automatically work correctly with reference type parameters as a result of applying the issue 106 resolution. Instead, template metaprogramming allows the template author to choose explicitly whether cv-qualifiers are propagated or dropped, according to the intended use of the template, and it is more important to respect the reasonable intuition that a declaration involving a template parameter will not change the type that the parameter represents.

As a sample of real-world code, tr1::tuple was examined. In both cases — the current resolution of issue 106 and one in which embedded cv-qualifiers were dropped — some metaprogramming was required to implement the intended interface, although the version reflecting the revised resolution was somewhat simpler.

Notes from the October, 2005 meeting:

The consensus of the CWG was that the resolution of issue 106 should be revised not to propagate embedded cv-qualification.

Note (February, 2006):

The wording included in the rvalue-reference paper, J16/06-0022 = WG21 N1952, incorporates changes intended to implement the October, 2005 consensus of the CWG.




11. How do the keywords typename/template interact with using-declarations?

Section: 7.3.3  namespace.udecl     Status: WP     Submitter: Bill Gibbons     Date: unknown

[Voted into WP at March 2004 meeting.]

Issue 1:

The working paper is not clear about how the typename/template keywords interact with using-declarations:

     template<class T> struct A {
         typedef int X;
     };
     
     template<class T> void f() {
         typename A<T>::X a;      // OK
         using typename A<T>::X;  // OK
         typename X b;  // ill-formed; X must be qualified
         X c;  // is this OK?
     }
When the rules for typename and the similar use of template were decided, we chose to require that they be used at every reference. The way to avoid typename at every use is to declare a typedef; then the typedef name itself is known to be a type. For using-declarations, we decided that they do not introduce new declarations but rather are aliases for existing declarations, like symbolic links. This makes it unclear whether the declaration "X c;" above should be well-formed, because there is no new name declared so there is no declaration with a "this is a type" attribute. (The same problem would occur with the template keyword when a member template of a dependent class is used). I think these are the main options:
  1. Continue to allow typename in using-declarations, and template (for member templates) too. Attach the "is a type" or "is a template" attribute to the placeholder name which the using-declaration "declares"
  2. Disallow typename and template in using-declarations (just as class-keys are disallowed now). Allow typename and template before unqualified names which refer to dependent qualified names through using-declarations.
  3. Document that this is broken.
Suggested Resolution:

The core WG already resolved this issue according to (1), but the wording does not seem to have been added to the standard. New wording needs to be drafted.

Issue 2:

Either way, one more point needs clarification. If the first option is adopted:

     template<class T> struct A {
         struct X { };
     };
     
     template<class T> void g() {
         using typename A<T>::X;
         X c;    // if this is OK, then X by itself is a  type
         int X;  // is this OK?
     }
When "g" is instantiated, the two declarations of X are compatible (7.3.3  namespace.udecl paragraph 10). But there is no way to know this when the definition of "g" is compiled. I think this case should be ill-formed under the first option. (It cannot happen under the second option.) If the second option is adopted:
     template<class T> struct A {
         struct X { };
     };
     
     template<class T> void g() {
         using A<T>::X;
         int X;  // is this OK?
     }
Again, the instantiation would work but there is no way to know that in the template definition. I think this case should be ill-formed under the second option. (It would already be ill-formed under the first option.)

From John Spicer:

The "not a new declaration" decision is more of a guiding principle than a hard and fast rule. For example, a name introduced in a using-declaration can have different access than the original declaration.

Like symbolic links, a using-declaration can be viewed as a declaration that declares an alias to another name, much like a typedef.

In my opinion, "X c;" is already well-formed. Why would we permit typename to be used in a using-declaration if not to permit this precise usage?

In my opinion, all that needs to be done is to clarify that the "typeness" or "templateness" attribute of the name referenced in the using-declaration is attached to the alias created by the using-declaration. This is solution #1.

Tentative Resolution:

The rules for multiple declarations with the same name in the same scope should treat a using-declaration which names a type as a typedef, just as a typedef of a class name is treated as a class declaration. This needs drafting work. Also see Core issue 36.

Rationale (04/99): Any semantics associated with the typename keyword in using-declarations should be considered an extension.

Notes from the April 2003 meeting:

This was reopened because we are now considering extensions again. We agreed that it is desirable for the typename to be "sticky" on a using-declaration, i.e., references to the name introduced by the using-declaration are known to be type names without the use of the typename keyword (which can't be specified on an unqualified name anyway, as of now). The related issue with the template keyword already has a separate issue 109.

Issue 2 deals with the "struct hack." There is an example in 7.3.3  namespace.udecl paragraph 10 that shows a use of using-declarations to import two names that coexist because of the "struct hack." After some deliberation, we decided that the template-dependent using-declaration case is different enough that we did not have to support the "struct hack" in that case. A name introduced in such a case is like a typedef, and no other hidden type can be accessed through an elaborated type specifier.

Proposed resolution (April 2003, revised October 2003):

Add a new paragraph to the bottom of 7.3.3  namespace.udecl:

If a using-declaration uses the keyword typename and specifies a dependent name (14.6.2  temp.dep), the name introduced by the using-declaration is treated as a typedef-name (7.1.3  dcl.typedef).



258. using-declarations and cv-qualifiers

Section: 7.3.3  namespace.udecl     Status: WP     Submitter: Liam Fitzpatrick     Date: 2 Nov 2000

[Voted into WP at April 2003 meeting.]

According to 7.3.3  namespace.udecl paragraph 12,

When a using-declaration brings names from a base class into a derived class scope, member functions in the derived class override and/or hide member functions with the same name and parameter types in a base class (rather than conflicting).

Note that this description says nothing about the cv-qualification of the hiding and hidden member functions. This means, for instance, that a non-const member function in the derived class hides a const member function with the same name and parameter types instead of overloading it in the derived class scope. For example,

    struct A {
      virtual int f() const;
      virtual int f();
    };
    struct B: A {
      B();
      int f();
      using A::f;
    };

    const B cb;
    int i = cb.f(); // ill-formed: A::f() const hidden in B

The same terminology is used in 10.3  class.virtual paragraph 2:

If a virtual member function vf is declared in a class Base and in a class Derived, derived directly or indirectly from Base, a member function vf with the same name and same parameter list as Base::vf is declared, then Derived::vf is also virtual (whether or not it is so declared) and it overrides Base::vf.

Notes on the 04/01 meeting:

The hiding and overriding should be on the basis of the function signature, which includes any cv-qualification on the function.

Proposed resolution (04/02):

In 7.3.3  namespace.udecl paragraph 12 change:

When a using-declaration brings names from a base class into a derived class scope, member functions in the derived class override and/or hide member functions with the same name and parameter types in a base class (rather than conflicting).
to read:
When a using-declaration brings names from a base class into a derived class scope, member functions and member function templates in the derived class override and/or hide member functions and member function templates with the same name, parameter-type-list (8.3.5  dcl.fct), and cv-qualification in a base class (rather than conflicting).

In 10.3  class.virtual paragraph 2 change:

If a virtual member function vf is declared in a class Base and in a class Derived, derived directly or indirectly from Base, a member function vf with the same name and same parameter list as Base::vf is declared, then Derived::vf is also virtual (whether or not it is so declared) and it overrides Base::vf.
to read:
If a virtual member function vf is declared in a class Base and in a class Derived, derived directly or indirectly from Base, a member function vf with the same name, parameter-type-list (8.3.5  dcl.fct), and cv-qualification as Base::vf is declared, then Derived::vf is also virtual (whether or not it is so declared) and it overrides Base::vf.

See issue 140 for the definition of parameter-type-list.




460. Can a using-declaration name a namespace?

Section: 7.3.3  namespace.udecl     Status: WP     Submitter: John Spicer     Date: 12 Feb 2004

[Voted into WP at April 2005 meeting.]

Can a using-declaration be used to import a namespace?

namespace my_namespace{
  namespace my_namespace2 {
    int function_of_my_name_space(){ return 2;}
  }
}

int main (){
  using  ::my_namespace::my_namespace2;
  return my_namespace2::function_of_my_name_space();
}

Several popular compilers give an error on this, but there doesn't seem to be anything in 7.3.3  namespace.udecl that prohibits it. It should be noted that the user can get the same effect by using a namespace alias:

  namespace my_namespace2 = ::my_namespace::my_namespace2;

Notes from the March 2004 meeting:

We agree that it should be an error.

Proposed resolution (October, 2004):

Add the following as a new paragraph after 7.3.3  namespace.udecl paragraph 5:

A using-declaration shall not name a namespace;



4. Does extern "C" affect the linkage of function names with internal linkage?

Section: 7.5  dcl.link     Status: WP     Submitter: Mike Anderson     Date: unknown

[Moved to DR at 4/01 meeting.]

7.5  dcl.link paragraph 6 says the following:

Does this apply to static functions as well? For example, is the following well-formed?
        extern "C" {
            static void f(int) {}
            static void f(float) {}
        };
Can a function with internal linkage "have C linkage" at all (assuming that phrase means "has extern "C" linkage"), for how can a function be extern "C" if it's not extern? The function type can have extern "C" linkage — but I think that's independent of the linkage of the function name. It should be perfectly reasonable to say, in the example above, that extern "C" applies only to the types of f(int) and f(float), not to the function names, and that the rule in 7.5  dcl.link paragraph 6 doesn't apply.

Suggested resolution: The extern "C" linkage specification applies only to the type of functions with internal linkage, and therefore some of the rules that have to do with name overloading don't apply.

Proposed Resolution:

The intent is to distingush implicit linkage from explicit linkage for both name linkage and language (function type) linkage. (It might be more clear to use the terms name linkage and type linkage to distinguish these concepts. A function can have a name with one kind of linkage and a type with a different kind of linkage. The function itself has no linkage: it has no name, only the declaration has a name. This becomes more obvious when you consider function pointers.)

The tentatively agreed proposal is to apply implicit linkage to names declared in brace-enclosed linkage specifications and to non-top-level names declared in simple linkage specifications; and to apply explicit linkage to top-level names declared in simple linkage specifications.

The language linkage of any function type formed through a function declarator is that of the nearest enclosing linkage-specification. For purposes of determining whether the declaration of a namespace-scope name matches a previous declaration, the language linkage portion of the type of a function declaration (that is, the language linkage of the function itself, not its parameters, return type or exception specification) is ignored.

For a linkage-specification using braces, i.e.

extern string-literal { declaration-seqopt }
the linkage of any declaration of a namespace-scope name (including local externs) which is not contained in a nested linkage-specification, is not declared to have no linkage (static), and does not match a previous declaration is given the linkage specified in the string-literal. The language linkage of the type of any function declaration of a namespace-scope name (including local externs) which is not contained in a nested linkage-specification and which is declared with function declarator syntax is the same as that of a matching previous declaration, if any, else is specified by string-literal.

For a linkage-specification without braces, i.e.

extern string-literal declaration

the linkage of the names declared in the top-level declarators of declaration is specified by string-literal; if this conflicts with the linkage of any matching previous declarations, the program is ill-formed. The language linkage of the type of any top-level function declarator is specified by string-literal; if this conflicts with the language linkage of the type of any matching previous function declarations, the program is ill-formed. The effect of the linkage-specification on other (non top-level) names declared in declaration is the same as that of the brace-enclosed form.

Bill Gibbons: In particular, these should be well-formed:

    extern "C" void f(void (*fp)());   // parameter type is pointer to
                                       // function with C language linkage
    extern "C++" void g(void (*fp)()); // parameter type is pointer to
                                       // function with C++ language linkage

    extern "C++" {                     // well-formed: the linkage of "f"
        void f(void(*fp)());           // and the function type used in the
    }                                  // parameter still "C"

    extern "C" {                       // well-formed: the linkage of "g"
        void g(void(*fp)());           // and the function type used in the
    }                                  // parameter still "C++"

but these should not:

    extern "C++" void f(void(*fp)());  // error - linkage of "f" does not
                                       // match previous declaration
                                       // (linkage of function type used in
                                       // parameter is still "C" and is not
                                       // by itself ill-formed)
    extern "C" void g(void(*fp)());    // error - linkage of "g" does not
                                       // match previous declaration
                                       // (linkage of function type used in
                                       // parameter is still "C++" and is not
                                       // by itself ill-formed)

That is, non-top-level declarators get their linkage from matching declarations, if any, else from the nearest enclosing linkage specification. (As already described, top-level declarators in a brace-enclosed linkage specification get the linkage from matching declarations, if any, else from the linkage specifcation; while top-level declarators in direct linkage specifications get their linkage from that specification.)

Mike Miller: This is a pretty significant change from the current specification, which treats the two forms of language linkage similarly for most purposes. I don't understand why it's desirable to expand the differences.

It seems very unintuitive to me that you could have a top-level declaration in an extern "C" block that would not receive "C" linkage.

In the current standard, the statement in 7.5  dcl.link paragraph 4 that

the specified language linkage applies to the function types of all function declarators, function names, and variable names introduced by the declaration(s)

applies to both forms. I would thus expect that in

    extern "C" void f(void(*)());
    extern "C++" {
        void f(void(*)());
    }
    extern "C++" f(void(*)());

both "C++" declarations would be well-formed, declaring an overloaded version of f that takes a pointer to a "C++" function as a parameter. I wouldn't expect that either declaration would be a redeclaration (valid or invalid) of the "C" version of f.

Bill Gibbons: The potential difficulty is the matching process and the handling of deliberate overloading based on language linkage. In the above examples, how are these two declarations matched:

    extern "C" void f(void (*fp1)());

    extern "C++" {
        void f(void(*fp2)());
    }

given that the linkage that is part of fp1 is "C" while the linkage (prior to the matching process) that is part of fp2 is "C++"?

The proposal is that the linkage which is part of the parameter type is not determined until after the match is attempted. This almost always correct because you can't overload "C" and "C++" functions; so if the function names match, it is likely that the declarations are supposed to be the same.

Mike Miller: This seems like more trouble than it's worth. This comparison of function types ignoring linkage specifications is, as far as I know, not found anywhere in the current standard. Why do we need to invent it?

Bill Gibbons: It is possible to construct pathological cases where this fails, e.g.

    extern "C" typedef void (*PFC)();  // pointer to "C" linkage function
    void f(PFC);         // parameter is pointer to "C" function
    void f(void (*)());  // matching declaration or overload based on
                         // difference in linkage type?

It is reasonable to require explicit typedefs in this case so that in the above example the second function declaration gets its parameter type function linkage from the first function declaration.

(In fact, I think you can't get into this situation without having already used typedefs to declare different language linkage for the top-level and parameter linkages.)

For example, if the intent is to overload based on linkage a typedef is needed:

    extern "C" typedef void (*PFC)();  // pointer to "C" linkage function
    void f(PFC);              // parameter is pointer to "C" function
    typedef void (*PFCPP)();  // pointer to "C++" linkage function
    void f(PFCPP);            // parameter is pointer to "C++" function

In this case the two function declarations refer to different functions.

Mike Miller: This seems pretty strange to me. I think it would be simpler to determine the type of the parameter based on the containing linkage specification (implicitly "C++") and require a typedef if the user wants to override the default behavior. For example:

    extern "C" {
        typedef void (*PFC)();    // pointer to "C" function
        void f(void(*)());        // takes pointer to "C" function
    }

    void f(void(*)());            // new overload of "f", taking
                                  // pointer to "C++" function

    void f(PFC);                  // redeclare extern "C" version

Notes from 04/00 meeting:

The following changes were tentatively approved, but because they do not completely implement the proposal above the issue is being kept for the moment in "drafting" status.

Notes from 10/00 meeting:

After further discussion, the core language working group determined that the more extensive proposal described above is not needed and that the following changes are sufficient.

Proposed resolution (04/01):

  1. Change the first sentence of 7.5  dcl.link paragraph 1 from

    All function types, function names, and variable names have a language linkage.

    to

    All function types, function names with external linkage, and variable names with external linkage have a language linkage.
  2. Change the following sentence of 7.5  dcl.link paragraph 4:
    In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names, and variable names introduced by the declaration(s).

    to

    In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names with external linkage, and variable names with external linkage declared within the linkage-specification.
  3. Add at the end of the final example on 7.5  dcl.link paragraph 4:

        extern "C" {
          static void f4();    // the name of the function f4 has
                               // internal linkage (not C language
                               // linkage) and the function's type
                               // has C language linkage
        }
        extern "C" void f5() {
          extern void f4();    // Okay -- name linkage (internal)
                               // and function type linkage (C
                               // language linkage) gotten from
                               // previous declaration.
        }
        extern void f4();      // Okay -- name linkage (internal)
                               // and function type linkage (C
                               // language linkage) gotten from
                               // previous declaration.
        void f6() {
          extern void f4();    // Okay -- name linkage (internal)
                               // and function type linkage (C
                               // language linkage) gotten from
                               // previous declaration.
        }
    
  4. Change 7.5  dcl.link paragraph 7 from

    Except for functions with internal linkage, a function first declared in a linkage-specification behaves as a function with external linkage. [Example:

        extern "C" double f();
        static double f();     // error
    

    is ill-formed (7.1.1  dcl.stc). ] The form of linkage-specification that contains a braced-enclosed declaration-seq does not affect whether the contained declarations are definitions or not (3.1  basic.def); the form of linkage-specification directly containing a single declaration is treated as an extern specifier (7.1.1  dcl.stc) for the purpose of determining whether the contained declaration is a definition. [Example:

        extern "C" int i;      // declaration
        extern "C" {
    	  int i;           // definition
        }
    

    end example] A linkage-specification directly containing a single declaration shall not specify a storage class. [Example:

        extern "C" static void f(); // error
    

    end example]

    to

    A declaration directly contained in a linkage-specification is treated as if it contains the extern specifier (7.1.1  dcl.stc) for the purpose of determining the linkage of the declared name and whether it is a definition. Such a declaration shall not specify a storage class. [Example:
        extern "C" double f();
        static double f();     // error
        extern "C" int i;      // declaration
        extern "C" {
    	    int i;         // definition
        }
        extern "C" static void g(); // error
    

    end example]




29. Linkage of locally declared functions

Section: 7.5  dcl.link     Status: WP     Submitter: Mike Ball     Date: 19 Mar 1998

[Moved to DR at October 2002 meeting. This was incorrectly marked as having DR status between 4/01 and 4/02. It was overlooked when issue 4 was moved to DR at the 4/01 meeting; this one should have been moved as well, because it's resolved by the changes there.]

Consider the following:

    extern "C" void foo()
    {
        extern void bar();
        bar();
    }
Does "bar()" have "C" language linkage?

The ARM is explicit and says

A linkage-specification for a function also applies to functions and objects declared within it.
The DIS says
In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names, and variable names introduced by the declaration(s).
Is the body of a function definition part of the declaration?

From Mike Miller:

Yes: from 7  dcl.dcl paragraph 1,

and 8.4  dcl.fct.def paragraph 1: At least that's how I'd read it.

From Dag Brück:

Consider the following where extern "C" has been moved to a separate declaration:

    extern "C" void foo();
    
    void foo() { extern void bar(); bar(); }
I think the ARM wording could possibly be interpreted such that bar() has "C" linkage in my example, but not the DIS wording.

As a side note, I have always wanted to think that placing extern "C" on a function definition or a separate declaration would produce identical programs.

Proposed Resolution (04/01):

See the proposed resolution for Core issue 4, which covers this case.

The ODR should also be checked to see whether it addresses name and type linkage.




160. Missing std:: qualification

Section: 8.2  dcl.ambig.res     Status: WP     Submitter: Al Stevens     Date: 23 Aug 1999

[Moved to DR at 10/01 meeting.]

8.2  dcl.ambig.res paragraph 3 shows an example that includes <cstddef> with no using declarations or directives and refers to size_t without the std:: qualification.

Many references to size_t throughout the document omit the std:: namespace qualification.

This is a typical case. The use of std:: is inconsistent throughout the document.

In addition, the use of exception specifications should be examined for consistency.

(See also issue 282.)

Proposed resolution:

In 1.9  intro.execution paragraph 9, replace all two instances of "sig_atomic_t" by "std::sig_atomic_t".

In 3.1  basic.def paragraph 4, replace all three instances of "string" by "std::string" in the example and insert "#include <string>" at the beginning of the example code.

In 3.6.1  basic.start.main paragraph 4, replace

Calling the function
void exit(int);
declared in <cstdlib>...

by

Calling the function std::exit(int) declared in <cstdlib>...

and also replace "exit" by "std::exit" in the last sentence of that paragraph.

In 3.6.1  basic.start.main first sentence of paragraph 5, replace "exit" by "std::exit".

In 3.6.2  basic.start.init paragraph 4, replace "terminate" by "std::terminate".

In 3.6.3  basic.start.term paragraph 1, replace "exit" by "std::exit" (see also issue 28).

In 3.6.3  basic.start.term paragraph 3, replace all three instances of "atexit" by "std::atexit" and both instances of "exit" by "std::exit" (see also issue 28).

In 3.6.3  basic.start.term paragraph 4, replace

Calling the function
void abort();
declared in <cstdlib>...

by

Calling the function std::abort() declared in <cstdlib>...
and "atexit" by "std::atexit" (see also issue 28).

In 3.7.3.1  basic.stc.dynamic.allocation paragraph 1 third sentence, replace "size_t" by "std::size_t".

In 3.7.3.1  basic.stc.dynamic.allocation paragraph 3, replace "new_handler" by "std::new_handler". Furthermore, replace "set_new_handler" by "std::set_new_handler" in the note.

In 3.7.3.1  basic.stc.dynamic.allocation paragraph 4, replace "type_info" by "std::type_info" in the note.

In 3.7.3.2  basic.stc.dynamic.deallocation paragraph 3, replace all four instances of "size_t" by "std::size_t".

In 3.8  basic.life paragraph 5, replace "malloc" by "std::malloc" in the example code and insert "#include <cstdlib>" at the beginning of the example code.

In 3.9  basic.types paragraph 2, replace "memcpy" by "std::memcpy" (the only instance in the footnote and both instances in the example) and replace "memmove" by "std::memmove" in the footnote (see also issue 43).

In 3.9  basic.types paragraph 3, replace "memcpy" by "std::memcpy", once in the normative text and once in the example (see also issue 43).

In 3.9.1  basic.fundamental paragraph 8 last sentence, replace "numeric_limits" by "std::numeric_limits".

In 5.2.7  expr.dynamic.cast paragraph 9 second sentence, replace "bad_cast" by "std::bad_cast".

In 5.2.8  expr.typeid paragraph 2, replace "type_info" by "std::type_info" and "bad_typeid" by "std::bad_typeid".

In 5.2.8  expr.typeid paragraph 3, replace "type_info" by "std::type_info".

In 5.2.8  expr.typeid paragraph 4, replace both instances of "type_info" by "std::type_info".

In 5.3.3  expr.sizeof paragraph 6, replace both instances of "size_t" by "std::size_t".

In 5.3.4  expr.new paragraph 11 last sentence, replace "size_t" by "std::size_t".

In 5.7  expr.add paragraph 6, replace both instances of "ptrdiff_t" by "std::ptrdiff_t".

In 5.7  expr.add paragraph 8, replace "ptrdiff_t" by "std::ptrdiff_t".

In 6.6  stmt.jump paragraph 2, replace "exit" by "std::exit" and "abort" by "std::abort" in the note.

In 8.2  dcl.ambig.res paragraph 3, replace "size_t" by "std::size_t" in the example.

In 8.4  dcl.fct.def paragraph 5, replace "printf" by "std::printf" in the note.

In 12.4  class.dtor paragraph 13, replace "size_t" by "std::size_t" in the example.

In 12.5  class.free paragraph 2, replace all four instances of "size_t" by "std::size_t" in the example.

In 12.5  class.free paragraph 6, replace both instances of "size_t" by "std::size_t" in the example.

In 12.5  class.free paragraph 7, replace all four instances of "size_t" by "std::size_t" in the two examples.

In 12.7  class.cdtor paragraph 4, replace "type_info" by "std::type_info".

In 13.6  over.built paragraph 13, replace all five instances of "ptrdiff_t" by "std::ptrdiff_t".

In 13.6  over.built paragraph 14, replace "ptrdiff_t" by "std::ptrdiff_t".

In 13.6  over.built paragraph 21, replace both instances of "ptrdiff_t" by "std::ptrdiff_t".

In 14.2  temp.names paragraph 4, replace both instances of "size_t" by "std::size_t" in the example. (The example is quoted in issue 96.)

In 14.3  temp.arg paragraph 1, replace "complex" by "std::complex", once in the example code and once in the comment.

In 14.7.3  temp.expl.spec paragraph 8, issue 24 has already corrected the example.

In 15.1  except.throw paragraph 6, replace "uncaught_exception" by "std::uncaught_exception".

In 15.1  except.throw paragraph 7, replace "terminate" by "std::terminate" and both instances of "unexpected" by "std::unexpected".

In 15.1  except.throw paragraph 8, replace "terminate" by "std::terminate".

In 15.2  except.ctor paragraph 3, replace "terminate" by "std::terminate".

In 15.3  except.handle paragraph 9, replace "terminate" by "std::terminate".

In 15.4  except.spec paragraph 8, replace "unexpected" by "std::unexpected".

In 15.4  except.spec paragraph 9, replace "unexpected" by "std::unexpected" and "terminate" by "std::terminate".

In 15.5  except.special paragraph 1, replace "terminate" by "std::terminate" and "unexpected" by "std::unexpected".

In the heading of 15.5.1  except.terminate, replace "terminate" by "std::terminate".

In 15.5.1  except.terminate paragraph 1, footnote in the first bullet, replace "terminate" by "std::terminate". In the same paragraph, fifth bullet, replace "atexit" by "std::atexit". In the same paragraph, last bullet, replace "unexpected_handler" by "std::unexpected_handler".

In 15.5.1  except.terminate paragraph 2, replace

In such cases,
void terminate();
is called...

by

In such cases, std::terminate() is called...

and replace all three instances of "terminate" by "std::terminate".

In the heading of 15.5.2  except.unexpected, replace "unexpected" by "std::unexpected".

In 15.5.2  except.unexpected paragraph 1, replace

...the function
void unexpected();
is called...

by

...the function std::unexpected() is called...
.

In 15.5.2  except.unexpected paragraph 2, replace "unexpected" by "std::unexpected" and "terminate" by "std::terminate".

In 15.5.2  except.unexpected paragraph 3, replace "unexpected" by "std::unexpected".

In the heading of 15.5.3  except.uncaught, replace "uncaught_exception" by "std::uncaught_exception".

In 15.5.3  except.uncaught paragraph 1, replace

The function
bool uncaught_exception()
returns true...

by

The function std::uncaught_exception() returns true...
.

In the last sentence of the same paragraph, replace "uncaught_exception" by "std::uncaught_exception".




112. Array types and cv-qualifiers

Section: 8.3.4  dcl.array     Status: WP     Submitter: Steve Clamage     Date: 4 May 1999

[Moved to DR at 10/01 meeting.]

Steve Clamage: Section 8.3.4  dcl.array paragraph 1 reads in part as follows:

Any type of the form "cv-qualifier-seq array of N T" is adjusted to "array of N cv-qualifier-seq T," and similarly for "array of unknown bound of T." [Example:
    typedef int A[5], AA[2][3];
    typedef const A CA;     // type is "array of 5 const int"
    typedef const AA CAA;   // type is "array of 2 array of 3 const int"
end example] [Note: an "array of N cv-qualifier-seq T" has cv-qualified type; such an array has internal linkage unless explicitly declared extern (7.1.5.1  dcl.type.cv ) and must be initialized as specified in 8.5  dcl.init . ]
The Note appears to contradict the sentence that precedes it.

Mike Miller: I disagree; all it says is that whether the qualification on the element type is direct ("const int x[5]") or indirect ("const A CA"), the array itself is qualified in the same way the elements are.

Steve Clamage: In addition, section 3.9.3  basic.type.qualifier paragraph 2 says:

A compound type (3.9.2  basic.compound ) is not cv-qualified by the cv-qualifiers (if any) of the types from which it is compounded. Any cv-qualifiers applied to an array type affect the array element type, not the array type (8.3.4  dcl.array )."
The Note appears to contradict that section as well.

Mike Miller: Yes, but consider the last two sentences of 3.9.3  basic.type.qualifier paragraph 5:

Cv-qualifiers applied to an array type attach to the underlying element type, so the notation "cv T," where T is an array type, refers to an array whose elements are so-qualified. Such array types can be said to be more (or less) cv-qualified than other types based on the cv-qualification of the underlying element types.
I think this says essentially the same thing as 8.3.4  dcl.array paragraph 1 and its note: the qualification of an array is (bidirectionally) equivalent to the qualification of its members.

Mike Ball: I find this a very far reach. The text in 8.3.4  dcl.array is essentially that which is in the C standard (and is a change from early versions of C++). I don't see any justification at all for the bidirectional equivalence. It seems to me that the note is left over from the earlier version of the language.

Steve Clamage: Finally, the Note seems to say that the declaration

    volatile char greet[6] = "Hello";
gives "greet" internal linkage, which makes no sense.

Have I missed something, or should that Note be entirely removed?

Mike Miller: At least the wording in the note should be repaired not to indicate that volatile-qualification gives an array internal linkage. Also, depending on how the discussion goes, either the wording in 3.9.3  basic.type.qualifier paragraph 2 or in paragraph 5 needs to be amended to be consistent regarding whether an array type is considered qualified by the qualification of its element type.

Steve Adamczyk pointed out that the current state of affairs resulted from the need to handle reference binding consistently. The wording is intended to define the question, "Is an array type cv-qualified?" as being equivalent to the question, "Is the element type of the array cv-qualified?"

Proposed resolution (10/00):

Replace the portion of the note in 8.3.4  dcl.array paragraph 1 reading

such an array has internal linkage unless explicitly declared extern (7.1.5.1  dcl.type.cv) and must be initialized as specified in 8.5  dcl.init.

with

see 3.9.3  basic.type.qualifier.



140. Agreement of parameter declarations

Section: 8.3.5  dcl.fct     Status: WP     Submitter: Steve Clamage     Date: 15 Jul 1999

[Moved to DR at 10/01 meeting.]

8.3.5  dcl.fct paragraph 3 says,

All declarations for a function with a given parameter list shall agree exactly both in the type of the value returned and in the number and type of parameters.
It is not clear what this requirement means with respect to a pair of declarations like the following:
    int f(const int);
    int f(int x) { ... }
Do they violate this requirement? Is x const in the body of the function declaration?

Tom Plum: I think the FDIS quotation means that the pair of decls are valid. But it doesn't clearly answer whether x is const inside the function definition. As to intent, I know the intent was that if the function definition wants to specify that x is const, the const must appear specifically in the defining decl, not just on some decl elsewhere. But I can't prove that intent from the drafted words.

Mike Miller: I think the intent was something along the following lines:

Two function declarations denote the same entity if the names are the same and the function signatures are the same. (Two function declarations with C language linkage denote the same entity if the names are the same.) All declarations of a given function shall agree exactly both in the type of the value returned and in the number and type of parameters; the presence or absence of the ellipsis is considered part of the signature.
(See 3.5  basic.link paragraph 9. That paragraph talks about names in different scopes and says that function references are the same if the "types are identical for purposes of overloading," i.e., the signatures are the same. See also 7.5  dcl.link paragraph 6 regarding C language linkage, where only the name is required to be the same for declarations in different namespaces to denote the same function.)

According to this paragraph, the type of a parameter is determined by considering its decl-specifier-seq and declarator and then applying the array-to-pointer and function-to-pointer adjustments. The cv-qualifier and storage class adjustments are performed for the function type but not for the parameter types.

If my interpretation of the intent of the second sentence of the paragraph is correct, the two declarations in the example violate that restriction — the parameter types are not the same, even though the function types are. Since there's no dispensation mentioned for "no diagnostic required," an implementation presumably must issue a diagnostic in this case. (I think "no diagnostic required" should be stated if the declarations occur in different translation units — unless there's a blanket statement to that effect that I have forgotten?)

(I'd also note in passing that, if my interpretation is correct,

    void f(int);
    void f(register int) { }
is also an invalid pair of declarations.)

Proposed resolution (10/00):

  1. In 1.3.10  defns.signature, change "the types of its parameters" to "its parameter-type-list (8.3.5  dcl.fct)".

  2. In the third bullet of 3.5  basic.link paragraph 9 change "the function types are identical for the purposes of overloading" to "the parameter-type-lists of the functions (8.3.5  dcl.fct) are identical."

  3. In the sub-bullets of the third bullet of 5.2.5  expr.ref paragraph 4, change all four occurrences of "function of (parameter type list)" to "function of parameter-type-list."

  4. In 8.3.5  dcl.fct paragraph 3, change

    All declarations for a function with a given parameter list shall agree exactly both in the type of the value returned and in the number and type of parameters; the presence or absence of the ellipsis is considered part of the function type.
    to
    All declarations for a function shall agree exactly in both the return type and the parameter-type-list.

  5. In 8.3.5  dcl.fct paragraph 3, change

    The resulting list of transformed parameter types is the function's parameter type list.
    to
    The resulting list of transformed parameter types and the presence or absence of the ellipsis is the function's parameter-type-list.

  6. In 8.3.5  dcl.fct paragraph 4, change "the parameter type list" to "the parameter-type-list."

  7. In the second bullet of 13.1  over.load paragraph 2, change all occurrences of "parameter types" to "parameter-type-list."

  8. In 13.3  over.match paragraph 1, change "the types of the parameters" to "the parameter-type-list."

  9. In the last sub-bullet of the third bullet of 13.3.1.2  over.match.oper paragraph 3, change "parameter type list" to "parameter-type-list."

Note, 7 Sep 2001:

Editorial changes while putting in issue 147 brought up the fact that injected-class-name is not a syntax term and therefore perhaps shouldn't be written with hyphens. The same can be said of parameter-type-list.




262. Default arguments and ellipsis

Section: 8.3.5  dcl.fct     Status: WP     Submitter: Jamie Schmeiser     Date: 13 Nov 2000

[Voted into WP at April 2003 meeting.]

The interaction of default arguments and ellipsis is not clearly spelled out in the current wording of the Standard. 8.3.6  dcl.fct.default paragraph 4 says,

In a given function declaration, all parameters subsequent to a parameter with a default argument shall have default arguments supplied in this or previous declarations.

Strictly speaking, ellipsis isn't a parameter, but this could be clearer. Also, in 8.3.5  dcl.fct paragraph 2,

If the parameter-declaration-clause terminates with an ellipsis, the number of arguments shall be equal to or greater than the number of parameters specified.

This could be interpreted to refer to the number of arguments after the addition of default arguments to the argument list given in the call expression, but again it could be clearer.

Notes from 04/01 meeting:

The consensus opinion was that an ellipsis is not a parameter and that default arguments should be permitted preceding an ellipsis.

Proposed Resolution (4/02):

Change the following sentence in 8.3.5  dcl.fct paragraph 2 from

If the parameter-declaration-clause terminates with an ellipsis, the number of arguments shall be equal to or greater than the number of parameters specified.

to

If the parameter-declaration-clause terminates with an ellipsis, the number of arguments shall be equal to or greater than the number of parameters that do not have a default argument.

As noted in the defect, section 8.3.6  dcl.fct.default is correct but could be clearer.

In 8.3.6  dcl.fct.default, add the following as the first line of the example in paragraph 4.

  void g(int = 0, ...);  // okay, ellipsis is not a parameter so it can follow 
                         // a parameter with a default argument



295. cv-qualifiers on function types

Section: 8.3.5  dcl.fct     Status: WP     Submitter: Nathan Sidwell     Date: 29 Jun 2001

[Moved to DR at October 2002 meeting.]

This concerns the inconsistent treatment of cv qualifiers on reference types and function types. The problem originated with GCC bug report c++/2810. The bug report is available at http://gcc.gnu.org/cgi-bin/gnatsweb.pl?cmd=view&pr=2810&database=gcc

8.3.2  dcl.ref describes references. Of interest is the statement (my emphasis)

Cv-qualified references are ill-formed except when the cv-qualifiers are introduced through the use of a typedef or of a template type argument, in which case the cv-qualifiers are ignored.

Though it is strange to ignore 'volatile' here, that is not the point of this defect report. 8.3.5  dcl.fct describes function types. Paragraph 4 states,

In fact, if at any time in the determination of a type a cv-qualified function type is formed, the program is ill-formed.

No allowance for typedefs or template type parameters is made here, which is inconsistent with the equivalent reference case.

The GCC bug report was template code which attempted to do,

    template <typename T> void foo (T const &);
    void baz ();
    ...
    foo (baz);

in the instantiation of foo, T is `void ()' and an attempt is made to const qualify that, which is ill-formed. This is a surprise.

Suggested resolution:

Replace the quoted sentence from paragraph 4 in 8.3.5  dcl.fct with

cv-qualified functions are ill-formed, except when the cv-qualifiers are introduced through the use of a typedef or of a template type argument, in which case the cv-qualifiers are ignored.

Adjust the example following to reflect this.

Proposed resolution (10/01):

In 8.3.5  dcl.fct paragraph 4, replace

The effect of a cv-qualifier-seq in a function declarator is not the same as adding cv-qualification on top of the function type, i.e., it does not create a cv-qualified function type. In fact, if at any time in the determination of a type a cv-qualified function type is formed, the program is ill-formed. [Example:
  typedef void F();
  struct S {
    const F f;          // ill-formed
  };
-- end example]
by
The effect of a cv-qualifier-seq in a function declarator is not the same as adding cv-qualification on top of the function type. In the latter case, the cv-qualifiers are ignored. [Example:
  typedef void F();
  struct S {
    const F f;          // ok; equivalent to void f();
  };
-- end example]

Strike the last bulleted item in 14.8.2  temp.deduct paragraph 2, which reads

Attempting to create a cv-qualified function type.

Nathan Sidwell comments (18 Dec 2001 ): The proposed resolution simply states attempts to add cv qualification on top of a function type are ignored. There is no mention of whether the function type was introduced via a typedef or template type parameter. This would appear to allow

  void (const *fptr) ();
but, that is not permitted by the grammar. This is inconsistent with the wording of adding cv qualifiers to a reference type, which does mention typedefs and template parameters, even though
  int &const ref;
is also not allowed by the grammar.

Is this difference intentional? It seems needlessly confusing.

Notes from 4/02 meeting:

Yes, the difference is intentional. There is no way to add cv-qualifiers other than those cases.

Notes from April 2003 meeting:

Nathan Sidwell pointed out that some libraries use the inability to add const to a type T as a way of testing that T is a function type. He will get back to us if he has a proposal for a change.




136. Default arguments and friend declarations

Section: 8.3.6  dcl.fct.default     Status: WP     Submitter: Daveed Vandevoorde     Date: 9 July 1999

[Moved to DR at 10/01 meeting.]

8.3.6  dcl.fct.default paragraph 4 says,

For non-template functions, default arguments can be added in later declarations of a function in the same scope. Declarations in different scopes have completely distinct sets of default arguments. That is, declarations in inner scopes do not acquire default arguments from declarations in outer scopes, and vice versa.
It is unclear how this wording applies to friend function declarations. For example,
    void f(int, int, int=0);             // #1
    class C {
        friend void f(int, int=0, int);  // #2
    };
    void f(int=0, int, int);             // #3
Does the declaration at #2 acquire the default argument from #1, and does the one at #3 acquire the default arguments from #2?

There are several related questions involved with this issue:

  1. Is the friend declaration in the scope of class C or in the surrounding namespace scope?

    Mike Miller: 8.3.6  dcl.fct.default paragraph 4 is speaking about the lexical location of the declaration... The friend declaration occurs in a different declarative region from the declaration at #1, so I would read [this paragraph] as saying that it starts out with a clean slate of default arguments.

    Bill Gibbons: Yes. It occurs in a different region, although it declares a name in the same region (i.e. a redeclaration). This is the same as with local externs and is intended to work the same way. We decided that local extern declarations cannot add (beyond the enclosing block) new default arguments, and the same should apply to friend declarations.

    John Spicer: The question is whether [this paragraph] does (or should) mean declarations that appear in the same lexical scope or declarations that declare names in the same scope. In my opinion, it really needs to be the latter. It seems somewhat paradoxical to say that a friend declaration declares a function in namespace scope yet the declaration in the class still has its own attributes. To make that work I think you'd have to make friends more like block externs that really do introduce a name into the scope in which the declaration is contained.

  2. Should default arguments be permitted in friend function declarations, and what effect should they have?

    Bill Gibbons: In the absence of a declaration visible in class scope to which they could be attached, default arguments on friend declarations do not make sense. [They should be] ill-formed, to prevent surprises.

    John Spicer: It is important that the following case work correctly:

            class X {
                    friend void f(X x, int i = 1){}
            };
    
            int main()
            {
                    X x;
                    f(x);
            }
    

    In other words, a function first declared in a friend declaration must be permitted to have default arguments and those default arguments must be usable when the function is found by argument dependent lookup. The reason that this is important is that it is common practice to define functions in friend declarations in templates, and that definition is the only place where the default arguments can be specified.

  3. What restrictions should be placed on default argument usage with friend declarations?

    John Spicer: We want to avoid instantiation side effects. IMO, the way to do this would be to prohibit a friend declaration from providing default arguments if a declaration of that function is already visible. Once a function has had a default specified in a friend declaration it should not be possible to add defaults in another declaration be it a friend or normal declaration.

    Mike Miller: The position that seems most reasonable to me is to allow default arguments in friend declarations to be used in Koenig lookup, but to say that they are completely unrelated to default arguments in declarations in the surrounding scope; and to forbid use of a default argument in a call if more than one declaration in the overload set has such a default, as in the proposed resolution for issue 1.

(See also issues 21, 95, 138, 139, 143, 165, and 166.)

Notes from 10/99 meeting:

Four possible outcomes were identified:

  1. If a friend declaration declares a default parameter, allow no other declarations of that function in the translation unit.
  2. Same as preceding, but only allow the friend declaration if it is also a definition.
  3. Disallow default arguments in friend declarations.
  4. Treat the default arguments in each friend declaration as a distinct set, causing an error if the call would be ambiguous.

The core group eliminated the first and fourth options from consideration, but split fairly evenly between the remaining two.

A straw poll of the full committee yielded the following results (given as number favoring/could live with/"over my dead body"):

  1. 0/14/5
  2. 8/13/5
  3. 11/7/14
  4. 7/10/9

Additional discussion is recorded in the "Record of Discussion" for the meeting, J16/99-0036 = WG21 N1212. See also paper J16/00-0040 = WG21 N1263.

Proposed resolution (10/00):

In 8.3.6  dcl.fct.default, add following paragraph 4:

If a friend declaration specifies a default argument expression, that declaration must be a definition and shall be the only declaration of the function or function template in the translation unit.



5. CV-qualifiers and type conversions

Section: 8.5  dcl.init     Status: WP     Submitter: Josee Lajoie     Date: unknown

[Moved to DR at 4/01 meeting.]

The description of copy-initialization in 8.5  dcl.init paragraph 14 says:

Should "destination type" in this last bullet refer to "cv-unqualified destination type" to make it clear that the destination type excludes any cv-qualifiers? This would make it clearer that the following example is well-formed:
     struct A {
       A(A&);
     };
     struct B : A { };
     
     struct C {
       operator B&();
     };
     
     C c;
     const A a = c; // allowed?

The temporary created with the conversion function is an lvalue of type B. If the temporary must have the cv-qualifiers of the destination type (i.e. const) then the copy-constructor for A cannot be called to create the object of type A from the lvalue of type const B. If the temporary has the cv-qualifiers of the result type of the conversion function, then the copy-constructor for A can be called to create the object of type A from the lvalue of type const B. This last outcome seems more appropriate.

Steve Adamczyk:

Because of late changes to this area, the relevant text is now the third sub-bullet of the fourth bullet of 8.5  dcl.init paragraph 14:

Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated... The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the destination type. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization.

The issue still remains whether the wording should refer to "the cv-unqualified version of the destination type." I think it should.

Notes from 10/00 meeting:

The original example does not illustrate the remaining problem. The following example does:

    struct C { };
    C c;
    struct A {
        A(const A&);
        A(const C&);
    };
    const volatile A a = c;    // Okay

Proposed Resolution (04/01):

In 8.5  dcl.init, paragraph 14, bullet 4, sub-bullet 3, change

if the function is a constructor, the call initializes a temporary of the destination type.

to

if the function is a constructor, the call initializes a temporary of the cv-unqualified version of the destination type.



78. Section 8.5 paragraph 9 should state it only applies to non-static objects

Section: 8.5  dcl.init     Status: WP     Submitter: Judy Ward     Date: 15 Dec 1998

Paragraph 9 of 8.5  dcl.init says:

If no initializer is specified for an object, and the object is of (possibly cv-qualified) non-POD class type (or array thereof), the object shall be default-initialized; if the object is of const-qualified type, the underlying class type shall have a user-declared default constructor. Otherwise, if no initializer is specified for an object, the object and its subobjects, if any, have an indeterminate initial value; if the object or any of its subobjects are of const-qualified type, the program is ill-formed.
It should be made clear that this paragraph does not apply to static objects.

Proposed resolution (10/00): In 8.5  dcl.init paragraph 9, replace

Otherwise, if no initializer is specified for an object..."
with
Otherwise, if no initializer is specified for a non-static object...



177. Lvalues vs rvalues in copy-initialization

Section: 8.5  dcl.init     Status: WP     Submitter: Steve Adamczyk     Date: 25 October 1999

[Moved to DR at 4/02 meeting.]

Is the temporary created during copy-initialization of a class object treated as an lvalue or an rvalue? That is, is the following example well-formed or not?

    struct B { };
    struct A {
        A(A&);    // not const
        A(const B&);
    };
    B b;
    A a = b;

According to 8.5  dcl.init paragraph 14, the initialization of a is performed in two steps. First, a temporary of type A is created using A::A(const B&). Second, the resulting temporary is used to direct-initialize a using A::A(A&).

The second step requires binding a reference to non-const to the temporary resulting from the first step. However, 8.5.3  dcl.init.ref paragraph 5 requires that such a reference be bound only to lvalues.

It is not clear from 3.10  basic.lval whether the temporary created in the process of copy-initialization should be treated as an lvalue or an rvalue. If it is an lvalue, the example is well-formed, otherwise it is ill-formed.

Proposed resolution (04/01):

  1. In 8.5  dcl.init paragraph 14, insert the following after "the call initializes a temporary of the destination type":

    The temporary is an rvalue.
  2. In 15.1  except.throw paragraph 3, replace

    The temporary is used to initialize the variable...

    with

    The temporary is an lvalue and is used to initialize the variable...

(See also issue 84.)




277. Zero-initialization of pointers

Section: 8.5  dcl.init     Status: WP     Submitter: Andrew Sawyer     Date: 5 Apr 2001

[Moved to DR at 10/01 meeting.]

The intent of 8.5  dcl.init paragraph 5 is that pointers that are zero-initialized will contain a null pointer value. Unfortunately, the wording used,

...set to the value of 0 (zero) converted to T

does not match the requirements for creating a null pointer value given in 4.10  conv.ptr paragraph 1:

A null pointer constant is an integral constant expression (5.19  expr.const) rvalue of integer type that evaluates to zero. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type...

The problem is that the "value of 0" in the description of zero-initialization is not specified to be an integral constant expression. Nonconstant expressions can also have the value 0, and converting a nonconst 0 to pointer type need not result in a null pointer value.

Proposed resolution (04/01):

In 8.5  dcl.init paragraph 5, change

...set to the value 0 (zero) converted to T;

to

...set to the value 0 (zero), taken as an integral constant expression, converted to T; [footnote: as specified in 4.10  conv.ptr, converting an integral constant expression whose value is 0 to a pointer type results in a null pointer value.]



302. Value-initialization and generation of default constructor

Section: 8.5  dcl.init     Status: WP     Submitter: Steve Adamczyk     Date: 23 Jul 2001

[Moved to DR at October 2002 meeting.]

We've been looking at implementing value-initialization. At one point, some years back, I remember Bjarne saying that something like X() in an expression should produce an X object with the same value one would get if one created a static X object, i.e., the uninitialized members would be zero-initialized because the whole object is initialized at program startup, before the constructor is called.

The formulation for default-initialization that made it into TC1 (in 8.5  dcl.init) is written a little differently (see issue 178), but I had always assumed that it would still be a valid implementation to zero the whole object and then call the default constructor for the troublesome "non-POD but no user-written constructor" cases.

That almost works correctly, but I found a problem case:

    struct A {
      A();
      ~A();
    };
    struct B {
      // B is a non-POD with no user-written constructor.
      // It has a nontrivial generated constructor.
      const int i;
      A a;
    };
    int main () {
      // Value-initializing a "B" doesn't call the default constructor for
      // "B"; it value-initializes the members of B.  Therefore it shouldn't
      // cause an error on generation of the default constructor for the
      // following:
      new B();
    }

If the definition of the B::B() constructor is generated, an error is issued because the const member "i" is not initialized. But the definition of value-initialization doesn't require calling the constructor, and therefore it doesn't require generating it, and therefore the error shouldn't be detected.

So this is a case where zero-initializing and then calling the constructor is not equivalent to value-initializing, because one case generates an error and the other doesn't.

This is sort of unfortunate, because one doesn't want to generate all the required initializations at the point where the "()" initialization occurs. One would like those initializations to be packaged in a function, and the default constructor is pretty much the function one wants.

I see several implementation choices:

  1. Zero the object, then call the default generated constructor. This is not valid unless the standard is changed to say that the default constructor might be generated for value-initialization cases like the above (that is, it's implementation-dependent whether the constructor definition is generated). The zeroing operation can of course be optimized, if necessary, to hit only the pieces of the object that would otherwise be left uninitialized. An alternative would be to require generation of the constructor for value-initialization cases, even if the implementation technique doesn't call the constructor at that point. It's pretty likely that the constructor is going to have to be generated at some point in the program anyway.
  2. Make a new value-initialization "constructor," whose body looks a lot like the usual generated constructor, but which also zeroes other members. No errors would be generated while generating this modified constructor, because it generates code that does full initialization. (Actually, it wouldn't guarantee initialization of reference members, and that might be an argument for generating the constructor, in order to get that error.) This is standard-conforming, but it destroys object-code compatibility.
  3. Variation on (1): Zero first, and generate the object code for the default constructor when it's needed for value-initialization cases, but don't issue any errors at that time. Issue the errors only if it turns out the constructor is "really" referenced. Aside from the essential shadiness of this approach, I fear that something in the generation of the constructor will cause a template instantiation which will be an abservable side effect.

Personally, I find option 1 the least objectionable.

Proposed resolution (10/01):

Add the indicated wording to the third-to-last sentence of 3.2  basic.def.odr pararaph 2:

A default constructor for a class is used by default initialization or value initialization as specified in 8.5  dcl.init.

Add a footnote to the indicated bullet in 8.5  dcl.init paragraph 5:

Add the indicated wording to the first sentence of 12.1  class.ctor paragraph 7:

An implicitly-declared default constructor for a class is implicitly defined when it is used (3.2  basic.def.odr) to create an object of its class type (1.8  intro.object).



491. Initializers for empty-class aggregrate members

Section: 8.5.1  dcl.init.aggr     Status: WP     Submitter: Nathan Sidwell     Date: 15 Dec 2004

[Voted into WP at April, 2007 meeting.]

The current wording of 8.5.1  dcl.init.aggr paragraph 8 requires that

An initializer for an aggregate member that is an empty class shall have the form of an empty initializer-list {}.

This is overly constraining. There is no reason that the following should be ill-formed:

    struct S { };
    S s;
    S arr[1] = { s };

Mike Miller: The wording of 8.5.1  dcl.init.aggr paragraph 8 is unclear. “An aggregate member” would most naturally mean “a member of an aggregate.” In context, however, I think it must mean “a member [of an aggregate] that is an aggregate”, that is, a subaggregate. Members of aggregates need not themselves be aggregates (cf paragraph 13 and 12.6.1  class.expl.init); it cannot be the case that an object of an empty class with a user-declared constructor must be initialized with {} when it is a member of an aggregate. This wording should be clarified, regardless of the decision on Nathan's point.

Proposed resolution (October, 2005):

This issue is resolved by the resolution of issue 413.




291. Overload resolution needed when binding reference to class rvalue

Section: 8.5.3  dcl.init.ref     Status: WP     Submitter: Andrei Iltchenko     Date: 15 Jun 2001

[Voted into WP at October 2005 meeting.]

There is a place in the Standard where overload resolution is implied but the way that a set of candidate functions is to be formed is omitted. See below.

According to the Standard, when initializing a reference to a non-volatile const class type (cv1 T1) with an rvalue expression (cv2 T2) where cv1 T1 is reference compatible with cv2 T2, the implementation shall proceed in one of the following ways (except when initializing the implicit object parameter of a copy constructor) 8.5.3  dcl.init.ref paragraph 5 bullet 2 sub-bullet 1:

While the first case is quite obvious, the second one is a bit unclear as it says "a constructor is called to copy the entire rvalue object into the temporary" without specifying how the temporary is created -- by direct-initialization or by copy-initialization? As stated in DR 152, this can make a difference when the copy constructor is declared as explicit. How should the set of candidate functions be formed? The most appropriate guess is that it shall proceed as per 13.3.1.3  over.match.ctor.

Another detail worth of note is that in the draft version of the Standard as of 2 December 1996 the second bullet read:

J. Stephen Adamczyk replied that the reason for changing "a copy constructor" to "a constructor" was to allow for member template converting constructors.

However, the new wording is somewhat in conflict with the footnote #93 that says that when initializing the implicit object parameter of a copy constructor an implementation must eventually choose the first alternative (binding without copying) to avoid infinite recursion. This seems to suggest that a copy constructor is always used for initializing the temporary of type "cv1 T2".

Furthermore, now that the set of candidate functions is not limited to only the copy constructors of T2, there might be some unpleasant consequences. Consider a rather contrived sample below:

    int   * pi = ::new(std::nothrow) int;
    const std::auto_ptr<int>   & ri = std::auto_ptr<int>(pi);

In this example the initialization of the temporary of type '<TT>const std::auto_ptr<int>' (to which 'ri' is meant to be subsequently bound) doesn't fail, as it would had the approach with copy constructors been retained, instead, a yet another temporary gets created as the well-known sequence:

    std::auto_ptr<int>::operator std::auto_ptr_ref<int>()
    std::auto_ptr<int>(std::auto_ptr_ref<int>)

is called (assuming, of course, that the set of candidate functions is formed as per 13.3.1.3  over.match.ctor). The second temporary is transient and gets destroyed at the end of the initialization. I doubt that this is the way that the committee wanted this kind of reference binding to go.

Besides, even if the approach restricting the set of candidates to copy constructors is restored, it is still not clear how the initialization of the temporary (to which the reference is intended to be bound) is to be performed -- using direct-initialization or copy-initialization.

Another place in the Standard that would benefit from a similar clarification is the creation of an exception object, which is delineated in 15.1  except.throw.

David Abrahams (February 2004): It appears, looking at core 291, that there may be a need to tighten up 8.5.3  dcl.init.ref/5.

Please see the attached example file, which demonstrates "move semantics" in C++98. Many compilers fail to compile test 10 because of the way 8.5.3/5 is interpreted. My problem with that interpretation is that test 20:

    typedef X const XC;
    sink2(XC(X()));
does compile. In other words, it *is* possible to construct the const temporary from the rvalue. IMO, that is the proper test.

8.5.3/5 doesn't demand that a "copy constructor" is used to copy the temporary, only that a constructor is used "to copy the temporary". I hope that when the language is tightened up to specify direct (or copy initialization), that it also unambiguously allows the enclosed test to compile. Not only is it, I believe, within the scope of reasonable interpretation of the current standard, but it's an incredibly important piece of functionality for library writers and users alike.

#include <iostream>
#include <cassert>

template <class T, class X>
struct enable_if_same
{
};

template <class X>
struct enable_if_same<X, X>
{
    typedef char type;
};

struct X
{
    static int cnt;  // count the number of Xs
    
    X()
      : id(++cnt)
      , owner(true)
    {
        std::cout << "X() #" << id << std::endl;
    }
    
    // non-const lvalue - copy ctor
    X(X& rhs)
      : id(++cnt)
      , owner(true)
    {
        std::cout << "copy #" << id << " <- #" << rhs.id << std::endl;
    }

    // const lvalue - T will be deduced as X const
    template <class T>
    X(T& rhs, typename enable_if_same<X const,T>::type = 0)
      : id(++cnt)
      , owner(true)
    {
        std::cout << "copy #" << id << " <- #" << rhs.id << " (const)" << std::endl;
    }

    ~X()
    {
        std::cout << "destroy #" << id << (owner?"":" (EMPTY)") << std::endl;
    }
    
 private:    // Move stuff
    struct ref { ref(X*p) : p(p) {} X* p; };

 public:    // Move stuff
    operator ref() {
        return ref(this);
    }

    // non-const rvalue
    X(ref rhs)
      : id(++cnt)
      , owner(rhs.p->owner)
    {
        std::cout << "MOVE #" << id << " <== #" << rhs.p->id << std::endl;
        rhs.p->owner = false;
        assert(owner);
    }
    
 private:   // Data members
    int id;
    bool owner;
};

int X::cnt;


X source()
{
    return X();
}

X const csource()
{
    return X();
}

void sink(X)
{
    std::cout << "in rvalue sink" << std::endl;
}

void sink2(X&)
{
    std::cout << "in non-const lvalue sink2" << std::endl;
}

void sink2(X const&)
{
    std::cout << "in const lvalue sink2" << std::endl;
}

void sink3(X&)
{
    std::cout << "in non-const lvalue sink3" << std::endl;
}

template <class T>
void tsink(T)
{
    std::cout << "in templated rvalue sink" << std::endl;
}

int main()
{
    std::cout << " ------ test 1, direct init from rvalue ------- " << std::endl;
#ifdef __GNUC__ // GCC having trouble parsing the extra parens
    X z2((0, X() ));
#else
    X z2((X()));
#endif 

    std::cout << " ------ test 2, copy init from rvalue ------- " << std::endl;
    X z4 = X();

    std::cout << " ------ test 3, copy init from lvalue ------- " << std::endl;
    X z5 = z4;

    std::cout << " ------ test 4, direct init from lvalue ------- " << std::endl;
    X z6(z4);
    
    std::cout << " ------ test 5, construct const ------- " << std::endl;
    X const z7;
    
    std::cout << " ------ test 6, copy init from lvalue ------- " << std::endl;
    X z8 = z7;

    std::cout << " ------ test 7, direct init from lvalue ------- " << std::endl;
    X z9(z7);
    
    std::cout << " ------ test 8, pass rvalue by-value ------- " << std::endl;
    sink(source());
    
    std::cout << " ------ test 9, pass const rvalue by-value ------- " << std::endl;
    sink(csource());

    std::cout << " ------ test 10, pass rvalue by overloaded reference ------- " << std::endl;
    // This one fails in Comeau's strict mode due to 8.5.3/5.  GCC 3.3.1 passes it.
    sink2(source());

    std::cout << " ------ test 11, pass const rvalue by overloaded reference ------- " << std::endl;
    sink2(csource());

#if 0    // These two correctly fail to compile, just as desired
    std::cout << " ------ test 12, pass rvalue by non-const reference ------- " << std::endl;
    sink3(source());

    std::cout << " ------ test 13, pass const rvalue by non-const reference ------- " << std::endl;
    sink3(csource());
#endif 

    std::cout << " ------ test 14, pass lvalue by-value ------- " << std::endl;
    sink(z5);
    
    std::cout << " ------ test 15, pass const lvalue by-value ------- " << std::endl;
    sink(z7);

    std::cout << " ------ test 16, pass lvalue by-reference ------- " << std::endl;
    sink2(z4);

    std::cout << " ------ test 17, pass const lvalue by const reference ------- " << std::endl;
    sink2(z7);

    std::cout << " ------ test 18, pass const lvalue by-reference ------- " << std::endl;
#if 0   // correctly fails to compile, just as desired
    sink3(z7);
#endif 

    std::cout << " ------ test 19, pass rvalue by value to template param ------- " << std::endl;
    tsink(source());

    std::cout << " ------ test 20, direct initialize a const A with an A ------- " << std::endl;
    typedef X const XC;
    sink2(XC(X()));
}

Proposed Resolution:

(As proposed by N1610 section 5, with editing.)

Change paragraph 5, second bullet, first sub-bullet, second sub-sub-bullet as follows:

A temporary of type "cv1 T2" [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary via copy-initialization from the entire rvalue object. The reference is bound to the temporary or to a sub-object within the temporary.

The text immediately following that is changed as follows:

The constructor that would be used to make the copy shall be callable whether or not the copy is actually done. The constructor and any conversion function that would be used in the initialization shall be callable whether or not the temporary is actually created.

Note, however, that the way the core working group is leaning on issue 391 (i.e., requiring direct binding) would make this change unnecessary.

Proposed resolution (April, 2005):

This issue is resolved by the resolution of issue 391.




391. Require direct binding of short-lived references to rvalues

Section: 8.5.3  dcl.init.ref     Status: WP     Submitter: Raoul Gough     Date: 14 Nov 2002

[Voted into WP at October 2005 meeting.]

After some email exchanges with Rani Sharoni, I've come up with the following proposal to allow reference binding to non-copyable rvalues in some cases. Rationale and some background appear afterwards.

---- proposal ----

Replace the section of 8.5.3  dcl.init.ref paragraph 5 that begins "If the initializer expression is an rvalue" with the following:

---- rationale ----

  1. The intention of the current wording is to provide the implementation freedom to construct an rvalue of class type at an arbitrary location and copy it zero or more times before binding any reference to it.
  2. The standard allows code to call a member function on an rvalue of class type (in 5.2.5  expr.ref, I guess). This means that the implementation can be forced to bind the reference directly, with no freedom to create any temporary copies. e.g.
       class nc {
         nc (nc const &);  // private, nowhere defined
       public:
         nc ();
         nc const &by_ref () const { return *this; }
       };
    
       void f () {
         void g (nc const &);
    
         g (nc());          // Ill-formed
         g (nc().by_ref()); // Ok - binds directly to rvalue
       }
    
    Forcing a direct binding in this way is possible wherever the lifetime of the reference does not extend beyond the containing full expression, since the reference returned by the member function remains valid for this long.
  3. As demonstrated above, existing implementations must already be capable of constructing an rvalue of class type in the "right" place the first time. Some compilers already silently allow the direct binding of references to non-copyable rvalues.
  4. The change will not break any portable user code. It would break any platform-specific user code that relies on copies being performed by the particular implementation.

---- background ----

The proposal is based on a recent discussion in this group. I originally wanted to leave the implementation free to copy the rvalue if there was a callable copy constructor, and only have to bind directly if none was callable. Unfortunately, a traditional compiler can't always tell whether a function is callable or not, e.g. if the copy constructor is declared but not defined. Rani pointed this out in an example, and suggested that maybe trivial copy constructors should still be allowed (by extension, maybe wherever the compiler can determine callability). I've gone with this version because it's simpler, and I also figure the "as if" rule gives the compiler some freedom with POD types anyway.

Notes from April 2003 meeting:

We agreed generally with the proposal. We were unsure about the need for the restriction regarding long-lived references. We will check with the proposer about that.

Jason Merrill points out that the test case in issue 86 may be a case where we do not want to require direct binding.

Further information from Rani Sharoni (April 2003):

I wasn't aware about the latest suggestion of Raoul as it appears in core issue 391. In our discussions we tried to formulate a different proposal.

The rational, as we understood, behind the implementation freedom to make an extra copying (8.5.3/5/2/12) of the rvalue is to allow return values in registers which on some architectures are not addressable. The example that Raoul and I presented shows that this implementation freedom is not always possible since we can "force" the rvalue to be addressable using additional member function (by_ref). The example only works for short lived rvalues and this is probably why Raoul narrow the suggestion.

I had different rational which was related to the implementation of conditional operator in VC. It seems that when conditional operator is involved VC does use an extra copying when the lifetime of the temporary is extended:

  struct A { /* ctor with side effect */};

  void f(A& x) {
    A const& r = cond ? A(1) : x; // VC actually make an extra copy of
                                  // the rvalue A(1)
  }

I don't know what the consideration behind the VC implementation was (I saw open bug on this issue) but it convinced me to narrow the suggestion.

IMHO such limitation seems to be too strict because it might limit the optimizer since returning class rvalues in registers might be useful (although I'm not aware about any implementation that actually does it). My suggestion was to forbid the extra copying if the ctor is not viable (e.g. A::A(A&) ). In this case the implementation "freedom" doesn't exist (since the code might not compile) and only limits the programmer freedom (e.g. Move Constructors - http://www.cuj.com/experts/2102/alexandr.htm).

Core issue 291 is strongly related to the above issue and I personally prefer to see it resolved first. It seems that VC already supports the resolution I prefer.

Notes from October 2003 meeting:

We ended up feeling that this is just one of a number of cases of optimizations that are widely done by compilers and allowed but not required by the standard. We don't see any strong reason to require compilers to do this particular optimization.

Notes from the March 2004 meeting:

After discussing issue 450, we found ourselves reconsidering this, and we are now inclined to make a change to require the direct binding in all cases, with no restriction on long-lived references. Note that such a change would eliminate the need for a change for issue 291.

Proposed resolution (October, 2004):

Change 8.5.3  dcl.init.ref paragraph 5 bullet 2 sub-bullet 1 as follows:

If the initializer expression is an rvalue, with T2 a class type, and "cv1 T1" is reference-compatible with "cv2 T2", the reference is bound to the object represented by the rvalue (see 3.10  basic.lval) or to a sub-object within that object. in one of the following ways (the choice is implementation-defined): The constructor that would be used to make the copy shall be callable whether or not the copy is actually done. [Example:
  struct A { };
  struct B : public A { } b;
  extern B f();
  const A& rca = f ();  // Bound Either bound to the A sub-object of the B rvalue,
                        // or the entire B object is copied and the reference
                        // is bound to the A sub-object of the copy
end example]

[This resolution also resolves issue 291.]




450. Binding a reference to const to a cv-qualified array rvalue

Section: 8.5.3  dcl.init.ref     Status: WP     Submitter: Steve Adamczyk     Date: 16 Jan 2004

[Voted into WP at October 2005 meeting.]

It's unclear whether the following is valid:

const int N = 10;
const int M = 20;
typedef int T;
void f(T const (&x)[N][M]){}

struct X {
	int i[10][20];
};

X g();

int main()
{
	f(g().i);
}

When you run this through 8.5.3  dcl.init.ref, you sort of end up falling off the end of the standard's description of reference binding. The standard says in the final bullet of paragraph 5 that an array temporary should be created and copy-initialized from the rvalue array, which seems implausible.

I'm not sure what the right answer is. I think I'd be happy with allowing the binding in this case. We would have to introduce a special case like the one for class rvalues.

Notes from the March 2004 meeting:

g++ and EDG give an error. Microsoft (8.0 beta) and Sun accept the example. Our preference is to allow the direct binding (no copy). See the similar issue with class rvalues in issue 391.

Proposed resolution (October, 2004):

  1. Insert a new bullet in 8.5.3  dcl.init.ref paragraph 5 bullet 2 before sub-bullet 2 (which begins, “Otherwise, a temporary of type ‘cv1 T1’ is created...”):

    If the initializer expression is an rvalue, with T2 an array type, and “cv1 T1” is reference-compatible with “cv2 T2”, the reference is bound to the object represented by the rvalue (see 3.10  basic.lval).
  2. Change 3.10  basic.lval paragraph 2 as follows:

    An lvalue refers to an object or function. Some rvalue expressions — those of (possibly cv-qualified) class or array type or cv-qualified class type — also refer to objects.



175. Class name injection and base name access

Section: class     Status: WP     Submitter: John Spicer     Date: 21 February 1999

[Moved to DR at 10/01 meeting.]

With class name injection, when a base class name is used in a derived class, the name found is the injected name in the base class, not the name of the class in the scope containing the base class. Consequently, if the base class name is not accessible (e.g., because is is in a private base class), the base class name cannot be used unless a qualified name is used to name the class in the class or namespace of which it is a member.

Without class name injection the following example is valid. With class name injection, A is inaccessible in class C.

    class A { };
    class B: private A { };
    class C: public B {
        A* p;    // error: A inaccessible
    };

At the least, the standard should be more explicit that this is, in fact, ill-formed.

(See paper J16/99-0010 = WG21 N1187.)

Proposed resolution (04/01):

Add to the end of 11.1  class.access.spec paragraph 3:

[Note: In a derived class, the lookup of a base class name will find the injected-class-name instead of the name of the base class in the scope in which it was declared. The injected-class-name might be less accessible than the name of the base class in the scope in which it was declared.] [Example:

    class A { };
    class B : private A { };
    class C : public B {
        A* p;    // error: injected-class-name A is inaccessible
        ::A* q;  // OK
    };

end example]




273. POD classes and operator&()

Section: class     Status: WP     Submitter: Andrei Iltchenko     Date: 10 Mar 2001

[Moved to DR at October 2002 meeting.]

I think that the definition of a POD class in the current version of the Standard is overly permissive in that it allows for POD classes for which a user-defined operator function operator& may be defined. Given that the idea behind POD classes was to achieve compatibility with C structs and unions, this makes 'Plain old' structs and unions behave not quite as one would expect them to.

In the C language, if x and y are variables of struct or union type S that has a member m, the following expression are allowed: &x, x.m, x = y. While the C++ standard guarantees that if x and y are objects of a POD class type S, the expressions x.m, x = y will have the same effect as they would in C, it is still possible for the expression &x to be interpreted differently, subject to the programmer supplying an appropriate version of a user-defined operator function operator& either as a member function or as a non-member function.

This may result in surprising effects. Consider:

    // POD_bomb is a POD-struct. It has no non-static non-public data members,
    // no virtual functions, no base classes, no constructors, no user-defined
    // destructor, no user-defined copy assignment operator, no non-static data
    // members of type pointer to member, reference, non-POD-struct, or
    // non-POD-union.
    struct  POD_bomb  {
       int   m_value1;
       int   m_value2;
       int  operator&()
       {   return  m_value1++;   }
       int  operator&() const
       {   return  m_value1 + m_value2;   }
    };

3.9  basic.types paragraph 2 states:

For any complete POD object type T, whether or not the object holds a valid value of type T, the underlying bytes (1.7  intro.memory) making up the object can be copied into an array of char or unsigned char [footnote: By using, for example, the library functions (17.4.1.2  lib.headers) memcpy or memmove]. If the content of the array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value. [Example:
    #define N sizeof(T)
    char buf[N];
    T obj;   // obj initialized to its original value
    memcpy(buf, &obj, N);
		// between these two calls to memcpy,
		// obj might be modified
    memcpy(&obj, buf, N);
		// at this point, each subobject of obj of scalar type
		// holds its original value
end example]

Now, supposing that the complete POD object type T in the example above is POD_bomb, and we cannot any more count on the assertions made in the comments to the example. Given a standard conforming implementation, the code will not even compile. And I see no legal way of copying the contents of an object of a complete object type POD_bomb into an array of char or unsigned char with memcpy or memmove without making use of the unary & operator. Except, of course, by means of an ugly construct like:

    struct  POD_without_ampersand  {
       POD_bomb   a_bomb;
    }  obj;
    #define N sizeof(POD_bomb)
    char buf[N];
    memcpy(buf, &obj, N);
    memcpy(&obj, buf, N);

The fact that the definition of a POD class allows for POD classes for which a user-defined operator& is defined, may also present major obstacles to implementers of the offsetof macro from <cstddef>

18.1  lib.support.types paragraph 5 says:

The macro offsetof accepts a restricted set of type arguments in this International Standard. type shall be a POD structure or a POD union (clause 9  class). The result of applying the offsetof macro to a field that is a static data member or a function is undefined."

Consider a well-formed C++ program below:

    #include <cstddef>
    #include <iostream>


    struct  POD_bomb  {
       int   m_value1;
       int   m_value2;
       int  operator&()
       {   return  m_value1++;   }
       int  operator&() const
       {   return  m_value1 + m_value2;   }
    };


    // POD_struct is a yet another example of a POD-struct.
    struct  POD_struct  {
       POD_bomb   m_nonstatic_bomb1;
       POD_bomb   m_nonstatic_bomb2;
    };


    int  main()
    {

       std::cout << "offset of m_nonstatic_bomb2: " << offsetof(POD_struct,
           m_nonstatic_bomb2) << '\n';
       return  0;

    }

See Jens Maurer's paper 01-0038=N1324 for an analysis of this issue.

Notes from 10/01 meeting:

A consensus was forming around the idea of disallowing operator& in POD classes when it was noticed that it is permitted to declare global-scope operator& functions, which cause the same problems. After more discussion, it was decided that such functions should not be prohibited in POD classes, and implementors should simply be required to "get the right answer" in constructs such as offsetof and va_start that are conventionally implemented using macros that use the "&" operator. It was noted that one can cast the original operand to char & to de-type it, after which one can use the built-in "&" safely.

Proposed resolution:




284. qualified-ids in class declarations

Section: class     Status: WP     Submitter: Mike Miller     Date: 01 May 2001

[Moved to DR at October 2002 meeting.]

Although 8.3  dcl.meaning requires that a declaration of a qualified-id refer to a member of the specified namespace or class and that the member not have been introduced by a using-declaration, it applies only to names declared in a declarator. It is not clear whether there is existing wording enforcing the same restriction for qualified-ids in class-specifiers and elaborated-type-specifiers or whether additional wording is required. Once such wording is found/created, the proposed resolution of issue 275 must be modified accordingly.

Notes from 10/01 meeting:

The sentiment was that this should be required on class definitions, but not on elaborated type specifiers in general (which are references, not declarations). We should also make sure we consider explicit instantiations, explicit specializations, and friend declarations.

Proposed resolution (10/01):

Add after the end of 9.1  class.name paragraph 3:

When a nested-name-specifier is specified in a class-head or in an elaborated-type-specifier, the resulting qualified name shall refer to a previously declared member of the class or namespace to which the nested-name-specifier refers, and the member shall not have been introduced by a using-declaration in the scope of the class or namespace nominated by the nested-name-specifier.



327. Use of "structure" without definition

Section: class     Status: WP     Submitter: James Kanze     Date: 9 Dec 2001

[Voted into WP at April, 2007 meeting.]

In 9  class paragraph 4, the first sentence says "A structure is a class definition defined with the class-key struct". As far as I know, there is no such thing as a structure in C++; it certainly isn't listed as one of the possible compound types in 3.9.2  basic.compound. And defining structures opens the question of whether a forward declaration is a structure or not. The parallel here with union (which follows immediately) suggests that structures and classes are really different things, since the same wording is used, and classes and unions do have some real differences, which manifest themselves outside of the definition. It also suggests that since one can't forward declare union with class and vice versa, the same should hold for struct and class -- I believe that the intent was that one could use struct and class interchangeably in forward declaration.

Suggested resolution:

I suggest something like the following:

If a class is defined with the class-key class, its members and base classes are private by default. If a class is defined with the class-key struct, its members and base classes are public by default. If a class is defined with the class-key union, its members are public by default, and it holds only one data member at a time. Such classes are called unions, and obey a number of additional restrictions, see 9.5  class.union.

Proposed resolution (April, 2006):

This issue is resolved by the resolution of issue 538.




379. Change "class declaration" to "class definition"

Section: class     Status: WP     Submitter: Jens Maurer     Date: 21 Oct 2002

[Voted into WP at March 2004 meeting.]

The ARM used the term "class declaration" to refer to what would usually be termed the definition of the class. The standard now often uses "class definition", but there are some surviving uses of "class declaration" with the old meaning. They should be found and changed.

Proposed resolution (April 2003):

Replace in 3.1  basic.def paragraph 2

A declaration is a definition unless it declares a function without specifying the function's body (8.4  dcl.fct.def), it contains the extern specifier (7.1.1  dcl.stc) or a linkage-specification [Footnote: Appearing inside the braced-enclosed declaration-seq in a linkage-specification does not affect whether a declaration is a definition. --- end footnote] (7.5  dcl.link) and neither an initializer nor a function-body, it declares a static data member in a class declaration definition (9.4  class.static), it is a class name declaration (9.1  class.name), or it is a typedef declaration (7.1.3  dcl.typedef), a using-declaration (7.3.3  namespace.udecl), or a using-directive (7.3.4  namespace.udir).

Replace in 7.1.2  dcl.fct.spec paragraphs 5 and 6

The virtual specifier shall only be used in declarations of nonstatic class member functions that appear within a member-specification of a class declaration definition; see 10.3  class.virtual.

The explicit specifier shall be used only in declarations of constructors within a class declaration definition; see 12.3.1  class.conv.ctor.

Replace in 7.1.3  dcl.typedef paragraph 4

A typedef-name that names a class is a class-name (9.1  class.name). If a typedef-name is used following the class-key in an elaborated-type-specifier (7.1.5.3  dcl.type.elab) or in the class-head of a class declaration definition (9  class), or is used as the identifier in the declarator for a constructor or destructor declaration (12.1  class.ctor, 12.4  class.dtor), the program is ill-formed.

Replace in 7.3.1.2  namespace.memdef paragraph 3

The name of the friend is not found by simple name lookup until a matching declaration is provided in that namespace scope (either before or after the class declaration definition granting friendship).

Replace in 8.3.2  dcl.ref paragraph 4

There shall be no references to references, no arrays of references, and no pointers to references. The declaration of a reference shall contain an initializer (8.5.3  dcl.init.ref) except when the declaration contains an explicit extern specifier (7.1.1  dcl.stc), is a class member (9.2  class.mem) declaration within a class declaration definition, or is the declaration of a parameter or a return type (8.3.5  dcl.fct); see 3.1  basic.def.

Replace in 8.5.3  dcl.init.ref paragraph 3

The initializer can be omitted for a reference only in a parameter declaration (8.3.5  dcl.fct), in the declaration of a function return type, in the declaration of a class member within its class declaration definition (9.2  class.mem), and where the extern specifier is explicitly used.

Replace in 9.1  class.name paragraph 2

A class definition declaration introduces the class name into the scope where it is defined declared and hides any class, object, function, or other declaration of that name in an enclosing scope (3.3  basic.scope). If a class name is declared in a scope where an object, function, or enumerator of the same name is also declared, then when both declarations are in scope, the class can be referred to only using an elaborated-type-specifier (3.4.4  basic.lookup.elab).

Replace in 9.4  class.static paragraph 4

Static members obey the usual class member access rules (clause 11  class.access). When used in the declaration of a class member, the static specifier shall only be used in the member declarations that appear within the member-specification of the class declaration definition.

Replace in 9.7  class.nest paragraph 1

A class can be defined declared within another class. A class defined declared within another is called a nested class. The name of a nested class is local to its enclosing class. The nested class is in the scope of its enclosing class. Except by using explicit pointers, references, and object names, declarations in a nested class can use only type names, static members, and enumerators from the enclosing class.

Replace in 9.8  class.local paragraph 1

A class can be defined declared within a function definition; such a class is called a local class. The name of a local class is local to its enclosing scope. The local class is in the scope of the enclosing scope, and has the same access to names outside the function as does the enclosing function. Declarations in a local class can use only type names, static variables, extern variables and functions, and enumerators from the enclosing scope.

Replace in 10  class.derived paragraph 1

... The class-name in a base-specifier shall not be an incompletely defined class (clause 9  class); this class is called a direct base class for the class being declared defined. During the lookup for a base class name, non-type names are ignored (3.3.7  basic.scope.hiding). If the name found is not a class-name, the program is ill-formed. A class B is a base class of a class D if it is a direct base class of D or a direct base class of one of D's base classes. A class is an indirect base class of another if it is a base class but not a direct base class. A class is said to be (directly or indirectly) derived from its (direct or indirect) base classes. [Note: See clause 11  class.access for the meaning of access-specifier.] Unless redefined redeclared in the derived class, members of a base class are also considered to be members of the derived class. The base class members are said to be inherited by the derived class. Inherited members can be referred to in expressions in the same manner as other members of the derived class, unless their names are hidden or ambiguous (10.2  class.member.lookup). [Note: the scope resolution operator :: (5.1  expr.prim) can be used to refer to a direct or indirect base member explicitly. This allows access to a name that has been redefined redeclared in the derived class. A derived class can itself serve as a base class subject to access control; see 11.2  class.access.base. A pointer to a derived class can be implicitly converted to a pointer to an accessible unambiguous base class (4.10  conv.ptr). An lvalue of a derived class type can be bound to a reference to an accessible unambiguous base class (8.5.3  dcl.init.ref).]

Replace in 10.1  class.mi paragraph 5

For another example,
class V { /* ... */ };
class A : virtual public V { /* ... */ };
class B : virtual public V { /* ... */ };
class C : public A, public B { /* ... */ };
for an object c of class type C, a single subobject of type V is shared by every base subobject of c that is declared to have has a virtual base class of type V.

Replace in the example in 10.2  class.member.lookup paragraph 6 (the whole paragraph was turned into a note by the resolution of core issue 39)

The names defined declared in V and the left hand instance of W are hidden by those in B, but the names defined declared in the right hand instance of W are not hidden at all.

Replace in 10.4  class.abstract paragraph 2

... A virtual function is specified pure by using a pure-specifier (9.2  class.mem) in the function declaration in the class declaration definition. ...

Replace in the footnote at the end of 11.2  class.access.base paragraph 1

[Footnote: As specified previously in clause 11  class.access, private members of a base class remain inaccessible even to derived classes unless friend declarations within the base class declaration definition are used to grant access explicitly.]

Replace in 11.3  class.access.dcl paragraph 1

The access of a member of a base class can be changed in the derived class by mentioning its qualified-id in the derived class declaration definition. Such mention is called an access declaration. ...

Replace in the example in 13.4  over.over paragraph 5

The initialization of pfe is ill-formed because no f() with type int(...) has been defined declared, and not because of any ambiguity.

Replace in C.1.5  diff.dcl paragraph 1

Rationale: Storage class specifiers don't have any meaning when associated with a type. In C++, class members can be defined declared with the static storage class specifier. Allowing storage class specifiers on type declarations could render the code confusing for users.

Replace in C.1.7  diff.class paragraph 3

In C++, a typedef name may not be redefined redeclared in a class declaration definition after being used in the declaration that definition
Drafting notes:

The resolution of core issue 45 (DR) deletes 11.8  class.access.nest paragraph 2.

The following occurrences of "class declaration" are not changed:




383. Is a class with a declared but not defined destructor a POD?

Section: class     Status: WP     Submitter: Gennaro Prota     Date: 18 Sep 2002

[Voted into WP at March 2004 meeting.]

The standard (9  class par. 4) says that "A POD-struct is an aggregate class that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-defined copy assignment operator and no user-defined destructor."

Note that it says 'user-defined', not 'user-declared'. Is it the intent that if e.g. a copy assignment operator is declared but not defined, this does not (per se) prevent the class to be a POD-struct?

Proposed resolution (April 2003):

Replace in 9  class paragraph 4

A POD-struct is an aggregate class that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-defined declared copy assignment operator and no user-defined declared destructor. Similarly, a POD-union is an aggregate union that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-defined declared copy assignment operator and no user-defined declared destructor.

Drafting note: The changes are shown relative to TC1, incorporating the changes from the resolution of core issue 148.




413. Definition of "empty class"

Section: class     Status: WP     Submitter: Pete Becker     Date: 30 Apr 2003

[Voted into WP at April, 2007 meeting.]

The proposal says that value is true if "T is an empty class (10)". Clause 10 doesn't define an empty class, although it has a note that says a base class may "be of zero size (clause 9)" 9/3 says "Complete objects and member subobjects of class type shall have nonzero size." This has a footnote, which says "Base class subobjects are not so constrained."

The standard uses the term "empty class" in two places (8.5.1  dcl.init.aggr), but neither of those places defines it. It's also listed in the index, which refers to the page that opens clause 9, i.e. the nonzero size stuff cited above.

So, what's the definition of "empty class" that determines whether the predicate is_empty is true?

The one place where it's used is 8.5.1  dcl.init.aggr paragraph 8, which says (roughly paraphrased) that an aggregate initializer for an empty class must be "{}", and when such an initializer is used for an aggregate that is not an empty class the members are default-initialized. In this context it's pretty clear what's meant. In the type traits proposal it's not as clear, and it was probably intended to have a different meaning. The boost implementation, after it eliminates non-class types, determines whether the trait is true by comparing the size of a class derived from T to the size of an otherwise-identical class that is not derived from T.

Howard Hinnant: is_empty was created to find out whether a type could be derived from and have the empty base class optimization successfully applied. It was created in part to support compressed_pair which attempts to optimize away the space for one of its members in an attempt to reduce spatial overhead. An example use is:

  template <class T, class Compare = std::less<T> >
  class SortedVec
  {
  public:
  ...
  private:
    T* data_;
    compressed_pair<Compare, size_type> comp_;

    Compare&       comp()       {return comp_.first();}
    const Compare& comp() const {return comp_.first();}
    size_type&     sz()         {return comp_.second();}
    size_type      sz() const   {return comp_.second();}
  };

Here the compare function is optimized away via the empty base optimization if Compare turns out to be an "empty" class. If Compare turns out to be a non-empty class, or a function pointer, the space is not optimized away. is_empty is key to making this work.

This work built on Nathan's article: http://www.cantrip.org/emptyopt.html.

Clark Nelson: I've been looking at issue 413, and I've reached the conclusion that there are two different kinds of empty class. A class containing only one or more anonymous bit-field members is empty for purposes of aggregate initialization, but not (necessarily) empty for purposes of empty base-class optimization.

Of course we need to add a definition of emptiness for purposes of aggregate initialization. Beyond that, there are a couple of questions:

  1. Should the definition of emptiness used by the is_empty predicate be defined in a language clause or a library clause?
  2. Do we need to open a new core issue pointing out the fact that the section on aggregate initialization does not currently say that unnamed bit-fields are skipped?

Notes from the October, 2005 meeting:

There are only two places in the Standard where the phrase “empty class” appears, both in 8.5.1  dcl.init.aggr paragraph 8. Because it is not clear whether the definition of “empty for initialization purposes” is suitable for use in defining the is_empty predicate, it would be better just to avoid using the phrase in the language clauses. The requirements of 8.5.1  dcl.init.aggr paragraph 8 appear to be redundant; paragraph 6 says that an initializer-list must have no more initializers than the number of elements to initialize, so an empty class already requires an empty initializer-list, and using an empty initializer-list with a non-empty class is covered adequately by paragraph 7's description of the handling of an initializer-list with fewer initializers than the number of members to initialize.

Proposed resolution (October, 2005):

  1. Change 8.5.1  dcl.init.aggr paragraph 5 by inserting the indicated text:

  2. Static data members and anonymous bit fields are not considered members of the class for purposes of aggregate initialization. [Example:

        struct A {
            int i;
            static int s;
            int j;
            int :17;
            int k;
        } a = { 1 , 2 , 3 };
    

    Here, the second initializer 2 initializes a.j and not the static data member A::s, and the third initializer 3 initializes a.k and not the padding before it.end example]

  3. Delete 8.5.1  dcl.init.aggr paragraph 8:

  4. An initializer for an aggregate member that is an empty class shall have the form of an empty initializer-list {}. [Example:

        struct S { };
        struct A {
            S s;
            int i;
        } a = { { } , 3 };
    

    end example] An empty initializer-list can be used to initialize any aggregate. If the aggregate is not an empty class, then each member of the aggregate shall be initialized with a value of the form T() (5.2.3  expr.type.conv), where T represents the type of the uninitialized member.

This resolution also resolves issue 491.

Additional note (October, 2005):

Deleting 8.5.1  dcl.init.aggr paragraph 8 altogether may not be a good idea. It would appear that, in its absence, the initializer elision rules of paragraph 11 would allow the initializer for a in the preceding example to be written { 3 } (because the empty-class member s would consume no initializers from the list).

Proposed resolution (October, 2006):

(Drafting note: this resolution also cleans up incorrect references to syntactic non-terminals in the nearby paragraphs.)

  1. Change 8.5.1  dcl.init.aggr paragraph 4 as indicated:

    An array of unknown size initialized with a brace-enclosed initializer-list containing n initializers initializer-clauses, where n shall be greater than zero... An empty initializer list {} shall not be used as the initializer initializer-clause for an array of unknown bound.
  2. Change 8.5.1  dcl.init.aggr paragraph 5 by inserting the indicated text:

    Static data members and anonymous bit fields are not considered members of the class for purposes of aggregate initialization. [Example:

        struct A {
            int i;
            static int s;
            int j;
            int :17;
            int k;
        } a = { 1 , 2 , 3 };
    

    Here, the second initializer 2 initializes a.j and not the static data member A::s, and the third initializer 3 initializes a.k and not the anonymous bit field before it.end example]

  3. Change 8.5.1  dcl.init.aggr paragraph 6 as indicated:

    An initializer-list is ill-formed if the number of initializers initializer-clauses exceeds the number of members...
  4. Change 8.5.1  dcl.init.aggr paragraph 7 as indicated:

    If there are fewer initializers initializer-clauses in the list than there are members...
  5. Replace 8.5.1  dcl.init.aggr paragraph 8:

    An initializer for an aggregate member that is an empty class shall have the form of an empty initializer-list {}. [Example:

        struct S { };
        struct A {
            S s;
            int i;
        } a = { { } , 3 };
    

    end example] An empty initializer-list can be used to initialize any aggregate. If the aggregate is not an empty class, then each member of the aggregate shall be initialized with a value of the form T() (5.2.3  expr.type.conv), where T represents the type of the uninitialized member.

    with:

    If an aggregate class C contains a subaggregate member m that has no members for purposes of aggregate initialization, the initializer-clause for m shall not be omitted from an initializer-list for an object of type C unless the initializer-clauses for all members of C following m are also omitted. [Example:

        struct S { } s;
        struct A {
            S s1;
            int i1;
            S s2;
            int i2;
            S s3;
            int i3;
        } a = {
            { },   // Required initialization
            0,
            s,     // Required initialization
            0
        };         // Initialization not required for A::s3 because A::i3 is also not initialized
    

    end example]

  6. Change 8.5.1  dcl.init.aggr paragraph 10 as indicated:

    When initializing a multi-dimensional array, the initializers initializer-clauses initialize the elements...
  7. Change 8.5.1  dcl.init.aggr paragraph 11 as indicated:

    Braces can be elided in an initializer-list as follows. If the initializer-list begins with a left brace, then the succeeding comma-separated list of initializers initializer-clauses initializes the members of a subaggregate; it is erroneous for there to be more initializers initializer-clauses than members. If, however, the initializer-list for a subaggregate does not begin with a left brace, then only enough initializers initializer-clauses from the list are taken to initialize the members of the subaggregate; any remaining initializers initializer-clauses are left to initialize the next member of the aggregate of which the current subaggregate is a member. [Example:...
  8. Change 8.5.1  dcl.init.aggr paragraph 12 as indicated:

    All implicit type conversions (clause 4  conv) are considered when initializing the aggregate member with an initializer from an initializer-list assignment-expression. If the initializer assignment-expression can initialize a member, the member is initialized. Otherwise, if the member is itself a non-empty subaggregate, brace elision is assumed and the initializer assignment-expression is considered for the initialization of the first member of the subaggregate. [Note: As specified above, brace elision cannot apply to subaggregates with no members for purposes of aggregate initialization; an initializer-clause for the entire subobject is required. —end note] [Example:... Braces are elided around the initializer initializer-clause for b.a1.i...
  9. Change 8.5.1  dcl.init.aggr paragraph 15 as indicated:

    When a union is initialized with a brace-enclosed initializer, the braces shall only contain an initializer initializer-clause for the first member of the union...
  10. Change 8.5.1  dcl.init.aggr paragraph 16 as indicated:

    [Note: as As described above, the braces around the initializer initializer-clause for a union member can be omitted if the union is a member of another aggregate. —end note]

(Note: this resolution also resolves issue 491.)




538. Definition and usage of structure, POD-struct, POD-union, and POD class

Section: class     Status: WP     Submitter: Alisdair Meredith     Date: 10 August 2005

[Voted into WP at April, 2007 meeting.]

There are several problems with the terms defined in 9  class paragraph 4:

A structure is a class defined with the class-key struct; its members and base classes (clause 10  class.derived) are public by default (clause 11  class.access). A union is a class defined with the class-key union; its members are public by default and it holds only one data member at a time (9.5  class.union). [Note: aggregates of class type are described in 8.5.1  dcl.init.aggr. —end note] A POD-struct is an aggregate class that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-declared copy assignment operator and no user-declared destructor. Similarly, a POD-union is an aggregate union that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-declared copy assignment operator and no user-declared destructor. A POD class is a class that is either a POD-struct or a POD-union.
  1. Although the term structure is defined here, it is used only infrequently throughout the Standard, often apparently inadvertently and consequently incorrectly:

    There does not appear to be a reason for defining the term structure. The one reference where it is arguably useful, in the note in 5.2.5  expr.ref, could be rewritten as something like, “'class objects' may be defined using the class, struct, or union class-keys; see clause 9  class.”

  2. Based on its usage later in the paragraph and elsewhere, “POD-struct” appears to be intended to exclude unions. However, the definition of “aggregate class” in 8.5.1  dcl.init.aggr paragraph 1 includes unions. Furthermore, the name itself is confusing, leading to the question of whether it was intended that only classes defined using struct could be POD-structs or if class-classes are included. The definition should probably be rewritten as, “A POD-struct is an aggregate class defined with the class-key struct or the class-key class that has no...

  3. In most references outside clause 9  class, POD-struct and POD-union are mentioned together and treated identically. These references should be changed to refer to the unified term, “POD class.”

  4. Noted in passing: 18.1  lib.support.types paragraph 4 refers to the undefined terms “POD structure” and (unhyphenated) “POD union;” the pair should be replaced by a single reference to “POD class.”

Proposed resolution (April, 2006):

  1. Change 9  class paragraph 4 as indicated:

    A structure is a class defined with the class-key struct; its members and base classes (clause 10  class.derived) are public by default (clause 11  class.access). A union is a class defined with the class-key union; its members are public by default and it holds only one data member at a time (9.5  class.union). [Note: aggregates of class type are described in 8.5.1  dcl.init.aggr. —end note] A POD-struct is an aggregate class that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-declared copy assignment operator and no user-declared destructor. Similarly, a POD-union is an aggregate union that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-declared copy assignment operator and no user-declared destructor. A POD class is a class that is either a POD-struct or a POD-union. A POD class is an aggregate class that has no non-static data members of non-POD type (or array of such a type) or reference, and has no user-declared copy assignment operator and no user-declared destructor. A POD-struct is a POD class defined with the class-key struct or the class-key class. A POD-union is a POD class defined with the class-key union.
  2. Change 11.2  class.access.base paragraph 2 as indicated:

    In the absence of an access-specifier for a base class, public is assumed when the derived class is declared defined with the class-key struct and private is assumed when the class is declared defined with the class-key class. [Example:...
  3. Delete the note in 5.2.5  expr.ref paragraph 4:

    [Note: “class objects” can be structures (9.2  class.mem) and unions (9.5  class.union). Classes are discussed in clause 9  class. —end note]
  4. Change the commentary in the example in 9.2  class.mem paragraph 11 as indicated:

    ...an integer, and two pointers to similar structures objects of the same type. Once this definition...

    ...the count member of the structure object to which sp points; s.left refers to the left subtree pointer of the structure object s; and...

  5. Change 17.1.8  defns.iostream.templates as indicated:

    ...the argument traits is a structure class which defines additional characteristics...
  6. Change 18.4  lib.support.dynamic paragraph 4 as indicated:

    If type is not a POD structure or a POD union POD class (clause 9), the results are undefined.
  7. Change the third bullet of B  limits paragraph 2 as indicated:

  8. Change the nineteenth bullet of B  limits paragraph 2 as indicated:

  9. Change the twenty-first bullet of B  limits paragraph 2 as indicated:

  10. Change C.2  diff.library paragraph 6 as indicated:

    The C++ Standard library provides 2 standard structures structs from the C library, as shown in Table 126.
  11. Change the last sentence of 3.9  basic.types paragraph 10 as indicated:

    Scalar types, POD-struct types, POD-union types POD classes (clause 9  class), arrays of such types and cv-qualified versions of these types (3.9.3  basic.type.qualifier) are collectively called POD types.

    Drafting note: Do not change 3.9  basic.types paragraph 11, because it's a note and the definition of “layout-compatible” is separate for POD-struct and POD-union in 9.2  class.mem.

(This resolution also resolves issue 327.)




417. Using derived-class qualified name in out-of-class nested class definition

Section: 9.1  class.name     Status: WP     Submitter: Jon Caves     Date: 19 May 2003

[Voted into WP at October 2004 meeting.]

We had a user complain that our compiler was allowing the following code:

  struct B {
    struct S;
  };

  struct D : B { };

  struct D::S {
  };

We took one look at the code and made the reasonable (I would claim) assumption that this was indeed a bug in our compiler. Especially as we had just fixed a very similar issue with the definition of static data members.

Imagine our surprise when code like this showed up in Boost and that every other compiler we tested accepts this code. So is this indeed legal (it seems like it must be) and if so is there any justification for this beyond 3.4.3.1  class.qual?

John Spicer: The equivalent case for a member function is covered by the declarator rules in 8.3  dcl.meaning paragraph 1. The committee has previously run into cases where a restriction should apply to both classes and non-classes, but fails to do so because there is no equivalent of 8.3  dcl.meaning paragraph 1 for classes.

Given that, by the letter of the standard, I would say that this case is allowed.

Notes from October 2003 meeting:

We feel this case should get an error.

Proposed Resolution (October 2003):

Note that the change here interacts with issue 432.

Add the following as a new paragraph immediately following 3.3.1  basic.scope.pdecl paragraph 2:

The point of declaration for a class first declared by a class-specifier is immediately after the identifier or template-id (if any) in its class-head (Clause 9  class). The point of declaration for an enumeration is immediately after the identifier (if any) in its enum-specifier (7.2  dcl.enum).

Change point 1 of 3.3.6  basic.scope.class paragraph 1 to read:

The potential scope of a name declared in a class consists not only of the declarative region following the name's declarator point of declaration, but also of all function bodies, default arguments, and constructor ctor-initializers in that class (including such things in nested classes).

[Note that the preceding change duplicates one of the changes in the proposed resolution of issue 432.]

Change 14.7.2  temp.explicit paragraph 2 to read:

If the explicit instantiation is for a member function, a member class or a static data member of a class template specialization, the name of the class template specialization in the qualified-id for the member declarator name shall be a template-id.

Add the following as paragraph 5 of Clause 9  class:

If a class-head contains a nested-name-specifier, the class-specifier shall refer to a class that was previously declared directly in the class or namespace to which the nested-name-specifier refers (i.e., neither inherited nor introduced by a using-declaration), and the class-specifier shall appear in a namespace enclosing the previous declaration.

Delete 9.1  class.name paragraph 4 (this was added by issue 284):

When a nested-name-specifier is specified in a class-head or in an elaborated-type-specifier, the resulting qualified name shall refer to a previously declared member of the class or namespace to which the nested-name-specifier refers, and the member shall not have been introduced by a using-declaration in the scope of the class or namespace nominated by the nested-name-specifier.



328. Missing requirement that class member types be complete

Section: 9.2  class.mem     Status: WP     Submitter: Michiel Salters     Date: 10 Dec 2001

[Voted into WP at March 2004 meeting.]

Is it legal to use an incomplete type (3.9  basic.types paragraph 6) as a class member, if no object of such class is ever created ?

And as a class template member, even if the template is instantiated, but no object of the instantiated class is created?

The consensus seems to be NO, but no wording was found in the standard which explicitly disallows it.

The problem seems to be that most of the restrictions on incomplete types are on their use in objects, but class members are not objects.

A possible resolution, if this is considered a defect, is to add to 3.2  basic.def.odr paragraph 4, (situations when T must be complete), the use of T as a member of a class or instantiated class template.

The thread on comp.std.c++ which brought up the issue was "Compiler differences: which is correct?", started 2001 11 30. <3c07c8fb$0$8507$ed9e5944@reading.news.pipex.net>

Proposed Resolution (April 2002, revised April 2003):

Change the first bullet of the note in 3.2  basic.def.odr paragraph 4 and add two new bullets following it, as follows:

Replace 9.2  class.mem paragraph 8 by:

Non-static (9.4  class.static) data members shall not have incomplete types. In particular, a class C shall not contain a non-static member of class C, but it can contain a pointer or reference to an object of class C.

See also 3.9  basic.types paragraph 6, which is relevant but not changed by the Proposed Resolution.




437. Is type of class allowed in member function exception specification?

Section: 9.2  class.mem     Status: WP     Submitter: Cary Coutant     Date: 10 Oct 2003

[Voted into WP at April 2005 meeting.]

I've encountered a C++ program in which a member function wants to declare that it may throw an object of its own class, e.g.:

  class Foo {
  private:
     int val;
  public:
     Foo( int &initval ) { val = initval; };
     void throwit() throw(Foo) { throw (*this); };
  };

The compiler is complaining that Foo is an incomplete type, and can't be used in the exception specification.

My reading of the standard [basic.types] is inconclusive. Although it does state that the class declaration is considered complete when the closing brace is read, I believe it also intends that the member function declarations should not be semantically validated until the class has been completely declared.

If this isn't allowed, I don't know how else a member function could be declared to throw an object of its own class.

John Spicer: The type is considered complete within function bodies, but not in their declaration (see 9.2  class.mem paragraph 2).

Proposed Resolution:

Change 9.2  class.mem paragraph 2 as follows:

Within the class member-specification, the class is regarded as complete within function bodies, default arguments, exception-specifications, and constructor ctor-initializers (including such things in nested classes).

Rationale: Taken with 8.3.5  dcl.fct paragraph 6, the exception-specification is the only part of a function declaration/definition in which the class name cannot be used because of its putative incompleteness. There is no justification for singling out exception specifications this way; both in the function body and in a catch clause, the class type will be complete, so there is no harm in allowing the class name to be used in the exception-specification.




613. Unevaluated uses of non-static class members

Section: 9.2  class.mem     Status: WP     Submitter: Herb Sutter     Date: 28 October 2006

[Voted into WP at April, 2007 meeting.]

According to 9.2  class.mem paragraph 9, the name of a non-static data member can only be used with an object reference (explicit or implied by the this pointer of a non-static member function) or to form a pointer to member. This restriction applies even in the operand of sizeof, although the operand is not evaluated and thus no object is needed to perform the operation. Consequently, determining the size of a non-static class member often requires a circumlocution like

    sizeof((C*) 0)->m

instead of the simpler and more obvious (but incorrect)

    sizeof(C::m)

The CWG considered this question as part of issue 198 and decided at that time to retain the restriction on consistency grounds: the rule was viewed as applying uniformly to expressions, and making an exception for sizeof would require introducing a special-purpose “wart.”

The issue has recently resurfaced, in part due to the fact that the restriction would also apply to the decltype operator. Like the unary & operator to form a pointer to member, sizeof and decltype need neither an lvalue nor an rvalue, requiring solely the declarative information of the named operand. One possible approach would be to define the concept of “unevaluated operand” or the like, exempt unevaluated operands from the requirement for an object reference in 9.2  class.mem paragraph 9, and then define the operands of these operators as “unevaluated.”

Proposed resolution (April, 2007):

The wording is given in paper J16/07-0113 = WG21 N2253.




406. Static data member in class with name for linkage purposes

Section: 9.4.2  class.static.data     Status: WP     Submitter: Jorgen Bundgaard     Date: 12 Mar 2003

[Voted into WP at March 2004 meeting.]

The following test program is claimed to be a negative C++ test case for "Unnamed classes shall not contain static data members", c.f. ISO/IEC 14882 section 9.4.2  class.static.data paragraph 5.

 
  struct B {
         typedef struct {
                 static int i;          // Is this legal C++ ?
         } A;
  };
 
  int B::A::i = 47;      // Is this legal C++ ?

We are not quite sure about what an "unnamed class" is. There is no exact definition in ISO/IEC 14882; the closest we can come to a hint is the wording of section 7.1.3  dcl.typedef paragraph 5, where it seems to be understood that a class-specifier with no identifier between "class" and "{" is unnamed. The identifier provided after "}" ( "A" in the test case above) is there for "linkage purposes" only.

To us, class B::A in the test program above seems "named" enough, and there is certainly a mechanism to provide the definition for B::A::i (in contrast to the note in section 9.4.2  class.static.data paragraph 5).

Our position is therefore that the above test program is indeed legal C++. Can you confirm or reject this claim?

Herb Sutter replied to the submitter as follows: Here are my notes based on a grep for "unnamed class" in the standard:

So yes, an unnamed class is one where there is no identifier (class name) between the class-key and the {. This is also in harmony with the production for class-name in 9  class paragraph 1:

Notes from the October 2003 meeting:

We agree that the example is not valid; this is an unnamed class. We will add wording to define an unnamed class. The note in 9.4.2  class.static.data paragraph 5 should be corrected or deleted.

Proposed Resolution (October 2003):

At the end of clause 9  class, paragraph 1, add the following:

A class-specifier where the class-head omits the optional identifier defines an unnamed class.

Delete the following from 9.4.2  class.static.data paragraph 5:

[ Note: this is because there is no mechanism to provide the definitions for such static data members. ]



454. When is a definition of a static data member required?

Section: 9.4.2  class.static.data     Status: WP     Submitter: Gennaro Prota     Date: 18 Jan 2004

[Voted into WP at the October, 2006 meeting.]

As a result of the resolution of core issue 48, the current C++ standard is not in sync with existing practice and with user expectations as far as definitions of static data members having const integral or const enumeration type are concerned. Basically what current implementations do is to require a definition only if the address of the constant is taken. Example:

void f() {

  std::string s;
  ... 

  // current implementations don't require a definition
  if (s.find('a', 3) == std::string::npos) {
   ...
  }

To the letter of the standard, though, the above requires a definition of npos, since the expression std::string::npos is potentially evaluated. I think this problem would be easily solved with simple changes to 9.4.2  class.static.data paragraph 4, 9.4.2  class.static.data paragraph 5 and 3.2  basic.def.odr paragraph 3.

Suggested resolution:

Replace 9.4.2  class.static.data paragraph 4 with:

If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be [note1] an integral constant expression (5.19). In that case, the member can appear in integral constant expressions. No definition of the member is required, unless an lvalue expression that designates it is potentially evaluated and either used as operand to the built-in unary & operator [note 2] or directly bound to a reference.

If a definition exists, it shall be at namespace scope and shall not contain an initializer.

In 9.4.2  class.static.data paragraph 5 change

There shall be exactly one definition of a static data member that is used in a program; no diagnostic is required; see 3.2.

to

Except as allowed by 9.4.2 par. 4, there shall be exactly one definition of a static data member that is potentially evaluated (3.2) in a program; no diagnostic is required.

In 3.2  basic.def.odr paragraph 3 add, at the beginning:

Except for the omission allowed by 9.4.2, par. 4, ...

[note 1] Actually it shall be a "= followed by a constant-expression". This could probably be an editorial fix, rather than a separate DR.

[note 2] Note that this is the case when reinterpret_cast-ing to a reference, like in

struct X { static const int value = 0; };
const char & c = reinterpret_cast<const char&>(X::value);
See 5.2.10  expr.reinterpret.cast/10

More information, in response to a question about why issue 48 does not resolve the problem:

The problem is that the issue was settled in a way that solves much less than it was supposed to solve; that's why I decided to file, so to speak, a DR on a DR.

I understand this may seem a little 'audacious' on my part, but please keep reading. Quoting from the text of DR 48 (emphasis mine):

Originally, all static data members still had to be defined outside the class whether they were used or not.

But that restriction was supposed to be lifted [...]

In particular, if an integral/enum const static data member is initialized within the class, and its address is never taken, we agreed that no namespace-scope definition was required.

The corresponding resolution doesn't reflect this intent, with the definition being still required in most situations anyway: it's enough that the constant appears outside a place where constants are required (ignoring the obvious cases of sizeof and typeid) and you have to provide a definition. For instance:

  struct X {
   static const int c = 1;
  };

  void f(int n)
  {
   if (n == X::c)   // <-- potentially evaluated
    ...
  }

<start digression>

Most usages of non-enum BOOST_STATIC_COSTANTs, for instance, are (or were, last time I checked) non-conforming. If you recall, Paul Mensonides pointed out that the following template

// map_integral

template<class T, T V> struct map_integral : identity<T> {
  static const T value = V;
};

template<class T, T V> const T map_integral<T, V>::value;

whose main goal is to map the same couples (type, value) to the same storage, also solves the definition problem. In this usage it is an excellent hack (if your compiler is good enough), but IMHO still a hack on a language defect.

<end digression>

What I propose is to solve the issue according to the original intent, which is also what users expect and all compilers that I know of already do. Or, in practice, we would have a rule that exists only as words in a standard document.

PS: I've sent a copy of this to Mr. Adamczyk to clarify an important doubt that occurred to me while writing this reply:

if no definition is provided for an integral static const data member is that member an object? Paragraph 1.8/1 seems to say no, and in fact it's difficult to think it is an object without assuming/pretending that a region of storage exists for it (an object *is* a region of storage according to the standard).

I would think that when no definition is required we have to assume that it could be a non-object. In that case there's nothing in 3.2 which says what 'used' means for such an entity and the current wording would thus be defective. Also, since the name of the member is an lvalue and 3.10/2 says an lvalue refers to an object we would have another problem.

OTOH the standard could pretend it is always an object (though the compiler can optimize it away) and in this case it should probably make a special case for it in 3.2/2.

Notes from the March 2004 meeting:

We sort of like this proposal, but we don't feel it has very high priority. We're not going to spend time discussing it, but if we get drafting for wording we'll review it.

Proposed resolution (October, 2005):

  1. Change the first two sentences of 3.2  basic.def.odr paragraph 2 from:

    An expression is potentially evaluated unless it appears where an integral constant expression is required (see 5.19  expr.const), is the operand of the sizeof operator (5.3.3  expr.sizeof), or is the operand of the typeid operator and the expression does not designate an lvalue of polymorphic class type (5.2.8  expr.typeid). An object or non-overloaded function is used if its name appears in a potentially-evaluated expression.

    to

    An expression that is the operand of the sizeof operator (5.3.3  expr.sizeof) is unevaluated, as is an expression that is the operand of the typeid operator if it is not an lvalue of a polymorphic class type (5.2.8  expr.typeid); all other expressions are potentially evaluated. An object or non-overloaded function whose name appears as a potentially-evaluated expression is used, unless it is an object that satisfies the requirements for appearing in an integral constant expression (5.19  expr.const) and the lvalue-to-rvalue conversion (4.1  conv.lval) is immediately applied.
  2. Change the first sentence of 9.4.2  class.static.data paragraph 2 as indicated:

  3. If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which whose constant-expression shall be an integral constant expression (5.19  expr.const).



58. Signedness of bit fields of enum type

Section: 9.6  class.bit     Status: WP     Submitter: Steve Adamczyk     Date: 13 Oct 1998

[Voted into WP at the October, 2006 meeting.]

Section 9.6  class.bit paragraph 4 needs to be more specific about the signedness of bit fields of enum type. How much leeway does an implementation have in choosing the signedness of a bit field? In particular, does the phrase "large enough to hold all the values of the enumeration" mean "the implementation decides on the signedness, and then we see whether all the values will fit in the bit field", or does it require the implementation to make the bit field signed or unsigned if that's what it takes to make it "large enough"?

(See also issue 172.)

Note (March, 2005): Clark Nelson observed that there is variation among implementations on this point.

Notes from April, 2005 meeting:

Although implementations enjoy a great deal of latitude in handling bit-fields, it was deemed more user-friendly to ensure that the example in paragraph 4 will work by requiring implementations to use an unsigned underlying type if the enumeration type has no negative values. (If the implementation is allowed to choose a signed representation for such bit-fields, the comparison against TRUE will be false.)

In addition, it was observed that there is an apparent circularity between 7.2  dcl.enum paragraph 7 and 9.6  class.bit paragraph 4 that should be resolved.

Proposed resolution (April, 2006):

  1. Replace 7.2  dcl.enum paragraph 7, deleting the embedded footnote 85, with the following:

    For an enumeration where emin is the smallest enumerator and emax is the largest, the values of the enumeration are the values in the range bmin to bmax, defined as follows: Let K be 1 for a two's complement representation and 0 for a one's complement or sign-magnitude representation. bmax is the smallest value greater than or equal to max(|emin|-K,|emax|) and equal to 2M-1, where M is a non-negative integer. bmin is zero if emin is non-negative and -(bmax+K) otherwise. The size of the smallest bit-field large enough to hold all the values of the enumeration type is max(M,1) if bmin is zero and M+1 otherwise. It is possible to define an enumeration that has values not defined by any of its enumerators.
  2. Add the indicated text to the second sentence of 9.6  class.bit paragraph 4:

    If the value of an enumerator is stored into a bit-field of the same enumeration type and the number of bits in the bit-field is large enough to hold all the values of that enumeration type (7.2  dcl.enum), the original enumerator value and the value of the bit-field shall compare equal.



436. Problem in example in 9.6 paragraph 4

Section: 9.6  class.bit     Status: WP     Submitter: Roberto Santos     Date: 10 October 2003

[Voted into WP at October 2004 meeting.]

It looks like the example on 9.6  class.bit paragraph 4 has both the enum and function contributing the identifier "f" for the same scope.

  enum BOOL { f=0, t=1 };
  struct A {
    BOOL b:1;
  };
  A a;
  void f() {
    a.b = t;
    if (a.b == t) // shall yield true
    { /* ... */ }
  }

Proposed resolution:

Change the example at the end of 9.6  class.bit/4 from:

  enum BOOL { f=0, t=1 };
  struct A {
    BOOL b:1;
  };
  A a;
  void f() {
    a.b = t;
    if (a.b == t) // shall yield true
    { /* ... */ }
  }

To:

  enum BOOL { FALSE=0, TRUE=1 };
  struct A {
    BOOL b:1;
  };
  A a;
  void f() {
    a.b = TRUE;
    if (a.b == TRUE) // shall yield true
    { /* ... */ }
  }




198. Definition of "use" in local and nested classes

Section: 9.8  class.local     Status: WP     Submitter: Erwin Unruh     Date: 27 Jan 2000

[Voted into WP at April 2003 meeting.]

9.8  class.local paragraph 1 says,

Declarations in a local class can use only type names, static variables, extern variables and functions, and enumerators from the enclosing scope.
The definition of when an object or function is "used" is found in 3.2  basic.def.odr paragraph 2 and essentially says that the operands of sizeof and non-polymorphic typeid operators are not used. (The resolution for issue 48 will add contexts in which integral constant expressions are required to the list of non-uses.)

This definition of "use" would presumably allow code like

    void foo() {
        int i;
        struct S {
            int a[sizeof(i)];
        };
    };
which is required for C compatibility.

However, the restrictions on nested classes in 9.7  class.nest paragraph 1 are very similar to those for local classes, and the example there explicitly states that a reference in a sizeof expression is a forbidden use (abbreviated for exposition):

    class enclose {
    public:
        int x;
        class inner {
            void f(int i)
            {
                int a = sizeof(x);  // error: refers to enclose::x
            }
        };
    };

[As a personal note, I have seen real-world code that was exactly like this; it was hard to persuade the author that the required writearound, sizeof(((enclose*) 0)->x), was an improvement over sizeof(x). —wmm]

Similarly, 9.2  class.mem paragraph 9 would appear to prohibit examples like the following:

    struct B {
        char x[10];
    };
    struct D: B {
        char y[sizeof(x)];
    };

Suggested resolution: Add cross-references to 3.2  basic.def.odr following the word "use" in both 9.7  class.nest and 9.8  class.local , and change the example in 9.7  class.nest to indicate that a reference in a sizeof expression is permitted. In 9.2  class.mem paragraph 9, "referred to" should be changed to "used" with a cross_reference to 3.2  basic.def.odr.

Notes from 10/01 meeting:

It was noted that the suggested resolution did not make the sizeof() example in 9.7  class.nest valid. Although the reference to the argument of sizeof() is not regarded as a use, the right syntax must be used nonetheless to reference a non-static member from the enclosing class. The use of the member name by itself is not valid. The consensus within the core working group was that nothing should be done about this case. It was later discovered that 9.4  class.static paragraph 3 states that

The definition of a static member shall not use directly the names of the nonstatic members of its class or of a base class of its class (including as operands of the sizeof operator). The definition of a static member may only refer to these members to form pointer to members (5.3.1  expr.unary.op) or with the class member access syntax (5.2.5  expr.ref).

This seems to reinforce the decision of the working group.

The use of "use" should still be cross-referenced. The statements in 9.7  class.nest and 9.8  class.local should also be rewritten to state the requirement positively rather than negatively as the list of "can't"s is already missing some cases such as template parameters.

Notes from the 4/02 meeting:

We backed away from "use" in the technical sense, because the requirements on the form of reference are the same whether or not the reference occurs inside a sizeof.

Proposed Resolution (revised October 2002):

In 9.2  class.mem paragraph 9, replace

Except when used to form a pointer to member (5.3.1  expr.unary.op), when used in the body of a nonstatic member function of its class or of a class derived from its class (9.3.1  class.mfct.nonstatic), or when used in a mem-initializer for a constructor for its class or for a class derived from its class (12.6.2  class.base.init), a nonstatic data or function member of a class shall only be referred to with the class member access syntax (5.2.5  expr.ref).

with the following paragraph

Each occurrence in an expression of the name of a nonstatic data member or nonstatic member function of a class shall be expressed as a class member access (5.2.5  expr.ref), except when it appears in the formation of a pointer to member (5.3.1  expr.unary.op), when it appears in the body of a nonstatic member function of its class or of a class derived from its class (9.3.1  class.mfct.nonstatic), or when it appears in a mem-initializer for a constructor for its class or for a class derived from its class (12.6.2  class.base.init).

In 9.7  class.nest paragraph 1, replace the last sentence,

Except by using explicit pointers, references, and object names, declarations in a nested class can use only type names, static members, and enumerators from the enclosing class.

with the following

[Note: In accordance with 9.2  class.mem, except by using explicit pointers, references, and object names, declarations in a nested class shall not use nonstatic data members or nonstatic member functions from the enclosing class. This restriction applies in all constructs including the operands of the sizeof operator.]

In the example following 9.7  class.nest paragraph 1, change the comment on the first statement of function f to emphasize that sizeof(x) is an error. The example reads in full:

  int x;
  int y;
  class enclose {
  public:
    int x;
    static int s;
    class inner {
      void f(int i)
      {
        int a = sizeof(x);  // error: direct use of enclose::x even in sizeof
        x = i;              // error: assign to enclose::x
        s = i;              // OK: assign to enclose::s
        ::x = i;            // OK: assign to global x
        y = i;              // OK: assign to global y
      }
      void g(enclose* p, int i)
      {
        p->x = i;        // OK: assign to enclose::x
      }
    };
  };
   
  inner* p = 0;             // error: inner not in scope



484. Can a base-specifier name a cv-qualified class type?

Section: 10  class.derived     Status: WP     Submitter: Richard Corden     Date: 21 Oct 2004

[Voted into WP at the October, 2006 meeting.]

Issue 298, recently approved, affirms that cv-qualified class types can be used as nested-name-specifiers. Should the same be true for base-specifiers?

Rationale (April, 2005):

The resolution of issue 298 added new text to 9.1  class.name paragraph 5 making it clear that a typedef that names a cv-qualified class type is a class-name. Because the definition of base-specifier simply refers to class-name, it is already the case that cv-qualified class types are permitted as base-specifiers.

Additional notes (June, 2005):

It's not completely clear what it means to have a cv-qualified type as a base-specifier. The original proposed resolution for issue 298 said that “the cv-qualifiers are ignored,” but that wording is not in the resolution that was ultimately approved.

If the cv-qualifiers are not ignored, does that mean that the base-class subobject should be treated as always similarly cv-qualified, regardless of the cv-qualification of the derived-class lvalue used to access the base-class subobject? For instance:

    typedef struct B {
        void f();
        void f() const;
        int i;
    } const CB;

    struct D: CB { };

    void g(D* dp) {
        dp->f();    // which B::f?
        dp->i = 3;  // permitted?
    }

Proposed resolution (October, 2005):

  1. Change 9.1  class.name paragraph 5 as indicated:

  2. A typedef-name (7.1.3  dcl.typedef) that names a class type, or a cv-qualified version thereof, is also a class-name, but class-name. If a typedef-name that names a cv-qualified class type is used where a class-name is required, the cv-qualifiers are ignored. A typedef-name shall not be used as the identifier in a class-head.
  3. Delete 7.1.3  dcl.typedef paragraph 8:

  4. [Note: if the typedef-name is used where a class-name (or enum-name) is required, the program is ill-formed. For example,

        typedef struct {
            S();     // error: requires a return type because S is
                      // an ordinary member function, not a constructor
        } S;
    

    end note]




39. Conflicting ambiguity rules

Section: 10.2  class.member.lookup     Status: WP     Submitter: Neal M Gafter     Date: 20 Aug 1998

[Voted into WP at April 2005 meeting.]

The ambiguity text in 10.2  class.member.lookup may not say what we intended. It makes the following example ill-formed:

    struct A {
        int x(int);
    };
    struct B: A {
        using A::x;
        float x(float);
    };
    
    int f(B* b) {
        b->x(3);  // ambiguous
    }
This is a name lookup ambiguity because of 10.2  class.member.lookup paragraph 2:
... Each of these declarations that was introduced by a using-declaration is considered to be from each sub-object of C that is of the type containing the declaration designated by the using-declaration. If the resulting set of declarations are not all from sub-objects of the same type, or the set has a nonstatic member and includes members from distinct sub-objects, there is an ambiguity and the program is ill-formed.
This contradicts the text and example in paragraph 12 of 7.3.3  namespace.udecl .

Proposed Resolution (10/00):

  1. Replace the two cited sentences from 10.2  class.member.lookup paragraph 2 with the following:

    The resulting set of declarations shall all be from sub-objects of the same type, or there shall be a set of declarations from sub-objects of a single type that contains using-declarations for the declarations found in all other sub-object types. Furthermore, for nonstatic members, the resulting set of declarations shall all be from a single sub-object, or there shall be a set of declarations from a single sub-object that contains using-declarations for the declarations found in all other sub-objects. Otherwise, there is an ambiguity and the program is ill-formed.
  2. Replace the examples in 10.2  class.member.lookup paragraph 3 with the following:

        struct A {
            int x(int);
            static int y(int);
        };
        struct V {
            int z(int);
        };
        struct B: A, virtual V {
            using A::x;
            float x(float);
            using A::y;
            static float y(float);
            using V::z;
            float z(float);
        };
        struct C: B, A, virtual V {
        };
    
        void f(C* c) {
            c->x(3);    // ambiguous -- more than one sub-object A
            c->y(3);    // not ambiguous
            c->z(3);    // not ambiguous
        }
    

Notes from 04/01 meeting:

The following example should be accepted but is rejected by the wording above:

    struct A { static void f(); };

    struct B1: virtual A {
        using A::f;
    };

    struct B2: virtual A {
        using A::f;
    };

    struct C: B1, B2 { };

    void g() {
        C::f();        // OK, calls A::f()
    }

Notes from 10/01 meeting (Jason Merrill):

The example in the issues list:

    struct A {
        int x(int);
    };
    struct B: A {
        using A::x;
        float x(float);
    };
    
    int f(B* b) {
        b->x(3);  // ambiguous
    }
Is broken under the existing wording:
... Each of these declarations that was introduced by a using-declaration is considered to be from each sub-object of C that is of the type containing the declaration designated by the using-declaration. If the resulting set of declarations are not all from sub-objects of the same type, or the set has a nonstatic member and includes members from distinct sub-objects, there is an ambiguity and the program is ill-formed.
Since the two x's are considered to be "from" different objects, looking up x produces a set including declarations "from" different objects, and the program is ill-formed. Clearly this is wrong. The problem with the existing wording is that it fails to consider lookup context.

The first proposed solution:

The resulting set of declarations shall all be from sub-objects of the same type, or there shall be a set of declarations from sub-objects of a single type that contains using-declarations for the declarations found in all other sub-object types. Furthermore, for nonstatic members, the resulting set of declarations shall all be from a single sub-object, or there shall be a set of declarations from a single sub-object that contains using-declarations for the declarations found in all other sub-objects. Otherwise, there is an ambiguity and the program is ill-formed.
breaks this testcase:
    struct A { static void f(); };

    struct B1: virtual A {
        using A::f;
    };

    struct B2: virtual A {
        using A::f;
    };

    struct C: B1, B2 { };

    void g() {
        C::f();        // OK, calls A::f()
    }
because it considers the lookup context, but not the definition context; under this definition of "from", the two declarations found are the using-declarations, which are "from" B1 and B2.

The solution is to separate the notions of lookup and definition context. I have taken an algorithmic approach to describing the strategy.

Incidentally, the earlier proposal allows one base to have a superset of the declarations in another base; that was an extension, and my proposal does not do that. One algorithmic benefit of this limitation is to simplify the case of a virtual base being hidden along one arm and not another ("domination"); if we allowed supersets, we would need to remember which subobjects had which declarations, while under the following resolution we need only keep two lists, of subobjects and declarations.

Proposed resolution (October 2002):

Replace 10.2  class.member.lookup paragraph 2 with:

The following steps define the result of name lookup for a member name f in a class scope C.

The lookup set for f in C, called S(f,C), consists of two component sets: the declaration set, a set of members named f; and the subobject set, a set of subobjects where declarations of these members (possibly including using-declarations) were found. In the declaration set, using-declarations are replaced by the members they designate, and type declarations (including injected-class-names) are replaced by the types they designate. S(f,C) is calculated as follows.

If C contains a declaration of the name f, the declaration set contains every declaration of f in C (excluding bases), the subobject set contains C itself, and calculation is complete.

Otherwise, S(f,C) is initially empty. If C has base classes, calculate the lookup set for f in each direct base class subjobject Bi, and merge each such lookup set S(f,Bi) in turn into S(f,C).

The following steps define the result of merging lookup set S(f,Bi) into the intermediate S(f,C):

The result of name lookup for f in C is the declaration set of S(f,C). If it is an invalid set, the program is ill-formed.

[Example:

    struct A { int x; };                    // S(x,A) = {{ A::x }, { A }}
    struct B { float x; };                  // S(x,B) = {{ B::x }, { B }}
    struct C: public A, public B { };       // S(x,C) = { invalid, { A in C, B in C }}
    struct D: public virtual C { };         // S(x,D) = S(x,C)
    struct E: public virtual C { char x; }; // S(x,E) = {{ E::x }, { E }}
    struct F: public D, public E { };       // S(x,F) = S(x,E)

    int main() {
      F f;
      f.x = 0;   // OK, lookup finds { E::x }
    }
S(x,F) is unambiguous because the A and B base subobjects of D are also base subobjects of E, so S(x,D) is discarded in the first merge step. --end example]

Turn 10.2  class.member.lookup paragraphs 5 and 6 into notes.

Notes from October 2003 meeting:

Mike Miller raised some new issues in N1543, and we adjusted the proposed resolution as indicated in that paper.

Further information from Mike Miller (January 2004):

Unfortunately, I've become aware of a minor glitch in the proposed resolution for issue 39 in N1543, so I'd like to suggest a change that we can discuss in Sydney.

A brief review and background of the problem: the major change we agreed on in Kona was to remove detection of multiple-subobject ambiguity from class lookup (10.2  class.member.lookup) and instead handle it as part of the class member access expression. It was pointed out in Kona that 11.2  class.access.base/5 has this effect:

If a class member access operator, including an implicit "this->," is used to access a nonstatic data member or nonstatic member function, the reference is ill-formed if the left operand (considered as a pointer in the "." operator case) cannot be implicitly converted to a pointer to the naming class of the right operand.

After the meeting, however, I realized that this requirement is not sufficient to handle all the cases. Consider, for instance,

    struct B {
        int i;
    };

    struct I1: B { };
    struct I2: B { };

    struct D: I1, I2 {
        void f() {
            i = 0;    // not ill-formed per 11.2p5
        }
    };

Here, both the object expression ("this") and the naming class are "D", so the reference to "i" satisfies the requirement in 11.2  class.access.base/5, even though it involves a multiple-subobject ambiguity.

In order to address this problem, I proposed in N1543 to add a paragraph following 5.2.5  expr.ref/4:

If E2 is a non-static data member or a non-static member function, the program is ill-formed if the class of E1 cannot be unambiguously converted (10.2) to the class of which E2 is directly a member.

That's not quite right. It does diagnose the case above as written; however, it breaks the case where qualification is used to circumvent the ambiguity:

    struct D2: I1, I2 {
        void f() {
            I2::i = 0;    // ill-formed per proposal
        }
    };

In my proposed wording, the class of "this" can't be converted to "B" (the qualifier is ignored), so the access is ill-formed. Oops.

I think the following is a correct formulation, so the proposed resolution we discuss in Sydney should contain the following paragraph instead of the one in N1543:

If E2 is a nonstatic data member or a non-static member function, the program is ill-formed if the naming class (11.2) of E2 cannot be unambiguously converted (10.2) to the class of which E2 is directly a member.

This reformulation also has the advantage of pointing readers to 11.2  class.access.base, where the the convertibility requirement from the class of E1 to the naming class is located and which might otherwise be overlooked.

Notes from the March 2004 meeting:

We discussed this further and agreed with these latest recommendations. Mike Miller has produced a paper N1626 that gives just the final collected set of changes.

(This resolution also resolves isssue 306.)




306. Ambiguity by class name injection

Section: 10.2  class.member.lookup     Status: WP     Submitter: Clark Nelson     Date: 19 Jul 2001

[Voted into WP at April 2005 meeting.]

Is the following well-formed?

    struct A {
        struct B { };
    };
    struct C : public A, public A::B {
        B *p;
    };
The lookup of B finds both the struct B in A and the injected B from the A::B base class. Are they the same thing? Does the standard say so?

What if a struct is found along one path and a typedef to that struct is found along another path? That should probably be valid, but does the standard say so?

This is resolved by issue 39

February 2004: Moved back to "Review" status because issue 39 was moved back to "Review".




390. Pure virtual must be defined when implicitly called

Section: 10.4  class.abstract     Status: WP     Submitter: Daniel Frey     Date: 14 Nov 2002

[Voted into WP at March 2004 meeting.]

In clause 10.4  class.abstract paragraph 2, it reads:

A pure virtual function need be defined only if explicitly called with the qualified-id syntax (5.1  expr.prim).

This is IMHO incomplete. A dtor is a function (well, a "special member function", but this also makes it a function, right?) but it is called implicitly and thus without a qualified-id syntax. Another alternative is that the pure virtual function is called directly or indirectly from the ctor. Thus the above sentence which specifies when a pure virtual function need be defined ("...only if...") needs to be extended:

A pure virtual function need be defined only if explicitly called with the qualified-id syntax (5.1  expr.prim) or if implicitly called (12.4  class.dtor or 12.7  class.cdtor).

Proposed resolution:

Change 10.4  class.abstract paragraph 2 from

A pure virtual function need be defined only if explicitly called with the qualified-id syntax (5.1  expr.prim).

to

A pure virtual function need be defined only if explicitly called with, or as if with (12.4  class.dtor), the qualified-id syntax (5.1  expr.prim).

Note: 12.4  class.dtor paragraph 6 defines the "as if" cited.




8. Access to template arguments used in a function return type and in the nested name specifier

Section: 11  class.access     Status: WP     Submitter: Mike Ball     Date: unknown

[Moved to DR at 4/01 meeting.]

Consider the following example:

    class A {
       class A1{};
       static void func(A1, int);
       static void func(float, int);
       static const int garbconst = 3;
     public:
       template < class T, int i, void (*f)(T, int) > class int_temp {};
       template<> class int_temp<A1, 5, func> { void func1() };
       friend int_temp<A1, 5, func>::func1();
       int_temp<A1, 5, func>* func2();
   };
   A::int_temp<A::A1, A::garbconst + 2, &A::func>* A::func2() {...}
ISSUE 1:

In 11  class.access paragraph 5 we have:

This means, if we take the loosest possible definition of "access from a particular scope", that we have to save and check later the following names

      A::int_temp
      A::A1
      A::garbconst (part of an expression)
      A::func (after overloading is done)
I suspect that member templates were not really considered when this was written, and that it might have been written rather differently if they had been. Note that access to the template arguments is only legal because the class has been declared a friend, which is probably not what most programmers would expect.

Rationale:

Not a defect. This behavior is as intended.

ISSUE 2:

Now consider void A::int_temp<A::A1, A::garbconst + 2, &A::func>::func1() {...} By my reading of 11.8  class.access.nest , the references to A::A1, A::garbconst and A::func are now illegal, and there is no way to define this function outside of the class. Is there any need to do anything about either of these Issues?

Proposed resolution (04/01):

The resolution for this issue is contained in the resolution for issue 45.




494. Problems with the resolution of issue 45

Section: 11  class.access     Status: WP     Submitter: Lloyd J. Lewins     Date: 17 Dec 2004

[Voted into WP at the October, 2006 meeting.]

The proposed resolution for issue 45 inserts the following sentence after 11  class.access paragraph 1:

A member of a class can also access all names as the class of which it is a member.

I don't think that this is correctly constructed English. I see two possibilities:

  1. This is a typo, and the correct change is:

    A member of a class can also access all names of the class of which it is a member.

  2. The intent is something more like:

    A member of a nested class can also access all names accessible by any other member of the class of which it is a member.

[Note: this was editorially corrected at the time defect resolutions were being incorporated into the Working Paper to read, “...can also access all the names declared in the class of which it is a member,” which is essentially the same as the preceding option 1.]

I would prefer to use the language proposed for 11.8  class.access.nest:

A nested class is a member and as such has the same access rights as any other member.

A second problem is with the text in 11.4  class.friend paragraph 2:

[Note: this means that access to private and protected names is also granted to member functions of the friend class (as if the functions were each friends) and to the static data member definitions of the friend class. This also means that private and protected type names from the class granting friendship can be used in the base-clause of a nested class of the friend class. However, the declarations of members of classes nested within the friend class cannot access the names of private and protected members from the class granting friendship. Also, because the base-clause of the friend class is not part of its member declarations, the base-clause of the friend class cannot access the names of the private and protected members from the class granting friendship. For example,
    class A {
        class B { };
        friend class X;
    };
    class X : A::B {     // ill-formed: A::B cannot be accessed
                         // in the base-clause for X
        A::B mx;         // OK: A::B used to declare member of X
        class Y: A::B {  // OK: A::B used to declare member of X
            A::B my;     // ill-formed: A::B cannot be accessed
                         // to declare members of nested class of X
        };
    };
end note]

This seems to be an oversight. The proposed change to 11.8  class.access.nest paragraph 1 would appear to have eliminated the restrictions on nested class access. However, at least one compiler (gcc 3.4.3) doesn't appear to take my view, and continues with the restrictions on access by classes within a friend class, while implementing the rest of the resolution of issue 45.

Note (March, 2005):

Andreas Hommel: I think issue 45 requires an additional change in 9.7  class.nest paragraph 4:

Like a member function, a friend function (11.4  class.friend) defined within a nested class is in the lexical scope of that class; it obeys the same rules for name binding as a static member function of that class (9.4  class.static) and has no special access rights to members of an enclosing class.

I believe the “no special access rights” language should be removed.

Proposed resolution (October, 2005):

This issue is resolved by the resolution of issue 372.




9. Clarification of access to base class members

Section: 11.2  class.access.base     Status: WP     Submitter: unknown     Date: unknown

[Moved to DR at 4/01 meeting.]

11.2  class.access.base paragraph 4 says:

A base class is said to be accessible if an invented public member of the base class is accessible. If a base class is accessible, one can implicitly convert a pointer to a derived class to a pointer to that base class.
Given the above, is the following well-formed?
    class D;
     
    class B
    {
     protected:
       int b1;
 
       friend void foo( D* pd );
    };
     
    class D : protected B { };
     
    void foo( D* pd )
    {
       if ( pd->b1 > 0 ); // Is 'b1' accessible?
    }
Can you access the protected member b1 of B in foo? Can you convert a D* to a B* in foo?

1st interpretation:

A public member of B is accessible within foo (since foo is a friend), therefore foo can refer to b1 and convert a D* to a B*.

2nd interpretation:

B is a protected base class of D, and a public member of B is a protected member of D and can only be accessed within members of D and friends of D. Therefore foo cannot refer to b1 and cannot convert a D* to a B*.

(See J16/99-0042 = WG21 N1218.)

Proposed Resolution (04/01):

  1. Add preceding 11.2  class.access.base paragraph 4:
    A base class B of N is accessible at R, if
    • an invented public member of B would be a public member of N, or
    • R occurs in a member or friend of class N, and an invented public member of B would be a private or protected member of N, or
    • R occurs in a member or friend of a class P derived from N, and an invented public member of B would be a private or protected member of P, or
    • there exists a class S such that B is a base class of S accessible at R and S is a base class of N accessible at R. [Example:
          class B {
          public:
              int m;
          };
      
          class S: private B {
              friend class N;
          };
      
          class N: private S {
              void f() {
      	    B* p = this;  // OK because class S satisfies the
      			// fourth condition above: B is a base
      			// class of N accessible in f() because
      			// B is an accessible base class of S
      			// and S is an accessible base class of N.
              }
          };
      
      end example]
  2. Delete the first sentence of 11.2  class.access.base paragraph 4:
    A base class is said to be accessible if an invented public member of the base class is accessible.
  3. Replace the last sentence ("A member m is accessible...") by the following:
    A member m is accessible at the point R when named in class N if
    • m as a member of N is public, or
    • m as a member of N is private, and R occurs in a member or friend of class N, or
    • m as a member of N is protected, and R occurs in a member or friend of class N, or in a member or friend of a class P derived from N, where m as a member of P is private or protected, or
    • there exists a base class B of N that is accessible at R, and m is accessible at R when named in class B. [Example:...

The resolution for issue 207 modifies this wording slightly.




16. Access to members of indirect private base classes

Section: 11.2  class.access.base     Status: WP     Submitter: unknown     Date: unknown

[Moved to DR at 4/01 meeting.]

The text in 11.2  class.access.base paragraph 4 does not seem to handle the following cases:

    class D;
     
    class B {
    private:
        int i;
        friend class D;
    };
     
    class C : private B { };
     
    class D : private C {
        void f() {
            B::i; //1: well-formed?
            i;    //2: well-formed?
        }
    };
The member i is not a member of D and cannot be accessed in the scope of D. What is the naming class of the member i on line //1 and line //2?

Proposed Resolution (04/01): The resolution for this issue is contained in the resolution for issue 9..




207. using-declarations and protected access

Section: 11.2  class.access.base     Status: WP     Submitter: Jason Merrill     Date: 28 Feb 2000

[Moved to DR at 10/01 meeting.]

Consider the following example:

  class A {
  protected:
    static void f() {};
  };

  class B : A {
  public:
    using A::f;
    void g() {
      A::f();
    }
  };

The standard says in 11.2  class.access.base paragraph 4 that the call to A::f is ill-formed:

A member m is accessible when named in class N if

Here, m is A::f and N is A.

It seems clear to me that the third bullet should say "public, private or protected".

Steve Adamczyk:The words were written before using-declarations existed, and therefore didn't anticipate this case.

Proposed resolution (04/01):

Modify the third bullet of the third change ("A member m is accessible...") in the resolution of issue 9 to read "public, private, or protected" instead of "private or protected."




77. The definition of friend does not allow nested classes to be friends

Section: 11.4  class.friend     Status: WP     Submitter: Judy Ward     Date: 15 Dec 1998

[Moved to DR at 4/02 meeting.]

The definition of "friend" in 11.4  class.friend says:

A friend of a class is a function or class that is not a member of the class but is permitted to use the private and protected member names from the class. ...
A nested class, i.e. INNER in the example below, is a member of class OUTER. The sentence above states that it cannot be a friend. I think this is a mistake.
    class OUTER {
        class INNER;
        friend class INNER;
        class INNER {};
    };

Proposed resolution (04/01):

Change the first sentence of 11.4  class.friend as follows:

A friend of a class is a function or class that is not a member of the class but is allowed given permission to use the private and protected member names from the class. The name of a friend is not in the scope of the class, and the friend is not called with the member access operators (5.2.5  expr.ref) unless it is a member of another class. A class specifies its friends, if any, by way of friend declarations. Such declarations give special access rights to the friends, but they do not make the nominated friends members of the befriending class.



500. Access in base-specifiers of friend and nested classes

Section: 11.4  class.friend     Status: WP     Submitter: Andreas Hommel     Date: 25 Jan 2005

[Voted into WP at the October, 2006 meeting.]

I don't know the reason for this distinction, but it seems to be surprising that Base::A is legal and D is illegal in this example:

    class D;
    class Base 
    { 
        class A;
        class B;
        friend class D;
    }; 
    class Base::B 
    {
    };
    class Base::A : public Base::B  // OK because of issue 45
    { 
    };
    class D : public Base::B        // illegal because of 11.4p4
    { 
    };

Shouldn't this be consistent (either way)?

Notes from the April, 2005 meeting:

In discussing issue 372, the CWG decided that access in the base-specifiers of a class should be the same as for its members, and that resolution will apply to friend declarations, as well.

Proposed resolution (October, 2005):

This issue is resolved by the resolution of issue 372.




385. How does protected member check of 11.5 interact with using-declarations?

Section: 11.5  class.protected     Status: WP     Submitter: Vincent Korstanje     Date: 24 Sep 2002

[Voted into WP at October 2004 meeting.]

We consider it not unreasonable to do the following

  class A { 
    protected: 
    void g();
  }; 
  class B : public A { 
    public: 
      using A::g; // B::g is a public synonym for A::g 
  }; 

  class C: public A {
    void foo();
  };

  void C::foo() { 
    B b; 
    b.g(); 
  } 

However the EDG front-end does not like and gives the error

  #410-D: protected function "A::g" is not accessible through a "B" pointer or  object 
    b.g();
      ^

Steve Adamczyk: The error in this case is due to 11.5  class.protected of the standard, which is an additional check on top of the other access checking. When that section says "a protected nonstatic member function ... of a base class" it doesn't indicate whether the fact that there is a using-declaration is relevant. I'd say the current wording taken at face value would suggest that the error is correct -- the function is protected, even if the using-declaration for it makes it accessible as a public function. But I'm quite sure the wording in 11.5  class.protected was written before using-declarations were invented and has not been reviewed since for consistency with that addition.

Notes from April 2003 meeting:

We agreed that the example should be allowed.

Proposed resolution (April 2003, revised October 2003):

Change 11.5  class.protected paragraph 1 from

When a friend or a member function of a derived class references a protected nonstatic member function or protected nonstatic data member of a base class, an access check applies in addition to those described earlier in clause 11  class.access. [Footnote: This additional check does not apply to other members, e.g. static data members or enumerator member constants.] Except when forming a pointer to member (5.3.1  expr.unary.op), the access must be through a pointer to, reference to, or object of the derived class itself (or any class derived from that class (5.2.5  expr.ref). If the access is to form a pointer to member, the nested-name-specifier shall name the derived class (or any class derived from that class).

to

An additional access check beyond those described earlier in clause 11  class.access is applied when a nonstatic data member or nonstatic member function is a protected member of its naming class (11.2  class.access.base). [Footnote: This additional check does not apply to other members, e.g., static data members or enumerator member constants.] As described earlier, access to a protected member is granted because the reference occurs in a friend or member of some class C. If the access is to form a pointer to member (5.3.1  expr.unary.op), the nested-name-specifier shall name C or a class derived from C. All other accesses involve a (possibly implicit) object expression (5.2.5  expr.ref). In this case, the class of the object expression shall be C or a class derived from C.

Additional discussion (September, 2004):

Steve Adamczyk: I wonder if this wording is incorrect. Consider:

    class A {
    public:
      int p;
    };
    class B : protected A {
      // p is a protected member of B
    };
    class C : public B {
      friend void fr();
    };
    void fr() {
      B *pb = new B;
      pb->p = 1;  // Access okay?  Naming class is B, p is a protected member of B,
                  // the "C" of the issue 385 wording is C, but access is not via
                  // an object of type C or a derived class thereof.
    }

I think the formulation that the member is a protected member of its naming class is not what we want. I think we intended that the member is protected in the declaration that is found, where the declaration found might be a using-declaration.

Mike Miller: I think the proposed wording makes the access pb->p ill-formed, and I think that's the right thing to do.

First, protected inheritance of A by B means that B intends the public and protected members of A to be part of B's implementation, available to B's descendants only. (That's why there's a restriction on converting from B* to A*, to enforce B's intention on the use of members of A.) Consequently, I see no difference in access policy between your example and

    class B {
    protected:
        int p;
    };

Second, the reason we have this rule is that C's use of inherited protected members might be different from their use in a sibling class, say D. Thus members and friends of C can only use B::p in a manner consistent with C's usage, i.e., in C or derived-from-C objects. If we rewrote your example slightly,

    class D: public B { };

    void fr(B* pb) {
        pb->p = 1;
    }

    void g() {
        fr(new D);
    }

it's clear that the intent of this rule is broken — fr would be accessing B::p assuming C's policies when the object in question actually required D's policies.

(See also issues 471 and 472.)




10. Can a nested class access its own class name as a qualified name if it is a private member of the enclosing class?

Section: 11.8  class.access.nest     Status: WP     Submitter: Josee Lajoie     Date: unknown

[Moved to DR at 4/01 meeting.]

Paragraph 1 says: "The members of a nested class have no special access to members of an enclosing class..."

This prevents a member of a nested class from being defined outside of its class definition. i.e. Should the following be well-formed?

    class D {
        class E {
            static E* m;
        };
    };
     
    D::E* D::E::m = 1; // ill-formed
This is because the nested class does not have access to the member E in D. 11  class.access paragraph 5 says that access to D::E is checked with member access to class E, but unfortunately that doesn't give access to D::E. 11  class.access paragraph 6 covers the access for D::E::m, but it doesn't affect the D::E access. Are there any implementations that are standard compliant that support this?

Here is another example:

    class C {
        class B
        {
            C::B *t; //2 error, C::B is inaccessible
        };
    };
This causes trouble for member functions declared outside of the class member list. For example:
    class C {
        class B
        {
            B& operator= (const B&);
        };
    };
     
    C::B& C::B::operator= (const B&) { } //3
If the return type (i.e. C::B) is access checked in the scope of class B (as implied by 11  class.access paragraph 5) as a qualified name, then the return type is an error just like referring to C::B in the member list of class B above (i.e. //2) is ill-formed.

Proposed resolution (04/01):

The resolution for this issue is incorporated into the resolution for issue 45.




45. Access to nested classes

Section: 11.8  class.access.nest     Status: WP     Submitter: Daveed Vandevoorde     Date: 29 Sep 1998

[Moved to DR at 4/01 meeting.]

Example:

    #include <iostream.h>
    
    class C {  // entire body is private
        struct Parent {
            Parent() { cout << "C::Parent::Parent()\n"; }
        };
    
        struct Derived : Parent {
            Derived() { cout << "C::Derived::Derived()\n"; }
        };
    
        Derived d;
    };
    
    
    int main() {
        C c;      //  Prints message from both nested classes
        return 0;
    }
How legal/illegal is this? Paragraphs that seem to apply here are:

11  class.access paragraph 1:

A member of a class can be
and 11.8  class.access.nest paragraph 1:
The members of a nested class have no special access to members of an enclosing class, nor to classes or functions that have granted friendship to an enclosing class; the usual access rules (clause 11  class.access ) shall be obeyed. [...]
This makes me think that the ': Parent' part is OK by itself, but that the implicit call of 'Parent::Parent()' by 'Derived::Derived()' is not.

From Mike Miller:

I think it is completely legal, by the reasoning given in the (non-normative) 11.8  class.access.nest paragraph 2. The use of a private nested class as a base of another nested class is explicitly declared to be acceptable there. I think the rationale in the comments in the example ("// OK because of injection of name A in A") presupposes that public members of the base class will be public members in a (publicly-derived) derived class, regardless of the access of the base class, so the constructor invocation should be okay as well.

I can't find anything normative that explicitly says that, though.

(See also papers J16/99-0009 = WG21 N1186, J16/00-0031 = WG21 N1254, and J16/00-0045 = WG21 N1268.)

Proposed Resolution (04/01):

  1. Insert the following as a new paragraph following 11  class.access paragraph 1:

    A member of a class can also access all names as the class of which it is a member. A local class of a member function may access the same names that the member function itself may access. [Footnote: Access permissions are thus transitive and cumulative to nested and local classes.]
  2. Delete 11  class.access paragraph 6.

  3. In 11.8  class.access.nest paragraph 1, change

    The members of a nested class have no special access to members of an enclosing class, nor to classes or functions that have granted friendship to an enclosing class; the usual access rules (clause 11  class.access) shall be obeyed.

    to

    A nested class is a member and as such has the same access rights as any other member.

    Change

        B b;       // error: E::B is private
    

    to

        B b;       // Okay, E::I can access E::B
    

    Change

        p->x = i;      // error: E::x is private
    

    to

        p->x = i;      // Okay, E::I can access E::x
    
  4. Delete 11.8  class.access.nest paragraph 2.

(This resolution also resolves issues 8 and 10.




263. Can a constructor be declared a friend?

Section: 12.1  class.ctor     Status: WP     Submitter: Martin Sebor     Date: 13 Nov 2000

[Voted into WP at April 2003 meeting.]

According to 12.1  class.ctor paragraph 1, a declaration of a constructor has a special limited syntax, in which only function-specifiers are allowed. A friend specifier is not a function-specifier, so one interpretation is that a constructor cannot be declared in a friend declaration.

(It should also be noted, however, that neither friend nor function-specifier is part of the declarator syntax, so it's not clear that anything conclusive can be derived from the wording of 12.1  class.ctor.)

Notes from 04/01 meeting:

The consensus of the core language working group was that it should be permitted to declare constructors as friends.

Proposed Resolution (revised October 2002):

Change paragraph 1a in 3.4.3.1  class.qual (added by the resolution of issue 147) as follows:

If the nested-name-specifier nominates a class C, and the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C (clause 9  class), the name is instead considered to name the constructor of class C. Such a constructor name shall be used only in the declarator-id of a constructor definition declaration that appears outside of the class definition names a constructor....

Note: the above does not allow qualified names to be used for in-class declarations; see 8.3  dcl.meaning paragraph 1. Also note that issue 318 updates the same paragraph.

Change the example in 11.4  class.friend, paragraph 4 as follows:

class Y {
  friend char* X::foo(int);
  friend X::X(char);   // constructors can be friends
  friend X::~X();      // destructors can be friends
  //...
};



326. Wording for definition of trivial constructor

Section: 12.1  class.ctor     Status: WP     Submitter: James Kanze     Date: 9 Dec 2001

[Voted into WP at October 2003 meeting.]

In 12.1  class.ctor paragraph 5, the standard says "A constructor is trivial if [...]", and goes on to define a trivial default constructor. Taken literally, this would mean that a copy constructor can't be trivial (contrary to 12.8  class.copy paragraph 6). I suggest changing this to "A default constructor is trivial if [...]". (I think the change is purely editorial.)

Proposed Resolution (revised October 2002):

Change 12.1  class.ctor paragraph 5-6 as follows:

A default constructor for a class X is a constructor of class X that can be called without an argument. If there is no user-declared user-declared constructor for class X, a default constructor is implicitly declared. An implicitly-declared implicitly-declared default constructor is an inline public member of its class. A default constructor is trivial if it is an implicitly-declared default constructor and if:

Otherwise, the default constructor is non-trivial.

Change 12.4  class.dtor paragraphs 3-4 as follows (the main changes are removing italics):

If a class has no user-declared user-declared destructor, a destructor is declared implicitly. An implicitly-declared implicitly-declared destructor is an inline public member of its class. A destructor is trivial if it is an implicitly-declared destructor and if:

Otherwise, the destructor is non-trivial non-trivial.

In 9.5  class.union paragraph 1, change "trivial constructor" to "trivial default constructor".

In 12.2  class.temporary paragraph 3, add to the reference to 12.1  class.ctor a second reference, to 12.8  class.copy.




331. Allowed copy constructor signatures

Section: 12.1  class.ctor     Status: WP     Submitter: Richard Smith     Date: 8 Jan 2002

[Voted into WP at October 2003 meeting.]

12.1  class.ctor paragraph 10 states

A copy constructor for a class X is a constructor with a first parameter of type X & or of type const X &. [Note: see 12.8  class.copy for more information on copy constructors.]

No mention is made of constructors with first parameters of types volatile X & or const volatile X &. This statement seems to be in contradiction with 12.8  class.copy paragraph 2 which states

A non-template constructor for class X is a copy constructor if its first parameter is of type X &, const X &, volatile X & or const volatile X &, ...

12.8  class.copy paragraph 5 also mentions the volatile versions of the copy constructor, and the comparable paragraphs for copy assignment (12.8  class.copy paragraphs 9 and 10) all allow volatile versions, so it seems that 12.1  class.ctor is at fault.

Proposed resolution (October 2002):

Change 12.1  class.ctor paragraph 10 from

A copy constructor for a class X is a constructor with a first parameter of type X& or of type const X&. [Note: see 12.8  class.copy for more information on copy constructors. ]
to (note that the dropping of italics is intentional):
A copy constructor (12.8  class.copy) is used to copy objects of class type.




86. Lifetime of temporaries in query expressions

Section: 12.2  class.temporary     Status: WP     Submitter: Steve Adamczyk     Date: Jan 1999

[Voted into WP at April, 2006 meeting.]

In 12.2  class.temporary paragraph 5, should binding a reference to the result of a "?" operation, each of whose branches is a temporary, extend both temporaries?

Here's an example:

    const SFileName &C = noDir ? SFileName("abc") : SFileName("bcd");

Do the temporaries created by the SFileName conversions survive the end of the full expression?

Notes from 10/00 meeting:

Other problematic examples include cases where the temporary from one branch is a base class of the temporary from the other (i.e., where the implementation must remember which type of temporary must be destroyed), or where one branch is a temporary and the other is not. Similar questions also apply to the comma operator. The sense of the core language working group was that implementations should be required to support these kinds of code.

Notes from the March 2004 meeting:

We decided that the cleanest model is one in which any "?" operation that returns a class rvalue always copies one of its operands to a temporary and returns the temporary as the result of the operation. (Note that this may involve slicing.) An implementation would be free to optimize this using the rules in 12.8  class.copy paragraph 15, and in fact we would expect that in many cases compilers would do such optimizations. For example, the compiler could construct both rvalues in the above example into a single temporary, and thus avoid a copy.

See also issue 446.

Proposed resolution (October, 2004):

This issue is resolved by the resolutions of issue 446.

Note (October, 2005):

This issue was overlooked when issue 446 was moved to “ready” status and was thus inadvertently omitted from the list of issues accepted as Defect Reports at the October, 2005 meeting.




124. Lifetime of temporaries in default initialization of class arrays

Section: 12.2  class.temporary     Status: WP     Submitter: Jack Rouse     Date: 3 June 1999

[Moved to DR at 4/01 meeting.]

Jack Rouse: 12.2  class.temporary states that temporary objects will normally be destroyed at the end of the full expression in which they are created. This can create some unique code generation requirements when initializing a class array with a default constructor that uses a default argument. Consider the code:

    struct T {
       int i;
       T( int );
       ~T();
    };

    struct S {
       S( int = T(0).i );
       ~S();
    };

    S* f( int n )
    {
       return new S[n];
    }
The full expression allocating the array in f(int) includes the default constructor for S. Therefore according to 1.9  intro.execution paragraph 14, it includes the default argument expression for S(int). So evaluation of the full expression should include evaluating the default argument "n" times and creating "n" temporaries of type T. But the destruction of the temporaries must be delayed until the end of the full expression so this requires allocating space at runtime for "n" distinct temporaries. It is unclear how these temporaries are supposed to be allocated and deallocated. They cannot readily be autos because a variable allocation is required.

I believe that many existing implementations will destroy the temporaries needed by the default constructor after each array element is initialized. But I can't find anything in the standard that allows the temporaries to be destroyed early in this case.

I think the standard should allow the early destruction of temporaries used in the default initialization of class array elements. I believe early destruction is the status quo, and I don't think the users of existing C++ compilers have been adversely impacted by it.

Proposed resolution (04/01):

The proposed resolution is contained in the proposal for issue 201.




201. Order of destruction of temporaries in initializers

Section: 12.2  class.temporary     Status: WP     Submitter: Alan Nash     Date: 31 Jan 2000

[Moved to DR at 4/01 meeting.]

According to 12.2  class.temporary paragraph 4, an expression appearing as the initializer in an object definition constitutes a context "in which temporaries are destroyed at a different point than the end of the full-expression." It goes on to say that the temporary containing the value of the expression persists until after the initialization is complete (see also issue 117). This seems to presume that the end of the full-expression is a point earlier than the completion of the initialization.

However, according to 1.9  intro.execution paragraphs 12-13, the full-expression in such cases is, in fact, the entire initialization. If this is the case, the behavior described for temporaries in an initializer expression is simply the normal behavior of temporaries in any expression, and treating it as an exception to the general rule is both incorrect and confusing.

Proposed resolution (04/01):

[Note: this proposal also addresses issue 124.]

  1. Add to the end of 1.9  intro.execution paragraph 12:

    If the initializer for an object or sub-object is a full-expression, the initialization of the object or sub-object (e.g., by calling a constructor or copying an expression value) is considered to be part of the full-expression.
  2. Replace 12.2  class.temporary paragraph 4 with:

    There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context is when a default constructor is called to initialize an element of an array. If the constructor has one or more default arguments, any temporaries created in the default argument expressions are destroyed immediately after return from the constructor.



320. Question on copy constructor elision example

Section: 12.2  class.temporary     Status: WP     Submitter: Steve Clamage     Date: 2 Nov 2001

[Voted into WP at April 2005 meeting.]

Section 12.2  class.temporary paragraph 2, abridged:

  X f(X);
  void g()
  {
	X a;
	a = f(a);
  }

a=f(a) requires a temporary for either the argument a or the result of f(a) to avoid undesired aliasing of a.

The note seems to imply that an implementation is allowed to omit copying "a" to f's formal argument, or to omit using a temporary for the return value of f. I don't find that license in normative text.

Function f returns an X by value, and in the expression the value is assigned (not copy-constructed) to "a". I don't see how that temporary can be omitted. (See also 12.8  class.copy p 15)

Since "a" is an lvalue and not a temporary, I don't see how copying "a" to f's formal parameter can be avoided.

Am I missing something, or is 12.2  class.temporary p 2 misleading?

Proposed resolution (October, 2004):

In 12.2  class.temporary paragraph 2, change the last sentence as indicated:

On the other hand, the expression a=f(a) requires a temporary for either the argument a or the result of f(a) to avoid undesired aliasing of a the result of f(a), which is then assigned to a.



392. Use of full expression lvalue before temporary destruction

Section: 12.2  class.temporary     Status: WP     Submitter: Stephen Clamage     Date: 21 Nov 2002

[Voted into WP at March 2004 meeting.]

class C {
public:
    C();
    ~C();
    int& get() { return p; } // reference return
private:
    int p;
};

int main ()
{
    if ( C().get() ) // OK?
}

Section 12.2  class.temporary paragraph 3 says a temp is destroyed as the last step in evaluating the full expression. But the expression C().get() has a reference type. Does 12.2  class.temporary paragraph 3 require that the dereference to get a boolean result occur before the destructor runs, making the code valid? Or does the code have undefined behavior?

Bill Gibbons: It has undefined behavior, though clearly this wasn't intended. The lvalue-to-rvalue conversion that occurs in the "if" statement is not currently part of the full-expression.

From section 12.2  class.temporary paragraph 3:

Temporary objects are destroyed as the last step in evaluating the full-expression (1.9  intro.execution) that (lexically) contains the point where they were created.

From section 1.9  intro.execution paragraph 12:

A full-expression is an expression that is not a subexpression of another expression. If a language construct is defined to produce an implicit call of a function, a use of the language construct is considered to be an expression for the purposes of this definition.

The note in section 1.9  intro.execution paragraph 12 goes on to explain that this covers expressions used as initializers, but it does not discuss lvalues within temporaries.

It is a small point but it is probably worth correcting 1.9  intro.execution paragraph 12. Instead of the "implicit call of a function" wording, it might be better to just say that a full-expression includes any implicit use of the expression value in the enclosing language construct, and include a note giving implicit calls and lvalue-to-rvalue conversions as examples.

Offhand the places where this matters include: initialization (including member initializers), selection statements, iteration statements, return, throw

Proposed resolution (April 2003):

Change 1.9  intro.execution paragraph 12-13 to read:

A full-expression is an expression that is not a subexpression of another expression. If a language construct is defined to produce an implicit call of a function, a use of the language construct is considered to be an expression for the purposes of this definition. Conversions applied to the result of an expression in order to satisfy the requirements of the language construct in which the expression appears are also considered to be part of the full-expression.

[Note: certain contexts in C++ cause the evaluation of a full-expression that results from a syntactic construct other than expression (5.18  expr.comma). For example, in 8.5  dcl.init one syntax for initializer is

but the resulting construct is a function call upon a constructor function with expression-list as an argument list; such a function call is a full-expression. For example, in 8.5  dcl.init, another syntax for initializer is

but again the resulting construct might be a function call upon a constructor function with one assignment-expression as an argument; again, the function call is a full-expression. ] [Example:

  struct S {
      S(int i): I(i) { }
      int& v() { return I; }
    private:
      int I;
  };

  S s1(1);           // full-expression is call of S::S(int)
  S s2 = 2;          // full-expression is call of S::S(int)

  void f() {
      if (S(3).v())  // full-expression includes lvalue-to-rvalue and
                     // int to bool conversions, performed before
                     // temporary is deleted at end of full-expression
      { }
  }

--- end example]




443. Wording nit in description of lifetime of temporaries

Section: 12.2  class.temporary     Status: WP     Submitter: Matthias Hofmann     Date: 2 Dec 2003

[Voted into WP at April 2005 meeting.]

There seems to be a typo in 12.2  class.temporary/5, which says "The temporary to which the reference is bound or the temporary that is the complete object TO a subobject OF which the TEMPORARY is bound persists for the lifetime of the reference except as specified below."

I think this should be "The temporary to which the reference is bound or the temporary that is the complete object OF a subobject TO which the REFERENCE is bound persists for the lifetime of the reference except as specified below."

I used upper-case letters for the parts I think need to be changed.

Proposed resolution (October, 2004):

Change 12.2  class.temporary paragraph 5 as indicated:

The temporary to which the reference is bound or the temporary that is the complete object to of a subobject of to which the temporary reference is bound persists for the lifetime of the reference except as specified below.



464. Wording nit on lifetime of temporaries to which references are bound

Section: 12.2  class.temporary     Status: WP     Submitter: Allan Odgaard     Date: 21 Feb 2004

[Voted into WP at April, 2006 meeting.]

Section 12.2  class.temporary paragraph 5 ends with this "rule":

[...] if obj2 is an object with static or automatic storage duration created after the temporary is created, the temporary shall be destroyed after obj2 is destroyed.

For the temporary to be destroyed after obj2 is destroyed, when obj2 has static storage, I would say that the reference to the temporary should also have static storage, but that is IMHO not clear from the paragraph.

Example:

    void f ()
    {
       const T1& ref = T1();
       static T2 obj2;
       ...
    }

Here the temporary would be destoyed before obj2, contrary to the rule above.

Steve Adamczyk: I agree there's a minor issue here. I think the clause quoted above meant for obj1 and obj2 to have the same storage duration. Replacing "obj2 is an object with static or automatic storage duration" by "obj2 is an object with the same storage duration as obj1" would, I believe, fix the problem.

Notes from October 2004 meeting:

We agreed with Steve Adamczyk's suggestion.

Proposed resolution (October, 2005):

Change 12.2  class.temporary paragraph 5 as follows:

... In addition, the destruction of temporaries bound to references shall take into account the ordering of destruction of objects with static or automatic storage duration (3.7.1  basic.stc.static, 3.7.2  basic.stc.auto); that is, if obj1 is an object with static or automatic storage duration created before the temporary is created with the same storage duration as the temporary, the temporary shall be destroyed before obj1 is destroyed; if obj2 is an object with static or automatic storage duration created after the temporary is created with the same storage duration as the temporary, the temporary shall be destroyed after obj2 is destroyed...



296. Can conversion functions be static?

Section: 12.3.2  class.conv.fct     Status: WP     Submitter: Scott Meyers     Date: 5 Jul 2001

[Moved to DR at October 2002 meeting.]

May user-defined conversion functions be static? That is, should this compile?

    class Widget {
    public:
      static operator bool() { return true; }
    };

All my compilers hate it. I hate it, too. However, I don't see anything in 12.3.2  class.conv.fct that makes it illegal. Is this a prohibition that arises from the grammar, i.e., the grammar doesn't allow "static" to be followed by a conversion-function-id in a member function declaration? Or am I just overlooking something obvious that forbids static conversion functions?

Proposed Resolution (4/02):

Add to 12.3.2  class.conv.fct as a new paragraph 7:

Conversion functions cannot be declared static.



244. Destructor lookup

Section: 12.4  class.dtor     Status: WP     Submitter: John Spicer     Date: 6 Sep 2000

[Moved to DR at October 2002 meeting.]

12.4  class.dtor contains this example:

    struct B {
        virtual ~B() { }
    };
    struct D : B {
        ~D() { }
    };

    D D_object;
    typedef B B_alias;
    B* B_ptr = &D_object;

    void f() {
        D_object.B::~B();               // calls B's destructor
        B_ptr->~B();                    // calls D's destructor
        B_ptr->~B_alias();              // calls D's destructor
        B_ptr->B_alias::~B();           // calls B's destructor
        B_ptr->B_alias::~B_alias();     // error, no B_alias in class B
    }

On the other hand, 3.4.3  basic.lookup.qual contains this example:

    struct C {
        typedef int I;
    };
    typedef int I1, I2;
    extern int* p;
    extern int* q;
    p->C::I::~I();       // I is looked up in the scope of C
    q->I1::~I2();        // I2 is looked up in the scope of
                         // the postfix-expression
    struct A {
        ~A();
    };
    typedef A AB;
    int main()
    {
        AB *p;
        p->AB::~AB();    // explicitly calls the destructor for A
    }

Note that

     B_ptr->B_alias::~B_alias();

is claimed to be an error, while the equivalent

     p->AB::~AB();

is claimed to be well-formed.

I believe that clause 3 is correct and that clause 12 is in error. We worked hard to get the destructor lookup rules in clause 3 to be right, and I think we failed to notice that a change was also needed in clause 12.

Mike Miller:

Unfortunately, I don't believe 3.4.3  basic.lookup.qual covers the case of p->AB::~AB(). It's clearly intended to do so, as evidenced by 3.4.3.1  class.qual paragraph 1 ("a destructor name is looked up as specified in 3.4.3  basic.lookup.qual"), but I don't think the language there does so.

The relevant paragraph is 3.4.3  basic.lookup.qual paragraph 5. (None of the other paragraphs in that section deal with this topic at all.) It has two parts. The first is

If a pseudo-destructor-name (5.2.4  expr.pseudo) contains a nested-name-specifier, the type-names are looked up as types in the scope designated by the nested-name-specifier.

This sentence doesn't apply, because ~AB isn't a pseudo-destructor-name. 5.2.4  expr.pseudo makes clear that this syntactic production (5.2  expr.post paragraph 1) only applies to cases where the type-name is not a class-name. p->AB::~AB is covered by the production using id-expression.

The second part of 3.4.3  basic.lookup.qual paragraph 5 says

In a qualified-id of the form:

    ::opt nested-name-specifier ~ class-name

where the nested-name-specifier designates a namespace name, and in a qualified-id of the form:

    ::opt nested-name-specifier class-name :: ~ class-name

the class-names are looked up as types in the scope designated by the nested-name-specifier.

This wording doesn't apply, either. The first one doesn't because the nested-name-specifier is a class-name, not a namespace name. The second doesn't because there's only one layer of qualification.

As far as I can tell, there's no normative text that specifies how the ~AB is looked up in p->AB::~AB(). 3.4.3.1  class.qual, where all the other class member qualified lookups are handled, defers to 3.4.3  basic.lookup.qual, and 3.4.3  basic.lookup.qual doesn't cover the case.

See also issue 305.

Jason Merrill: My thoughts on the subject were that the name we use in a destructor call is really meaningless; as soon as we see the ~ we know what the user means, all we're doing from that point is testing their ability to name the destructor in a conformant way. I think that everyone will agree that

  anything::B::~B()
should be well-formed, regardless of the origins of the name "B". I believe that the rule about looking up the second "B" in the same context as the first was intended to provide this behavior, but to me this seems much more heavyweight than necessary. We don't need a whole new type of lookup to be able to use the same name before and after the ~; we can just say that if the two names match, the call is well-formed. This is significantly simpler to express, both in the standard and in an implementation.

Anyone writing two different names here is either deliberately writing obfuscated code, trying to call the destructor of a nested class, or fighting an ornery compiler (i.e. one that still wants to see B_alias::~B()). I think we can ignore the first case. The third would be handled by reverting to the old rule (look up the name after ~ in the normal way) with the lexical matching exception described above -- or we could decide to break such code, do no lookup at all, and only accept a matching name. In a good implementation, the second should probably get an error message telling them to write Outer::Inner::~Inner instead.

We discussed this at the meetings, but I don't remember if we came to any sort of consensus on a direction. I see three options:

  1. Stick with the status quo, i.e. the special lookup rule such that if the name before ::~ is a class name, the name after ::~ is looked up in the same scope as the previous one. If we choose this option, we just need better wording that actually expresses this, as suggested in the issue list. This option breaks old B_alias::~B code where B_alias is declared in a different scope from B.
  2. Revert to the old rules, whereby the name after ::~ is looked up just like a name after ::, with the exception that if it matches the name before ::~ then it is considered to name the same class. This option supports old code and code that writes B_alias::~B_alias. It does not support the q->I1::~I2 usage of 3.4.3  basic.lookup.qual, but that seems like deliberate obfuscation. This option is simpler to implement than #1.
  3. Do no lookup for a name after ::~; it must match the name before. This breaks old code as #1, but supports the most important case where the names match. This option may be slightly simpler to implement than #2. It is certainly easier to teach.

My order of preference is 2, 3, 1.

Incidentally, it seems to me oddly inconsistent to allow Namespace::~Class, but not Outer::~Inner. Prohibiting the latter makes sense from the standpoint of avoiding ambiguity, but what was the rationale for allowing the former?

John Spicer: I agree that allowing Namespace::~Class is odd. I'm not sure where this came from. If we eliminated that special case, then I believe the #1 rule would just be that in A::B1::~B2 you look up B1 and B2 in the same place in all cases.

I don't like #2. I don't think the "old" rules represent a deliberate design choice, just an error in the way the lookup was described. The usage that rule permits p->X::~Y (where Y is a typedef to X defined in X), but I doubt people really do that. In other words, I think that #1 a more useful special case than #2 does, not that I think either special case is very important.

One problem with the name matching rule is handling cases like:

  A<int> *aip;

  aip->A<int>::~A<int>();  // should work
  aip->A<int>::~A<char>(); // should not
I would favor #1, while eliminating the special case of Namespace::~Class.

Proposed resolution (10/01):

Replace the normative text of 3.4.3  basic.lookup.qual paragraph 5 after the first sentence with:

Similarly, in a qualified-id of the form:

    ::opt nested-name-specifieropt class-name :: ~ class-name
the second class-name is looked up in the same scope as the first.

In 12.4  class.dtor paragraph 12, change the example to

D D_object;
typedef B B_alias;
B* B_ptr = &D_object;

void f() {
  D_object.B::~B();                //  calls  B's destructor
  B_ptr->~B();                    //  calls  D's destructor
  B_ptr->~B_alias();              //  calls  D's destructor
  B_ptr->B_alias::~B();           //  calls  B's destructor
  B_ptr->B_alias::~B_alias();     //  calls  B's destructor
}

April 2003: See issue 399.




252. Looking up deallocation functions in virtual destructors

Section: 12.4  class.dtor     Status: WP     Submitter: Steve Clamage     Date: 19 Oct 2000

[Moved to DR at 10/01 meeting.]

There is a mismatch between 12.4  class.dtor paragraph 11 and 12.5  class.free paragraph 4 regarding the lookup of deallocation functions in virtual destructors. 12.4  class.dtor says,

At the point of definition of a virtual destructor (including an implicit definition (12.8  class.copy)), non-placement operator delete shall be looked up in the scope of the destructor's class (3.4.1  basic.lookup.unqual) and if found shall be accessible and unambiguous. [Note: this assures that an operator delete corresponding to the dynamic type of an object is available for the delete-expression (12.5  class.free). ]

The salient features to note from this description are:

  1. The lookup is "in the scope of the destructor's class," which implies that only members are found (cf 12.2  class.temporary). (The cross-reference would indicate otherwise, however, since it refers to the description of looking up unqualified names; this kind of lookup "spills over" into the surrounding scope.)
  2. Only non-placement operator delete is looked up. Presumably this means that a placement operator delete is ignored in the lookup.

On the other hand, 12.5  class.free says,

If a delete-expression begins with a unary :: operator, the deallocation function's name is looked up in global scope. Otherwise, if the delete-expression is used to deallocate a class object whose static type has a virtual destructor, the deallocation function is the one found by the lookup in the definition of the dynamic type's virtual destructor (12.4  class.dtor). Otherwise, if the delete-expression is used to deallocate an object of class T or array thereof, the static and dynamic types of the object shall be identical and the deallocation function's name is looked up in the scope of T. If this lookup fails to find the name, the name is looked up in the global scope. If the result of the lookup is ambiguous or inaccessible, or if the lookup selects a placement deallocation function, the program is ill-formed.

Points of interest in this description include:

  1. For a class type with a virtual destructor, the lookup is described as being "in the definition of the dynamic type's virtual destructor," rather than "in the scope of the dynamic type." That is, the lookup is assumed to be an unqualified lookup, presumably terminating in the global scope.
  2. The assumption is made that the lookup in the virtual destructor was successful ("...the one found...", not "...the one found..., if any"). This will not be the case if the deallocation function was not declared as a member somewhere in the inheritance hierarchy.
  3. The lookup in the non-virtual-destructor case does find placement deallocation functions and can fail as a result.

Suggested resolution: Change the description of the lookup in 12.4  class.dtor paragraph 11 to match the one in 12.5  class.free paragraph 4.

Proposed resolution (10/00):

  1. Replace 12.4  class.dtor paragraph 11 with the following:

    At the point of definition of a virtual destructor (including an implicit definition), the non-array deallocation function is looked up in the scope of the destructor's class (10.2  class.member.lookup), and, if no declaration is found, the function is looked up in the global scope. If the result of this lookup is ambiguous or inaccessible, or if the lookup selects a placement deallocation function, the program is ill-formed. [Note: this assures that a deallocation function corresponding to the dynamic type of an object is available for the delete-expression (12.5  class.free).]
  2. In 12.5  class.free paragraph 4, change

    ...the deallocation function is the one found by the lookup in the definition of the dynamic type's virtual destructor (12.4  class.dtor).

    to

    ...the deallocation function is the one selected at the point of definition of the dynamic type's virtual destructor (12.4  class.dtor).



272. Explicit destructor invocation and qualified-ids

Section: 12.4  class.dtor     Status: WP     Submitter: Mike Miller     Date: 22 Feb 2001

[Moved to DR at 10/01 meeting.]

12.4  class.dtor paragraph 12 contains the following note:
an explicit destructor call must always be written using a member access operator (5.2.5  expr.ref); in particular, the unary-expression ~X() in a member function is not an explicit destructor call (5.3.1  expr.unary.op).

This note is incorrect, as an explicit destructor call can be written as a qualified-id, e.g., X::~X(), which does not use a member access operator.

Proposed resolution (04/01):

Change 12.4  class.dtor paragraph 12 as follows:

[Note: an explicit destructor call must always be written using a member access operator (5.2.5  expr.ref) or a qualified-id (5.1  expr.prim); in particular, the unary-expression ~X() in a member function is not an explicit destructor call (5.3.1  expr.unary.op).]



510. Default initialization of POD classes?

Section: 12.6  class.init     Status: WP     Submitter: Mike Miller     Date: 18 Mar 2005

[Voted into WP at April, 2006 meeting.]

8.5  dcl.init paragraph 10 makes it clear that non-static POD class objects with no initializer are left uninitialized and have an indeterminate initial value:

If no initializer is specified for an object, and the object is of (possibly cv-qualified) non-POD class type (or array thereof), the object shall be default-initialized; if the object is of const-qualified type, the underlying class type shall have a user-declared default constructor. Otherwise, if no initializer is specified for a non-static object, the object and its subobjects, if any, have an indeterminate initial value; if the object or any of its subobjects are of const-qualified type, the program is ill-formed.

12.6  class.init paragraph 1, however, implies that all class objects without initializers, whether POD or not, are default-initialized:

When no initializer is specified for an object of (possibly cv-qualified) class type (or array thereof), or the initializer has the form (), the object is initialized as specified in 8.5  dcl.init. The object is default-initialized if there is no initializer, or value-initialized if the initializer is ().

Proposed resolution (October, 2005):

Remove the indicated words from 12.6  class.init paragraph 1:

When no initializer is specified for an object of (possibly cv-qualified) class type (or array thereof), or the initializer has the form (), the object is initialized as specified in 8.5  dcl.init. The object is default-initialized if there is no initializer, or value-initialized if the initializer is ().



162. (&C::f)() with nonstatic members

Section: 13.3.1.1  over.match.call     Status: WP     Submitter: Steve Adamczyk     Date: 26 Aug 1999

[Moved to DR at October 2002 meeting.]

13.3.1.1  over.match.call paragraph 3 says that when a call of the form

   (&C::f)()
is written, the set of overloaded functions named by C::f must not contain any nonstatic member functions. A footnote gives the rationale: if a member of C::f is a nonstatic member function, &C::f is a pointer to member constant, and therefore the call is invalid.

This is clear, it's implementable, and it doesn't directly contradict anything else in the standard. However, I'm not sure it's consistent with some similar cases.

In 13.4  over.over paragraph 5, second example, it is made amply clear that when &C::f is used as the address of a function, e.g.,

   int (*pf)(int) = &C::f;
the overload set can contain both static and nonstatic member functions. The function with the matching signature is selected, and if it is nonstatic &C::f is a pointer to member function, and otherwise &C::f is a normal pointer to function.

Similarly, 13.3.1.1.1  over.call.func paragraph 3 makes it clear that

   C::f();
is a valid call even if the overload set contains both static and nonstatic member functions. Overload resolution is done, and if a nonstatic member function is selected, an implicit this-> is added, if that is possible.

Those paragraphs seem to suggest the general rule that you do overload resolution first and then you interpret the construct you have according to the function selected. The fact that there are static and nonstatic functions in the overload set is irrelevant; it's only necessary that the chosen function be static or nonstatic to match the context.

Given that, I think it would be more consistent if the (&C::f)() case would also do overload resolution first. If a nonstatic member is chosen, the program would be ill-formed.

Proposed resolution (04/01):

  1. Change the indicated text in 13.3.1.1  over.match.call paragraph 3:

    The fourth case arises from a postfix-expression of the form &F, where F names a set of overloaded functions. In the context of a function call, the set of functions named by F shall contain only non-member functions and static member functions. [Footnote: If F names a non-static member function, &F is a pointer-to-member, which cannot be used with the function call syntax.] And in this context using &F behaves the same as using &F is treated the same as the name F by itself. Thus, (&F)(expression-listopt) is simply (F)(expression-listopt), which is discussed in 13.3.1.1.1  over.call.func. If the function selected by overload resolution according to 13.3.1.1.1  over.call.func is a nonstatic member function, the program is ill-formed. [Footnote: When F is a nonstatic member function, a reference of the form &A::F is a pointer-to-member, which cannot be used with the function-call syntax, and a reference of the form &F is an invalid use of the "&" operator on a nonstatic member function.] (The resolution of &F in other contexts is described in 13.4  over.over.)



239. Footnote 116 and Koenig lookup

Section: 13.3.1.1.1  over.call.func     Status: WP     Submitter: Steve Clamage     Date: 2 Aug 2000

[Moved to DR at 4/01 meeting.]

In describing non-member functions in an overload set, footnote 116 (13.3.1.1.1  over.call.func) says,
Because of the usual name hiding rules, these will be introduced by declarations or by using-directives all found in the same block or all found at namespace scope.

At least in terms of the current state of the Standard, this is not correct: a block extern declaration does not prevent Koenig lookup from occurring. For example,

    enum E { zero };
    void f(E);
    void g() {
        void f(int);
        f(zero);
    }

In this example, the overload set will include declarations from both namespace and block scope.

(See also issue 12.)

Proposed resolution (04/01):

  1. In 3.4.2  basic.lookup.argdep paragraph 2, change

    If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated namespaces and classes are not considered.

    to

    If the ordinary unqualified lookup of the name finds the declaration of a class member function, or a block-scope function declaration that is not a using-declaration, the associated namespaces and classes are not considered.

    and change the example to:

        namespace NS {
            class T { };
            void f(T);
            void g(T, int);
        }
        NS::T parm;
        void g(NS::T, float);
        int main() {
            f(parm);            // OK: calls NS::f
            extern void g(NS::T, float);
            g(parm, 1);         // OK: calls g(NS::T, float)
        }
    
  2. In 13.3.1.1.1  over.call.func paragraph 3 from:

    If the name resolves to a non-member function declaration, that function and its overloaded declarations constitute the set of candidate functions.

    to

    If the name resolves to a set of non-member function declarations, that set of functions constitutes the set of candidate functions.

    Note that this text is also edited by issue 364. Also, remove the associated footnote 116.




364. Calling overloaded function with static in set, with no object

Section: 13.3.1.1.1  over.call.func     Status: WP     Submitter: Steve Adamczyk     Date: 23 July 2002

[Voted into WP at October 2003 meeting.]

Consider this program:

   struct S {
     static void f (int);
     void f (char);
   };

   void g () {
     S::f ('a');
   }

G++ 3.1 rejects it, saying:

   test.C:7: cannot call member function `void S::f(char)' without object

Mark Mitchell: It looks to me like G++ is correct, given 13.3.1.1.1  over.call.func. This case is the "unqualified function call" case described in paragraph 3 of that section. ("Unqualified" here means that there is no "x->" or "x." in front of the call, not that the name is unqualified.)

That paragraph says that you first do name lookup. It then asks you to look at what declaration is returned. (That's a bit confusing; you presumably get a set of declarations. Or maybe not; the name lookup section says that if name lookup finds a non-static member in a context like this the program is in error. But surely this program is not erroneous. Hmm.)

Anyhow, you have -- at least -- "S::f(char)" as the result of the lookup.

The keyword "this" is not in scope, so "all overloaded declarations of the function name in T become candidate functions and a contrived object of type T becomes the implied object argument." That means we get both versions of "f" at this point. Then, "the call is ill-formed, however, if overload resolution selects one of the non-static members of T in this case." Since, in this case, "S::f(char)" is the winner, the program is ill-formed.

Steve Adamczyk: This result is surprising, because we've selected a function that we cannot call, when there is another function that can be called. This should either be ambiguous, or it should select the static member function. See also 13.3.1  over.match.funcs paragraph 2: "Similarly, when appropriate, the context can construct an argument list that contains an implied object argument..."

Notes from October 2002 meeting:

We agreed that g++ has it right, but the standard needs to be clearer.

Proposed resolution (October 2002, revised April 2003):

Change 13.3.1.1.1  over.call.func paragraphs 2 and 3 as follows:

In qualified function calls, the name to be resolved is an id-expression and is preceded by an -> or . operator. Since the construct A->B is generally equivalent to (*A).B, the rest of clause 13  over assumes, without loss of generality, that all member function calls have been normalized to the form that uses an object and the . operator. Furthermore, clause 13  over assumes that the postfix-expression that is the left operand of the . operator has type ``cv T'' where T denotes a class. [Footnote: Note that cv-qualifiers on the type of objects are significant in overload resolution for both lvalue and class rvalue objects. --- end footnote] Under this assumption, the id-expression in the call is looked up as a member function of T following the rules for looking up names in classes (10.2  class.member.lookup). If a member function is found, that function and its overloaded declarations The function declarations found by that lookup constitute the set of candidate functions. The argument list is the expression-list in the call augmented by the addition of the left operand of the . operator in the normalized member function call as the implied object argument (13.3.1  over.match.funcs).

In unqualified function calls, the name is not qualified by an -> or . operator and has the more general form of a primary-expression. The name is looked up in the context of the function call following the normal rules for name lookup in function calls (3.4.2  basic.lookup.argdep 3.4  basic.lookup). If the name resolves to a non-member function declaration, that function and its overloaded declarations The function declarations found by that lookup constitute the set of candidate functions. [Footnote: Because of the usual name hiding rules, these will be introduced by declarations or by using-directives all found in the same block or all found at namespace scope. --- end footnote] Because of the rules for name lookup, the set of candidate functions consists (1) entirely of non-member functions or (2) entirely of member functions of some class T. In case (1), tThe argument list is the same as the expression-list in the call. If the name resolves to a nonstatic member function, then the function call is actually a member function call. In case (2), the argument list is the expression-list in the call augmented by the addition of an implied object argument as in a qualified function call. If the keyword this (9.3.2  class.this) is in scope and refers to the class T of that member function, or a derived class thereof of T, then the function call is transformed into a normalized qualified function call using implied object argument is(*this) as the postfix-expression to the left of the . operator. The candidate functions and argument list are as described for qualified function calls above. If the keyword this is not in scope or refers to another class, then name resolution found a static member of some class T. In this case, all overloaded declarations of the function name in T become candidate functions and a contrived object of type T becomes the implied object argument. [Footnote: An implied object argument must be contrived to correspond to the implicit object parameter attributed to member functions during overload resolution. It is not used in the call to the selected function. Since the member functions all have the same implicit object parameter, the contrived object will not be the cause to select or reject a function. --- end footnote] If the argument list is augmented by a contrived object and The call is ill-formed, however, if overload resolution selects one of the non-static member functions of T, the call is ill-formed in this case.

Note that issue 239 also edits paragraph 3.




280. Access and surrogate call functions

Section: 13.3.1.1.2  over.call.object     Status: WP     Submitter: Andrei Iltchenko     Date: 16 Apr 2001

[Voted into WP at October 2003 meeting.]

According to 13.3.1.1.2  over.call.object paragraph 2, when the primary-expression E in the function call syntax evaluates to a class object of type "cv T", a surrogate call function corresponding to an appropriate conversion function declared in a direct or indirect base class B of T is included or not included in the set of candidate functions based on class B being accessible.

For instance in the following code sample, as per the paragraph in question, the expression c(3) calls f2, instead of the construct being ill-formed due to the conversion function A::operator fp1 being inaccessible and its corresponding surrogate call function providing a better match than the surrogate call function corresponding to C::operator fp2:

    void  f1(int)  {   }
    void  f2(float)  {   }
    typedef void  (* fp1)(int);
    typedef void  (* fp2)(float);

    struct  A  {
       operator fp1()
       {   return  f1;   }
    };

    struct  B :  private A  {   };

    struct  C :  B  {
       operator  fp2()
       {   return  f2;   }
    };

    int  main()
    {
       C   c;
       c(3);  // f2 is called, instead of the construct being ill-formed.
       return  0;
    }

The fact that the accessibility of a base class influences the overload resolution process contradicts the fundamental language rule (3.4  basic.lookup paragraph 1, and 13.3  over.match paragraph 2) that access checks are applied only once name lookup and function overload resolution (if applicable) have succeeded.

Notes from 4/02 meeting:

There was some concern about whether 10.2  class.member.lookup (or anything else, for that matter) actually defines "ambiguous base class". See issue 39. See also issue 156.

Notes from October 2002 meeting:

It was suggested that the ambiguity check is done as part of the call of the conversion function.

Proposed resolution (revised October 2002):

In 13.3.1.1.2  over.call.object paragraph 2, replace the last sentence

Similarly, surrogate call functions are added to the set of candidate functions for each conversion function declared in an accessible base class provided the function is not hidden within T by another intervening declaration.

with

Similarly, surrogate call functions are added to the set of candidate functions for each conversion function declared in a base class of T provided the function is not hidden within T by another intervening declaration.

Replace 13.3.1.1.2  over.call.object paragraph 3

If such a surrogate call function is selected by overload resolution, its body, as defined above, will be executed to convert E to the appropriate function and then to invoke that function with the arguments of the call.
by
If such a surrogate call function is selected by overload resolution, the corresponding conversion function will be called to convert E to the appropriate function pointer or reference, and the function will then be invoked with the arguments of the call. If the conversion function cannot be called (e.g., because of an ambiguity), the program is ill-formed.




416. Class must be complete to allow operator lookup?

Section: 13.3.1.2  over.match.oper     Status: WP     Submitter: Greg Comeau     Date: 22 May 2003

[Voted into WP at October 2004 meeting.]

Normally reference semantics allow incomplete types in certain contexts, but isn't this:

  class A;

  A& operator<<(A& a, const char* msg);
  void foo(A& a)
  {
    a << "Hello";
  }

required to be diagnosed because of the op<<? The reason being that the class may actually have an op<<(const char *) in it.

What is it? un- or ill-something? Diagnosable? No problem at all?

Steve Adamczyk: I don't know of any requirement in the standard that the class be complete. There is a rule that will instantiate a class template in order to be able to see whether it has any operators. But I wouldn't think one wants to outlaw the above example merely because the user might have an operator<< in the class; if he doesn't, he would not be pleased that the above is considered invalid.

Mike Miller: Hmm, interesting question. My initial reaction is that it just uses ::operator<<; any A::operator<< simply won't be considered in overload resolution. I can't find anything in the Standard that would say any different.

The closest analogy to this situation, I'd guess, would be deleting a pointer to an incomplete class; 5.3.5  expr.delete paragraph 5 says that that's undefined behavior if the complete type has a non-trivial destructor or an operator delete. However, I tend to think that that's because it deals with storage and resource management, not just because it might have called a different function. Generally, overload resolution that goes one way when it might have gone another with more declarations in scope is considered to be not an error, cf 7.3.3  namespace.udecl paragraph 9, 14.6.3  temp.nondep paragraph 1, etc.

So my bottom line take on it would be that it's okay, it's up to the programmer to ensure that all necessary declarations are in scope for overload resolution. Worst case, it would be like the operator delete in an incomplete class -- undefined behavior, and thus not required to be diagnosed.

13.3.1.2  over.match.oper paragraph 3, bullet 1, says, "If T1 is a class type, the set of member candidates is the result of the qualified lookup of T1::operator@ (13.3.1.1.1  over.call.func)." Obviously, that lookup is not possible if T1 is incomplete. Should 13.3.1.2  over.match.oper paragraph 3, bullet 1, say "complete class type"? Or does the inability to perform the lookup mean that the program is ill-formed? 3.2  basic.def.odr paragraph 4 doesn't apply, I don't think, because you don't know whether you'll be applying a class member access operator until you know whether the operator involved is a member or not.

Notes from October 2003 meeting:

We noticed that the title of this issue did not match the body. We checked the original source and then corrected the title (so it no longer mentions templates).

We decided that this is similar to other cases like deleting a pointer to an incomplete class, and it should not be necessary to have a complete class. There is no undefined behavior.

Proposed Resolution (October 2003):

Change the first bullet of 13.3.1.2  over.match.oper paragraph 3 to read:

If T1 is a complete class type, the set of member candidates is the result of the qualified lookup of T1::operator@ (13.3.1.1.1  over.call.func); otherwise, the set of member candidates is empty.



60. Reference binding and valid conversion sequences

Section: 13.3.3.1.4  over.ics.ref     Status: WP     Submitter: Steve Adamczyk     Date: 13 Oct 1998

[Moved to DR at October 2002 meeting.]

Does dropping a cv-qualifier on a reference binding prevent the binding as far as overload resolution is concerned? Paragraph 4 says "Other restrictions on binding a reference to a particular argument do not affect the formation of a conversion sequence." This was intended to refer to things like access checking, but some readers have taken that to mean that any aspects of reference binding not mentioned in this section do not preclude the binding.

Proposed resolution (10/01):

In 13.3.3.1.4  over.ics.ref paragraph 4 add the indicated text:

Other restrictions on binding a reference to a particular argument that are not based on the types of the reference and the argument do not affect the formation of a standard conversion sequence, however.



115. Address of template-id

Section: 13.4  over.over     Status: WP     Submitter: John Spicer     Date: 7 May 1999

[Voted into WP at October 2003 meeting.]

    template <class T> void f(T);
    template <class T> void g(T);
    template <class T> void g(T,T);

    int main()
    {
        (&f<int>);
        (&g<int>);
    }
The question is whether &f<int> identifies a unique function. &g<int> is clearly ambiguous.

13.4  over.over paragraph 1 says that a function template name is considered to name a set of overloaded functions. I believe it should be expanded to say that a function template name with an explicit template argument list is also considered to name a set of overloaded functions.

In the general case, you need to have a destination type in order to identify a unique function. While it is possible to permit this, I don't think it is a good idea because such code depends on there only being one template of that name that is visible.

The EDG front end issues an error on this use of "f". egcs 1.1.1 allows it, but the most current snapshot of egcs that I have also issues an error on it.

It has been pointed out that when dealing with nontemplates, the rules for taking the address of a single function differ from the rules for an overload set, but this asymmetry is needed for C compatibility. This need does not exist for the template case.

My feeling is that a general rule is better than a general rule plus an exception. The general rule is that you need a destination type to be sure that the operation will succeed. The exception is when there is only one template in the set and only then when you provide values for all of the template arguments.

It is true that in some cases you can provide a shorthand, but only if you encourage a fragile coding style (that will cause programs to break when additional templates are added).

I think the standard needs to specify one way or the other how this case should be handled. My recommendation would be that it is ill-formed.

Nico Josuttis: Consider the following example:

    template <int VAL>
    int add (int elem)
    {
	return elem + VAL;
    }

    std::transform(coll.begin(), coll.end(),
		   coll.begin(),
		   add<10>);

If John's recommendation is adopted, this code will become ill-formed. I bet there will be a lot of explanation for users necessary why this fails and that they have to change add<10> to something like (int (*)(int))add<10>.

This example code is probably common practice because this use of the STL is typical and is accepted in many current implementations. I strongly urge that this issue be resolved in favor of keeping this code valid.

Bill Gibbons: I find this rather surprising. Shouldn't a template-id which specifies all of the template arguments be treated like a declaration-only explicit instantiation, producing a set of ordinary function declarations? And when that set happens to contain only one function, shouldn't the example code work?

(See also issue 250.)

Notes from 04/01 meeting:

The consensus of the group was that the add example should not be an error.

Proposed resolution (October 2002):

In 13.4 add to the end of paragraph 2:

[Note: As described in 14.8.1  temp.arg.explicit, if deduction fails and the function template name is followed by an explicit template argument list, the template-id is then examined to see whether it identifies a single function template specialization. If it does, the template-id is considered to be an lvalue for that function template specialization. The target type is not used in that determination.]

In 14.8.1  temp.arg.explicit paragraph 2 insert before the first example:

In contexts where deduction is done and fails, or in contexts where deduction is not done, if a template argument list is specified and it, along with any default template arguments, identifies a single function template specialization, then the template-id is an lvalue for the function template specialization.

Change the first example of 14.8.1  temp.arg.explicit paragraph 2:

  template<class X, class Y> X f(Y);
  void g()
  {
    int i = f<int>(5.6);    // Y is deduced to be double
    int j = f(5.6);         // ill-formed: X cannot be deduced
  }
to read:
  template<class X, class Y> X f(Y);
  void g()
  {
    int i = f<int>(5.6);    // Y is deduced to be double
    int j = f(5.6);         // ill-formed: X cannot be deduced
    f<void>(f<int, bool>);  // Y for outer f deduced to be
                            //   int (*)(bool)
    f<void>(f<int>);        // ill-formed: f<int> does not denote a
                            //   single template function specialization
  }

Note: This interacts with the resolution of issue 226 (default template arguments for function templates).




221. Must compound assignment operators be member functions?

Section: 13.5.3  over.ass     Status: WP     Submitter: Jim Hyslop     Date: 3 Apr 2000

[Moved to DR at 4/01 meeting.]

Is the intent of 13.5.3  over.ass paragraph 1 that all assignment operators be non-static member functions (including operator+=, operator*=, etc.) or only simple assignment operators (operator=)?

Notes from 04/00 meeting:

Nearly all references to "assignment operator" in the IS mean operator= and not the compound assignment operators. The ARM was specific that this restriction applied only to operator=. If it did apply to compound assignment operators, it would be impossible to overload these operators for bool operands.

Proposed resolution (04/01):

  1. Change the title of 5.17  expr.ass from "Assignment operators" to "Assignment and compound assignment operators."

  2. Change the first sentence of 5.17  expr.ass paragraph 1 from

    There are several assignment operators, all of which group right-to-left. All require a modifiable lvalue as their left operand, and the type of an assignment expression is that of its left operand. The result of the assignment operation is the value stored in the left operand after the assignment has taken place; the result is an lvalue.

    to

    The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue with the type and value of the left operand after the assignment has taken place.

Additional note (10/00): Paragraphs 2-6 of 5.17  expr.ass should all be understood to apply to simple assignment only and not to compound assignment operators.




420. postfixexpression->scalar_type_dtor() inconsistent

Section: 13.5.6  over.ref     Status: WP     Submitter: Markus Mauhart     Date: 8 June 2003

[Voted into WP at April, 2006 meeting.]

Lets start with the proposed solution. In 13.5.6  over.ref, replace line ...

postfix-expression -> id-expression
.... with the lines ...
postfix-expression -> templateopt id-expression
postfix-expression -> pseudo-destructor-name
(This then is a copy of the two lines in 5.2  expr.post covering "->dtor")

Alternatively remove the sentence "It implements class member access using ->" and the syntax line following.

Reasons:

Currently stdc++ is inconsistent when handling expressions of the form "postfixexpression->scalar_type_dtor()": If "postfixexpression" is a pointer to the scalar type, it is OK, but if "postfixexpression" refers to any smart pointer class (e.g. iterator or allocator::pointer) with class specific CLASS::operator->() returning pointer to the scalar type, then it is ill-formed; so while c++98 does allow CLASS::operator->() returning pointer to scalar type, c++98 prohibits any '->'-expression involving this overloaded operator function.

Not only is this behaviour inconsistent, but also when comparing the corresponding chapters of c++pl2 and stdc++98 it looks like an oversight and unintended result. Mapping between stdc++98 and c++pl2:

c++pl2.r.5.2 -> 5.2 [expr.post]
c++pl2.r.5.2.4 -> 5.2.4 [expr.pseudo] + 5.2.5 [expr.ref]
c++pl2.r.13.4 -> 13.3.1.2 [over.match.oper]
c++pl2.r.13.4.6 -> 13.5.6 [over.ref]
For the single line of c++pl2.r.5.2 covering "->dtor", 5.2 [expr.post] has two lines. Analogously c++pl2.r.5.2.4 has been doubled to 5.2.4 [expr.pseudo] and 5.2.5 [expr.ref]. From 13.5.6 [over.ref], the sentence forbiding CLASS::operator->() returning pointer to scalar type has been removed. Only the single line of c++pl2.r.13.4.6 (<-> c++pl2.r.5.2's single line) has not gotten its 2nd line when converted into 13.5.6 [over.ref].

Additionally GCC32 does is right (but against 13.5.6 [over.ref]).

AFAICS this would not break old code except compilers like VC7x and Comeau4301.

It does not add new functionality, cause any expression class_type->scalar_type_dtor() even today can be substituted through (*class_type).scalar_type_dtor().

Without this fix, template functions like some_allocator<T>::destroy(p) must use "(*p).~T()" or "(*p).T::~T()" when calling the destructor, otherwise the simpler versions "p->~T()" or "p->T::~T()" could be used.

Sample code, compiled with GCC32, VC7[1] and Comeau4301:

struct A {};//any class

template <class T>
struct PTR
    {
    T& operator*  () const;
    T* operator-> () const;
    };

template <class T>
void f ()
    {
        {
        T*  p               ;
        p = new T           ;
        (*p).T::~T()        ;//OK
        p = new T           ;
        (*p).~T()           ;//OK
        p = new T           ;
        p->T::~T()          ;//OK
        p = new T           ;
        p->~T()             ;//OK
        }

        {
        PTR<T> p = PTR<T>() ;
        (*p).T::~T()        ;//OK
        (*p).~T()           ;//OK
        p.operator->()      ;//OK !!!
        p->T::~T()          ;//GCC32: OK; VC7x,Com4301: OK for A; ERROR w/ int
        p->~T()             ;//GCC32: OK; VC7x,Com4301: OK for A; ERROR w/ int
        }
    }

void test ()
    {
    f <A>  ();
    f <int>();
    }

Proposed resolution (April, 2005):

Change 13.5.6  over.ref paragraph 1 as indicated:

operator-> shall be a non-static member function taking no parameters. It implements the class member access using syntax that uses ->

An expression x->m is interpreted as (x.operator->())->m for a class object x of type T if T::operator->() exists and if the operator is selected as the best match function by the overload resolution mechanism (13.3  over.match).




425. Set of candidates for overloaded built-in operator with float operand

Section: 13.6  over.built     Status: WP     Submitter: Daniel Frey     Date: 30 June 2003

[Voted into WP at March 2004 meeting.]

During a discussion over at the boost mailing list (www.boost.org), we came across the following "puzzle":

  struct A {
    template< typename T > operator T() const;
  } a;

  template<> A::operator float() const
  {
    return 1.0f;
  }

  int main()
  {
    float f = 1.0f * a;
  }

The code is compiled without errors or warnings from EDG-based compilers (Comeau, Intel), but rejected from others (GCC, MSVC [7.1]). The question: Who is correct? Where should I file the bug report?

To explain the problem: The EDG seems to see 1.0f*a as a call to the unambiguous operator*(float,float) and thus casts 'a' to 'float'. The other compilers have several operators (float*float, float*double, float*int, ...) available and thus can't decide which cast is appropriate. I think the latter is the correct behaviour, but I'd like to hear some comments from the language lawyers about the standard's point of view on this problem.

Andreas Hommel: Our compiler also rejects this code:

Error   : function call 'operator*(float, {lval} A)' is ambiguous
'operator*(float, unsigned long long)'
'operator*(float, int)'
'operator*(float, unsigned int)'
'operator*(float, long)'
'operator*(float, unsigned long)'
'operator*(float, float)'
'operator*(float, double)'
'operator*(float, long double)'
'operator*(float, long long)'
Test.cp line 12       float f = 1.0f * a;

Is this example really legal? It was my understanding that all candidates from 13.6  over.built participate in overload resolution.

Daveed Vandevoorde: I believe the EDG-based compiler is right. Note that the built-in operator* requires "usual arithmetic conversions" (see 5.6  expr.mul paragraph 2 and 5  expr paragaph 9). This means that there is no candidate taking (float, double) arguments: Only (float, float) or (double, double).

Since your first argument is of type float, the (float, float) case is preferred over the (double, double) case (the latter would require a floating-point promotion).

Stave Adamczyk: Daveed's statement is wrong; as Andreas says, the prototypes in 13.6  over.built paragraph 12 have pairs of types, not the same type twice. However, the list of possibilities considered in Andreas' message is wrong also: 13.6  over.built paragraph 12 calls for pairs of promoted arithmetic types, and float is not a promoted type (it promotes to double -- see 4.6  conv.fpprom).

Nevertheless, the example is ambiguous. Let's look at the overload resolution costs. The right operand is always going to have a user-defined-conversion cost (the template conversion function will convert directly to the const version of the second parameter of the prototype). The left operand is always going to have a promotion (float --> double) or a standard conversion (anything else). So the cases with promotions are better than the others. However, there are several of those cases, with second parameters of type int, unsigned int, long, unsigned long, double, and long double, and all of those are equally good. Therefore the example is ambiguous.

Here's a reduced version that should be equivalent:

  struct A {
    template <typename T> operator T() const;
  } a;
  void f(double, int);
  void f(double, unsigned int);
  void f(double, long);
  void f(double, unsigned long);
  void f(double, double);
  void f(double, long double);
  int main() {
    f(1.0f, a);  // Ambiguous
  }

Personally, I think this is evidence that 13.6  over.built doesn't really do quite what it should. But the standard is clear, if possibly flawed.

Andreas Hommel: You are right, "float" is not a promoted arithmetic type, this is a bug in our compiler.

However, the usual arithmetic conversions (5  expr paragraph 9) do not promote the floating point types, so

  float operator+(float, float);
is a legal built-in operator function, so I wonder if it shouldn't be included in the candidate list.

Steve Adamczyk: Hmm, the definition of the term in 13.6  over.built paragraph 2 is highly ambiguous:

Similarly, the term promoted arithmetic type refers to promoted integral types plus floating types.
I can't tell if that's "promoted integral types plus (all) floating types" or "promoted integral types plus (promoted) floating types". I thought the latter was intended, but indeed the usual arithmetic conversions could give you "float + float", so it makes sense that float would be one of the possibilities. We should discuss this to make sure everyone has the same interpretation.

Proposed Resolution (October 2003):

Change the second sentence of 13.6 paragraph 2 as follows:

Similarly, the term promoted arithmetic type refers to promoted integral types plus floating types floating types plus promoted integral types.



204. Exported class templates

Section: 14  temp     Status: WP     Submitter: Robert Klarer     Date: 11 Feb 2000

[Voted into WP at April 2003 meeting.]

14  temp paragraph 7 allows class templates to be declared exported, including member classes and member class templates (implicitly by virtue of exporting the containing template class). However, paragraph 8 does not exclude exported class templates from the statement that

An exported template need only be declared (and not necessarily defined) in a translation unit in which it is instantiated.
This is an incorrect implication; however, it is also not dispelled in 14.7.1  temp.inst paragraph 6:
If an implicit instantiation of a class template specialization is required and the template is declared but not defined, the program is ill-formed.
This wording says nothing about the translation unit in which the definition must be provided. Contrast this with 14.7.2  temp.explicit paragraph 3:
A definition of a class template or a class member template shall be in scope at the point of the explicit instantiation of the class template or class member template.

Suggested resolution:


(See also issue 212.)

Notes from 04/00 meeting:

John Spicer opined that even though 14  temp paragraph 7 speaks of "declaring a class template exported," that does not mean that the class template is "an exported template" in the sense of paragraph 8. He suggested clarifying paragraph 7 to that effect instead of the change to paragraph 8 suggested above, and questioned the need for a change to 14.7.1  temp.inst.

Notes from the 4/02 meeting:

This is resolved by the proposed changes for issue 323.




323. Where must export appear?

Section: 14  temp     Status: WP     Submitter: Daveed Vandevoorde     Date: 14 Nov 2001

[Voted into WP at April 2003 meeting.]

The standard doesn't seem to describe whether the keyword export should appear on exported template declarations that are not used or defined in that particular translation unit.

For example:

  // File 1:
  template<typename T> void f();  // export omitted

  // File 2:
  export template<typename T> void f() {}

  int main() { f<int>(); }

Another example is:

  // File 1:
  struct S {
     template<typename T> void m();
  };

  // File 2:
  struct S {
     template<typename T> void m();
  };

  export template<typename T> void S::m() {}

  int main() {
     S s;
     S.m<int>();
  }

I think both examples should be clarified to be invalid. If a template is exported in one translation unit, it should be declared export in all translation units in which it appears.

With the current wording, it seems that even the following is valid:

  // File 1:
  export template<typename T> void f();  // export effectively ignored

  // File 2:
  template<typename T> void f() {}  // Inclusion model
  void g() { f<int>(); }

  // File 3:
  void g();
  template<typename T> void f() {}  // Inclusion model

  int main() {
     g();
     f<int>();
  }

In fact, I think the declaration in "File 1" could be a definition and this would still satisfy the the requirements of the standard, which definitely seems wrong.

Proposed Resolution (revised October 2002):

Replace 14  temp paragraphs 6, 7, and 8 by the following text:

A template-declaration may be preceded by the export keyword. Such a template is said to be exported. Declaring exported a class template is equivalent to declaring exported all of its non-inline member functions, static data members, member classes, member class templates, and non-inline member function templates.

If a template is exported in one translation unit, it shall be exported in all translation units in which it appears; no diagnostic is required. A declaration of an exported template shall appear with the export keyword before any point of instantiation (14.6.4.1  temp.point) of that template in that translation unit. In addition, the first declaration of an exported template containing the export keyword must not follow the definition of that template. The export keyword shall not be used in a friend declaration.

Templates defined in an unnamed namespace, inline functions, and inline function templates shall not be exported. An exported non-class template shall be defined only once in a program; no diagnostic is required. An exported non-class template need only be declared (and not necessarily defined) in a translation unit in which it is instantiated.

A non-exported non-class template must be defined in every translation unit in which it is implicitly instantiated (14.7.1  temp.inst), unless the corresponding specialization is explicitly instantiated (14.7.2  temp.explicit) in some translation unit; no diagnostic is required.

Note: This change also resolves issues 204 and 335.




335. Allowing export on template members of nontemplate classes

Section: 14  temp     Status: WP     Submitter: John Spicer     Date: 30 Jan 2002

[Voted into WP at April 2003 meeting.]

The syntax for "export" permits it only on template declarations. Clause 14  temp paragraph 6 further restricts "export" to appear only on namespace scope declarations. This means that you can't export a member template of a non-template class, as in:

  class A {
    template <class T> void f(T);
  };
You can, of course, put export on the definition:
  export template <class T> void A<T>::f(T){}
but in order for the template to be used from other translation units (the whole point of export) the declaration in the other translation unit must also be declared export.

There is also the issue of whether or not we should permit this usage:

  export struct A {
    template <class T> void f(T);
  };
My initial reaction is to retain this prohibition as all current uses of "export" are preceding the "template" keyword.

If we eliminate the requirement that "export" precede "template" there is a similar issue regarding this case, which is currently prohibited:

  template <class T> struct B {
    export void f();
  };
My preference is still to permit only "export template".

Notes from the 4/02 meeting:

This is resolved by the proposed changes for issue 323.




534. template-names and operator-function-ids

Section: 14  temp     Status: WP     Submitter: Jens Maurer     Date: 5 October 2005

[Voted into WP at the October, 2006 meeting.]

Taken literally, 14  temp paragraph 2 does not permit operator functions to be templates:

In a function template declaration, the declarator-id shall be a template-name (i.e., not a template-id).

and, in 14.2  temp.names paragraph 1, a template-name is defined to be simply an identifier.

Issue 301 considered and rejected the idea of changing the definition of template-name to include operator-function-ids and conversion-function-ids. Either that decision should be reconsidered or the various references in the text to template-name should be examined to determine if they should also mention the non-identifier possibilities for function template names.

Proposed resolution (April, 2006):

This issue is resolved by the resolution of issue 301.




184. Default arguments in template template-parameters

Section: 14.1  temp.param     Status: WP     Submitter: John Spicer     Date: 11 Nov 1999

[Voted into WP at April 2003 meeting.]

John Spicer: Where can default values for the template parameters of template template parameters be specified and where so they apply?

For normal template parameters, defaults can be specified only in class template declarations and definitions, and they accumulate across multiple declarations in the same way that function default arguments do.

I think that defaults for parameters of template template parameters should be handled differently, though. I see no reason why such a default should extend beyond the template declaration with which it is associated. In other words, such defaults are a property of a specific template declaration and are not part of the interface of the template.

    template <class T = float> struct B {};

    template <template <class _T = float> class T> struct A {
        inline void f();
        inline void g();
    };

    template <template <class _T> class T> void A<T>::f() {
        T<> t;  // Okay? (proposed answer - no)
    }

    template <template <class _T = char> class T> // Okay? (proposed answer - yes)
    void A<T>::g() {
        T<> t;  // T<char> or T<float>?  (proposed answer - T<char>)
    }

    int main() {
        A<B> ab;
        ab.f();
    }

I don't think this is clear in the standard.

Gabriel Dos Reis: On the other hand I fail to see the reasons why we should introduce yet another special rule to handle that situation differently. I think we should try to keep rules as uniform as possible. For default values, it has been the case that one should look for any declaration specifying default values. Breaking that rules doesn't buy us anything, at least as far as I can see. My feeling is that [allowing different defaults in different declarations] is very confusing.

Mike Miller: I'm with John on this one. Although we don't have the concept of "prototype scope" for template parameter lists, the analogy with function parameters would suggest that the two declarations of T (in the template class definition and the template member function definition) are separate declarations and completely unrelated. While it's true that you accumulate default arguments on top-level declarations in the same scope, it seems to me a far leap to say that we ought also to accumulate default arguments in nested declarations. I would expect those to be treated as being in different scopes and thus not to share default argument information.

When you look up the name T in the definition of A<T>::f(), the declaration you find has no default argument for the parameter of T, so T<> should not be allowed.

Proposed Resolution (revised October 2002):

In 14.1  temp.param, add the following as a new paragraph at the end of this section:

A template-parameter of a template template-parameter is permitted to have a default template-argument. When such default arguments are specified, they apply to the template template-parameter in the scope of the template template-parameter. [Example:
    template <class T = float> struct B {};

    template <template <class TT = float> class T> struct A {
        inline void f();
        inline void g();
    };

    template <template <class TT> class T> void A<T>::f() {
        T<> t;  // error - TT has no default template argument
    }

    template <template <class TT = char> class T>void A<T>::g() {
        T<> t;  // OK - T<char>
    }
-- end example]



215. Template parameters are not allowed in nested-name-specifiers

Section: 14.1  temp.param     Status: WP     Submitter: Martin von Loewis     Date: 13 Mar 2000

[Voted into WP at April, 2007 meeting.]

According to 14.1  temp.param paragraph 3, the following fragment is ill-formed:

    template <class T>
    class X{
      friend void T::foo();
    };

In the friend declaration, the T:: part is a nested-name-specifier (8  dcl.decl paragraph 4), and T must be a class-name or a namespace-name (5.1  expr.prim paragraph 7). However, according to 14.1  temp.param paragraph 3, it is only a type-name. The fragment should be well-formed, and instantiations of the template allowed as long as the actual template argument is a class which provides a function member foo. As a result of this defect, any usage of template parameters in nested names is ill-formed, e.g., in the example of 14.6  temp.res paragraph 2.

Notes from 04/00 meeting:

The discussion at the meeting revealed a self-contradiction in the current IS in the description of nested-name-specifiers. According to the grammar in 5.1  expr.prim paragraph 7, the components of a nested-name-specifier must be either class-names or namespace-names, i.e., the constraint is syntactic rather than semantic. On the other hand, 3.4.3  basic.lookup.qual paragraph 1 describes a semantic constraint: only object, function, and enumerator names are ignored in the lookup for the component, and the program is ill-formed if the lookup finds anything other than a class-name or namespace-name. It was generally agreed that the syntactic constraint should be eliminated, i.e., that the grammar ought to be changed not to use class-or-namespace-name.

A related point is the explicit prohibition of use of template parameters in elaborated-type-specifiers in 7.1.5.3  dcl.type.elab paragraph 2. This rule was the result of an explicit Committee decision and should not be unintentionally voided by the resolution of this issue.

Proposed resolution (04/01):

Change 5.1  expr.prim paragraph 7 and A.4  gram.expr from

to

This resolution depends on the resolutions for issues 245 (to change the name lookup rules in elaborated-type-specifiers to include all type-names) and 283 (to categorize template type-parameters as type-names).

Notes from 10/01 meeting:

There was some sentiment for going with simply identifier in front of the "::", and stronger sentiment for going with something with a more descriptive name if possible. See also issue 180.

Notes from April 2003 meeting:

This was partly resolved by the changes for issue 125. However, we also need to add a semantic check in 3.4.3  basic.lookup.qual to allow T::foo and we need to reword the first sentence of 3.4.3  basic.lookup.qual.

Proposed resolution (October, 2004):

Change 3.4.3  basic.lookup.qual paragraph 1 as follows:

The name of a class or namespace member can be referred to after the :: scope resolution operator (5.1  expr.prim) applied to a nested-name-specifier that nominates its class or namespace. During the lookup for a name preceding the :: scope resolution operator, object, function, and enumerator names are ignored. If the name found is not a class-name (clause 9  class) or namespace-name (7.3.1  namespace.def) does not designate a class or namespace, the program is ill-formed. [...]

Notes from the April, 2005 meeting:

The 10/2004 resolution does not take into account the fact that template type parameters do not designate class types in the context of the template definition. Further drafting is required.

Proposed resolution (April, 2006):

Change 3.4.3  basic.lookup.qual paragraph 1 as follows:

The name of a class or namespace member can be referred to after the :: scope resolution operator (5.1  expr.prim) applied to a nested-name-specifier that nominates its class or namespace. During the lookup for a name preceding the :: scope resolution operator, object, function, and enumerator names are ignored. If the name found is not a class-name (clause 9  class) or namespace-name (7.3.1  namespace.def) does not designate a namespace or a class or dependent type, the program is ill-formed. [...]



226. Default template arguments for function templates

Section: 14.1  temp.param     Status: WP     Submitter: Bjarne Stroustrup     Date: 19 Apr 2000

[Voted into WP at April 2003 meeting.]

The prohibition of default template arguments for function templates is a misbegotten remnant of the time where freestanding functions were treated as second class citizens and required all template arguments to be deduced from the function arguments rather than specified.

The restriction seriously cramps programming style by unnecessarily making freestanding functions different from member functions, thus making it harder to write STL-style code.

Suggested resolution:

Replace

A default template-argument shall not be specified in a function template declaration or a function template definition, nor in the template-parameter-list of the definition of a member of a class template.

by

A default template-argument shall not be specified in the template-parameter-list of the definition of a member of a class template.

The actual rules are as stated for arguments to class templates.

Notes from 10/00 meeting:

The core language working group was amenable to this change. Questions arose, however, over the interaction between default template arguments and template argument deduction: should it be allowed or forbidden to specify a default argument for a deduced parameter? If it is allowed, what is the meaning: should one or the other have priority, or is it an error if the default and deduced arguments are different?

Notes from the 10/01 meeting:

It was decided that default arguments should be allowed on friend declarations only when the declaration is a definition. It was also noted that it is not necessary to insist that if there is a default argument for a given parameter all following parameters have default arguments, because (unlike in the class case) arguments can be deduced if they are not specified.

Note that there is an interaction with issue 115.

Proposed resolution (revised October 2002):

  1. In 14.1  temp.param paragraph 9, replace

    A default template-argument may be specified in a class template declaration or a class template definition. A default template-argument shall not be specified in a function template declaration or a function template definition, nor in the template-parameter-list of the definition of a member of a class template.

    with

    A default template-argument may be specified in a template declaration. A default template-argument shall not be specified in the template-parameter-lists of the definition of a member of a class template that appears outside of the member's class.
  2. In 14.1  temp.param paragraph 9, replace

    A default template-argument shall not be specified in a friend template declaration.

    with

    A default template-argument shall not be specified in a friend class template declaration. If a friend function template declaration specifies a default template-argument, that declaration shall be a definition and shall be the only declaration of the function template in the translation unit.
  3. In 14.1  temp.param paragraph 11, replace

    If a template-parameter has a default template-argument, all subsequent template-parameters shall have a default template-argument supplied.

    with

    If a template-parameter of a class template has a default template-argument, all subsequent template-parameters shall have a default template-argument supplied. [Note: This is not a requirement for function templates because template arguments might be deduced (14.8.2  temp.deduct).]
  4. In 14.8  temp.fct.spec paragraph 1, replace

    Template arguments can either be explicitly specified when naming the function template specialization or be deduced (14.8.2  temp.deduct) from the context, e.g. from the function arguments in a call to the function template specialization.

    with

    Template arguments can be explicitly specified when naming the function template specialization, deduced from the context (14.8.2  temp.deduct), e.g., deduced from the function arguments in a call to the function template specialization), or obtained from default template arguments.
  5. In 14.8.1  temp.arg.explicit paragraph 2, replace

    Trailing template arguments that can be deduced (14.8.2  temp.deduct) may be omitted from the list of explicit template-arguments.

    with

    Trailing template arguments that can be deduced (14.8.2  temp.deduct) or obtained from default template-arguments may be omitted from the list of explicit template-arguments.
  6. In 14.8.2  temp.deduct paragraph 1, replace

    The values can be either explicitly specified or, in some cases, deduced from the use.

    with

    The values can be explicitly specified or, in some cases, be deduced from the use or obtained from default template-arguments.
  7. In 14.8.2  temp.deduct paragraph 4, replace

    The resulting substituted and adjusted function type is used as the type of the function template for template argument deduction. When all template arguments have been deduced, all uses of template parameters in nondeduced contexts are replaced with the corresponding deduced argument values. If the substitution results in an invalid type, as described above, type deduction fails.

    with

    The resulting substituted and adjusted function type is used as the type of the function template for template argument deduction. If a template argument has not been deduced, its default template argument, if any, is used. [Example:

        template <class T, class U = double>
        void f(T t = 0, U u = 0);
    
        void g()
        {
            f(1, 'c');         // f<int,char>(1,'c')
            f(1)               // f<int,double>(1,0)
            f();               // error: T cannot be deduced
            f<int>();          // f<int,double>(0,0)
            f<int,char>();     // f<int,char>(0,0)
        }
    

    ---end example]

    When all template arguments have been deduced or obtained from default template arguments, all uses of template parameters in nondeduced contexts are replaced with the corresponding deduced or default argument values. If the substitution results in an invalid type, as described above, type deduction fails.




401. When is access for template parameter default arguments checked?

Section: 14.1  temp.param     Status: WP     Submitter: Steve Adamczyk     Date: 27 Jan 2003

[Voted into WP at October 2005 meeting.]

Is the following well-formed?

  class policy {};
  class policy_interface {};
  template <class POLICY_INTERFACE>
  class aph {
  protected:
    typedef POLICY_INTERFACE PI;
  };
  template <class POLICY, class BASE, class PI = typename BASE::PI>
  class ConcretePolicyHolder : public BASE, protected POLICY
  {};
  ConcretePolicyHolder < policy , aph < policy_interface > > foo;
  void xx() { }

The issue is whether the access to the default argument type BASE::PI is checked before or after it is known that BASE is a base class of the template. To some extent, one needs to develop the list of template arguments (and therefore evaluate the default argument) before one can instantiate the template, and one does not know what base classes the template has until it has been instantiated.

Notes from April 2003 meeting:

Shortened example:

  class B {
  protected:
    typedef int A;
  };
  template<class T, class U = typename T::A>
  class X : public T
  { };

The convincing argument here is that if we had only the declaration of the template (including the default argument), we would expect it to be usable in exactly the same way as the version with the definition. However, the special access needed is visible only when the definition is available. So the above should be an error, and information from the definition cannot affect the access of the default arguments.

Proposed Resolution (April 2003):

Add a new paragraph 16 to 14.1  temp.param after paragraph 15:

Since a default template-argument is encountered before any base-clause there is no special access to members used in a default template-argument. [Example:
  class B {};
  template <class T> class C {
  protected:
     typedef T TT;
  };

  template <class U, class V = typename U::TT>
  class D : public U {};

  D <C<B> > d;  // access error, C::TT is protected
--- end example]

Notes from October 2003 meeting:

We decided that template parameter default arguments should have their access checked in the context where they appear without special access for the entity declared (i.e., they are different than normal function default arguments). One reason: we don't know the instance of the template when we need the value. Second reason: compilers want to parse and throw away the form of the template parameter default argument, not save it and check it for each instantiation.

Class templates should be treated the same as function templates in this regard. The base class information is in the same category as friend declarations inside the class itself -- not available. If the body were used one would need to instantiate it in order to know whether one can name it.

Proposed resolution (October, 2004):

Add the following as a new paragraph following the last paragraph of 11  class.access (but before the new paragraph inserted by the resolution of issue 372, if adopted):

The names in a default template-argument (14.1  temp.param) have their access checked in the context in which they appear rather than at any points of use of the default template-argument. [Example:

    class B {};
    template <class T> class C {
    protected:
       typedef T TT;
    };

    template <class U, class V = typename U::TT>
    class D : public U {};

    D <C<B> >* d;  // access error, C::TT is protected

end example]




228. Use of template keyword with non-member templates

Section: 14.2  temp.names     Status: WP     Submitter: Daveed Vandevoorde     Date: 4 May 2000

[Voted into WP at April 2003 meeting.]

Consider the following example:

    template<class T>
    struct X {
       virtual void f();
    };

    template<class T>
    struct Y {
       void g(X<T> *p) {
	  p->template X<T>::f();
       }
    };

This is an error because X is not a member template; 14.2  temp.names paragraph 5 says:

If a name prefixed by the keyword template is not the name of a member template, the program is ill-formed.

In a way this makes perfect sense: X is found to be a template using ordinary lookup even though p has a dependent type. However, I think this makes the use of the template prefix even harder to teach.

Was this intentionally outlawed?

Proposed Resolution (4/02):

Elide the first use of the word "member" in 14.2  temp.names paragraph 5 so that its first sentence reads:

If a name prefixed by the keyword template is not the name of a member template, the program is ill-formed.



301. Syntax for template-name

Section: 14.2  temp.names     Status: WP     Submitter: Mark Mitchell     Date: 24 Jul 2001

[Voted into WP at the October, 2006 meeting.]

The grammar for a template-name is:

That's not right; consider:

    template <class T> T operator+(const T&, const T&);
    template <> S operator+<S>(const S&, const S&);

This is ill-formed according to the standard, since operator+ is not a template-name.

Suggested resolution:

I think the right rule is

John Spicer adds that there's some question about whether conversion functions should be included, as they cannot have template argument lists.

Notes from 4/02 meeting:

If the change is made as a syntax change, we'll need a semantic restriction to avoid operator+<int> as a class. Clark Nelson will work on a compromise proposal -- not the minimal change to the syntax proposed, not the maximal change either.

Clark Nelson (April 2003):

The proposed solution (adding operator-function-id as an alternative for template-name) would have a large impact on the language described by the grammar. Specifically, for example, operator+<int> would become a syntactically valid class-name.

On the other hand, a change with (I believe) exactly the desired effect on the language accepted, would be to modify operator-function-id itself:

(Steve Adamczyk: this change was already made by issue 38 and is in TC1.)

Then there is the first sentence of 14.2  temp.names paragraph 3:

After name lookup (3.4  basic.lookup) finds that a name is a template-name, if this name is followed by a <, the < is always taken as the beginning of a template-argument-list and never as a name followed by the less-than operator.

This description seems to be adequate for names of class templates. As far as I can tell, the only ambiguity it resolves is from something that starts with new X <, in the scope of a class template X. But as far as I can tell is already inadequate for names of function templates, and is even worse for operator function templates.

Probably < should always be interpreted as introducing a template-argument-list if any member of the overload set is a function template. After all, function pointers are very rarely compared for ordering, and it's not clear what other rule might be workable.

I'm inclined to propose the simplest rule possible for operator-function-ids: if one is followed by <, then what follows is interpreted as a template-argument-list, unconditionally. Of course, if no template for that operator has been declared, then there's an error.

Also, note that if the operator in question is < or <<, it is possible to run into a problem similar to the famous >> nested template argument list closing delimiter problem. However, since in this case (at least) one of the < characters has a radically different interpretation than the other, and for other reasons as well, this is unlikely to be nearly as much of a practical problem as the >> problem.

Notes from April 2003 meeting:

We felt that the operator functions should not be special-cased. They should be treated like any other name.

September 2003:

Clark Nelson has provided the changes in N1490=03-0073.

Notes from October 2003 meeting:

We reviewed Clark Nelson's N1490. Clark will revise it and introduce a new syntax term for an identifier or the name of an operator function.

Notes from the April, 2005 meeting:

The CWG suggested a new approach to resolving this issue: the existing term template-id will be renamed to class-template-id, the term template-id will be defined to include operator functions with template arguments, and any current uses of template-id (such as in the definition of elaborated-type-specifier) where an operator function is not appropriate will be changed to refer to class-template-id.

Proposed resolution (April, 2006):

As specified in document J16/05-0156 = WG21 N1896, except that:

  1. In change 9 (3.4.5  basic.lookup.classref), omit the change from “entire postfix-expression” to “nested-name-specifier.”

  2. In change a (3.4.3.1  class.qual paragraph 1, third bullet), omit the change from “entire postfix-expression” to “qualified-id.”

  3. In change b (3.4.3.2  namespace.qual paragraph 1), omit the change from “entire postfix-expression” to “qualified-id.”

(Some of these omitted changes are addressed by issue 562.)

(This resolution also resolves issue 534.)




468. Allow ::template outside of templates

Section: 14.2  temp.names     Status: WP     Submitter: John Spicer     Date: 9 Apr 2004

[Voted into WP at the October, 2006 meeting.]

For the same reasons that issue 382 proposes for relaxation of the requirements on typename, it would make sense to allow the ::template disambiguator outside of templates.

See also issues 11, 30, 96, and 109.

Proposed resolution (October, 2005):

Change 14.2  temp.names paragraph 5 as indicated:

If a name prefixed by the keyword template is not the name of a template, the program is ill-formed. [Note: the keyword template may not be applied to non-template members of class templates. —end note] Furthermore, names of member templates shall not be prefixed by the keyword template if the postfix-expression or qualified-id does not appear in the scope of a template. [Note: just as is the case with the typename prefix, the template prefix is allowed in cases where it is not strictly necessary; i.e., when the nested-name-specifier or the expression on the left of the -> or ., or the nested-name-specifier is not dependent on a template-parameter, or the use does not appear in the scope of a template. —end note]



246. Jumps in function-try-block handlers

Section: 14.3  temp.arg     Status: WP     Submitter: Mike Miller     Date: 15 Sep 2000

[Moved to DR at 4/01 meeting.]

Is it permitted to jump from a handler of a function-try-block into the body of the function?

15  except paragraph 2 would appear to disallow such a jump:

A goto, break, return, or continue statement can be used to transfer control out of a try block or handler, but not into one.

However, 15.3  except.handle paragraph 14 mentions only constructors and destructors for the prohibition:

If the handlers of a function-try-block contain a jump into the body of a constructor or destructor, the program is ill-formed.

Is this paragraph simply reemphasizing the more general restriction, or does it assume that such a jump would be permitted for functions other than constructors or destructors? If the former interpretation is correct, it is confusing and should be either eliminated or turned into a note. If the latter interpretation is accurate, 15  except paragraph 2 must be revised.

(See also issue 98.)

Proposed resolution (04/01):

Delete 15.3  except.handle paragraph 14.




372. Is access granted by base class specifiers available in following base class specifiers?

Section: 14.3  temp.arg     Status: WP     Submitter: Clark Nelson     Date: 13 August 2002

[Voted into WP at the October, 2006 meeting.]

I'm not really sure what the standard says about this. Personally, I'd like for it to be ill-formed, but I can't find any words that I can interpret to say so.

  template<class T>
  class X
  {
  protected:
    typedef T Type;
  };
  template<class T>
  class Y
  {
  };
  template<class T,
           template<class> class T1,
           template<class> class T2>
  class Z:
    public T2<typename T1<T>::Type>,
    public T1<T>
  {
  };
  Z<int, X, Y> z;

John Spicer: I don't think the standard really addresses this case. There is wording about access checking of things used as template arguments, but that doesn't address accessing members of the template argument type (or template) from within the template.

This example is similar, but does not use template template arguments.

  class X {
  private:
    struct Type {};
  };
  template <class T> struct A {
    typename T::Type t;
  };
  A<X> ax;

This gets an error from most compilers, though the standard is probably mute on this as well. An error makes sense -- if there is no error, there is a hole in the access checking. (The special rule about no access checks on template parameters is not a hole, because the access is checked on the type passed in as an argument. But when you look up something in the scope of a template parameter type, you need to check the access to the member found.)

The logic in the template template parameter case should be similar: anytime you look up something in a template-dependent class, the member's access must be checked, because it could be different for different template instances.

Proposed Resolution (October 2002):

Change the last sentence of 14.3  temp.arg paragraph 3 from:

For a template-argument of class type, the template definition has no special access rights to the inaccessible members of the template argument type.
to:
For a template-argument that is a class type or a class template, the template definition has no special access rights to the members of the template-argument. [Example:
  template <template <class TT> class T> class A {
    typename T<int>::S s;
  };

  template <class U> class B {
    private:
    struct S { /* ... */ };
  };

  A<B> b;   // ill-formed, A has no access to B::S
-- end example]

Daniel Frey posts on comp.std.c++ in July 2003: I just read DR 372 and I think that the problem presented is not really discussed/solved properly. Consider this example:

  class A {
  protected:
     typedef int N;
  };

  template< typename T >
  class B
  {};

  template< typename U >
  class C : public U, public B< typename U::N >
  {};

  C< A > x;

The question is: If C is derived from A as above, is it allowed to access A::N before the classes opening '{'?

The main problem is that you need to access U's protected parts in C's base-clause. This pattern is common when using policies, Andrei's Loki library was bitten by it as he tried to make some parts of the policies 'protected' but some compilers rejected the code. They were right to reject it, I think it's 11.4  class.friend/2 that applies here and prevents the code above to be legal, although it addresses a different and reasonable example. To me, it seems wrong to reject the code as it is perfectly reasonable to write such stuff. The questions are:

Steve Adamczyk: In other words, the point of the issue is over what range access derived from base class specifiers is granted, and whether any part of that range is the base specifier list itself, either the parts afterwards or the whole base specifier list. (Clark Nelson confirms this is what he was asking with the original question.) Personally, I find it somewhat disturbing that access might arrive incrementally; I'd prefer that the access happen all at once, at the opening brace of the class.

Notes from October 2003 meeting:

We decided it makes sense to delay the access checking for the base class specifiers until the opening brace of the class is seen. In other words, the base specifiers will be checked using the full access available for the class, and the order of the base classes is not significant in that determination. The implementors present all said they already had code to handle accumulation of delayed access checks, because it is already needed in other contexts.

Proposed resolution (October, 2004):

  1. Change the last sentence of 14.3  temp.arg paragraph 3 as indicated:

    For a template-argument of that is a class type or a class template, the template definition has no special access rights to the inaccessible members of the template argument type template-argument. [Example:
        template <template <class TT> class T> class A {
           typename T<int>::S s;
        };
    
        template <class U> class B {
           private:
           struct S { /* ... */ };
        };
    
        A<B> b;   // ill-formed, A has no access to B::S
    

    end example]

  2. Insert the following as a new paragraph after the final paragraph of 11  class.access:

    The access of names in a class base-specifier-list are checked at the end of the list after all base classes are known. [Example:

        class A {
          protected:
             typedef int N;
        };
    
        template<typename T> class B {};
    
        template<typename U> class C : public B<typename U::N>, public U {};
    
        C<A> x;  // OK: A is a base class so A::N in B<A::N> is accessible
    

    end example]

Notes from the April, 2005 meeting:

The 10/2004 resolution is not sufficient to implement the CWG's intent to allow these examples: clause 11  class.access paragraph 1 grants protected access only to “members and friends” of derived classes, not to their base-specifiers. The resolution needs to be extended to say either that access in base-specifiers is determined as if they were members of the class being defined or that access is granted to the class as an entity, including its base-specifiers. See also issue 500, which touches on the same issue and should be resolved in the same way.

Proposed resolution (October, 2005):

  1. Change the second bullet of 11  class.access paragraph 1 as indicated:

  2. Change 11  class.access paragraph 2 as indicated:

    A member of a class can also access all the names declared in the class of which it is a member to which the class has access.
  3. Change 11  class.access paragraph 6 as indicated:

    All access controls in clause 11  class.access affect the ability to access a class member name from a particular scope. The access control for names used in the definition of a class member that appears outside of the member's class definition is done as if the entire member definition appeared in the scope of the member's class. For purposes of access control, the base-specifiers of a class and the definitions of class members that appear outside of the class definition are considered to be within the scope of that class. In particular...
  4. Change the example and commentary in 11  class.access paragraphs 6-7 as indicated:

    [Example:

        class A {
          typedef int I; // private member
          I f();
          friend I g(I);
          static I x;
        protected:
          struct B { };
        };
    
        A::I A::f () { return 0; }
        A::I g(A::I p = A::x);
        A::I g(A::I p) { return 0; }
        A::I A::x = 0;
    
        struct D: A::B, A { };
    
    Here, all the uses of A::I are well-formed because A::f and A::x are members of class A and g is a friend of class A. This implies, for example, that access checking on the first use of A::I must be deferred until it is determined that this use of A::I is as the return type of a member of class A. Similarly, the use of A::B as a base-specifier is well-formed because D is derived from A, so access checking of base-specifiers must be deferred until the entire base-specifier-list has been seen.end example]
  5. In 11.4  class.friend paragraph 2, replace the following text:

    Declaring a class to be a friend implies that the names of private and protected members from the class granting friendship can be accessed in declarations of members of the befriended class. [Note: this means that access to private and protected names is also granted to member functions of the friend class (as if the functions were each friends) and to the static data member definitions of the friend class. This also means that private and protected type names from the class granting friendship can be used in the base-clause of a nested class of the friend class. However, the declarations of members of classes nested within the friend class cannot access the names of private and protected members from the class granting friendship. Also, because the base-clause of the friend class is not part of its member declarations, the base-clause of the friend class cannot access the names of the private and protected members from the class granting friendship. For example,

        class A {
          class B { };
          friend class X;
        };
        class X: A::B {     // ill-formed: A::B cannot be accessed
                            // in the base-clause for X
          A::B mx;          // OK: A::B used to declare member of X
          class Y: A::B {   // OK: A::B used to declare member of X
            A::B my;        // ill-formed: A::B cannot be accessed
                            // to declare members of nested class of X
          };
        };
    

    end note]

    with:

    Declaring a class to be a friend implies that the names of private and protected members from the class granting friendship can be accessed in the base-specifiers and member declarations of the befriended class. [Example:

        class A {
          class B { };
          friend class X;
        };
    
        struct X: A::B {   // OK: A::B accessible to friend
          A::B mx;         // OK: A::B accessible to member of friend
          class Y {
            A::B my;       // OK: A::B accessible to nested member of friend
          };
        };
    

    end example]

  6. Change the last sentence of 14.3  temp.arg paragraph 3 as indicated:

    For a template-argument of that is a class type or a class template, the template definition has no special access rights to the inaccessible members of the template argument type. template-argument. [Example:

    
        template <template <class TT> class T> class A {
          typename T<int>::S s;
        };
    
        template <class U> class B {
        private:
          struct S { /* ... */ };
        };
    
        A<B> b;    // ill-formed, A has no access to B::S
    
    

    end example]

  7. Change 9.7  class.nest paragraph 4 as indicated:

    Like a member function, a friend function (11.4  class.friend) defined within a nested class is in the lexical scope of that class; it obeys the same rules for name binding as a static member function of that class (9.4  class.static), but it and has no special access rights to members of an enclosing class.

(Note: this resolution also resolves issues 494 and 500.)




62. Unnamed members of classes used as type parameters

Section: 14.3.1  temp.arg.type     Status: WP     Submitter: Steve Adamczyk     Date: 13 Oct 1998

[Moved to DR at 4/01 meeting.]

Section 14.3.1  temp.arg.type paragraph 2 says

A local type, a type with no linkage, an unnamed type or a type compounded from any of these types shall not be used as a template-argument for a template type-parameter.

It probably wasn't intended that classes with unnamed members should be included in this list, but they are arguably compounded from unnamed types.

Proposed resolution (04/01):

In 14.3.1  temp.arg.type paragraph 2, change

A local type, a type with no linkage, an unnamed type or a type compounded from any of these types shall not be used as a template-argument for a template type-parameter.

to

The following types shall not be used as a template-argument for a template type-parameter:



354. Null as nontype template argument

Section: 14.3.2  temp.arg.nontype     Status: WP     Submitter: John Spicer     Date: 2 May 2002

[Voted into WP at October 2005 meeting.]

The standard does not permit a null value to be used as a nontype template argument for a nontype template parameter that is a pointer.

This code is accepted by EDG, Microsoft, Borland and Cfront, but rejected by g++ and Sun:

  template <int *p> struct A {};
  A<(int*)0> ai;

I'm not sure this was ever explicitly considered by the committee. Is there any reason to permit this kind of usage?

Jason Merrill: I suppose it might be useful for a program to be able to express a degenerate case using a null template argument. I think allowing it would be harmless.

Notes from October 2004 meeting:

CWG decided that it would be desirable to allow null pointers as nontype template arguments, even though they are not representable in some current ABIs. There was some discussion over whether to allow a bare 0 to be used with a pointer nontype template parameter. The following case was decisive:

    template<int i> void foo();
    template<int* i> void foo();
    ...
    foo<0>();

The current wording of 14.3  temp.arg paragraph 7 disambiguates the function call in favor of the int version. If the null pointer conversion were allowed for pointer nontype template parameters, this case would become ambiguous, so it was decided to require a cast.

Proposed resolution (April, 2005):

  1. In 14.3.2  temp.arg.nontype paragraph 1, insert the following after the third bullet:

  2. Add the indicated text to the note in the second bullet of 14.3.2  temp.arg.nontype paragraph 5:

    [Note: In particular, neither the null pointer conversion (4.10  conv.ptr) nor the derived-to-base conversion (4.10  conv.ptr) are applied. Although 0 is a valid template-argument for a non-type template-parameter of integral type, it is not a valid template-argument for a non-type template-parameter of pointer type. However, (int*)0 is a valid template-argument for a non-type template-parameter of type “pointer to int.”end note]
  3. Replace the normative wording of 14.4  temp.type paragraph 1 with the following:

    Two template-ids refer to the same class or function if

    • their template-names refer to the same template, and
    • their corresponding type template-arguments are the same type, and
    • their corresponding non-type template-arguments of integral or enumeration type have identical values, and
    • their corresponding non-type template-arguments of pointer type refer to the same external object or function or are both the null pointer value, and
    • their corresponding non-type template-arguments of pointer-to-member type refer to the same class member or are both the null member pointer value, and
    • their corresponding non-type template-argumentss for template parameters of reference type refer to the same external object or function, and
    • their corresponding template template-arguments refer to the same template.



582. Template conversion functions

Section: 14.5.2  temp.mem     Status: WP     Submitter: PremAnand Rao     Date: 23 May 2006

[Voted into WP at April, 2007 meeting.]

The wholesale replacement of the phrase “template function” by the resolution of issue 105 seems to have overlooked the similar phrase “template conversion function.” This phrase appears a number of times in 13.3.3.1.2  over.ics.user paragraph 3, 14.5.2  temp.mem paragraphs 5-8, and 14.8.2  temp.deduct paragraph 1. It should be systematically replaced in similar fashion to the resolution of issue 105.

Proposed resolution (October, 2006):

  1. Change 13.3.3.1.2  over.ics.user paragraph 3 as follows:

  2. If the user-defined conversion is specified by a template conversion function specialization of a conversion function template, the second standard conversion sequence must have exact match rank.
  3. Change 14.5.2  temp.mem paragraph 5 as follows:

  4. A specialization of a template conversion function conversion function template is referenced in the same way as a non-template conversion function that converts to the same type.
  5. Change 14.5.2  temp.mem paragraph 6 as follows:

  6. A specialization of a template conversion function conversion function template is not found by name lookup. Instead, any template conversion functions conversion function templates visible in the context of the use are considered.
  7. Change 14.5.2  temp.mem paragraph 7 as follows:

  8. A using-declaration using-declaration in a derived class cannot refer to a specialization of a template conversion function conversion function template in a base class.
  9. Change 14.5.2  temp.mem paragraph 8 as follows:

  10. Overload resolution (13.3.3.2  over.ics.rank) and partial ordering (14.5.5.2  temp.func.order) are used to select the best conversion function among multiple template conversion functions specializations of conversion function templates and/or non-template conversion functions.
  11. Change 14.8.2.3  temp.deduct.conv paragraph 1 as follows:

  12. Template argument deduction is done by comparing the return type of the template conversion function conversion function template (call it P) with the type that is required as the result of the conversion (call it A) as described in 14.8.2.5  temp.deduct.type.



329. Evaluation of friends of templates

Section: 14.5.3  temp.friend     Status: WP     Submitter: John Spicer     Date: 19 Dec 2001

[Voted into WP at October 2003 meeting.]

14.5.3  temp.friend paragraph 5 says:

When a function is defined in a friend function declaration in a class template, the function is defined at each instantiation of the class template. The function is defined even if it is never used. The same restrictions on multiple declarations and definitions which apply to non-template function declarations and definitions also apply to these implicit definitions. [Note: if the function definition is ill-formed for a given specialization of the enclosing class template, the program is ill-formed even if the function is never used. ]

This means that the following program is invalid, even without the call of f(ai):

  template <class T> struct A {
    friend void f(A a) {
      g(a);
    }
  };
  int main()
  {
    A<int> ai;
  // f(ai);  // Error if f(ai) is actually called
  }

The EDG front end issues an error on this case even if f(ai) is never called. Of the compilers I tried (g++, Sun, Microsoft, Borland) we are the only ones to issue such an error.

This issue came up because there is a library that either deliberately or accidentally makes use of friend functions that are not valid for certain instantiations.

The wording in the standard is the result of a deliberate decision made long ago, but given the fact that most implementations do otherwise it raises the issue of whether we did the right thing.

Upon further investigation, the current rule was adopted as the resolution to issue 6.47 in my series of template issue papers. At the time the issue was discussed (7/96) most compilers did evaluate such friends. So it seems that a number of compilers have changed their behavior since then.

Based on current practice, I think the standard should be changed to evaluate such friends only when used.

Proposed resolution (October 2002):

Change section 14.5.3  temp.friend paragraph 5 from:

When a function is defined in a friend function declaration in a class template, the function is defined at each instantiation of the class template. The function is defined even if it is never used. The same restrictions on multiple declarations and definitions which apply to non-template function declarations and definitions also apply to these implicit definitions. [Note: if the function definition is ill-formed for a given specialization of the enclosing class template, the program is ill-formed even if the function is never used. ]
to:
When a function is defined in a friend function declaration in a class template, the function is instantiated when the function is used. The same restrictions on multiple declarations and definitions that apply to non-template function declarations and definitions also apply to these implicit definitions.
Note the change from "which" to "that" in the last sentence.




410. Paragraph missed in changes for issue 166

Section: 14.5.3  temp.friend     Status: WP     Submitter: John Spicer     Date: 18 Apr 2003

[Voted into WP at October 2004 meeting.]

14.5.3  temp.friend paragraph 2 was overlooked when the changes for issue 166 were made.

The friend declaration of f<>(int) is now valid.

A friend function declaration that is not a template declaration and in which the name of the friend is an unqualified template-id shall refer to a specialization of a function template declared in the nearest enclosing namespace scope. [Example:
  namespace N {
          template <class T> void f(T);
          void g(int);
          namespace M {
                  template <class T> void h(T);
                  template <class T> void i(T);
                  struct A {
                          friend void f<>(int);   // ill-formed - N::f
                          friend void h<>(int);   // OK - M::h
                          friend void g(int);     // OK - new decl of M::g
                          template <class T> void i(T);
                          friend void i<>(int);   // ill-formed - A::i
                  };
          }
  }
--end example]

Proposed Resolution (October 2003):

Remove 14.5.3  temp.friend paragraph 2:

A friend function declaration that is not a template declaration and in which the name of the friend is an unqualified template-id shall refer to a specialization of a function template declared in the nearest enclosing namespace scope. [Example:
  namespace N {
          template <class T> void f(T);
          void g(int);
          namespace M {
                  template <class T> void h(T);
                  template <class T> void i(T);
                  struct A {
                          friend void f<>(int);   // ill-formed - N::f
                          friend void h<>(int);   // OK - M::h
                          friend void g(int);     // OK - new decl of M::g
                          template <class T> void i(T);
                          friend void i<>(int);   // ill-formed - A::i
                  };
          }
  }
--end example]



286. Incorrect example in partial specialization

Section: 14.5.4  temp.class.spec     Status: WP     Submitter: Martin Sebor     Date: 09 May 2001

[Moved to DR at 4/02 meeting.]

The example in 14.5.4  temp.class.spec paragraph 6 is incorrect. It reads,

    template<class T> struct A {
        class C {
            template<class T2> struct B { };
        };
    };

    // partial specialization of A<T>::C::B<T2>
    template<class T> template<class T2>
        struct A<T>::C::B<T2*> { };

    A<short>::C::B<int*> absip; // uses partial specialization

Because C is a class rather than a struct, the use of the name B is inaccessible.

Proposed Resolution (10/01):

Change class C to struct C in the example in 14.5.4  temp.class.spec paragraph 6. The example becomes

    template<class T> struct A {
        struct C {
            template<class T2> struct B { };
        };
    };

    // partial specialization of A<T>::C::B<T2>
    template<class T> template<class T2>
        struct A<T>::C::B<T2*> { };

    A<short>::C::B<int*> absip; // uses partial specialization



517. Partial specialization following explicit instantiation

Section: 14.5.4  temp.class.spec     Status: WP     Submitter: John Spicer     Date: 03 May 2005

[Voted into WP at the October, 2006 meeting.]

According to 14.5.4  temp.class.spec paragraph 1,

If a template is partially specialized then that partial specialization shall be declared before the first use of that partial specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required.

This leaves the impression that an explicit instantiation of the primary template may precede the declaration of an applicable partial specialization. Is the following example well-formed?

    template<typename T> class X{
        public:
        void foo(){};
    };

    template class X<void *>;

    template<typename T> class X<T*>{
        public:
        void baz();
    };

    void bar() {
        X<void *> x;
        x.foo();
    }

Proposed resolution (October, 2005):

Replace the last sentence of 14.5.4  temp.class.spec paragraph 1:

If a template is partially specialized then that partial specialization shall be declared before the first use of that partial specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required.

with:

A partial specialization shall be declared before the first use of a class template specialization that would make use of the partial specialization as the result of an implicit or explicit instantiation in every translation unit in which such a use occurs; no diagnostic is required.



214. Partial ordering of function templates is underspecified

Section: 14.5.5.2  temp.func.order     Status: WP     Submitter: Martin von Loewis/Martin Sebor     Date: 13 Mar 2000

[Voted into WP at October 2003 meeting.]

In 14.5.5.2  temp.func.order, partial ordering is explained in terms of template argument deduction. However, the exact procedure for doing so is not specified. A number of details are missing, they are explained as sub-issues below.

  1. 14.5.5.2  temp.func.order paragraph 2 refers to 14.8.2  temp.deduct for argument deduction. This is the wrong reference; it explains how explicit arguments are processed (paragraph 2) and how function parameter types are adjusted (paragraph 3). Neither of these steps is meaningful in the context of partial ordering. Next in deduction follows one of the steps in 14.8.2.1  temp.deduct.call, 14.8.2.2  temp.deduct.funcaddr, 14.8.2.3  temp.deduct.conv, or 14.8.2.5  temp.deduct.type. The standard does not specify which of these contexts apply to partial ordering.
  2. Because 14.8.2.1  temp.deduct.call and 14.8.2.3  temp.deduct.conv both start with actual function parameters, it is meaningful to assume that partial ordering uses 14.8.2.5  temp.deduct.type, which only requires types. With that assumption, the example in 14.5.5.2  temp.func.order paragraph 5 becomes incorrect, considering the two templates
        template<class T> void g(T);  // #1
        template<class T> void g(T&); // #2
    
    Here, #2 is at least as specialized as #1: With a synthetic type U, #2 becomes g(U&); argument deduction against #1 succeeds with T=U&. However, #1 is not at least as specialized as #2: Deducing g(U) against g(T&) fails. Therefore, the second template is more specialized than the first, and the call g(x) is not ambiguous.
  3. According to John Spicer, the intent of the partial ordering was that it uses deduction as in a function call (14.8.2.1  temp.deduct.call), which is indicated by the mentioning of "exact match" in 14.5.5.2  temp.func.order paragraph 4. If that is indeed the intent, it should be specified how values are obtained for the step in 14.8.2.1  temp.deduct.call paragraph 1, where the types of the arguments are determined. Also, since 14.8.2.1  temp.deduct.call paragraph 2 drops references from the parameter type, symmetrically, references should be dropped from the argument type (which is done in 5  expr paragraph 2, for a true function call).
  4. 14.5.5.2  temp.func.order paragraph 4 requires an "exact match" for the "deduced parameter types". It is not clear whether this refers to the template parameters, or the parameters of the template function. Considering the example
        template<class S> void g(S);  // #1
        template<class T> void g(T const &); // #3
    
    Here, #3 is clearly at least as specialized as #1. To determine whether #1 is at least as specialized as #3, a unique type U is synthesized, and deduction of g<U>(U) is performed against #3. Following the rules in 14.8.2.1  temp.deduct.call, deduction succeeds with T=U. Since the template argument is U, and the deduced template parameter is also U, we have an exact match between the template parameters. Even though the conversion from U to U const & is an exact match, it is not clear whether the added qualification should be taken into account, as it is in other places.

Issue 200 covers a related issue, illustrated by the following example:

    template <class T> T f(int);
    template <class T, class U> T f(U);
    void g() {
        f<int>(1);
    }

Even though one template is "obviously" more specialized than the other, deduction fails in both directions because neither function parameter list allows template parameter T to be deduced.

(See also issue 250.)

Nathan Sidwell:

14.5.5.2  temp.func.order describes the partial ordering of function templates. Paragraph 5 states,

A template is more specialized than another if, and only if, it is at least as specialized as the other template and that template is not at least as specialized as the first.
To paraphrase, given two templates A & B, if A's template parameters can be deduced by B, but B's cannot be deduced by A, then A is more specialized than B. Deduction is done as if for a function call. In particular, except for conversion operators, the return type is not involved in deduction. This leads to the following templates and use being unordered. (This example is culled from G++ bug report 4672 http://gcc.gnu.org/cgi-bin/gnatsweb.pl?cmd=view&pr=4672)
  template <typename T, class U> T checked_cast(U from); //#1
  template <typename T, class U> T checked_cast(U * from); //#2
  class C {};

  void foo (int *arg)
  {
    checked_cast <C const *> (arg);
  }
In the call,

#1 can be deduced with T = 'C const *' and U = 'int *'

#2 can be deduced with T = 'C const *' and U = 'int'

It looks like #2 is more specialized that #1, but 14.5.5.2  temp.func.order does not make it so, as neither template can deduce 'T' from the other template's function parameters.

Possible Resolutions:

There are several possible solutions, however through experimentation I have discounted two of them.

Option 1:

When deducing function ordering, if the return type of one of the templates uses a template parameter, then return types should be used for deduction. This, unfortunately, makes existing well formed programs ill formed. For example

  template <class T> class X {};

  template <class T> X<T> Foo (T *);	// #1
  template <class T> int Foo (T const *); // #2

  void Baz (int *p1, int const *p2)
  {
    int j = Foo (p2); //#3
  }
Here, neither #1 nor #2 can deduce the other, as the return types fail to match. Considering only the function parameters gives #2 more specialized than #1, and hence makes the call #3 well formed.

Option 2:

As option 1, but only consider the return type when deducing the template whose return type involves template parameters. This has the same flaw as option 1, and that example is similarly ill formed, as #1's return type 'X<T,0>' fails to match 'int' so #1 cannot deduce #2. In the converse direction, return types are not considered, but the function parameters fail to deduce.

Option 3:

It is observed that the original example is only callable with a template-id-expr to supply a value for the first, undeducible, parameter. If that parameter were deducible it would also appear within at least one of the function parameters. We can alter paragraph 4 of [temp.func.order] to indicate that it is not necessary to deduce the parameters which are provided explicitly, when the call has the form of a template-id-expr. This is a safe extension as it only serves to make ill formed programs well formed. It is also in line with the concept that deduction for function specialization order should proceed in a similar manner to function calling, in that explicitly provided parameter values are taken into consideration.

Suggested resolution:

Insert after the first sentence of paragraph 4 in  

Should any template parameters remain undeduced, and the function call be of the form of a template-id-expr, those template parameters provided in the template-id-expr may be arbitrarily synthesized prior to determining whether the deduced arguments generate a valid function type.

See also issue 200.

(April 2002) John Spicer and John Wiegley have written a paper on this. See 02-0051/N1393.

Proposed resolution (October 2002):

Change 14.5.5.2  temp.func.order paragraph 2 to read:

Partial ordering selects which of two function templates is more specialized than the other by transforming each template in turn (see next paragraph) and performing template argument deduction using the function parameter types, or in the case of a conversion function the return type. The deduction process determines whether one of the templates is more specialized than the other. If so, the more specialized template is the one chosen by the partial ordering process.

Change 14.5.5.2  temp.func.order paragraph 3 to read:

To produce the transformed template, for each type, non-type, or template template parameter synthesize a unique type, value, or class template respectively and substitute it for each occurrence of that parameter in the function type of the template.

Change 14.5.5.2  temp.func.order paragraph 4 to read (note: the section reference should refer to the section added to 14.8.2  temp.deduct below):

Using the transformed function template's function parameter list, or in the case of a conversion function its transformed return type, perform type deduction against the function parameter list (or return type) of the other function. The mechanism for performing these deductions is given in 14.8.2.x.

Remove the text of 14.5.5.2  temp.func.order paragraph 5 but retain the example. The removed text is:

A template is more specialized than another if, and only if, it is at least as specialized as the other template and that template is not at least as specialized as the first.

Insert the following section before 14.8.2.5 (Note that this would either be a new 14.8.2.4, or would be given a number like 14.8.2.3a. If neither of these is possible from a troff point of view, this could be made 14.8.2.5. )

Deducing template arguments when determining the partial ordering of function templates (temp.deduct.partial)

Template argument deduction is done by comparing certain types associated with the two function templates being compared.

Two sets of types are used to determine the partial ordering. For each of the templates involved there is the original function type and the transformed function type. [Note: The creation of the transformed type is described in 14.5.5.2  temp.func.order.] The deduction process uses the transformed type as the argument template and the original type of the other template as the parameter template. This process is done twice for each type involved in the partial ordering comparison: once using the transformed template-1 as the argument template and template-2 as the parameter template and again using the transformed template-2 as the argument template and template-1 as the parameter template.

The types used to determine the ordering depend on the context in which the partial ordering is

Each type from the parameter template and the corresponding type from the argument template are used as the types of P and A.

Before the partial ordering is done, certain transformations are performed on the types used for partial ordering:

If both P and A were reference types (before being replaced with the type referred to above), determine which of the two types (if any) is more cv-qualified than the other; otherwise the types are considered to be equally cv-qualified for partial ordering purposes. The result of this determination will be used below.

Remove any top-level cv-qualifiers:

Using the resulting types P and A the deduction is then done as described in (14.8.2.5  temp.deduct.type). If deduction succeeds for a given type, the type from the argument template is considered to be at least as specialized as the type from the parameter template.

If, for a given type, deduction succeeds in both directions (i.e., the types are identical after the transformations above) if the type from the argument template is more cv-qualified than the type from the parameter template (as described above) that type is considered to be more specialized than the other. If neither type is more cv-qualified than the other then neither type is more specialized than the other.

If for each type being considered a given template is at least as specialized for all types and more specialized for some set of types and the other template is not more specialized for any types or is not at least as specialized for any types, then the given template is more specialized than the other template. Otherwise, neither template is more specialized than the other.

In most cases, all template parameters must have values in order for deduction to succeed, but for partial ordering purposes a template parameter may remain without a value provided it is not used in the types being used for partial ordering. [Note: A template parameter used in a non-deduced context is considered used.]

[Example:

template <class T> T f(int);        // #1
template <class T, class U> T f(U); // #2
void g() {
    f<int>(1);  // Calls #1
}
--end example]




180. typename and elaborated types

Section: 14.6  temp.res     Status: WP     Submitter: Mike Miller     Date: 21 Dec 1999

[Moved to DR at 4/02 meeting.]

Mike Miller: A question about typename came up in the discussion of issue 68 that is somewhat relevant to the idea of omitting typename in contexts where it is clear that a type is required: consider something like

        template <class T>
        class X {
            friend class T::nested;
        };
Is typename required here? If so, where would it go? (The grammar doesn't seem to allow it anywhere in an elaborated-type-specifier that has a class-key.)

Bill Gibbons: The class applies to the last identifier in the qualified name, since all the previous names must be classes or namespaces. Since the name is specified to be a class it does not need typename. [However,] it looks like 14.6  temp.res paragraph 3 requires typename and the following paragraphs do not exempt this case. This is not what we agreed on.

Proposed resolution (04/01):

In 14.6  temp.res paragraph 5, change

The keyword typename is not permitted in a base-specifier or in a mem-initializer; in these contexts a qualified-name that depends on a template-parameter (14.6.2  temp.dep) is implicitly assumed to be a type name.

to

A qualified name used as the name in a mem-initializer-id, a base-specifier, or an elaborated-type-specifier (in the class-key and enum forms) is implicitly assumed to name a type, without the use of the typename keyword. [Note: the typename keyword is not permitted by the syntax of these constructs.]

(The expected resolution for issue 254 will remove the typename forms from the grammar for elaborated-type-specifier. If that resolution is adopted, the parenthetical phrase "(in the class-key and enum forms)" in the preceding wording should be removed because those will be the only forms of elaborated-type-specifier.)

This has been consolidated with the edits for some other issues. See N1376=02-0034.




345. Misleading comment on example in templates chapter

Section: 14.6  temp.res     Status: WP     Submitter: Jason Shirk     Date: 18 March 2002

[Voted into WP at April 2003 meeting.]

The following example from 14.6  temp.res paragraph 4:

struct A {
	struct X { };
	int X;
};
template<class T> void f(T t) {
	typename T::X x;        //  ill-formed: finds the data member  X
					//  not the member type  X
}

is not ill-formed. The intent of the example is obvious, but some mention should be made that it is only ill-formed when T=A. For other T's, it could be well formed.

Proposed resolution (October 2002):

In 14.6  temp.res paragraph 4, replace the example with:

struct A {
  struct X { };
  int X; 
} ; 
struct B {
  struct X { };
} ;
template<class T> void f(T t) {
  typename T::X x; 
}
void foo() {
  A a; 
  B b;
  f(b);  // OK -- T::X refers to B::X.
  f(a);  // error: T::X refers to the data member A::X not 
         // the struct A::X.
}



382. Allow typename outside of templates

Section: 14.6  temp.res     Status: WP     Submitter: Steve Adamczyk     Date: 8 Nov 2002

[Voted into WP at October 2005 meeting.]

P. J. Plauger, among others, has noted that typename is hard to use, because in a given context it's either required or forbidden, and it's often hard to tell which. It would make life easier for programmers if typename could be allowed in places where it is not required, e.g., outside of templates.

Notes from the April 2003 meeting:

There was unanimity on relaxing this requirement on typename. The question was how much to relax it. Everyone agreed on allowing it on all qualified names, which is an easy fix (no syntax change required). But should it be allowed other places? P.J. Plauger said he'd like to see it allowed anywhere a type name is allowed, and that it could actually be a decades-late assist for the infamous "the ice is thin here" typedef problem noted in K&R I.

Proposed resolution (April 2003):

Replace the text at the start of 14.6  temp.res paragraph 3:

A qualified-id that refers to a type and in which the nested-name-specifier depends on a template-parameter (14.6.2  temp.dep) shall be prefixed by the keyword typename to indicate that the qualified-id denotes a type, forming an elaborated-type-specifier (7.1.5.3  dcl.type.elab).

With:

The keyword typename can only be applied to a qualified-id. A qualified-id that refers to a type and in which the nested-name-specifier depends on a template-parameter (14.6.2  temp.dep) shall be prefixed by the keyword typename to indicate that the qualified-id denotes a type, forming an elaborated-type-specifier (7.1.5.3  dcl.type.elab). If a qualified-id which has been prefixed by the keyword typename does not denote a type the program is ill-formed. [ Note: The keyword is only required on a qualified-id within a template declaration or definition in which the nested-name-specifier depends on a template-parameter. ]

Remove 14.6  temp.res paragraph 5:

The keyword typename shall only be used in template declarations and definitions, including in the return type of a function template or member function template, in the return type for the definition of a member function of a class template or of a class nested within a class template, and in the type-specifier for the definition of a static member of a class template or of a class nested within a class template. The keyword typename shall be applied only to qualified names, but those names need not be dependent. The keyword typename shall be used only in contexts in which dependent names can be used. This includes template declarations and definitions but excludes explicit specialization declarations and explicit instantiation declarations. The keyword typename is not permitted in a base-specifier or in a mem-initializer; in these contexts a qualified-id that depends on a template-parameter (temp.dep) is implicitly assumed to be a type name.

Note: the claim here that a qualified name preceded by typename forms an elaborated type specifier conflicts with the changes made in issue 254 (see N1376=02-0034), which introduces typename-specifier.

Notes from October 2003 meeting:

We considered whether typename should be allowed in more places, and decided we only wanted to allow it in qualified names (for now at least).

Core issue 254 changed elaborated-type-specifier to typename-specifier. It also changed 14.6  temp.res paragraph 5, which this proposed resolution deletes.

See also issue 468.

Proposed resolution (October, 2004):

  1. Change 14.6  temp.res paragraph 3 as follows:

    A When a qualified-id that refers to a type and in which the nested-name-specifier depends on a template-parameter (14.6.2  temp.dep) is intended to refer to a type, it shall be prefixed by the keyword typename to indicate that the qualified-id denotes a type, forming a typename-specifier. If the qualified-id in a typename-specifier does not denote a type, the program is ill-formed.
  2. Change 14.6  temp.res paragraph 5 as follows:

    The keyword typename shall only be used in template declarations and definitions, including in the return type of a function template or member function template, in the return type for the definition of a member function of a class template or of a class nested within a class template, and in the type-specifier for the definition of a static member of a class template or of a class nested within a class template. The keyword typename shall be applied only to qualified names, but those names need not be dependent. The keyword typename shall be used only in contexts in which dependent names can be used. This includes template declarations and definitions but excludes explicit specialization declarations and explicit instantiation declarations. A qualified name used as the name in a mem-initializer-id, a base-specifier, or an elaborated-type-specifier is implicitly assumed to name a type, without the use of the typename keyword. [Note: the typename keyword is not permitted by the syntax of these constructs. —end note]



409. Obsolete paragraph missed by changes for issue 224

Section: 14.6  temp.res     Status: WP     Submitter: John Spicer     Date: 18 Apr 2003

[Voted into WP at October 2004 meeting.]

Paragraph 6 of 14.6  temp.res is obsolete as result of issue 224, and needs to be revised.

Within the definition of a class template or within the definition of a member of a class template, the keyword typename is not required when referring to the unqualified name of a previously declared member of the class template that declares a type. The keyword typename shall always be specified when the member is referred to using a qual- ified name, even if the qualifier is simply the class template name. [Example:
  template<class T> struct A {
      typedef int B;
      A::B b;                     // ill-formed: typename required before A::B
      void f(A<T>::B);            // ill-formed: typename required before A<T>::B
      typename A::B g();          // OK
  };
]

Proposed Resolution:

Change 14.6  temp.res paragraph 6 as follows

Within the definition of a class template or within the definition of a member of a class template, the keyword typename is not required when referring to the unqualified name of a previously declared member of the class template that declares a type. The keyword typename shall always be specified when the member is referred to using a qualified name, even if the qualifier is simply the class template name. [Example:
template<class T> struct A {
    typedef int B;
    B b;                      // ok, no typename required
    A::B b;                   //  ill-formed: typename required before  A::B
    void f(A<T>::B);          //  ill-formed: typename required before  A<T>::B
    typename A::B g();        //  OK
};
The keyword typename is required whether the qualified name is A or A<T> because A or A<T> are synonyms within a class template with the parameter list <T>. ]



559. Editing error in issue 382 resolution

Section: 14.6  temp.res     Status: WP     Submitter: Mike Miller     Date: 11 February 2006

[Voted into WP at April, 2006 meeting.]

Part of the resolution for issue 224 was the addition of the phrase “but does not refer to a member of the current instantiation” to 14.6  temp.res paragraph 3. When the resolution of issue 382 was added to the current working draft, however, that phrase was inadvertently removed. Equivalent phrasing should be restored.

Proposed resolution (April, 2006):

Replace the first sentence of 14.6  temp.res paragraph 3 with the following text:

When a qualified-id is intended to refer to a type that is not a member of the current instantiation (14.6.2.1  temp.dep.type) and its nested-name-specifier depends on a template-parameter (14.6.2  temp.dep), it shall be prefixed by the keyword typename, forming a typename-specifier.



515. Non-dependent references to base class members

Section: 14.6.2  temp.dep     Status: WP     Submitter: Mike Miller     Date: 18 Apr 2005

[Voted into WP at the October, 2006 meeting.]

Implementations vary in their treatment of the following code:

    struct A {
      int foo_;
    };
    template <typename T> struct B: public A { };
    template <typename T> struct C: B<T> {
      int foo() {
        return A::foo_;  // #1
      }
    };
    int f(C<int>* p) {
      return p->foo();
    }

According to one analysis, because the expression A::foo_ on line #1 is non-dependent, it must be analyzed in the definition context. It that context, it violates the restrictions of 9.2  class.mem paragraph 10 on how the name of a nonstatic data member of a class can be used and thus should be treated as an error.

On the other hand, the description of the transformation of an id-expression into a class member access expression (9.3.1  class.mfct.nonstatic paragraph 3) does not have any special treatment of templates; when C<int>::foo() is instantiated, the reference to A::foo_ turns out to be to a base class member and is thus transformed into (*this).A::foo_ and is thus not an error.

Proposed resolution (October, 2005):

Change 9.3.1  class.mfct.nonstatic paragraph 3 as indicated:

When an id-expression (5.1  expr.prim) that is not part of a class member access syntax (5.2.5  expr.ref) and not used to form a pointer to member (5.3.1  expr.unary.op) is used in the body of a non-static member function of class X or used in the mem-initializer for a constructor of class X, if name lookup (3.4.1  basic.lookup.unqual) resolves the name in the id-expression to a non-static non-type member of class X or of a base class of X some class C, the id-expression is transformed into a class member access expression (5.2.5  expr.ref) using (*this) (9.3.2  class.this) as the postfix-expression to the left of the . operator. [Note: If C is not X or a base class of X, the class member access expression is ill-formed. —end note] The member name then refers to the member of the object for which the function is called. Similarly during name lookup...



524. Can function-notation calls to operator functions be dependent?

Section: 14.6.2  temp.dep     Status: WP     Submitter: Daveed Vandevoorde     Date: 19 July 2005

[Voted into WP at the October, 2006 meeting.]

The description of dependent function calls in 14.6.2  temp.dep paragraph 1 applies only to identifiers in postfix-notation function calls and to operator notation calls for operator functions:

In an expression of the form:

where the postfix-expression is an identifier, the identifier denotes a dependent name if and only if any of the expressions in the expression-list is a type-dependent expression (14.6.2.2  temp.dep.expr). If an operand of an operator is a type-dependent expression, the operator also denotes a dependent name.

It would appear from the related passage in 14.6.4.2  temp.dep.candidate paragraph 1 that the description of postfix-notation function calls should apply to all unqualified-ids that are not template-ids, including operator-function-ids, not just to identifiers:

For a function call that depends on a template parameter, if the function name is an unqualified-id but not a template-id, the candidate functions are found...

Proposed resolution (October, 2005):

  1. Change 14.6.2  temp.dep paragraph 1 as indicated:

  2. ...In an expression of the form:

    where the postfix-expression is an identifier unqualified-id but not a template-id, the identifier unqualified-id denotes a dependent name if and only if any of the expressions in the expression-list is a type-dependent expression (14.6.2.2  temp.dep.expr)...

  3. Change 14.6.4.2  temp.dep.candidate paragraph 1 as indicated:

  4. For a function call that depends on a template parameter, if the function name is an unqualified-id but not a template-id, or if the function is called using operator notation, the candidate functions are found using the usual lookup rules (3.4.1  basic.lookup.unqual, 3.4.2  basic.lookup.argdep) except that...

(See also issue 561.)




224. Definition of dependent names

Section: 14.6.2.1  temp.dep.type     Status: WP     Submitter: Derek Inglis     Date: 30 Nov 1999

[Moved to DR at 10/01 meeting.]

The definition of when a type is dependent, given in 14.6.2.1  temp.dep.type, is essentially syntactic: if the reference is a qualified-id and one of the class-names in the nested-name-specifier is dependent, the type is dependent. This approach leads to surprising results:

    template <class T> class X {
        typedef int I;
	I a;                 // non-dependent
        typename X<T>::I b;  // dependent
        typename X::I c;     // dependent (X is equivalent to X<T>)
    };

Suggested resolution:

The decision on whether a name is dependent or non-dependent should be based on lookup, not on the form of the name: if the name can be looked up in the definition context and cannot be anything else as the result of specialization, the name should be non-dependent.

See papers J16/00-0028 = WG21 N1251 and J16/00-0056 = WG21 N1279.

Proposed resolution (10/00):

  1. Replace section 14.6.2.1  temp.dep.type with the following:

    In the definition of a class template, a nested class of a class template, a member of a class template, or a member of a nested class of a class template, a name refers to the current instantiation if it is

    • the injected-class-name (clause 9  class) of the class template or nested class,
    • in the definition of a primary class template, the name of the class template followed by the template argument list of the primary template (as described below) enclosed in <>,
    • in the definition of a nested class of a class template, the name of the nested class referenced as a member of the current instantiation, or
    • in the definition of a partial specialization, the name of the class template followed by the template argument list of the partial specialization enclosed in <>.

    The template argument list of a primary template is a template argument list in which the nth template argument has the value of the nth template parameter of the class template.

    A template argument that is equivalent to a template parameter (i.e., has the same constant value or the same type as the template parameter) can be used in place of that template parameter in a reference to the current instantiation. In the case of a nontype template argument, the argument must have been given the value of the template parameter and not an expression involving the template parameter.

    [Example:

    template <class T> class A {
        A* p1;      // A is the current instantiation
        A<T>* p2;   // A<T> is the current instantiation
        A<T*> p3;   // A<T*> is not the current instantiation
        ::A<T>* p4; // ::A<T> is the current instantiation
        class B {
    	B* p1;        // B is the current instantiation
    	A<T>::B* p2;  // A<T>::B is the current instantiation
    	typename A<T*>::B* p3; // A<T*>::B is not the
    			     // current instantiation
        };
    };
    
    template <class T> class A<T*> {
        A<T*>* p1;  // A<T*> is the current instantiation
        A<T>* p2;   // A<T> is not the current instantiation
    };
    
    template <class T1, class T2, int I> struct B {
        B<T1, T2, I>*	b1;        // refers to the current instantiation
        B<T2, T1, I>*	b2;        // not the current instantiation
        typedef T1 my_T1;
        static const int my_I = I;
        static const int my_I2 = I+0;
        static const int my_I3 = my_I;
        B<my_T1, T2, my_I>* b3;  // refers to the current instantiation
        B<my_T1, T2, my_I2>* b4; // not the current instantiation
        B<my_T1, T2, my_I3>* b5; // refers to the current instantiation
    };
    

    end example]

    A name is a member of the current instantiation if it is

    • An unqualified name that, when looked up, refers to a member of a class template. [Note: This can only occur when looking up a name in a scope enclosed by the definition of a class template.]
    • A qualified-id in which the nested-name-specifier refers to the current instantiation.

    [Example:

    template <class T> class A {
        static const int i = 5;
        int n1[i];        // i refers to a member of the current instantiation 
        int n2[A::i];     // A::i refers to a member of the current instantiation 
        int n3[A<T>::i];  // A<T>::i refers to a member of the current instantiation 
        int f();
    };
    
    template <class T> int A<T>::f()
    {
        return i;  // i refers to a member of the current instantiation
    }
    

    end example]

    A name is a member of an unknown specialization if the name is a qualified-id in which the nested-name-specifier names a dependent type that is not the current instantiation.

    A type is dependent if it is

    • a template parameter,
    • a member of an unknown specialization,
    • a nested class that is a member of the current instantiation,
    • a cv-qualified type where the cv-unqualified type is dependent,
    • a compound type constructed from any dependent type,
    • an array type constructed from any dependent type or whose size is specified by a constant expression that is value-dependent, or
    • a template-id in which either the template name is a template parameter or any of the template arguments is a dependent type or an expression that is type-dependent or value-dependent.

    [Note: Because typedefs to not introduce new types, but instead simply refer to other types, a name that refers to a typedef that is a member of the current instantiation is dependent only if the type referred to is dependent.]

  2. In 14.6.2.2  temp.dep.expr paragraph 3, replace

    • a nested-name-specifier that contains a class-name that names a dependent type.

    with

    • a nested-name-specifier or qualified-id that names a member of an unknown specialization.
  3. In 14.6.2.2  temp.dep.expr, add the following paragraph:

    A class member access expression (5.2.5  expr.ref) is type-dependent if the type of the referenced member is dependent. [Note: In an expression of the form x.y or xp->y the type of the expression is usually the type of the member y of the class of x (or the class pointed to by xp). However, if x or xp refers to a dependent type that is not the current instantiation, the type of y is always dependent. If x or xp refers to a non-dependent type or refers to the current instantiation, the type of y is the type of the class member access expression.]
  4. In 14.6  temp.res paragraph 3, replace

    A qualified-name that refers to a type and that depends on a template-parameter (14.6.2  temp.dep) shall be prefixed by the keyword typename.

    with

    A qualified-id that refers to a type and that depends on a template-parameter (14.6.2  temp.dep) but does not refer to a member of the current instantiation shall be prefixed by the keyword typename.

    Note: the wording for this paragraph was changed in TC1. The words shown here are the pre-TC1 words.

  5. In 14.2  temp.names paragraph 4, replace

    When the name of a member template specialization appears after . or -> in a postfix-expression, or after a nested-name-specifier in a qualified-id, and the postfix-expression or qualified-id explicitly depends on a template-parameter (14.6.2  temp.dep), the member template name must be prefixed by the keyword template. Otherwise the name is assumed to name a non-template.

    with

    When the name of a member template specialization appears after . or -> in a postfix-expression, or after a nested-name-specifier in a qualified-id, and the postfix-expression or qualified-id explicitly depends on a template-parameter (14.6.2  temp.dep) but does not refer to a member of the current instantiation (14.6.2.1  temp.dep.type), the member template name must be prefixed by the keyword template. Otherwise the name is assumed to name a non-template.
  6. In 14.6.1  temp.local paragraph 2, remove the following text, which was added for issue 108. The updated definition of dependent name now addresses this case.

    Within the scope of a class template, when the unqualified name of a nested class of the class template is referred to, it is equivalent to the name of the nested class qualified by the name of the enclosing class template. [Example:

    template <class T> struct A {
    	class B {};
    	// B is equivalent to A::B, which is equivalent to A<T>::B,
    	// which is dependent.
    	class C : B { };
    };
    

    end example]




447. Is offsetof type-dependent?

Section: 14.6.2.3  temp.dep.constexpr     Status: WP     Submitter: Mark Mitchell     Date: 7 Jan 2004

[Voted into WP at October 2005 meeting.]

As far as I can tell, the standard doesn't say whether "offsetof(...)" is type-dependent. In the abstract, it shouldn't be -- an "offsetof" expression is always of type "size_t". But the standard doesn't say to what the definition of the macro is, so I don't think one can deduce that it will always be considered non-dependent by a conforming compiler.

John Spicer: (1) I agree that you can't know if offsetof is dependent because you don't know what it expands to. (2) In principle, offsetof should be like sizeof -- it is value-dependent if its argument is type-dependent.

Mark Mitchell: I think we should say that: (a) offsetof is not type-dependent, and (b) offsetof is value dependent iff the first argument is type-dependent

Everyone is using slightly different builtins to implement this functionality, and I don't think that there's any guarantee that they're all behaving the same here.

Notes from the March 2004 meeting:

Note that any such requirement would be in the library section, not core.

Proposed resolution (October, 2004):

  1. At the end of 14.6.2.2  temp.dep.expr paragraph 4, add after the list that ends with throw assignment-expression:

    [Note: For the standard library macro offsetof, see 18.1  lib.support.types. —end note]
  2. At the end of 14.6.2.3  temp.dep.constexpr paragraph 2, add after the list that ends with sizeof(type-id):

    [Note: For the standard library macro offsetof, see 18.1  lib.support.types. —end note]
  3. In 18.1  lib.support.types paragraph 4, replace

    The macro offsetof accepts a restricted set of type arguments in this International Standard. If type is not a POD structure or a POD union the results are undefined. The result of applying the offsetof macro to a field that is a static data member or a function member is undefined.

    with

    The macro offsetof(type, member-designator) accepts a restricted set of type arguments in this International Standard. If type is not a POD structure or a POD union (clause 9  class), the results are undefined. The expression offsetof(type, member-designator) is never type-dependent (14.6.2.2  temp.dep.expr) and it is value-dependent (14.6.2.3  temp.dep.constexpr) if and only if type is dependent. The result of applying the offsetof macro to a field that is a static data member or a function member is undefined.

    [Note: the original wording shown here reflects the resolutions of library issues 306 and 449.]




197. Issues with two-stage lookup of dependent names

Section: 14.6.4.2  temp.dep.candidate     Status: WP     Submitter: Derek Inglis     Date: 26 Jan 2000

[Voted into WP at October 2005 meeting.]

The example in 14.6  temp.res paragraph 9 is incorrect, according to 14.6.4.2  temp.dep.candidate . The example reads,

    void f(char);

    template <class T> void g(T t)
    {
        f(1);        // f(char);
        f(T(1));     // dependent
        f(t);        // dependent
        dd++;        // not dependent
                     // error: declaration for dd not found
    }

    void f(int);

    double dd;
    void h()
    {
        g(2);        // will cause one call of f(char) followed
                     // by two calls of f(int)
        g('a');      // will cause three calls of f(char)
    }
Since 14.6.4.2  temp.dep.candidate says that only Koenig lookup is done from the instantiation context, and since 3.4.2  basic.lookup.argdep says that fundamental types have no associated namespaces, either the example is incorrect (and f(int) will never be called) or the specification in 14.6.4.2  temp.dep.candidate is incorrect.

Notes from 04/00 meeting:

The core working group agreed that the example as written is incorrect and should be reformulated to use a class type instead of a fundamental type. It was also decided to open a new issue dealing more generally with Koenig lookup and fundamental types.

(See also issues 213 and 225.)

Proposed resolution (April, 2005):

Change the example in 14.6  temp.res paragraph 9 as follows:

    void f(char);

    template <class T> void g(T t)
    {
        f(1);        // f(char);
        f(T(1));     // dependent
        f(t);        // dependent
        dd++;        // not dependent
                     // error: declaration for dd not found
    }

    enum E { e };
    void f(intE);

    double dd;
    void h()
    {
        g(2e);       // will cause one call of f(char) followed
                     // by two calls of f(intE)
        g('a');      // will cause three calls of f(char)
    }



387. Errors in example in 14.6.5

Section: 14.6.5  temp.inject     Status: WP     Submitter: Aleksey Gurtovoy     Date: 27 Oct 2002

[Voted into WP at March 2004 meeting.]

The example in 14.6.5  temp.inject paragraph 2 is incorrect:

  template<typename T> class number {
      number(int);
      //...
      friend number gcd(number& x, number& y) { /* ... */ }
      //...
  };

  void g()
  {
      number<double> a(3), b(4);
      //...
      a = gcd(a,b);   // finds gcd because number<double> is an
                      // associated class, making gcd visible
                      // in its namespace (global scope)
      b = gcd(3,4);   // ill-formed; gcd is not visible
  }

Regardless of the last statement ("b = gcd(3,4);"), the above code is ill-formed:

a) number's constructor is private;

b) the definition of (non-void) friend 'gcd' function does not contain a return statement.

Proposed resolution (April 2003):

Replace the example in 14.6.5  temp.inject paragraph 2

  template<typename T> class number {
          number(int);
          //...
          friend number gcd(number& x, number& y) { /* ... */ }
          //...
  };

  void g()
  {
          number<double> a(3), b(4);
          //...
          a = gcd(a,b);           //  finds  gcd  because  number<double>  is an
                                  //  associated class, making  gcd  visible
                                  //  in its namespace (global scope)
          b = gcd(3,4);           //  ill-formed;  gcd  is not visible
  }
by
  template<typename T> class number {
     public:
          number(int);
          //...
          friend number gcd(number x, number y) { return 0; }
     private:
          //...
  };

  void g()
  {
          number<double> a(3), b(4);
          //...
          a = gcd(a,b);           //  finds  gcd  because  number<double>  is an
                                  //  associated class, making  gcd  visible
                                  //  in its namespace (global scope)
          b = gcd(3,4);           //  ill-formed;  gcd  is not visible
  }

Drafting note: Added "return" to the friend function, removed references in gcd arguments, added access specifiers.




259. Restrictions on explicit specialization and instantiation

Section: 14.7  temp.spec     Status: WP     Submitter: Matt Austern     Date: 2 Nov 2000

[Moved to DR at 4/02 meeting.]

According to 14.7  temp.spec paragraph 5,

No program shall explicitly instantiate any template more than once, both explicitly instantiate and explicitly specialize a template, or specialize a template more than once for a given set of template-arguments.

This rule has an impact on library issue 120. Library authors would like to have the freedom to specialize (or not) various library functions without having to document their choices, while users need the flexibility to explicitly instantiate library functions in certain translation units.

If this rule could be slightly weakened, it would reduce the need for constraining either the library author or the programmer. For instance, the rule might be recast to say that if a specialization is followed by an explicit instantiation in the same translation unit, the explicit instantiation is ignored. A specialization and an explicit instantiation of the same template in two different translation units would still be an error, no diagnostic required.

Proposed resolution (04/01):

  1. Replace the first sentence of 14.7  temp.spec paragraph 5,

    No program shall explicitly instantiate any template more than once, both explicitly instantiate and explicitly specialize a template, or specialize a template more than once for a given set of template-arguments.

    by

    For a given template and a given set of template-arguments,
    • an explicit instantiation shall appear at most once in a program,
    • an explicit specialization shall be defined at most once according to 3.2  basic.def.odr in a program, and
    • both an explicit instantiation and a declaration of an explicit specialization shall not appear in a program unless the explicit instantiation follows a declaration of the explicit specialization.
  2. Replace 14.7.2  temp.explicit paragraph 4,

    The definition of a non-exported function template, a non-exported member function template, or a non-exported member function or static data member of a class template shall be present in every translation unit in which it is explicitly instantiated.

    by

    For a given set of template parameters, if an explicit instantiation of a template appears after a declaration of an explicit specialization for that template, the explicit instantiation has no effect. Otherwise, the definition of a non-exported function template, a non-exported member function template, or a non-exported member function or static data member of a class template shall be present in every translation unit in which it is explicitly instantiated.



63. Class instantiation from pointer conversion to void*, null and self

Section: 14.7.1  temp.inst     Status: WP     Submitter: Steve Adamczyk     Date: 13 Oct 1998

[Moved to DR at October 2002 meeting.]

A template is implicitly instantiated because of a "pointer conversion" on an argument. This was intended to include related-class conversions, but it also inadvertently includes conversions to void*, null pointer conversions, cv-qualification conversions and the identity conversion.

It is not clear whether a reinterpret_cast of a pointer should cause implicit instantiation.

Proposed resolution (10/01): Replace 14.7.1  temp.inst paragraph 4, up to the example, with the following:

A class template specialization is implicitly instantiated if the class type is used in a context that requires a completely-defined object type or if the completeness of the class type might affect the semantics of the program. [Note: in particular, if the semantics of an expression depend on the member or base class lists of a class template specialization, the class template specialization is implicitly generated. For instance, deleting a pointer to class type depends on whether or not the class declares a destructor, and conversion between pointer to class types depends on the inheritance relationship between the two classes involved. ]

This version differs from the previous version is its use of the word "might" in the first sentence.

(See also issue 212.)




525. Missing * in example

Section: 14.7.1  temp.inst     Status: WP     Submitter: Mike Miller     Date: 25 July 2005

[Voted into WP at April, 2006 meeting.]

The example in 14.7.1  temp.inst paragraph 4 has a typographical error: the third parameter of function g should be D<double>* ppp, but it is missing the *:

  template <class T> class B { /* ... */ };
  template <class T> class D : public B<T> { /* ... */ };
  void f(void*);
  void f(B<int >*);

  void g(D<int>* p, D<char>* pp, D<double> ppp)
  {
    f(p);             // instantiation of D<int> required: call f(B<int>*)

    B<char>* q = pp;  // instantiation of D<char> required:
                      // convert D<char>* to B<char>*
    delete ppp;       // instantiation of D<double> required
  }

Proposed resolution (October, 2005):

As suggested.




237. Explicit instantiation and base class members

Section: 14.7.2  temp.explicit     Status: WP     Submitter: Christophe de Dinechin     Date: 28 Jul 2000

[Voted into WP at October 2005 meeting.]

In 14.7.2  temp.explicit paragraph 7 we read:

The explicit instantiation of a class template specialization implies the instantiation of all of its members not previously explicitly specialized in the translation unit containing the explicit instantiation.

Is "member" intended to mean "non-inherited member?" If yes, maybe it should be clarified since 10  class.derived paragraph 1 says,

Unless redefined in the derived class, members of a base class are also considered to be members of the derived class.

Proposed resolution (October, 2004):

Fixed by the resolution of issue 470.




470. Instantiation of members of an explicitly-instantiated class template

Section: 14.7.2  temp.explicit     Status: WP     Submitter: Matt Austern     Date: 11 May 2004

[Voted into WP at October 2005 meeting.]

14.7.2  temp.explicit paragraph 7 says,

The explicit instantiation of a class template specialization implies the instantiation of all of its members not previously explicitly specialized in the translation unit containing the explicit instantiation.

It's not clear whether this “implied” instantiation is implicit or explicit instantiation. It makes a difference in cases like the following:

    template <typename T> struct foo {
        struct bar { };
    };

    template struct foo<int>;         // #1

    template struct foo<int>::bar;    // #2

If the instantiation of foo<int>::bar implied by #1 is implicit, the explicit instantiation in #2 is well-formed. Otherwise, #2 violates the requirement in 14.7  temp.spec that

No program shall explicitly instantiate any template more than once ... for a given set of template-arguments.

It's also unclear whether the implied instantiation applies only to direct members of the class template or to inherited members, as well.

John Spicer: I have always interpreted this as meaning only the members declared in the class, not those inherited from other classes. This is what EDG does, and appears to be what g++, Microsoft and Sun do, too. I also think this is the correct thing for the Standard to require. If I were to derive a class from a class in the standard library, an explicit instantiation of my class should not cause the explicit instantiation of things in the standard library (because the library might provide such explicit instantiations, thus causing my program to run afoul of the "can't instantiate more than once" rule).

Proposed resolution (October, 2004):

Change 14.7.2  temp.explicit paragraph 7 as follows:

The explicit instantiation of a class template specialization implies the instantiation of all also explicitly instantiates each of its members not (not including members inherited from base classes) whose definition is visible at the point of instantiation and that has not been previously explicitly specialized in the translation unit containing the explicit instantiation.



44. Member specializations

Section: 14.7.3  temp.expl.spec     Status: WP     Submitter: Nathan Myers     Date: 19 Sep 1998

[Moved to DR at 4/01 meeting.]

Some compilers reject the following:

    struct A {
        template <int I> void f();
        template <> void f<0>();
    };
on the basis of 14.7.3  temp.expl.spec paragraph 2:
An explicit specialization shall be declared in the namespace of which the template is a member, or, for member templates, in the namespace of which the enclosing class or enclosing class template is a member. An explicit specialization of a member function, member class or static data member of a class template shall be declared in the namespace of which the class template is a member. ...
claiming that the specialization above is not "in the namespace of which the enclosing class ... is a member". Elsewhere, declarations are sometimes required to be "at" or "in" "namespace scope", which is not what it says here. Paragraph 17 says:
A member or a member template may be nested within many enclosing class templates. If the declaration of an explicit specialization for such a member appears in namespace scope, the member declaration shall be preceded by a template<> for each enclosing class template that is explicitly specialized.
The qualification "if the declaration ... appears in namespace scope", implies that it might appear elsewhere. The only other place I can think of for a member specialization is in class scope.

Was it the intent of the committee to forbid the construction above? (Note that A itself is not a template.) If so, why?

Proposed resolution (04/01): In-class specializations of member templates are not allowed. In 14.7.3  temp.expl.spec paragraph 17, replace

If the declaration of an explicit specialization for such a member appears in namespace scope...
with
In an explicit specialization for such a member...

Notes from 04/00 meeting:

This issue was kept in "review" status for two major reasons:

  1. It's not clear that a change is actually needed. All uses of the phrase "in the namespace" in the IS mean "directly in the namespace," not in a scope nested within the namespace.
  2. There was substantial sentiment for actually adding support for in-class specializations at a future time, and it might be perceived as a reversal of direction to pass a change aimed at reinforcing the absence of the feature, only to turn around afterward and add it.

Notes from 10/00 meeting:

The core working group felt that the value of additional clarity here outweighs the potential disadvantages that were noted at the preceding meeting.




275. Explicit instantiation/specialization and using-directives

Section: 14.7.3  temp.expl.spec     Status: WP     Submitter: John Spicer     Date: 15 Feb 2001

[Moved to DR at 4/02 meeting.]

Consider this example:

    namespace N {
	template <class T> void f(T){}
	template <class T> void g(T){}
	template <> void f(int);
	template <> void f(char);
	template <> void g(char);
    }

    using namespace N;

    namespace M {
	template <> void N::f(char){}  // prohibited by standard
	template <class T> void g(T){}
	template <> void g(char){}     // specialization of M::g or ambiguous?
	template void f(long);         // instantiation of N::f?
    }

    template <class T> void g(T){}

    template <> void N::f(char){}  // okay
    template <> void f(int){}      // is this a valid specialization of N::f?

    template void g(int);          // instantiation of ::g(int) or ambiguous?

The question here is whether unqualified names made visible by a using-directive can be used as the declarator in an explicit instantiation or explicit specialization.

Note that this question is already answered for qualified names in 8.3  dcl.meaning paragraph 1. In a qualified name such as N::f, f must be a member of class or namespace N, not a name made visible in N by a using-directive (or a using-declaration, for that matter).

The standard does not, as far as I can tell, specify the behavior of these cases one way or another.

My opinion is that names from using-directives should not be considered when looking up the name in an unqualified declarator in an explicit specialization or explicit instantiation. In such cases, it is reasonable to insist that the programmer know exactly which template is being specialized or instantiated, and that a qualified name must be used if the template is a member of a namespace.

As the example illustrates, allowing names from using-directives to be used would also have the affect of making ambiguous otherwise valid instantiation and specialization directives.

Furthermore, permitting names from using-directives would require an additional rule to prohibit the explicit instantiation of an entity in one namespace from being done in another (non-enclosing) namespace (as in the instantiation of f in namespace M in the example).

Mike Miller: I believe the explicit specialization case is already covered by 7.3.1.2  namespace.memdef paragraph 2, which requires using a qualified name to define a namespace member outside its namespace.

John Spicer: 7.3.1.2  namespace.memdef deals with namespace members. An explicit specialization directive deals with something that is a specialization of a namespace member. I don't think the rules in 7.3.1.2  namespace.memdef could be taken to apply to specializations unless the standard said so explicitly.

Proposed resolution (suggested 04/01, proposed 10/01):

(The first change below will need to be revised in accordance with the resolution of issue 284 to add a cross-reference to the text dealing with class names.)

  1. Add in 14.7.2  temp.explicit paragraph 2 before the example:

    An explicit instantiation shall appear in an enclosing namespace of its template. If the name declared in the explicit instantiation is an unqualified name, the explicit instantiation shall appear in the namespace where its template is declared. [Note: Regarding qualified names in declarators, see 8.3  dcl.meaning.]
  2. Change the first sentence of 7.3.1.2  namespace.memdef paragraph 1 from

    Members of a namespace can be defined within that namespace.

    to

    Members (including explicit specializations of templates (14.7.3  temp.expl.spec)) of a namespace can be defined within that namespace.
  3. Change the first sentence of 7.3.1.2  namespace.memdef paragraph 2 from

    Members of a named namespace can also be defined...

    to

    Members (including explicit specializations of templates (14.7.3  temp.expl.spec)) of a named namespace can also be defined...
  4. Change the last sentence of 14.7.3  temp.expl.spec paragraph 2 from

    If the declaration is not a definition, the specialization may be defined later in the namespace in which the explicit specialization was declared, or in a namespace that encloses the one in which the explicit specialization was declared.

    to

    If the declaration is not a definition, the specialization may be defined later (7.3.1.2  namespace.memdef).



336. Explicit specialization examples are still incorrect

Section: 14.7.3  temp.expl.spec     Status: WP     Submitter: Jason Shirk     Date: 29 Jan 2002

[Voted into WP at April 2003 meeting.]

The examples corrected by issue 24 are still wrong in one case.

In item #4 (a correction to the example in paragraph 18), the proposed resolution is:

  template<class T1> class A { 
    template<class T2> class B { 
      template<class T3> void mf1(T3); 
        void mf2(); 
      }; 
  }; 
  template<> template<class X> 
    class A<int>::B { }; 
  template<> template<> template<class T> 
    void A<int>::B<double>::mf1(T t) { }
  template<class Y> template<> 
    void A<Y>::B<double>::mf2() { } // ill-formed; B<double> is specialized but 
                                    // its enclosing class template A is not 

The explicit specialization of member A<int>::B<double>::mf1 is ill-formed. The class template A<int>::B is explicitly specialized and contains no members, so any implicit specialization (such as A<int>::B<double>) would also contain no members.

Proposed Resolution (4/02):

Fix the example in 14.7.3  temp.expl.spec paragraph 18 to read:

  template<class T1> class A { 
    template<class T2> class B { 
      template<class T3> void mf1(T3);
      void mf2(); 
    }; 
  }; 
  template<> template<class X> 
    class A<int>::B {
      template<class T> void mf1(T);
    }; 
  template<> template<> template<class T> 
    void A<int>::B<double>::mf1(T t) { }
  template<class Y> template<> 
    void A<Y>::B<double>::mf2() { } // ill-formed; B<double> is specialized but 
                                    // its enclosing class template A is not 



337. Attempt to create array of abtract type should cause deduction to fail

Section: 14.8.2  temp.deduct     Status: WP     Submitter: John Spicer     Date: 30 Jan 2002

[Voted into WP at April 2003 meeting.]

In 14.8.2  temp.deduct, attempting to create an array of abstract class type should be included in the list of things that cause type deduction to fail.

Proposed Resolution (4/02):

In 14.8.2  temp.deduct paragraph 2 amend the bullet item:

Attempting to create an array with an element type that is void, a function type, or a reference type, or attempting to create an array with a size that is zero or negative.

To the following:

Attempting to create an array with an element type that is void, a function type, or a reference type, or an abstract class type, or attempting to create an array with a size that is zero or negative.



368. Uses of non-type parameters that should cause deduction to fail

Section: 14.8.2  temp.deduct     Status: WP     Submitter: Jason Shirk     Date: 29 July 2002

[Voted into WP at October 2003 meeting.]

I understand the rules in 14.8.2  temp.deduct paragraph 2 are meant to be an exhaustive list of what can cause type deduction to fail.

Consider:

  template<typename U,U u> struct wrap_t;

  template<typename U> static yes check( wrap_t<U,U(0)>* );

  struct X { X(int); };
  int main() {
    check<X>(0);
  }

I can see 2 reasons this might cause type deduction to fail:

Neither case is mentioned in 14.8.2  temp.deduct paragraph 2, nor do I see a DR mentioning these.

Proposed resolution (October 2002):

Add after the fourth-to-last bullet of 14.8.2  temp.deduct paragraph 2:




398. Ambiguous wording on naming a type in deduction

Section: 14.8.2  temp.deduct     Status: WP     Submitter: Daveed Vandevoorde     Date: 16 Jan 2003

[Voted into WP at March 2004 meeting.]

The following example (simplified from a posting to comp.lang.c++.moderated) is accepted by some compilers (e.g., EDG), but not by other (e.g., g++).

  struct S {
    static int const I = 42;
  };

  template<int N> struct X {};

  template<typename T> void f(X<T::I>*) {}

  template<typename T> void f(X<T::J>*) {}

  int main() {
    f<S>(0);
  }

The wording in the standard that normally would cover this (third sub-bullet in 14.8.2  temp.deduct paragraph 2) says:

Attempting to use a type in the qualifier portion of a qualified name that names a type when that type does not contain the specified member, or if the specified member is not a type where a type is required.
(emphasis mine). If the phrase "that names a type" applies to "a qualified name," then the example is invalid. If it applies to "the qualifier portion," then it is valid (because the second candidate is simply discarded).

I suspect we want this example to work. Either way, I believe the sub-bullet deserves clarification.

Notes from April 2003 meeting:

We agreed that the example should be valid. The phrase "that names a type" applies to "the qualifier portion."

Proposed resolution (October 2003):

In 14.8.2  temp.deduct, paragraph 2, bullet 3, sub-bullet 3, replace

Attempting to use a type in the qualifier portion of a qualified name that names a type when that type does not contain the specified member, or if the specified member is not a type where a type is required.

With

Attempting to use a type in a nested-name-specifier of a qualified-id when that type does not contain the specified member, or

[Example:

Replace the example that follows the above text with

template <int I> struct X { };
template <template <class T> class> struct Z {};
template <class T> void f(typename T::Y*){}
template <class T> void g(X<T::N>*){}
template <class T> void h(Z<T::template TT>*){}
struct A {};
struct B { int Y; };
struct C {
	typedef int N;
};
struct D {
	typedef int TT;
};

int main()
{
	// Deduction fails in each of these cases:
	f<A>(0); // A does not contain a member Y
	f<B>(0); // The Y member of B is not a type
	g<C>(0); // The N member of C is not a nontype
	h<D>(0); // The TT member of D is not a template
}
]



486. Invalid return types and template argument deduction

Section: 14.8.2  temp.deduct     Status: WP     Submitter: John Spicer     Date: 16 Nov 2004

[Voted into WP at April, 2006 meeting.]

According to 14.8.2  temp.deduct paragraph 2,

If a substitution in a template parameter or in the function type of the function template results in an invalid type, type deduction fails.

That would seem to apply to cases like the following:

    template <class T> T f(T&){}
    void f(const int*){}
    int main() {
      int a[5];
      f(a);
    }

Here, the return type of f is deduced as int[5], which is invalid according to 8.3.5  dcl.fct paragraph 6. The outcome of this example, then, should presumably be that type deduction fails and overload resolution selects the non-template function. However, the list of reasons in 14.8.2  temp.deduct for which type deduction can fail does not include function and array types as a function return type. Those cases should be added to the list.

Proposed resolution (October, 2005):

Change the last sub-bullet of 14.8.2  temp.deduct paragraph 2 as indicated:




352. Nondeduced contexts

Section: 14.8.2.1  temp.deduct.call     Status: WP     Submitter: Andrei Iltchenko     Date: 24 April 2002

[Voted into WP at March 2004 meeting.]

The current definition of the C++ language speaks about nondeduced contexts only in terms of deducing template arguments from a type 14.8.2.5  temp.deduct.type paragraph 4. Those cases, however, don't seem to be the only ones when template argument deduction is not possible. The example below illustrates that:

namespace  A  {
   enum  ae  {   };
   template<class R, class A>
   int  foo(ae, R(*)(A))
   {   return  1;   }
}

template<typename T>
void  tfoo(T)
{   }

template<typename T>
int  tfoo(T)
{   return  1;   }

/*int  tfoo(int)
{   return  1;   }*/


int  main()
{
   A::ae   a;
   foo(a, &tfoo);
}

Here argument-dependent name lookup finds the function template 'A::foo' as a candidate function. None of the function template's function parameter types constitutes a nondeduced context as per 14.8.2.5  temp.deduct.type paragraph 4. And yet, quite clearly, argument deduction is not possible in this context. Furthermore it is not clear what a conforming implementation shall do when the definition of the non-template function '::tfoo' is uncommented.

Suggested resolution:

Add the following as a new paragraph immediately before paragraph 3 of 14.8.2.1  temp.deduct.call:

After the above transformations, in the event of P being a function type, a pointer to function type, or a pointer to member function type and the corresponding A designating a set of overloaded (member) functions with at least one of the (member) functions introduced by the use of a (member) function template name (13.4  over.over) or by the use of a conversion function template (14.8.2.3  temp.deduct.conv), the whole function call expression is considered to be a nondeduced context. [Example:

namespace  A  {
   enum  ae  {   };
   template<class R, class A>
   int  foo(ae, R(*)(A))
   {   return  1;   }
}

template<typename T>
void  tfoo(T)
{   }

template<typename T>
int  tfoo(T)
{   return  1;   }

int  tfoo(int)
{   return  1;   }

int  main()
{
   A::ae   a;
   foo(a, &tfoo);   //  ill-formed, the call is a nondeduced context
   using  A::foo;
   foo<void,int>(a, &tfoo);  // well-formed, the address of the spe-
                             // cialization 'void tfoo<int>(int)' is
                             // the second argument of the call
}

Notes from October 2002 meeting:

There was agreement that deduction should fail but it's still possible to get a result -- it's just not a "nondeduced context" in the sense of the standard.

The presence of a template in the overload set should not automatically disqualify the overload set.

Proposed Resolution (April 2003, revised October 2003):

In 14.8.2.5  temp.deduct.type paragraph 4 replace:

The nondeduced contexts are:
with:
The nondeduced contexts are:

In 14.8.2.1  temp.deduct.call, add after paragraph 3:

When P is a function type, pointer to function type, or pointer to member function type:

[Example:

// Only one function of an overload set matches the call so the function
// parameter is a deduced context.
template <class T> int f(T (*p)(T));
int g(int);
int g(char);
int i = f(g);  // calls f(int (*)(int))
--end example]

[Example:

// Ambiguous deduction causes the second function parameter to be a
// nondeduced context.
template <class T> int f(T, T (*p)(T));
int g(int);
char g(char);
int i = f(1, g);  // calls f(int, int (*)(int))
--end example]

[Example:

// The overload set contains a template, causing the second function
// parameter to be a nondeduced context.
template <class T> int f(T, T (*p)(T));
char g(char);
template <class T> T g(T);
int i = f(1, g);  // calls f(int, int (*)(int))
--end example]

In 14.8.2.5  temp.deduct.type paragraph 14, replace:

If, in the declaration of a function template with a non-type template-parameter, the non-type template-parameter is used in an expression in the function parameter-list, the corresponding template-argument must always be explicitly specified or deduced elsewhere because type deduction would otherwise always fail for such a template-argument.
With:
If, in the declaration of a function template with a non-type template parameter, the non-type template parameter is used in an expression in the function parameter list, the expression is a nondeduced context.

Replace the example with:

[Example:
template<int i> class A { /* ... */ };
template<int i> void g(A<i+1>);
template<int i> void f(A<i>, A<i+1>);
void k() {
  A<1> a1;
  A<2> a2;
  g(a1);  //error: deduction fails for expression i+1
  g<0>(a1); //OK
  f(a1, a2);  // OK
}
--end example]

In 14.8.2.5  temp.deduct.type paragraph 16, replace:

A template-argument can be deduced from a pointer to function or pointer to member function argument if the set of overloaded functions does not contain function templates and at most one of a set of overloaded functions provides a unique match.

With:

A template-argument can be deduced from a function, pointer to function, or pointer to member function type.



522. Array-to-pointer decay in template argument deduction

Section: 14.8.2.1  temp.deduct.call     Status: WP     Submitter: Daveed Vandevoorde     Date: 3 June 2005

[Voted into WP at the October, 2006 meeting.]

Consider the following example:

    char* cmdline3_[1] = {};

    template<class charT>
    void func(const charT* const argv[]) {}

    int main()
    {
        func(cmdline3_);
    }

In terms of the process described in 14.8.2.1  temp.deduct.call, P is const charT* const * and A is char*[1]. According to the first bullet in paragraph 2, the type used in deduction is not A but “the pointer type produced by the array-to-pointer standard conversion.”

According to paragraph 4,

In general, the deduction process attempts to find template argument values that will make the deduced A identical to A (after the type A is transformed as described above). However, there are three cases that allow a difference:

In this example, the deduced A is not identical to the transformed A, because the deduced A has additional cv-qualification, so the three exceptions must be examined to see if they apply. The only one that might apply is the second bullet of paragraph 4:

However, A is not a pointer type but an array type; this provision does not apply and deduction fails.

It has been argued that the phrase “after the type A is transformed as described above” should be understood to apply to the A in the three bullets of paragraph 4. If that is the intent, the wording should be changed to make that explicit.

Proposed resolution (October, 2005):

Add the indicated words to 14.8.2.1  temp.deduct.call paragraph 4:

In general, the deduction process attempts to find template argument values that will make the deduced A identical to A (after the type A is transformed as described above). However, there are three cases that allow a difference:




322. Deduction of reference conversions

Section: 14.8.2.3  temp.deduct.conv     Status: WP     Submitter: Jason Merrill     Date: 14 Nov 2001

[Voted into WP at April 2003 meeting.]

Consider:

  struct S {
    template <class T> operator T& ();
  };

  int main ()
  {
    S s;
    int i = static_cast<int&> (s);
  }
14.8.2.3  temp.deduct.conv says that we strip the reference from int&, but doesn't say anything about T&. As a result, P (T&) and A (int) have incompatible forms and deduction fails.

Proposed Resolution (4/02):

Change the last chunk of 14.8.2.3  temp.deduct.conv paragraph 2 from

If A is a cv-qualified type, the top level cv-qualifiers of A's type are ignored for type deduction. If A is a reference type, the type referred to by A is used for type deduction.
to
If A is a cv-qualified type, the top level cv-qualifiers of A's type are ignored for type deduction. If A is a reference type, the type referred to by A is used for type deduction. If P is a reference type, the type referred to by P is used for type deduction.




349. Template argument deduction for conversion functions and qualification conversions

Section: 14.8.2.3  temp.deduct.conv     Status: WP     Submitter: John Spicer     Date: 16 April 2002

[Voted into WP at October 2003 meeting.]

We ran into an issue concerning qualification conversions when doing template argument deduction for conversion functions.

The question is: What is the type of T in the conversion functions called by this example? Is T "int" or "const int"?

If T is "int", the conversion function in class A works and the one in class B fails (because the return expression cannot be converted to the return type of the function). If T is "const int", A fails and B works.

Because the qualification conversion is performed on the result of the conversion function, I see no benefit in deducing T as const int.

In addition, I think the code in class A is more likely to occur than the code in class B. If the author of the class was planning on returning a pointer to a const entity, I would expect the function to have been written with a const in the return type.

Consequently, I believe the correct result should be that T is int.

struct A {
	template <class T> operator T***() {
		int*** p = 0;
		return p;
	}
};

struct B {
	template <class T> operator T***() {
		const int*** p = 0;
		return p;
	}
};

int main()
{
	A a;
	const int * const * const * p1 = a;
	B b;
	const int * const * const * p2 = b;
}

We have just implemented this feature, and pending clarification by the committee, we deduce T as int. It appears that g++ and the Sun compiler deduce T as const int.

One way or the other, I think the standard should be clarified to specify how cases like this should be handled.

Notes from October 2002 meeting:

There was consensus on having the deduced type be "int" in the above.

Proposed resolution (April 2003):

Add to the end of 14.8.2.3  temp.deduct.conv (as a new paragraph following paragraph 3):

When the deduction process requires a qualification conversion for a pointer or pointer to member type as described above, the following process is used to determine the deduced template argument values:

If A is a type cv1,0 pointer to ... cv 1,n-1 pointer to cv1,n T1

and P is a type cv2,0 pointer to ... cv2,n-1 pointer to cv2,n T2

The cv-unqualified T1 and T2 are used as the types of A and P respectively for type deduction.

[Example:

struct A {
	template <class T> operator T***();
};

A a;
const int * const * const * p1 = a;  // T is deduced as int, not const int
-- end example]




70. Is an array bound a nondeduced context?

Section: 14.8.2.5  temp.deduct.type     Status: WP     Submitter: Jack Rouse     Date: 29 Sep 1998

[Moved to DR at 4/01 meeting.]

Paragraph 4 lists contexts in which template formals are not deduced. Were template formals in an expression in the array bound of an array type specification intentionally left out of this list? Or was the intent that such formals always be explicitly specified? Otherwise I believe the following should be valid:

    template <int I> class IntArr {};

    template <int I, int J>
    void concat( int (&d)[I+J], const IntArr<I>& a, const IntArr<J>& b ) {}

    int testing()
    {
        IntArr<2> a;
        IntArr<3> b;
        int d[5];

        concat( d, a, b );
    }
Can anybody shed some light on this?

From John Spicer:

Expressions involving nontype template parameters are nondeduced contexts, even though they are omitted from the list in 14.8.2.5  temp.deduct.type paragraph 4. See 14.8.2.5  temp.deduct.type paragraphs 12-14:

  1. A template type argument cannot be deduced from the type of a non-type template-argument.

     ...

  1. If, in the declaration of a function template with a non-type template-parameter, the non-type template-parameter is used in an expression in the function parameter-list, the corresponding template-argument must always be explicitly specified or deduced elsewhere because type deduction would otherwise always fail for such a template-argument.

Proposed resolution (04/01): In 14.8.2.5  temp.deduct.type paragraph 4, add a third bullet:




300. References to functions in template argument deduction

Section: 14.8.2.5  temp.deduct.type     Status: WP     Submitter: Andrei Iltchenko     Date: 11 Jul 2001

[Moved to DR at October 2002 meeting.]

Paragraph 9 of 14.8.2.5  temp.deduct.type enumerates the forms that the types P and A need to have in order for template argument deduction to succeed.

For P denoting a pointer to function the paragraph lists the following forms as allowing for template argument deduction:

type(*)(T)
T(*)()
T(*)(T)

On the other hand, no provision has been made to accommodate similar cases for references to functions, which in light of the wording of 14.8.2.5  temp.deduct.type paragraph 11 means that the program below is ill-formed (some of the C++ compilers do not reject it however):

    template<typename Arg, typename Result, typename T>
    Result  foo_r(Result(& rf)(Arg), T x)
    {   return  rf(Arg(x));   }

    template<typename Arg, typename Result, typename T>
    Result  foo_p(Result(* pf)(Arg), T x)
    {   return  pf(Arg(x));   }

    #include <iostream>
    int  show_arg(char c)
    {
       std::cout << c << ' ';
       if(std::cout)  return  0;
       return  -1;
    }

    int  main()
    {
                                                   // The deduction 
       int  (& rf1)(int(&)(char), double) = foo_r; // shall fail here
                                                   // While here
       int  (& rf2)(int(*)(char), double) = foo_p; // it shall succeed
       return  rf2(show_arg, 2);
    }

Proposed resolution (10/01, same as suggested resolution):

In the list of allowable forms for the types P and A in paragraph 9 of 14.8.2.5  temp.deduct.type replace

type(*)(T)
T(*)()
T(*)(T)

by

type(T)
T()
T(T)



526. Confusing aspects in the specification of non-deduced contexts

Section: 14.8.2.5  temp.deduct.type     Status: WP     Submitter: Mike Miller     Date: 25 July 2005

[Voted into WP at the October, 2006 meeting.]

14.8.2.5  temp.deduct.type paragraph 5 reads:

The non-deduced contexts are:

There are two problems with this list:

  1. The last bullet is redundant with the second bullet. This appears to have been the result of applying the resolutions of issues 70 and 352 independently instead of in coordination.

  2. The second bullet appears to be contradicted by the statement in paragraph 8 saying that an argument can be deduced if P and A have the forms type[i] and template-name<i>.

    The intent of the wording in bullet 2 appears to have been that deduction cannot be done if the template parameter is a sub-expression of the template argument or array bound expression and that it can be done if it is the complete expression, but the current wording does not say that very clearly. (Similar wording also appears in 14.6.2.1  temp.dep.type paragraph 3 and 14.8.2.5  temp.deduct.type paragraph 14.)

Proposed resolution (October, 2005):

  1. Change 14.8.2.5  temp.deduct.type paragraph 5 as indicated:

  2. The non-deduced contexts are:

  3. Change 14.8.2.5  temp.deduct.type paragraph 14 as indicated:

  4. If, in the declaration of a function template with a non-type template parameter, the non-type template parameter is used in an expression a subexpression in the function parameter list, the expression is a non-deduced context as specified above...
  5. Change 14.6.2.1  temp.dep.type paragraph 3 as indicated:

  6. A template argument that is equivalent to a template parameter (i.e., has the same constant value or the same type as the template parameter) can be used in place of that template parameter in a reference to the current instantiation. In the case of a non-type template argument, the argument must have been given the value of the template parameter and not an expression involving that contains the template parameter as a subexpression...



415. Template deduction does not cause instantiation

Section: 14.8.3  temp.over     Status: WP     Submitter: John Spicer     Date: 4 May 2003

[Voted into WP at the October, 2006 meeting.]

Mike Miller: In fact, now that I've looked more closely, that appears not to be the case. (At least, it's not the error I get when I compile his example.) Here's a minimal extract (without the inflammatory using-directive :-) that illustrates what I think is going on:

  template <typename _Iterator>
  struct iterator_traits {
    typedef typename _Iterator::difference_type difference_type;
  };

  template <typename _InputIterator>
  inline typename iterator_traits<_InputIterator>::difference_type
  distance(_InputIterator, _InputIterator);

  double distance(const int&, const int&);

  void f() {
    int i = 0;
    int j = 0;
    double d = distance(i, j);
  }

What happens is that iterator_traits<int> is instantiated as part of type deduction for the function template distance, and the instantiation fails. (Note that it can't be instantiation of distance<int>, as I had originally posited, because in this case only a declaration, not a definition, of that template is in scope.)

John Spicer: Yes, I believe that is what is going on.

Mike Miller: I seem to recall that there was some discussion of questions related to this during the core meetings in Oxford. I think Steve Adamczyk said something to the effect that it's infeasible to suppress all instantiation errors during template type deduction and simply call any such errors a deduction failure. (I could be misremembering, and I could be misapplying that comment to this situation.)

John Spicer: Regardless of other conditions in which this may apply, I don't think it would be reasonable for compilers to have to do "speculative instantiations" during template argument deduction. One class instantiation could kick off a series of other instantiations, etc.

Mike Miller: I don't see anything in the Standard that tells me whether it's legitimate or not to report an error in this case. I hope John or another template expert can enlighten me on that.

John Spicer: My opinion is that, because this case is not among those enumerated that cause deduction failure (rather than being ill-formed) that reporting an error is the right thing to do.

Mike Miller: I am still interested, though, in the question of why 14.8.3  temp.over says that viable function template specializations are instantiated, even if they are not selected by overload resolution.

John Spicer: I believe the wording in 14.8.3  temp.over is incorrect. I researched this and found that a change was made during the clause 14 restructuring that was incorporated in March of 1996. The prior wording was "the deduced template arguments are used to generate a single template function". This was changed to "deduced template arguments are used to instantiate a single function template specialization". I believe this resulted from what was basically a global replace of "generate" with "instantiate" and of "template function" with "function template specialization". In this case, the substitution changed the meaning. This paragraph needs reworking.

Proposed resolution (April, 2006):

Change 14.8.3  temp.over paragraph 1 as indicated:

...For each function template, if the argument deduction and checking succeeds, the template-arguments (deduced and/or explicit) are used to instantiate synthesize the declaration of a single function template specialization which is added to the candidate functions set to be used in overload resolution. If, for a given function template, argument deduction fails, no such function is added to the set of candidate functions for that template. The complete set of candidate functions includes all the function templates instantiated in this way synthesized declarations and all of the non-template overloaded functions of the same name. The function template specializations synthesized declarations are treated like any other functions in the remainder of overload resolution, except as explicitly noted in 13.3.3  over.match.best.



208. Rethrowing exceptions in nested handlers

Section: 15.1  except.throw     Status: WP     Submitter: Bill Wade     Date: 28 Feb 2000

[Moved to DR at 4/01 meeting.]

Paragraph 7 of 15.1  except.throw discusses which exception is thrown by a throw-expression with no operand.

May an expression which has been "finished" (paragraph 7) by an inner catch block be rethrown by an outer catch block?

    catch(...)    // Catch the original exception
    {
      try{ throw; }    // rethrow it at an inner level
                       // (in reality this is probably
                       // inside a function)
      catch (...)
      {
      }   // Here, an exception (the original object)
          // is "finished" according to 15.1p7 wording

      // 15.1p7 says that only an unfinished exception
      // may be rethrown.
      throw;    // Can we throw it again anyway?  It is
                // certainly still alive (15.1p4).
    }

I believe this is ok, since the paragraph says that the exception is finished when the "corresponding" catch clause exits. However since we have two clauses, and only one exception, it would seem that the one exception gets "finished" twice.

Proposed resolution (04/01):

  1. In 15.1  except.throw paragraph 4, change

    When the last handler being executed for the exception exits by any means other than throw; ...
    to
    When the last remaining active handler for the exception exits by any means other than throw; ...

  2. In 15.1  except.throw paragraph 6, change

    A throw-expression with no operand rethrows the exception being handled.
    to
    A throw-expression with no operand rethrows the currently handled exception (15.3  except.handle).

  3. Delete 15.1  except.throw paragraph 7.

  4. Add the following before 15.1  except.throw paragraph 6:

    An exception is considered caught when a handler for that exception becomes active (15.3  except.handle). [Note: an exception can have active handlers and still be considered uncaught if it is rethrown.]

  5. Change 15.3  except.handle paragraph 8 from

    An exception is considered handled upon entry to a handler. [Note: the stack will have been unwound at that point.]
    to

    A handler is considered active when initialization is complete for the formal parameter (if any) of the catch clause. [Note: the stack will have been unwound at that point.] Also, an implicit handler is considered active when std::terminate() or std::unexpected() is entered due to a throw. A handler is no longer considered active when the catch clause exits or when std::unexpected() exits after being entered due to a throw.

    The exception with the most recently activated handler that is still active is called the currently handled exception.

  6. In 15.3  except.handle paragraph 16, change "exception being handled" to "currently handled exception."




428. Mention of expression with reference type

Section: 15.1  except.throw     Status: WP     Submitter: Steve Adamczyk     Date: 14 July 2003

[Voted into WP at March 2004 meeting.]

15.1  except.throw paragraph 3 says that the type of a throw expression shall not be a pointer or reference to an incomplete type. But an expression never has reference type.

Proposed Resolution (October 2003):

Change the penultimate sentence of 15.1  except.throw paragraph 3 as follows:

The type of the throw-expression shall not be an incomplete type, or a pointer or reference to an incomplete type other than (possibly cv-qualified) void, other than void*, const void*, volatile void*, or const volatile void*.



479. Copy elision in exception handling

Section: 15.1  except.throw     Status: WP     Submitter: Mike Miller     Date: 07 Oct 2004

[Voted into WP at April, 2006 meeting.]

I have noticed a couple of confusing and overlapping passages dealing with copy elision. The first is 15.1  except.throw paragraph 5:

If the use of the temporary object can be eliminated without changing the meaning of the program except for the execution of constructors and destructors associated with the use of the temporary object (12.2  class.temporary), then the exception in the handler can be initialized directly with the argument of the throw expression.

The other is 15.3  except.handle paragraph 17:

If the use of a temporary object can be eliminated without changing the meaning of the program except for execution of constructors and destructors associated with the use of the temporary object, then the optional name can be bound directly to the temporary object specified in a throw-expression causing the handler to be executed.

I think these two passages are intended to describe the same optimization. However, as is often the case where something is described twice, there are significant differences. One is just different terminology — is “the exception in the handler” the same as “the object declared in the exception-declaration or, if the exception-declaration does not specify a name, a temporary object of that type” (15.3  except.handle paragraph 16)?

More significant, there is a difference in which kinds of throw-expressions are eligible for the optimization. In 15.1  except.throw paragraph 5, it appears that any object is a candidate, while in 15.3  except.handle paragraph 17 the thrown object must be a temporary (“the temporary object specified in a throw-expression”). For example, it's not clear looking at these two passages whether the copy of a local automatic can be elided. I.e., by analogy with the return value optimization described in 12.8  class.copy paragraph 15:

    X x;
    return x;    // copy may be elided

    X x;
    throw x;     // unclear whether copy may be elided

Which brings up another point: 12.8  class.copy paragraph 15 purports to be an exhaustive list in which copy elision is permitted even if the constructor and/or destructor have side effects; however, these two passages describe another case that is not mentioned in 12.8  class.copy paragraph 15.

A final point of confusion: in the unoptimized abstract machine, there are actually two copies in throwing and handling an exception: the copy from the object being thrown to the exception object, and the copy from the exception object to the object or temporary in the exception-declaration. 15.1  except.throw paragraph 5 speaks only of eliminating the exception object, copying the thrown object directly into the exception-declaration object, while 15.3  except.handle paragraph 17 refers to directly binding the exception-declaration object to the thrown object (if it's a temporary). Shouldn't these be separated, with a throw of an automatic object or temporary being like the return value optimization and the initialization of the object/temporary in the exception-declaration being a separate optimizable step (which could, presumably, be combined to effectively alias the exception-declaration onto the thrown object)?

(See paper J16/04-0165 = WG21 N1725.)

Proposed resolution (April, 2005):

  1. Add two items to the bulleted list in 12.8  class.copy paragraph 15 as follows:

    This elision of copy operations is permitted in the following circumstances (which may be combined to eliminate multiple copies):

    • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object with the same cv-unqualified type as the function return type, the copy operation can be omitted by constructing the automatic object directly into the function’s return value

    • in a throw-expression, when the operand is the name of a non-volatile automatic object, the copy operation from the operand to the exception object (15.1  except.throw) can be omitted by constructing the automatic object directly into the exception object

    • when a temporary class object that has not been bound to a reference (12.2  class.temporary) would be copied to a class object with the same cv-unqualified type, the copy operation can be omitted by constructing the temporary object directly into the target of the omitted copy

    • when the exception-declaration of an exception handler (clause 15  except) declares an object of the same type (except for cv-qualification) as the exception object (15.1  except.throw), the copy operation can be omitted by treating the exception-declaration as an alias for the exception object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by the exception-declaration

  2. Change 15.1  except.throw paragraph 5 as follows:

    If the use of the temporary object can be eliminated without changing the meaning of the program except for the execution of constructors and destructors associated with the use of the temporary object (12.2  class.temporary), then the exception in the handler can be initialized directly with the argument of the throw expression. When the thrown object is a class object, and the copy constructor used to initialize the temporary copy is not and the destructor shall be accessible, the program is ill-formed (even when the temporary object could otherwise be eliminated) even if the copy operation is elided (12.8  class.copy). Similarly, if the destructor for that object is not accessible, the program is ill-formed (even when the temporary object could otherwise be eliminated).
  3. Change 15.3  except.handle paragraph 17 as follows:

    If the use of a temporary object can be eliminated without changing the meaning of the program except for execution of constructors and destructors associated with the use of the temporary object, then the optional name can be bound directly to the temporary object specified in a throw-expression causing the handler to be executed. The copy constructor and destructor associated with the object shall be accessible even when the temporary object is eliminated if the copy operation is elided (12.8  class.copy).



87. Exception specifications on function parameters

Section: 15.4  except.spec     Status: WP     Submitter: Steve Adamczyk     Date: 25 Jan 1999

[Moved to DR at 4/01 meeting.]

In 15.4  except.spec paragraph 2:

An exception-specification shall appear only on a function declarator in a function, pointer, reference or pointer to member declaration or definition.
Does that mean in the top-level function declarator, or one at any level? Can one, for example, specify an exception specification on a pointer-to-function parameter of a function?
    void f(int (*pf)(float) throw(A))
Suggested answer: no. The exception specifications are valid only on the top-level function declarators.

However, if exception specifications are made part of a function's type as has been tentatively agreed, they would have to be allowed on any function declaration.

There is already an example of an exception specification for a parameter in the example in 15.4  except.spec paragraph 1.

Proposed resolution (04/01): Change text in 15.4  except.spec paragraph 1 from:

An exception-specification shall appear only on a function declarator in a function, pointer, reference or pointer to member declaration or definition.
to:
An exception-specification shall appear only on a function declarator for a function type, pointer to function type, reference to function type, or pointer to member function type that is the top-level type of a declaration or definition, or on such a type appearing as a parameter or return type in a function declarator.

(See also issues 25, 92, and 133.)




394. identifier-list is never defined

Section: 16  cpp     Status: WP     Submitter: Nicola Musatti     Date: 16 Dec 2002

[Voted into WP at October 2004 meeting.]

In clause 16  cpp, paragraph 1, the control-line non-terminal symbol is defined in terms of the identifier-list non-terminal, which is never defined within the standard document.

The same definition is repeated in clause A.14  gram.cpp.

I suggest that the following definition is added to clause 16  cpp, paragraph 1, after the one for replacement-list:

This should be repeated again in clause A.14  gram.cpp, again after the one for replacement-list. It might also be desirable to include a third repetition in clause 16.3  cpp.replace, paragraph 9.

Proposed Resolution (Clark Nelson, Dec 2003):

In clause 16  cpp, paragraph 1, immediately before the definition of replacement-list, add:

If the correct TROFF macros are used, the definition will appear automatically in appendix A. It doesn't need to be repeated in 16.3p9.

With respect to the question of having the preprocessor description be synchronized with C99, this would fall into the category of a justified difference. (Other justified differences include those for Boolean expressions, alternative tokens, and terminology differences.)




370. Can #include <...> form be used other than for standard C++ headers?

Section: 16.2  cpp.include     Status: WP     Submitter: Beman Dawes     Date: 01 August 2002

[Voted into WP at the October, 2006 meeting.]

The motivation for this issue is a desire to write portable programs which will work with any conforming implementation.

The C++ Standard (16.2  cpp.include) provides two forms of #include directives, with the <...> form being described (16.2  cpp.include paragraph 2) as "for a header", and the "..." form (16.2  cpp.include paragraph 3) as for "the source file" identified between the delimiters. When the standard uses the term "header", it often appears to be limiting the term to apply to the Standard Library headers only. Users of the standard almost always use the term "header" more broadly, to cover all #included source files, but particularly those containing interface declarations.

Headers, including source files, can be categorized according to their origin and usage:

  1. C++ Standard Library headers (which aren't necessarily files).
  2. Other standard libraries such as the POSIX headers.
  3. Operating system API's such as windows.h.
  4. Third party libraries, such as Boost, ACE, or commercial offerings.
  5. Organization-wide "standard" header files, such as a company's config.hpp.
  6. A project's "public" header files, often shared by all developers working on the same project.
  7. A project or user's "private", "local", or "detail" headers, in the same directory or sub-directory as the compilation unit.

Existing practice varies widely, but it is fairly easy to find users advocating:

Do any of the practices A, B, or C result in programs which can be rejected by a conforming implementation?

The first defect is that readers of the standard have not been able to reach consensus on the answers to the above question.

A second possible defect is that if A, B, or C can be rejected by a conforming implementation, then the standard should be changed because would mean there is a wide variance between the standard and existing practice.

Matt Austern: I really only see two positions:

  1. Implementations have some unspecified mechanism (copying files to a magic directory, adding a magic flag,...) such that the line #include <foo> results in textual inclusion of the file foo.
  2. Implementations are not required to have any mechanism for getting #include <foo> to perform textual inclusion of an arbitrary file foo. That form is reserved for standard library headers only.

I agree that the standard should clarify which of those two is the case (I imagine it'll hinge on finding one crucual sentence that either says "implementation defined" or "unspecified"), but from the standpoint of portability I don't see much difference between the two. I claim that, with either of those two interpretations, using #include <foo> is already nonportable.

(Of course, I claim that almost anything having to do with headers, including the #include "foo" form, is also nonportable. In practice there's wide variation in how compilers handle paths, especially relative paths.)

Beman Dawes: The whole issue can be resolved by replacing "header" with "header or source file" in 16.2  cpp.include paragraph 2. That will bring the standard into alignment with existing practice by both users and implementations. The "header and/or source file" wording is used at least three other places in the standard where otherwise there might be some confusion.

John Skaller: In light of Andrew Koenig's comments, this doesn't appear to be the case, since the mapping of include names to file names is implementation defined, and therefore source file inclusion cannot be made portable within the ISO C/C++ standards (since that provision obviously cannot be removed).

A possible idea is to create a binding standard, outside the C/C++ ISO Standards, which specifies not only the path lookup mechanism but also the translation from include names to file names. Clearly that is OS dependent, encoding dependent, etc, but there is no reason not to have a binding standard for Unix, Windows, etc, and specify these bindings in such a way that copying directories from one OS to the other can result in programs working on both OS's.

Andy Koenig: An easier solution might be to specify a (presumably unbounded, or bounded only by implementation capacity) collection of header-file names that every implementation must make it possible for programs to access somehow, without specifying exactly how.

Notes from October 2002 meeting:

This was discussed at some length. While there was widespread agreement that such inclusion is inherently implementation-dependent, we agreed to try to add wording that would make it clear that implementations are permitted (but not required) to allow inclusion of files using the <...> form of #include.

Proposed resolution (April, 2005):

Change 16.2  cpp.include paragraph 7 from:

[Example: The most common uses of #include preprocessing directives are as in the following:
    #include <stdio.h>
    #include "myprog.h"
end example]

to:

[Note: Although an implementation may provide a mechanism for making arbitrary source files available to the < > search, in general programmers should use the < > form for headers provided with the implementation, and the " " form for sources outside the control of the implementation. For instance:
    #include <stdio.h>
    #include <unistd.h>
    #include "usefullib.h"
    #include "myprog.h"
end note]

Notes from October, 2005 meeting:

Some doubt was expressed as to whether the benefit of this non-normative clarification outweighs the overall goal of synchronizing clause 16 with the corresponding text in the C99 Standard. As a result, this issue is being left in “review” status to allow further discussion.

Additional notes (October, 2006):

WG14 takes no position on this change.




106. Creating references to references during template deduction/instantiation

Section: unknown  unknown     Status: WP     Submitter: Bjarne Stroustrup     Date: unknown

[Moved to DR at 10/01 meeting.]

The main defect is in the library, where the binder template can easily lead to reference-to-reference situations.

Proposed resolution (04/01):

  1. Add the following as paragraph 6 of 7.1.3  dcl.typedef:

    If a typedef TD names a type "reference to cv1 S," an attempt to create the type "reference to cv2 TD" creates the type "reference to cv12" S," where cv12 is the union of the cv-qualifiers cv1 and cv2. Redundant qualifiers are ignored. [Example:

        int i;
        typedef int& RI;
        RI& r = i;          // r has the type int&
        const RI& r = i;    // r has the type const int&
    
    end example]
  2. Add the following as paragraph 4 of 14.3.1  temp.arg.type:

    If a template-argument for a template-parameter T names a type "reference to cv1 S," an attempt to create the type "reference to cv2 T" creates the type "reference to cv12 S," where cv12 is the union of the cv-qualifiers cv1 and cv2. Redundant cv-qualifiers are ignored. [Example:

        template <class T> class X {
            f(const T&);
            /* ... */
        };
        X<int&> x;    // X<int&>::f has the parameter type const int&
    
    end example]
  3. In 14.8.2  temp.deduct paragraph 2 bullet 3 sub-bullet 5, remove the indicated text:
    Attempting to create a reference to a reference type or a reference to void.

(See also paper J16/00-0022 = WG21 N1245.)






Issues with "TC1" Status


173. Constraints on execution character set

Section: 2.2  lex.charset     Status: TC1     Submitter: Markus Mauhart     Date: 27 Sep 1999

22.2.1.1.2  lib.locale.ctype.virtuals paragraph 13 states a constraint on the values of the characters representing the decimal digits in the execution character set:

for any digit character c, the expression (do_narrow( c, dfault)-'0') evaluates to the digit value of the character.
This requirement is not reflected in the description of the execution character set (2.2  lex.charset paragraph 3).

Proposed resolution (10/00):

In 2.2  lex.charset paragraph 3, after the sentence

For each basic execution character set, the values of the members shall be non-negative and distinct from one another.
insert the following:
In both the source and execution basic character sets, the value of each character after 0 in the above list of decimal digits shall be one greater than the value of the previous.



41. Clarification of lookup of names after declarator-id

Section: 3.4.1  basic.lookup.unqual     Status: TC1     Submitter: Mike Miller     Date: 1 Sep 1998

Footnotes 26 and 29 both use the phrase "following the function declarator" incorrectly: the function declarator includes the parameter list, but the footnotes make clear that they intend what's said to apply to names inside the parameter list. Presumably the phrase should be "following the function declarator-id."

Proposed Resolution (04/99): Change the text in 3.4.1  basic.lookup.unqual paragraph 6 from:

A name used in the definition of a function [footnote: This refers to unqualified names following the function declarator; such a name may be used as a type or as a default argument name in the parameter-declaration-clause, or may be used in the function body. end footnote] that is ...
to:
A name used in the definition of a function following the function's declarator-id [footnote: This refers to unqualified names that occur, for instance, in a type or default argument expression in the parameter-declaration-clause or used in the function body. end footnote] that is ...
Change the text in 3.4.1  basic.lookup.unqual paragraph 8 from:
A name used in the definition of a function that is a member function (9.3  class.mfct ) [footnote: That is, an unqualified name following the function declarator; such a name may be used as a type or as a default argument name in the parameter-declaration-clause, or may be used in the function body, or, if the function is a constructor, may be used in the expression of a mem-initializer. end footnote] of class X shall be ...
to:
A name used in the definition of a member function (9.3  class.mfct ) of class X following the function's declarator-id [footnote: That is, an unqualified name that occurs, for instance, in a type or default argument expression in the parameter-declaration-clause, in the function body, or in an expression of a mem-initializer in a constructor definition. end footnote] shall be ...



33. Argument dependent lookup and overloaded functions

Section: 3.4.2  basic.lookup.argdep     Status: TC1     Submitter: Jason Merrill     Date: 15 Jul 1998

If an argument used for lookup is the address of a group of overloaded functions, are there any associated namespaces or classes? What if it's the address of a function template?

My inclination is to say no to both.

From Mike Miller:

We discussed this on the reflector a few weeks ago. I'll leave the template case for the Core III experts, but I'd find it surprising if the overload case weren't handled as the obvious generalization of the single-function case. For a single function, the associated namespaces are those of the types used in the parameters and return type; I would expect that using an overloaded function name would simply be the union of the namespaces from the members of the overload set. That would be the simplest and most intuitive, IMHO — is there an argument for doing it differently?

Proposed Resolution (04/99): In 3.4.2  basic.lookup.argdep paragraph 2, add following the last bullet in the list of associated classes and namespaces for various argument types (not a bullet itself because overload sets and templates do not have a type):

In addition, if the argument is the name or address of a set of overloaded functions and/or function templates, its associated classes and namespaces are the union of those associated with each of the members of the set: the namespace in which the function or function template is defined and the classes and namespaces associated with its (non-dependent) parameter types and return type.



90. Should the enclosing class be an "associated class" too?

Section: 3.4.2  basic.lookup.argdep     Status: TC1     Submitter: John Spicer     Date: 2 Feb 1999

Section 3.4.2  basic.lookup.argdep includes the following:

Note that for a union, the enclosing class is an "associated class", but for a class type the enclosing class is not an "associated class". This results in some surprising behavior, as shown in the example below.
    struct A {
        union U {};
        friend void f(U);
    };
            
    struct B {
        struct S {};
        friend void f(S);
    };
             
    int main() { 
        A::U    u; 
        f(u);        // okay: A is an associated class
        B::S    s;
        f(s);        // error: no matching f(), B is not an associated class
    }

Certainly the enclosing class should also be an associated class for nested class types, shouldn't it?

Proposed Resolution (10/99): Change the two referenced bullets to read:

(This proposal also addresses Core issue 91.)


164. Overlap between Koenig and normal lookup

Section: 3.4.2  basic.lookup.argdep     Status: TC1     Submitter: Derek Inglis     Date: 3 Sep 1999

The description of Koenig lookup in 3.4.2  basic.lookup.argdep paragraph 1 says,

...other namespaces not considered during the usual unqualified lookup (3.4.1  basic.lookup.unqual ) may be searched.
Does this mean that Koenig lookup does not search namespaces that were already searched during the usual unqualified lookup? The answer is academic except for the two-stage lookup during template instantiation. If a given namespace is searched in the context of the template definition, are declarations in that namespace in the instantiation context ignored during the Koenig lookup? For instance,
    void f(int);

    template <class T> void g(T t) {
        f(t);
    }

    enum E { e };

    void f(E);

    void h() {
        g(e);
    }
In this example, the call f(t) in the template function will resolve to f(E) if Koenig lookup reexamines already-searched namespaces and to f(int) if not.

Proposed Resolution (10/00):

Immediately preceding the example at the end of 3.4.2  basic.lookup.argdep paragraph 2, add the following:

[Note: the namespaces and classes associated with the argument types can include namespaces and classes already considered by the ordinary unqualified lookup.]



85. Redeclaration of member class

Section: 3.4.4  basic.lookup.elab     Status: TC1     Submitter: Steve Adamczyk     Date: 25 Jan 1999

In 3.4.4  basic.lookup.elab paragraph 3, there is the example

    struct Base {
        // ...
        struct Data { /* ... */ };  // Defines nested Data
        struct Data;                // OK: Redeclares nested Data
    };
The final redeclaration is invalid according to 9.2  class.mem paragraph 1 last sentence.

Proposed resolution (10/00): Remove the line

        struct Data;                // OK: Redeclares nested Data

See also Core issue 36 and Core issue 56.




89. Object lifetime does not account for reference rebinding

Section: 3.8  basic.life     Status: TC1     Submitter: AFNOR     Date: 27 Oct 1998

From J16/98-0026 = WG21 N1169, "Proposed Defect Reports on ISO/IEC 14882, Programming Languages - C++":
A reference is rebindable. This is surprising and unnatural. This can also cause subtle optimizer bugs.

Example:

    struct T {
        int& ri;
        T (int& r) : ri (r) { }
    };
    
    void bar (T*);
    
    void foo () {
        int i;
        T x (i);
        x.ri = 3;   // the optimizer understands that this is really i = 3
        bar (&x);
        x.ri = 4;   // optimizer assumes that this writes to i, but this is incorrect
    }
    
    int gi;
    
    void bar (T* p) {
        p->~T ();
        new (p) T (gi);
    }
If we replace T& with T* const in the example then undefined behavior result and the optimizer is correct.

Proposal: make T& equivalent to T* const by extending the scope of 3.8  basic.life paragraph 9 to references.

(See also J16/99-0005 = WG21 N1182, "Proposed Resolutions for Core Language Issues 6, 14, 20, 40, and 89")

In addition, Lisa Lippincott pointed out the following example:

    void f( const bool * );
    void g();

    int main() {
       const bool *b = new const bool( false );
       f(b);
       if (*b)
          g();
    }

    void f( const bool *b ) {
       new ( const_cast<bool *>(b) ) const bool( true );
    }

The proposed wording in the paper would still permit this usage and thus prevent an optimizer from eliminating the call to g().

Proposed Resolution (10/00):

Add a new bullet to the list of restrictions in 3.8  basic.life paragraph 7, following the second bullet ("the new object is of the same type..."):




93. Missing word in 3.8 basic.life paragraph 2

Section: 3.8  basic.life     Status: TC1     Submitter: Mike Miller     Date: 6 Feb 1999

The text of 3.8  basic.life paragraph 2 currently reads,

The phrase "an object of type" is obviously incorrect. I believe it should read "an object of POD type." Does anyone disagree?

Proposed Resolution (10/99): As suggested.




43. Copying base classes (PODs) using memcpy

Section: 3.9  basic.types     Status: TC1     Submitter: Nathan Myers     Date: 15 Sep 1998

Can you use memcpy on non-member POD subobjects of non-POD objects?

In 3.9  basic.types paragraphs 2 and 3 we have:

For any complete POD object type T, whether or not the object holds a valid value of type T, the underlying bytes (1.7  intro.memory ) making up the object can be copied into an array of char or unsigned char*. If the content of the array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value. [Example elided]
*[Footnote: By using, for example, the library functions (17.4.1.2  lib.headers ) memcpy or memmove. end footnote]
For any POD type T, if two pointers to T point to distinct T objects obj1 and obj2, if the value of obj1 is copied into obj2, using the memcpy library function, obj2 shall subsequently hold the same value as obj1.
Paragraph 3 doesn't repeat the restriction of paragraph 2. Should it be assumed? Otherwise only complete POD types are copyable to an array of char and back, but scribbling over subobjects is OK. (Or perhaps a "distinct T object" is a complete object...)

Proposed Resolution (04/99): Change the text in 3.9  basic.types paragraph 2 from:

For any complete POD object type T, ...
to:
For any object (other than a base class subobject) of POD type T, ...
Change the text in 3.9  basic.types paragraph 3 from:
For any POD type T, if two pointers to T point to distinct T objects obj1 and obj2,
to:
For any POD type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base class subobject, ...



149. Accessibility and ambiguity

Section: 4.10  conv.ptr     Status: TC1     Submitter: Nathan Sidwell     Date: 31 Jul 1999

The Standard uses confusing terminology when referring to accessibility in connection with ambiguity. For instance:

4.10  conv.ptr paragraph 3:

If B is an inaccessible or ambiguous base ...
5.2.7  expr.dynamic.cast paragraph 8:
... has an unambiguous public base ...
10.3  class.virtual paragraph 5:
... is an unambiguous direct or indirect base ... and is accessible ...
15.3  except.handle paragraph 3:
not involving conversions to pointers to private or protected or ambiguous classes

The phrase "unambiguous public base" is unfortunate as it could mean either "an unambiguous base not considering accessibility, which is public" or "an unambiguous base considering only the publicly accessible bases." I believe the former interpretation correct, as accessibility is applied after visibility (11  class.access paragraph 4) and ambiguity is described in terms of visibility (10.2  class.member.lookup paragraph 2).

Suggested Resolution: Use the phrases "public and unambiguous," "accessible and unambiguous," "non-public or ambiguous," or "inaccessible or ambiguous" as appropriate.

Proposed resolution (10/00):




123. Bad cross-reference

Section: 5.1  expr.prim     Status: TC1     Submitter: Mike Miller     Date: 3 June 1999
The cross-reference is incorrect in the first sentence after the grammar in 5.1  expr.prim paragraph 7:
A nested-name-specifier that names a class, optionally followed by the keyword template (14.8.1  temp.arg.explicit ), ...
The use of the template keyword in this context is discussed in 14.2  temp.names , not 14.8.1  temp.arg.explicit .


147. Naming the constructor

Section: 5.1  expr.prim     Status: TC1     Submitter: John Spicer     Date: 21 Feb 1999

From paper J16/99-0010 = WG21 N1187.

5.1  expr.prim paragraph 7 says that class-name::class-name names the constructor when both class-name refer to the same class. (Note the different perspective, at least, in 12.1  class.ctor paragraph 1, in which constructors have no names and are recognized by syntactic context rather than by name.)

This formulation does not address the case of classes in which a function template is declared as a constructor, for example:

    template <class T> struct A {
        template <class T2> A(T2);
    };
    template<> template<> A<int>::A<int>(int);

Here there is an ambiguity as to whether the second template argument list is for the injected class name or for the constructor.

Suggested resolution: restate the rule as a component of name lookup. Specifically, if when doing a qualified lookup in a given class you look up a name that is the same as the name of the class, the entity found is the constructor and not the injected class name. In all other cases, the name found is the injected class name. For example:

    class B { };
    class A: public B {
        A::B ab;       // B is the inherited injected B
        A::A aa;       // Error: A::A is the constructor
    };

Without this rule some very nasty backtracking is needed. For example, if the injected class name could be qualified by its own class name, the following code would be well-formed:

    template <class T> struct A {
        template <class T2> A(T2);
        static A x;
    };
    template<> A<int>::A<int>(A<int>::x);

Here the declarator for the definition of the static data member has redundant parentheses, and it's only after seeing the declarator that the parser can know that the second A<int> is the injected class name rather than the constructor.

Proposed resolution (10/00):

In 9  class paragraph 2, change

The class-name is also inserted into the scope of the class itself. For purposes of access checking the inserted class name...

to

The class-name is also inserted into the scope of the class itself; this is known as the injected-class-name. For purposes of access checking, the injected-class-name...

Also, in 3.4.3.1  class.qual, add the following before paragraph 2:

If the nested-name-specifier nominates a class C, and the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C (clause 9  class), the name is instead considered to name the constructor of class C. Such a constructor name shall only be used in the declarator-id of a constructor definition that appears outside of the class definition. [Example:
    struct A { A(); };
    struct B: public A { B(); };

    A::A() { }
    B::B() { }

    B::A ba;    // object of type A
    A::A a;     // error, A::A is not a type name
end example]

Also, change 3.4  basic.lookup paragraph 3 from

Because the name of a class is inserted in its class scope (clause 9  class), the name of a class is also considered a member of that class for the purposes of name hiding and lookup.

to

The injected-class-name of a class (clause 9  class) is also considered to be a member of that class for the purposes of name hiding and lookup.

(See also issue 194.)




52. Non-static members, member selection and access checking

Section: 5.2.5  expr.ref     Status: TC1     Submitter: Steve Adamczyk     Date: 13 Oct 1998

5.2.5  expr.ref paragraph 4 should make it clear that when a nonstatic member is referenced in a member selection operation, the type of the left operand is implicitly cast to the naming class of the member. This allows for the detection of access and ambiguity errors on that implicit cast.

Proposed Resolution (10/00):

  1. In 11.2  class.access.base paragraph 4, remove the following from the second note:

    If the member m is accessible when named in the naming class according to the rules below, the access to m is nonetheless ill-formed if the type of p cannot be implicitly converted to type T (for example, if T is an inaccessible base class of p's class).
  2. Add the following as a new paragraph 5 of 11.2  class.access.base:

    If a class member access operator, including an implicit "this->," is used to access a nonstatic data member or nonstatic member function, the reference is ill-formed if the left operand (considered as a pointer in the "." operator case) cannot be implicitly converted to a pointer to the naming class of the right operand. [Note: this requirement is in addition to the requirement that the member be accessible as named.]
  3. In 11.2  class.access.base paragraph 4, fix a typographical error by adding the missing right parenthesis following the text

    (including cases where an implicit "this->" is added
  4. Add following the first sentence of 5.2.2  expr.call paragraph 4:

    If the function is a nonstatic member function, the "this" parameter of the function (9.3.2  class.this) shall be initialized with a pointer to the object of the call, converted as if by an explicit type conversion (5.4  expr.cast). [Note: there is no access checking on this conversion; the access checking is done as part of the (possibly implicit) class member access operator. See 11.2  class.access.base.]



53. Lvalue-to-rvalue conversion before certain static_casts

Section: 5.2.9  expr.static.cast     Status: TC1     Submitter: Steve Adamczyk     Date: 13 Oct 1998

Section 5.2.9  expr.static.cast paragraph 6 should make it clear that when any of the "inverse of any standard conversion sequence" static_casts are done, the operand undergoes the lvalue-to-rvalue conversions first.

Proposed Resolution (10/00):

In 5.2.9  expr.static.cast paragraph 6, change

can be performed explicitly using static_cast subject to the restriction that the explicit conversion does not cast away constness (5.2.11  expr.const.cast), ...

to

can be performed explicitly using static_cast. The lvalue-to-rvalue (4.1  conv.lval), array-to-pointer (4.2  conv.array), and function-to-pointer (4.3  conv.func) conversions are applied to the operand. Such a static_cast is subject to the restriction that it does not cast away constness (5.2.11  expr.const.cast), ...



128. Casting between enum types

Section: 5.2.9  expr.static.cast     Status: TC1     Submitter: Clark Nelson     Date: 10 June 1999

According to 7.2  dcl.enum paragraph 9, it is permitted to convert from one enumeration type to another. However, neither 5.2.9  expr.static.cast nor 5.4  expr.cast allows this conversion.

Proposed resolution (10/00): Change the first two sentences of 5.2.9  expr.static.cast paragraph 7 to read

A value of integral or enumeration type can be explicitly converted to an enumeration type. The value is unchanged if the original value is within the range of the enumeration values (7.2  dcl.enum ).



137. static_cast of cv void*

Section: 5.2.9  expr.static.cast     Status: TC1     Submitter: Mike Miller     Date: 13 July 1999

According to 5.2.9  expr.static.cast paragraph 10,

An rvalue of type "pointer to cv void" can be explicitly converted to a pointer to object type.
No requirements are stated regarding the cv-qualification of the pointer to object type. Contrast this with the formula used in paragraphs 5, 8, and 9, where the treatment of cv-qualification is explicit, requiring that the target type be at least as cv-qualified as the source. There is an apparently general requirement on all forms of static_cast in 5.2.9  expr.static.cast paragraph 1 that it "shall not cast away constness." Assuming that this restriction applies to paragraph 10, since there is no explicit exception to the general rule, that still leaves open the question of whether one can "cast away volatility" in a conversion from volatile void* to a pointer to object type. Should 5.2.9  expr.static.cast paragraph 10 be rewritten to handle cv-qualification in the same way as paragraphs 5, 8, and 9?

Proposed resolution (10/00):

Change the first sentence of 5.2.9  expr.static.cast paragraph 10 to

An rvalue of type "pointer to cv1 void" can be converted to an rvalue of type "pointer to cv2 T", where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1.



74. Enumeration value in direct-new-declarator

Section: 5.3.4  expr.new     Status: TC1     Submitter: Jason Merrill     Date: 16 Nov 1998

5.3.4  expr.new paragraph 6 says:

The expression in a direct-new-declarator shall have integral type (3.9.1  basic.fundamental ) with a non-negative value.
I assume the intent was to also allow enumeral types, as we do in 5.2.1  expr.sub ?

Proposed Resolution (10/99): Replace "integral type" by "integral or enumeration type" in 5.3.4  expr.new paragraph 6.




127. Ambiguity in description of matching deallocation function

Section: 5.3.4  expr.new     Status: TC1     Submitter: Alexander Schiemann     Date: 8 June 1999

If a placement allocation function has default arguments for all its parameters except the first, it can be called using non-placement syntax. In such a case, it is not clear whether the deallocation function to be called if the constructor terminates by throwing an expression is determined on the basis of the syntax of the new-expression (i.e., a non-placement deallocation function) or the declaration of the selected (placement) allocation function. 5.3.4  expr.new paragraph 19 indicates that the deallocation function must match the declaration of the allocation function. However, 15.2  except.ctor says that the distinction is based on whether the new-expression contains a new-placement or not.

Proposed resolution (10/00):

In 15.2  except.ctor paragraph 2, replace

If the object or array was allocated in a new-expression and the new-expression does not contain a new-placement, the deallocation function (3.7.3.2  basic.stc.dynamic.deallocation, 12.5  class.free) is called to free the storage occupied by the object; the deallocation function is chosen as specified in 5.3.4  expr.new. If the object or array was allocated in a new-expression and the new-expression contains a new-placement, the storage occupied by the object is deallocated only if an appropriate placement operator delete is found, as specified in 5.3.4  expr.new.

with

If the object or array was allocated in a new-expression, the matching deallocation function (3.7.3.2  basic.stc.dynamic.deallocation, 5.3.4  expr.new, 12.5  class.free), if any, is called to free the storage occupied by the object.

See also issue 429.




179. Function pointers and subtraction

Section: 5.7  expr.add     Status: TC1     Submitter: Mike Miller     Date: Nov 1999

5.7  expr.add paragraph 8 explicitly allows subtraction of two pointers to functions:

If two pointers point to the same object or function... and the two pointers are subtracted...
However, 5.7  expr.add paragraph 2 requires that two pointers that are subtracted be pointers to an object type; function pointers are not allowed.

Being able to subtract two pointers to functions doesn't seem terribly useful, especially considering that subtracting two pointers to different functions appears to produce undefined behavior rather than simply a non-zero result, according to paragraph 6:

Unless both pointers point to elements of the same array object, or one past the last element of the array object, the behavior is undefined.

Proposed resolution (10/00):

Remove the words or function from paragraph 8.




73. Pointer equality

Section: 5.10  expr.eq     Status: TC1     Submitter: Nathan Myers     Date: 13 Nov 1998

Nathan Myers: In 5.10  expr.eq , we have:

Pointers to objects or functions of the same type (after pointer conversions) can be compared for equality. Two pointers of the same type compare equal if and only if they are both null, both point to the same object or function, or both point one past the end of the same array.
What does this say, when we have
    int i[1];
    int j[1];
about the expression (i+1 == j) ? It seems to require padding between i[0] and j[0] so that the comparison will come out false.

I think this may be a defect, in that the quoted paragraph extends operator=='s domain too far beyond operator<'s. It should permit (but not require) an off-the-end pointer to compare equal to another object, but not to any element of the same array.

Mike Miller: I think this is reading more into the statement in 5.10  expr.eq paragraph 1 than is actually there. What does it mean for a pointer to "point to" an object? I can't find anything that definitively says that i+1 cannot "point to" j[0] (although it's obviously not required to do so). If i+1 is allowed to "point to" j[0], then i+1==j is allowed to be true, and there's no defect. There are places where aliasing is forbidden, but the N+1th element of an array doesn't appear to be one of them.

To put it another way, "points to" is undefined in the Standard. The only definition I can think of that encompasses the possible ways in which a pointer can get its value (e.g., the implementation-defined conversion of an arbitrary integer value to a pointer) is that it means "having the same value representation as would be produced by applying the (builtin) & operator to an lvalue expression designating that object". In other words, if the bits are right, it doesn't matter how you produced the value, as long as you didn't perform any operations that have undefined results. The expression i+1 is not undefined, so if the bits of i+1 are the same as those of &j[0], then i+1 "points to" j[0] and i+i==j is allowed to be true.

Tom MacDonald: C9X contains the following words for the "==" operator:

Two pointers compare equal if both are null pointers, both are pointers to the same object (including a pointer to an object and a subobject at its beginning) or function, both are pointers to one past the last element of the same array object, or one is a pointer to one past the end of one array object and the other is a pointer to the start of a different array object that happens to immediately follow the first array object in the address space.
Matt Austern: I don't think there's anything wrong with saying that the result of
    int x[1];
    int y[1]; 
    std::cout << (y == x + 1) << std::endl;
is implementation defined, or even that it's undefined.

Mike Miller: A similar question could be raised about different objects that (sequentially) share the same storage. Consider the following:

    struct B {
        virtual void f();
    };
    struct D1: B { };
    struct D2: B { };
    void g() {
        B* bp1 = new D1;
        B* bp2 = new (bp1) D2;
        bp1 == bp2; // ???
    }
Section 3.8  basic.life paragraph 5 does not list this kind of comparison among the pointer operations that cause undefined behavior, so presumably the comparison is allowed. However, 5.10  expr.eq paragraph 1 describes pointer comparison in terms of "[pointing] to the same object," which bp1 and bp2 clearly do not do. How should we describe the result of this comparison?

Jason Merrill: When you consider comparing pointers to void, this seems to suggest that no two objects can have the same address, depending on your interpretation of "point to the same object." This would cripple the empty base optimization.

3.9.2  basic.compound refers to 'pointers to void or objects or functions'. In that case, 5.10  expr.eq does not allow you to compare them; it only allows comparing pointers to objects and functions.

Proposed Resolution (10/00):

(See also paper J16/00-0011 = WG21 N1234.)




188. Comma operator and rvalue conversion

Section: 5.18  expr.comma     Status: TC1     Submitter: Mike Miller     Date: 20 Dec 1999

Given

    char arr[100];
    sizeof(0,arr);

What does the sizeof expression return? According to 5.18  expr.comma paragraph 1, the comma operator yields an lvalue if the second argument is an lvalue. Since 4.2  conv.array paragraph 1 says that the array-to-pointer conversion yields an rvalue, it seems that sizeof should see an array type and give the answer 100. If so, the value of the sizeof expression would be different from that of the corresponding expression in C, but there is nothing in Annex C  diff to indicate that an incompatible change was intended.

Proposed resolution (10/00):

Add the following as paragraph 3 of C.1.3  diff.expr:

5.16, 5.17, 5.18

Change: The result of a conditional expression, an assignment expression, or a comma expression may be an lvalue.
Rationale: C++ is an object-oriented language, placing relatively more emphasis on lvalues. For example, functions may return lvalues.
Effect on original feature: Change to semantics of well-defined feature. Some C expressions that implicitly rely on lvalue-to-rvalue conversions will yield different results. For example,

    char arr[100];
    sizeof(0, arr)
yields 100 in C++ and sizeof(char*) in C.
Difficulty of converting: Programs must add explicit casts to the appropriate rvalue.
How widely used: Rare.




94. Inconsistencies in the descriptions of constant expressions

Section: 5.19  expr.const     Status: TC1     Submitter: Mike Miller     Date: 8 Feb 1999
  1. According to 9.4.2  class.static.data paragraph 4, a static const integral or const enumeration data member initialized with an integral constant expression "can appear in integral constant expressions within its scope" [emphasis mine]. This means that the following is not permitted:
        struct S {
            static const int c = 5;
        };
        int a[S::c];    // error: S::c not in scope
    
    Is this restriction intentional? If so, what was the rationale for the restriction?

    Bjarne Stroustrup: I think that once you have said S::, c is in scope so that

        int a[S::c];
    
    is ok.

    Mike Miller: I'd like to think that's what it meant, but I don't believe that's what it said. According to 3.3  basic.scope paragraph 1, the scope of a name is the region "in which that name may be used as an unqualified name." You can, indeed, use a qualified name to refer to a name that is not in scope, but that only goes to reinforce my point that "S::c" is not in scope at the point where the expression containing it is used. I think the phrase "within its scope" is at best misleading and should be removed. (Unless there's a reason I'm missing for restricting the use of static member constants to their scope.)

  2. According to 5.19  expr.const paragraph 1, integral constant expressions can "involve...const variables or static data members of integral or enumeration types initialized with constant expressions." However, in 5.19  expr.const paragraph 3, arithmetic constant expressions cannot include them. This seems a rather gratuitous distinction and one likely to bite programmers trained always to use const variables instead of preprocessor definitions. Again, is there a rationale for the difference?

    As far as I can tell from 5.19  expr.const paragraph 2, "arithmetic constant expressions" (as distinct from "integral constant expressions") are used only in static initializers to distinguish between static and dynamic initialization. They include floating point types and exclude non-type template parameters, as well as the const variables and static data members.

  3. There is a minor error in 5.19  expr.const paragraph 2. The first sentence says, "Other expressions are considered constant expressions only for the purpose of non-local static object initialization." However, 6.7  stmt.dcl paragraph 4 appears to rely on the same definition dealing with the initialization of local static objects. I think that the words "non-local" should be dropped and a cross reference to 6.7  stmt.dcl added.

  4. 5.19  expr.const paragraph 4 says, "An expression that designates the address of a member or base class of a non-POD class object (clause 9) is not an address constant expression (12.7  class.cdtor )."

    I'm guessing that should be "non-static member," like the similar prohibition in 12.7  class.cdtor regarding out-of-lifetime access to members of non-POD class objects.

Proposed resolutions (10/00):

  1. Remove the phrase "within its scope" in 9.4.2  class.static.data paragraph 4.

  2. Replace 5.19  expr.const paragraph 3 with the following:
    An arithmetic constant expression shall satisfy the requirements for an integral constant expression, except that
    • floating literals need not be cast to integral or enumeration type, and
    • conversions to floating point types are permitted.
  3. This is not a defect; no change is required. The suggested wording would be more accurate, but since the effect on local initialization is unobservable the current wording is adequate.

  4. Change the referenced sentence in 5.19  expr.const paragraph 4 to "An expression that designates the address of a subobject of a non-POD class object is not an address constant expression."




227. How many scopes in an if statement?

Section: 6.4  stmt.select     Status: TC1     Submitter: Marc Paterno     Date: 21 Apr 2000

The wording of 6.4  stmt.select paragraph 1 is misleading. Instead of

The substatement in a selection-statement (both substatements, in the else form of the if statement) implicitly defines a local scope (3.3  basic.scope).

it should say

... each substatement, in the else form...

As is, one is left with the impression that both "then" and "else" clauses together form a single scope.

Proposed resolution (10/00): As suggested.




69. Storage class specifiers on template declarations

Section: 7.1.1  dcl.stc     Status: TC1     Submitter: Mike Ball     Date: 17 Oct 1998

Mike Ball: I cannot find anything in the standard that tells me the meaning of a storage-class-specifier on a function template declaration. In particular, there is no indication what effect, if any, it has on the storage class of the instantiations.

There is an explicit prohibition of storage-class-specifiers on explicit specializations.

For example, if we have

    template<class T> static int foo(T) { return sizeof(T); }
does this generate static functions for all instantiations? By 7.1.1  dcl.stc the storage class applies to the name declared in the declarator, which is the template foo, not an instantiation of foo, which is named with a template-id. There is a statement in clause 14 that template names have linkage, which supports the contention that "static" applies to the template, not to instantiations.

So what does the specifier mean? Lacking a direct statement in the standard, I see the following posibilities, in my preference order.

  1. storage-class-specifiers have no meaning on template declarations, their use being subsumed by "export" (for the template name) and the unnamed namespace (for instantiations)
  2. storage-class-specifiers have no effect on the template name, but do affect the linkage of the instantiations, though this now applies linkage to template-ids, which I can find no support for. I suspect this is what was intended, though I don't remember
Of course, if anybody can find some concrete statement, that would settle it.

From John Spicer

The standard does say that a namespace scope template has external linkage unless it is a function template declared "static". It doesn't explicitly say that the linkage of the template is also the linkage of the instantiations, but I believe that is the intent. For example, a storage class is prohibited on an explicit specialization to ensure that a specialization cannot be given a different storage class than the template on which it is based.

Mike: This makes sense, but I couldn't find much support in the document. Sounds like yet another interpretation to add to the list.

John: Agreed.

The standard does not talk about the linkage of instantiations, because only "names" are considered to have linkage, and instances are not really names. So, from an implementation point of view, instances have linkage, but from a language point of view, only the template from which the instances are generated has linkage.
Mike: Which is why I think it would be cleaner to eliminate storage class specifiers entirely and rely on the unnamed namespace. There is a statement that specializations go into the namespace of the template. No big deal, it's not something it says, so we live with what's there.

John: That would mean prohibiting static function templates. I doubt those are common, but I don't really see much motivation for getting rid of them at this point.

"export" is an additional attribute that is separate from linkage, but that can only be applied to templates with external linkage.
Mike: I can't find that restriction in the standard, though there is one that templates in an unnamed namespace can't be exported. I'm pretty sure that we intended it, though.

John: I can't find it either. The "inline" case seems to be addressed, but not static. Surely this is an error as, by definition, a static template can't be used from elsewhere.

Proposed resolution (10/00):

Change the text in 14  temp paragraph 4 from:
A template name may have linkage (3.5  basic.link).
to:
A template name has linkage (3.5  basic.link). A non-member function template can have internal linkage; any other template name shall have external linkage. Entities generated from a template with internal linkage are distinct from all entities generated in other translation units.



56. Redeclaring typedefs within classes

Section: 7.1.3  dcl.typedef     Status: TC1     Submitter: Steve Adamczyk     Date: 13 Oct 1998

Can a typedef redeclaration be done within a class?

    class X { 
        typedef int I; 
        typedef int I; 
    };
See also 9.2  class.mem , Core issue 36, and Core issue 85.

Proposed Resolution (10/99): Change 7.1.3  dcl.typedef paragraph 2 from "In a given scope" to "In a given non-class scope."




76. Are const volatile variables considered "constant expressions"?

Section: 7.1.5.1  dcl.type.cv     Status: TC1     Submitter: Judy Ward     Date: 15 Dec 1998

The following code does not compile with the EDG compiler:

    volatile const int a = 5;
    int b[a];
The standard, 7.1.5.1  dcl.type.cv , says:
A variable of const-qualified integral or enumeration type initialized by an integral constant expression can be used in integral constant expressions.
This doesn't say it can't be const volatile-qualified, although I think that was what was intended.

Proposed Resolution (10/99): Change the referenced text in paragraph 2 of 7.1.5.1  dcl.type.cv to read:




68. Grammar does not allow "friend class A<int>;"

Section: 7.1.5.3  dcl.type.elab     Status: TC1     Submitter: Mike Ball     Date: 17 Oct 1998

I can't find the answer to the following in the standard. Does anybody have a reference?

The syntax for elaborated type specifier is

Which does not allow the production

    class foo<int> // foo is a template
On the other hand, a friend declaration seems to require this production,
An elaborated-type-specifier shall be used in a friend declaration for a class.*

[Footnote: The class-key of the elaborated-type-specifier is required. —end footnote]

And in 14.5.3  temp.friend we find the example
[Example:
    template<class T> class task;
    template<class T> task<T>* preempt(task<T>*);

    template<class T> class task {
        // ...
        friend void next_time();
        friend void process(task<T>*);
        friend task<T>* preempt<T>(task<T>*);
        template<class C> friend int func(C);

        friend class task<int>;
        template<class P> friend class frd;
        // ...
    };
Is there some special dispensation somewhere to allow the syntax in this context? Is there something I've missed about elaborated-type-specifier? Is it just another bug in the standard?

An additional problem was reported via comp.std.c++: the grammar does not allow the following example:

    namespace A{
      class B{};
    };

    namespace B{
      class A{};
      class C{
	friend class ::A::B;
      };
    };

Proposed resolution (10/00):

Change the grammar in 7.1.5.3  dcl.type.elab to read

and change the forms allowed in paragraph 1 to




171. Global namespace scope

Section: 7.3  basic.namespace     Status: TC1     Submitter: Greg Lutz     Date: 19 Sep 1999

7.3  basic.namespace paragraph 2 says:

A name declared outside all named namespaces, blocks (6.3  stmt.block ) and classes (clause 9  class ) has global namespace scope (3.3.5  basic.scope.namespace ).
But 3.3.5  basic.scope.namespace paragraph 3 says:
A name declared outside all named or unnamed namespaces (7.3  basic.namespace ), blocks (6.3  stmt.block ), function declarations (8.3.5  dcl.fct ), function definitions (8.4  dcl.fct.def ) and classes (clause 9  class ) has global namespace scope (also called global scope).
7.3  basic.namespace should evidently be changed to match the wording in 3.3.5  basic.scope.namespace — the unnamed namespace is not global scope.

Proposed resolution (10/00):

  1. Replace the first sentence of 3.3.5  basic.scope.namespace paragraph 3 with

    The outermost declarative region of a translation unit is also a namespace, called the global namespace. A name declared in the global namespace has global namespace scope (also called global scope).
  2. In the last sentence of the same paragraph, change "Names declared in the global namespace scope" to "Names with global namespace scope."

  3. Replace 7.3  basic.namespace paragraph 2 with

    The outermost declarative region of a translation unit is a namespace; see 3.3.5  basic.scope.namespace.




166. Friend declarations of template-ids

Section: 7.3.1.2  namespace.memdef     Status: TC1     Submitter: John Spicer     Date: 8 Sep 1999

John Spicer: I believe the standard is not clear with respect to this example:

    namespace N {
      template <class T> void f(T);
      namespace M {
        struct A {
          friend void f<int>(int);  // okay - refers to N::f
        };
      }
    }
At issue is whether the friend declaration refers to N::f, or whether it is invalid.

A note in 3.3.1  basic.scope.pdecl paragraph 6 says

friend declarations refer to functions or classes that are members of the nearest enclosing namespace ...
I believe it is intended to mean unqualified friend declarations. Certainly friend void A::B() need not refer to a member of the nearest enclosing namespace. Only when the declarator is unqualified (i.e., it is a declaration and not a reference) does this rule need to apply. The presence of an explicit template argument list requires that a previous declaration be visible and renders this a reference and not a declaration that is subject to this rule.

Mike Miller: 7.3.1.2  namespace.memdef paragraph 3 says,

When looking for a prior declaration of a class or a function declared as a friend, scopes outside the innermost enclosing namespace scope are not considered.
On the other hand, the friend declaration would be a syntax error if f weren't declared as a template name; it would seem very strange not to find the declaration that made the friend declaration syntactically correct. However, it also seems strange to treat this case differently from ordinary functions and from templates:
    namespace N {
      template <class T> void f(T);
      void g();
      namespace M {
        struct A {
          friend void f<int>(int);               // N::f
          template <class T> friend void f(T);   // M::f
          friend void g();                       // M::g
        };
      }
    }

John Spicer: This section refers to "looking for a prior declaration". This gets back to an earlier discussion we've had about the difference between matching two declarations of the same name and doing name lookup. I would maintain that in f<int> the f is looked up using a normal lookup. In practice, this is really how it has to be done because the declaration could actually be f<int>::x.

Proposed resolution (10/00):

In 7.3.1.2  namespace.memdef paragraph 3, change

When looking for a prior declaration of a class or a function declared as a friend, scopes outside the innermost enclosing namespace scope are not considered.
to
When looking for a prior declaration of a class or a function declared as a friend, and when the name of the friend class or function is neither a qualified name nor a template-id, scopes outside the innermost enclosing namespace scope are not considered.
Also, change the example in that paragraph as follows:
    void h(int);
    template <class T> void f2(T);
    namespace A {
        class X {
            friend void f(X);       // A::f(X) is a friend
            friend void f2<>(int);  // ::f2<>(int) is a friend
    ...

(See also issues 95, 136, 138, 139, 143, and 165.)




101. Redeclaration of extern "C" names via using-declarations

Section: 7.3.3  namespace.udecl     Status: TC1     Submitter: Mike Miller     Date: 10 Mar 1999

Consider the following:

    extern "C" void f();
    namespace N {
        extern "C" void f();
    }
    using N::f;
According to 7.3.3  namespace.udecl paragraph 11, the using-declaration is an error:
If a function declaration in namespace scope or block scope has the same name and the same parameter types as a function introduced by a using-declaration, the program is ill-formed.
Based on the context (7.3.3  namespace.udecl paragraph 10 simply reiterates the requirements of 3.3  basic.scope ), one might wonder if the failure to exempt extern "C" functions was intentional or an oversight. After all, there is only one function f() involved, because it's extern "C", so ambiguity is not a reason to prohibit the using-declaration.

This also breaks the relatively strong parallel between extern "C" functions and typedefs established in our discussion of Core issue 14 in Santa Cruz. There the question was for using-directives:

    typedef unsigned int size_t;
    extern "C" int f();
    namespace N {
        typedef unsigned int size_t;
        extern "C" int f();
    }
    using namespace N;
    int i = f();        // ambiguous "f"?
    size_t x;           // ambiguous "size_t"?
We decided for both that there was no ambiguity because each pair of declarations declares the same entity. (According to 3  basic paragraph 3, a typedef name is not an entity, but a type is; thus the declarations of size_t declare the same entity "unsigned int".)

In the context of using-declarations, there is no explicit extension of the restrictions in 3.3  basic.scope paragraph 4 except as noted above for function declarations; thus the parallel scenario for a typedef is not ill-formed:

    typedef unsigned int size_t;
    namespace N {
        typedef unsigned int size_t;
    };
    using N::size_t;        // okay, both declarations
                            // refer to the same entity
I think the first sentence of 7.3.3  namespace.udecl paragraph 11 ought to be rewritten as:
If a function declaration in namespace scope or block scope has the same name and the same parameter types as a function introduced by a using-declaration, and the declarations do not declare the same function, the program is ill-formed.

Proposed Resolution (10/99): As suggested.




103. Is it extended-namespace-definition or extension-namespace-definition ?

Section: 7.3.4  namespace.udir     Status: TC1     Submitter: Herb Sutter     Date: 20 Mar 1999

Section 7.3.4  namespace.udir paragraph 3 uses the term extended-namespace-definition three times:

If a namespace is extended by an extended-namespace-definition after a using-directive for that namespace is given, the additional members of the extended namespace and the members of namespaces nominated by using-directives in the extended-namespace-definition can be used after the extended-namespace-definition.
I think the intent is clear, but unfortunately I cannot find any other mention (or definition) of this term.

Mike Miller: True enough; in Section 7.3.1  namespace.def [the grammar] it's called an extension-namespace-definition.

Proposed Resolution (10/99): Systematically replace "extended-namespace-definition" by "extension-namespace-definition".




40. Syntax of declarator-id

Section: 8.3  dcl.meaning     Status: TC1     Submitter: Mike Miller     Date: 01 Sep 1998

(From J16/99-0005 = WG21 N1182, "Proposed Resolutions for Core Language Issues 6, 14, 20, 40, and 89")

There are two sub-issues. The first concerns the statement in 8.3  dcl.meaning paragraph 1,

The id-expression of a declarator-id shall be a simple identifier except for the declaration of some special functions (12.3  class.conv , 12.4  class.dtor , 13.5  over.oper ) and for the declaration of template specializations or partial specializations (14.7  temp.spec ).
The second sub-issue is regarding another statement in the same paragraph:
A declarator-id shall not be qualified except for the definition of a member function (9.3  class.mfct ) or static data member (9.4  class.static ) or nested class (9.7  class.nest ) outside of its class, the definition or explicit instantiation of a function, variable or class member of a namespace outside of its namespace, or...
Analysis

The problem in the first sub-issue is that the wrong syntactic non-terminal is mentioned. The relevant portions of the grammar are:

The exceptions in the citation from 8.3  dcl.meaning paragraph 1 are all the non-identifier cases of unqualified-id: 12.3  class.conv is for conversion-function-ids, 12.4  class.dtor is for destructors, 13.5  over.oper is for overloaded operators, and 14.7  temp.spec is for template-ids. If taken literally, this sentence would exclude all qualified-ids, which it obviously is not intended to do. Instead, the apparent intent is something along the lines of
If an unqualified-id is used as the id-expression of a declarator-id, it shall be a simple identifier except...
However, it does not appear that this restriction has any meaning; all of the possible cases of unqualified-ids are represented in the list of exceptions! Rather than recasting the sentence into a correct but useless form, it would be better to remove it altogether.

The second sub-issue deals with the conditions under which a qualified-id can be used in a declarator, including "the definition of a...nested class" and "the definition or explicit instantiation of a...class member of a namespace." However, the name in a class definition is not part of a declarator; these constructs do not belong in a list of declarator contexts.

Proposed Resolution for sub-issue 1 (04/99):

The suggested resolution for the first sub-issue overlooked the fact that the existing wording has the additional effect of prohibiting the use of the non-identifier syntax for declaring other than the listed entities. Thus the proposed wording for the first sub-issue is:

Change 8.3  dcl.meaning paragraph 1 from:

The id-expression of a declarator-id shall be a simple identifier except...
to:
An unqualified-id occurring in a declarator-id shall be a simple identifier except...

Proposed Resolution for sub-issue 2 (10/99):

Change 8.3  dcl.meaning paragraph 1 from:

A declarator-id shall not be qualified except for the definition of a member function (9.3  class.mfct ) or static data member (9.4  class.static ) or nested class (9.7  class.nest ) outside of its class, the definition or explicit instantiation of a function, variable or class member of a namespace outside of its namespace, or...
to
A declarator-id shall not be qualified except for the definition of a member function (9.3  class.mfct ) or static data member (9.4  class.static ) outside of its class, the definition or explicit instantiation of a function or variable member of a namespace outside of its namespace, or...



159. Namespace qualification in declarators

Section: 8.3  dcl.meaning     Status: TC1     Submitter: John Spicer     Date: 23 Aug 1999

8.3  dcl.meaning paragraph 1 says:

In the qualified declarator-id for a class or namespace member definition that appears outside of the member's class or namespace, the nested-name-specifier shall not name any of the namespaces that enclose the member's definition.
This results in the following behavior:
    namespace N {
        namespace M {
            void f();
            void g();
        }
        void M::f(){}     // okay
        void N::M::g(){}  // error
    }
I was very surprised when this rule was pointed out to me. The change appears to have been introduced around the time of the first Santa Cruz meeting, but I don't recall discussion of it and could not find a motion related to it.

Regardless of where it came from, I also can't understand why it is there. Certainly it shouldn't matter how you name a given class or namespace.

For example, the standard permits:

    namespace N {
        namespace M {
            void f();
            void g();
        }
        namespace X = M;
        namespace Y = N::M;
        void X::f(){}  // okay
        void Y::g(){}  // okay
    }
So, it is okay to use an alias for N::M, but not to use N::M directly. Note that it is okay to use N::M in any other context at this point in the program (i.e., the rule is a specific restriction on declarator names, not a general rule on the use of qualified names).

Does anyone recall the intent of this rule or any rationale for its existence?

Notes from 04/00 meeting:

There was some question as to whether this issue actually constituted a defect in the Standard. John Spicer suggested that machine-generated source code would be likely to run afoul of this prohibition. Francis Glassborow expressed support for a rule that would allow full qualification, or qualification relative to the namespace containing the definition, but not qualification relative to a containing namespace. There was no consensus for moving forward with a DR at this point, so the issue was left in "review" status.

Proposed resolution (10/00):

Remove the last sentence of 8.3  dcl.meaning paragraph 1 (cited above) and the example that follows.




135. Class type in in-class member function definitions

Section: 8.3.5  dcl.fct     Status: TC1     Submitter: Gabriel Netterdag     Date: 1 July 1999

3.2  basic.def.odr paragraph 4 and 8.3.5  dcl.fct paragraph 6 indicate that the return type and parameter types must be complete in a function definition. However, when 9.2  class.mem paragraph 2 lists the contexts in a class member-specification in which the class is considered complete, the return type and parameter types of a member function defined in the class definition are not included. It thus appears that the following example is ill-formed:

    struct S {
        S f() { return S(); }    // error: incomplete return type
        void g(S) { }            // error: incomplete parameter type
    };
Jack Rouse: I suggest supplementing the text in 8.3.5p6 with something like:
The type of a parameter or the return type for a function definition shall not be an incomplete class type unless the function definition is nested in the member-specification for that class (including definitions in nested classes defined within the class).

Proposed resolution (10/00): Replace the last sentence of 8.3.5  dcl.fct paragraph 6 with

The type of a parameter or the return type for a function definition shall not be an incomplete class type (possibly cv-qualified) unless the function definition is nested within the member-specification for that class (including definitions in nested classes defined within the class).



1. What if two using-declarations refer to the same function but the declarations introduce different default-arguments?

Section: 8.3.6  dcl.fct.default     Status: TC1     Submitter: Bill Gibbons     Date: unknown

3.3  basic.scope paragraph 4 says:

Given a set of declarations in a single declarative region, each of which specifies the same unqualified name,
8.3.6  dcl.fct.default paragraph 9 says:
When a declaration of a function is introduced by way of a using-declaration (7.3.3  namespace.udecl), any default argument information associated with the declaration is imported as well.
This is not really clear regarding what happens in the following case:
    namespace A {
            extern "C" void f(int = 5);
    }
    namespace B {
            extern "C" void f(int = 7);
    }
     
    using A::f;
    using B::f;
     
    f(); // ???
Proposed resolution (10/00):

Add the following at the end of 13.3.3  over.match.best:

If the best viable function resolves to a function for which multiple declarations were found, and if at least two of these declarations — or the declarations they refer to in the case of using-declarations — specify a default argument that made the function viable, the program is ill-formed. [Example:
    namespace A {
       extern "C" void f(int = 5);
    }
    namespace B {
       extern "C" void f(int = 5);
    }

    using A::f;
    using B::f;

    void use() {
       f(3);       // OK, default argument was not used for viability
       f();        // Error: found default argument twice
    }

  —end example]




65. Typo in default argument example

Section: 8.3.6  dcl.fct.default     Status: TC1     Submitter: Mike Miller     Date: 6 Oct 1998

Proposed Resolution (04/99): Change the text in the example of section 8.3.6  dcl.fct.default paragraph 5 from:

... g will be called with the value f(1).
to:
... g will be called with the value f(2).



217. Default arguments for non-template member functions of class templates

Section: 8.3.6  dcl.fct.default     Status: TC1     Submitter: Martin Sebor     Date: 22 Mar 2000

According to 8.3.6  dcl.fct.default paragraphs 4 and 6,

For non-template functions, default arguments can be added in later declarations of a function in the same scope.

The default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition.

This would appear to allow the following example, in which a default argument is added to a non-template member function of a class template:

    template <class T>
    struct S
    {
	void foo (int);
    };

    template <class T>
    void S<T>::foo (int = 0) { }

John Spicer: The wording "non-template functions" is somewhat unclear with respect to member functions of class templates, but I know that this was intended to include them because it originates from issue 3.13 of the template issues list that I maintained for several years.

Having said that, the rationale for this restriction has since been made obsolete, so this could (in theory) be changed in the standard if it is problematic for users.

(See also issue 205.)

Proposed resolution (10/00):

In 8.3.6  dcl.fct.default paragraph 6, replace

The default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition.

with

Except for member functions of class templates, the default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition. Default arguments for a member function of a class template must be specified on the initial declaration of the member function within the class template.



35. Definition of default-initialization

Section: 8.5  dcl.init     Status: TC1     Submitter: Andrew Koenig     Date: 29 Jul 1998

Given:

    struct S1 {
        int x;
    };
    
    struct S2 {
        int x;
        double y;
    };
    
    struct S3 {
        int x;
        double y;
        string s;
    };
Once upon a time, we went through a fairly protracted discussion to ensure that S1().x would be guaranteed to be 0. Note that if we declare
    void f()
    {
        S1 s1;
    
        // ...
    }
there is no guarantee of the value of s1.x, and that is intentional. But S1().x is different, because S1() is an rvalue, and unless all of its members are defined, the effect of copying it is undefined.

Similarly, S2().x and S2().y are also defined to be equal to zero, and here it really matters for many implementations, because if S2().y is just a bunch of random bits, it is entirely possible that trying to copy S2().y will yield a floating-point trap.

However, rather to my surprise, the standard does not define the value of S3().x or S3().y, because S3 is not a POD. It does define S3().s (by running the string constructor), but once a structure is no longer a POD, the values of uninitialized members are no longer guaranteed in expressions of the form T().

In my opinion, this definition is a mistake, and the committee's intention was to zero-initialize all members that do not have an explicitly defined constructor, whether or not the class is a POD.

See also paper J16/99-0014 = WG21 N1191.

[Note: this issue is resolved by the resolution of issue 178.]




151. Terminology of zero-initialization

Section: 8.5  dcl.init     Status: TC1     Submitter: Valentin Bonnard     Date: 4 August 1999

In 3.6.2  basic.start.init paragraph 1 and 8.5  dcl.init paragraphs 5 and 6, the terms "memory" and "storage" are used in connection with zero-initialization. This is inaccurate; it is the variables that are zero-initialized, not the storage. (An all-zero bit pattern in the storage may, in fact, not correspond to the representation of zero converted to the appropriate type, and it is the latter that is being described.)

Suggested resolution: remove the words "storage" and "memory" in these contexts.

Proposed resolution (10/00):

Delete the words "The storage for" from the first sentence of 3.6.2  basic.start.init paragraph 1.

[Note: Revised wording in 8.5  dcl.init relating to this issue is also found in issue 178.]




178. More on value-initialization

Section: 8.5  dcl.init     Status: TC1     Submitter: Andrew Koenig     Date: 25 Oct 1999

When the Committee considered issue 35, another context in which value initialization might be relevant was overlooked: mem-initializers. It would seem reasonable that if T() as an expression invokes value initialization, that the same syntactic construct in a mem-initializer-list would do the same, and the usefulness of value initialization in that context is at least as great as the standalone case.

Proposed resolution (10/00):

[Note: this resolution supersedes the resolution to issue 35.]

In 5.2.3  expr.type.conv paragraph 2, replace "whose value is determined by default-initialization" by "which is value-initialized".

In 5.3.4  expr.new paragraph 15,

Replace 8.5  dcl.init paragraph 5 by:

To zero-initialize an object of type T means:

To default-initialize an object of type T means:

To value-initialize an object of type T means:

A program that calls for default-initialization of an entity of reference type is ill-formed. If T is a cv-qualified type, the cv-unqualified version of T is used for these definitions of zero-initialization, default-initialization, and value-initialization.

In 8.5  dcl.init paragraph 6, change "The memory occupied by any" to "Every".

In 8.5  dcl.init paragraph 7, replace "default-initialized" by "value-initialized".

In 8.5.1  dcl.init.aggr paragraph 7, replace "default-initialized" by "value-initialized".

In 12.3.1  class.conv.ctor paragraph 2, insert "or value-initialization" after the first occurrence of "default-initialization".

In 12.6  class.init paragraph 1, replace the note by "The object is default-initialized if there is no initializer, or value-initialized if the initializer is ()" [i.e., replace the non-normative note by different, normative text].

In 12.6.1  class.expl.init paragraph 2, replace "default-initialized" by "value-initialized".

In 12.6.2  class.base.init paragraph 3, replace "default-initialized" by "value-initialized" in the first bulleted item.

In 12.6.2  class.base.init paragraph 4, replace "default-initialized, nor initialized" by "default-initialized, nor value-initialized, nor assigned".




304. Value-initialization of a reference

Section: 8.5  dcl.init     Status: TC1     Submitter: Steve Adamczyk     Date: 25 Jul 2001

Another glitch in the TC1/core issue 178 definition of value-initialization: it's no longer an error to value-initialize a reference. That makes an example like

typedef struct { int &r; } S;
int main() {
  S();  // Error in C++98, okay in TC1!
}
valid, which has got to be wrong. See 8.5  dcl.init paragraph 5, where there is wording that forbids default-initialization of a reference, but not value-initialization thereof. As noted in issue 302, if the default constructor were required to be generated when a value-initialization is done, that would force an error.

Proposed resolution (10/01):

Add the indicated wording to the indicated sentence in 8.5  dcl.init paragraph 5:

A program that calls for default-initialization or value-initialization of an entity of reference type is ill-formed.



163. Description of subaggregate initializer

Section: 8.5.1  dcl.init.aggr     Status: TC1     Submitter: Mike Miller     Date: 12 Aug 1999

8.5.1  dcl.init.aggr paragraph 2 says,

When an aggregate is initialized the initializer can be an initializer-clause consisting of a brace-enclosed, comma-separated list of initializers for the members of the aggregate.
Neither of these uses of the syntactic nonterminal initializer corresponds to the grammar:

Proposed resolution (10/99): replace the quoted words with:

When an aggregate is initialized the initializer can contain an initializer-clause consisting of a brace-enclosed, comma-separated list of initializer-clauses for the members of the aggregate.



148. POD classes and pointers to members

Section: class     Status: TC1     Submitter: Nathan Sidwell     Date: 31 Jul 1999

3.9  basic.types paragraph 10 defines pointer to member types to be scalar types. It also defines scalar types to be one of the POD types.

class paragraph 4 defines a POD struct as an aggregate class with no non-static data members of type pointer to member.

It seems contradictory that a type can be POD, yet a class containing that type is non-POD.

Suggested resolution: Alter 9  class paragraph 4 to allow pointer to member objects as non-static data members of POD class.

Proposed resolution (10/00):

In 9  class paragraph 4, remove all occurrences of "pointer to member."




176. Name injection and templates

Section: class     Status: TC1     Submitter: John Spicer     Date: 21 February 1999

There is some controversy about whether class name injection applies to class templates. If it does apply, what is injected? Is a class name injected or is the thing that is injected actually a template?

Clause 9  class paragraph 2 says,

The class-name is also inserted into the scope of the class itself.
In general, clause 9 applies to both classes and class templates, so I would take this to mean that class name imjection does indeed apply to class templates. One problem with this is that clause 9 uses the syntactic term class-name, which I would take to imply that the inserted name is always a class. This is clearly unacceptable for class templates as it makes the template itself unusable from with the template. For example:
    template <class T> struct A {
        A<T*> ptr;    // Invalid: A refers to a class
    };

Clearly the injected name must be usable as both a class and a class template. This kind of magic already exists in the standard. In 14.6.1  temp.local it says,

Within the scope of a class template, when the name of the template is neither qualified nor followed by <, it is equivalent to the name of the template followed by the template-parameters enclosed in <>.

The proposal here is that we clarify that name injection does indeed apply to class templates, and that it is the injected name that has the special property of being usable as both a class and a template name (as described in 14.6.1  temp.local ). This would eliminate the need for special wording regarding the qualification of the name, but would achieve the same result. This would also make this "special" name available to a derived class of a class template — something which is necessary if the benefits of class name injection are to be made uniformly available for class templates, too.

    template <class T> struct Base {
        Base* p;
        Base<T*>* p2;
        ::Base* p3;    // Error: only injected name usable as class
    };

    template <class T> struct Derived: public Base<T> {
        Base* p;    // Now okay
        Base<T*>* p2;    // Still okay
        Derived::Base* p3;    // Now okay
Note that by giving the special attribute of being usable as both a class and a template to the injected name it is now clear where this attribute can and cannot be used.

(See paper J16/99-0010 = WG21 N1187.)

Proposed resolution (10/00):

[Note: these changes depend on the resolution for issue 147.]

Replace 14.6.1  temp.local paragraphs 1 and 2 with the following:

Like normal (non-template) classes, class templates have an injected-class-name (clause 9  class). The injected-class-name can be used with or without a template-argument-list. When it is used without a template-argument-list, it is equivalent to the injected-class-name followed by the template-parameters of the class template enclosed in <>. When it is used with a template-argument-list, it refers to the specified class template specialization, which could be the current specialization or another specialization.

Within the scope of a class template specialization or partial specialization, when the injected-class-name is not followed by a <, it is equivalent to the injected-class-name followed by the template-arguments of the class template specialization or partial specialization enclosed in <>. [Example:

    template<class T> class Y;
    template<> class Y<int> {
        Y* p;          // meaning Y<int>
        Y<char>* q;    // meaning Y<char>
    };

end example]

The injected-class-name of a class template or class template specialization can be used either with or without a template-argument-list wherever it is in scope. [Example:

    template <class T> struct Base {
        Base* p;
    };

    template <class T> struct Derived: public Base<T> {
        typename Derived::Base* p;  // meaning Derived::Base<T>
    };

end example]

A lookup that finds an injected-class-name (10.2  class.member.lookup) can result in an ambiguity in certain cases (for example, if it is found in more than one base class). If all of the injected-class-names that are found refer to specializations of the same class template, and if the name is followed by a template-argument-list, the reference refers to the class template itself and not a specialization thereof, and is not ambiguous. [Example:

    template <class T> struct Base { };
    template <class T> struct Derived: Base<int>, Base<char> {
        typename Derived::Base b;            // error: ambiguous
        typename Derived::Base<double> d;    // OK
    };

end example]

When the normal name of the template (i.e., the name from the enclosing scope, not the injected-class-name) is used without a template-argument-list, it refers to the class template itself and not a specialization of the template. [Example:

    template <class T> class X {
        X* p;         // meaning X<T>
        X<T>* p2;
        X<int>* p3;
        ::X* p4;      // error: missing template argument list
                      // ::X does not refer to the injected-class-name
    };

end example]




75. In-class initialized members must be const

Section: 9.2  class.mem     Status: TC1     Submitter: John Wiegley     Date: 29 Dec 1998

The standard says, in 9.2  class.mem paragraph 4:

A member-declarator can contain a constant-initializer only if it declares a static member (9.4  class.static ) of integral or enumeration type, see 9.4.2  class.static.data .
But later, in the section on static class data member initialization, 9.4.2  class.static.data paragraph 4, it says:
If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be an integral constant expression (5.19  expr.const ). In that case, the member can appear in integral constant expressions within its scope.
The first paragraph should be modified to make it clear that it is not possible to initialize a static data member in-line with a constant-initializer if that data member is of integral (or enumeration) type, and yet not const.

Proposed Resolution (10/99): Change the sentence in 9.2  class.mem paragraph 4 to read:

A member-declarator can contain a constant-initializer only if it declares a static member (9.4  class.static ) of const integral or const enumeration type, see 9.4.2  class.static.data .



80. Class members with same name as class

Section: 9.2  class.mem     Status: TC1     Submitter: Jason Merrill     Date: 5 Dec 1998

Between the May '96 and September '96 working papers, the text in 9.2  class.mem paragraph 13:

If T is the name of a class, then each of the following shall have a name different from T:
was changed by removing the word 'static'. Looking over the meeting minutes from Stockholm, none of the proposals seem to include this change, which breaks C compatibility and is not mentioned in the compatibility annex. Was this change actually voted in by the committee?

Specifically, this breaks /usr/include/netinet/in.h under Linux, in which "struct ip_opts" shares its name with one of its members.

Proposed resolution (10/00):

  1. Change the first bullet of 9.2  class.mem paragraph 13 to say
  2. Add another paragraph before 9.2  class.mem paragraph 14, reading
    In addition, if class T has a user-declared constructor (12.1  class.ctor ), every nonstatic data member of class T shall have a name different from T.



190. Layout-compatible POD-struct types

Section: 9.2  class.mem     Status: TC1     Submitter: Steve Adamczyk     Date: 20 Dec 1999

The definition of layout-compatible POD-struct types in 9.2  class.mem paragraph 14 requires that the two types

have the same number of members, and corresponding members (in order) have layout-compatible types (3.9).
There does not appear to be any reason for including member functions and static data members in this requirement. It would be more logical to require only that the non-static data members of the two types must match.

The characteristics of layout-compatible types are not well described in the current wording, either. Apart from their use in 9.2  class.mem paragraph 16 to define the term "common initial sequence," there appears to be nothing said about which operations are possible between objects of layout-compatible types. For example, 3.9  basic.types paragraphs 2-3 give certain guarantees regarding use of memcpy on objects of the same type; it might be reasonable to assume that the same kinds of guarantees might apply to objects of layout-compatible types, but that is not said. Similarly, 3.10  basic.lval paragraph 15 describes permissible "type punning" but does not mention layout-compatible types.

Proposed resolution (10/00):

In 9.2  class.mem paragraphs 14 and 15, change all occurrences of "members" to "nonstatic data members."




67. Evaluation of left side of object-expression

Section: 9.4  class.static     Status: TC1     Submitter: Mike Miller     Date: 6 Oct 1998

Paragraph 2 says that "the object-expression is always evaluated" when the class member syntax is used to refer to a static member. This presumably should say that the object expression is evaluated if the member access is performed, i.e., not if the overall expression is the operand of sizeof or the unevaluated branch of ?:, ||, or &&.

Proposed Resolution (10/99): Replace "is always evaluated" by "is evaluated" in 9.4  class.static paragraph 2.




48. Definitions of unused static members

Section: 9.4.2  class.static.data     Status: TC1     Submitter: Bill Gibbons     Date: 23 Nov 1997

Also see section: 3.2  basic.def.odr .

Originally, all static data members still had to be defined outside the class whether they were used or not.

But that restriction was supposed to be lifted so that static data members need not be defined outside the class unless they are used in a manner which requires their definition, in the same manner as namespace-scope variables. In particular, if an integral/enum const static data member is initialized within the class, and its address is never taken, we agreed that no namespace-scope definition was required.

For example:

    struct A {
        static const int size = 10;
        int array[size];
    };
    
    int main() {
        A a;
        return 0;
    }
However, 9.4.2  class.static.data paragraph 4 says:
The member shall still be defined in a namespace scope if it is used in the program and the namespace scope definition shall not contain an initializer.
A narrow interpreration of "used" in this rule would make the example ill-formed because there is no namespace-scope definition of "size". A better wording for this rule would be:
The member shall still be defined in a namespace scope if it is used in the program in the manner described in 3.2  basic.def.odr . The namespace scope definition shall not contain an initializer.
Also, the wording in 3.2  basic.def.odr paragraph 2:
An expression is potentially evaluated unless either it is the operand of the sizeof operator (5.3.3  expr.sizeof ), or it is the operand of the typeid operator and does not designate an lvalue of polymorphic class type (5.2.8  expr.typeid ).
is incomplete because it does not mention the use of a compile-time constant as an array bound or template argument. It should say something like:
An expression is potentially evaluated unless it is the operand of the sizeof operator (5.3.3  expr.sizeof ), the operand of the typeid operator, an integral constant-expression used as an array bound or an integral constant-expression used as a template-argument for a non-reference template-parameter; and the expression does not designate an lvalue of polymorphic class type (5.2.8  expr.typeid ).

Proposed Resolution (04/99): Change the first sentence of 3.2  basic.def.odr paragraph 2 from:

An expression is potentially evaluated unless either it is the operand of the sizeof operator (5.3.3  expr.sizeof ), or it is the operand of the typeid operator and does not designate an lvalue of polymorphic class type (5.2.8  expr.typeid ).
to:
An expression is potentially evaluated unless it appears where an integral constant expression is required (see 5.19  expr.const ), is the operand of the sizeof operator (5.3.3  expr.sizeof ), or is the operand of the typeid operator and the expression does not designate an lvalue of polymorphic class type (5.2.8  expr.typeid ).



142. Injection-related errors in access example

Section: 11.2  class.access.base     Status: TC1     Submitter: Steve Adamczyk     Date: 16 Jul 1999

In the example in paragraph 3 of 11.2  class.access.base , all the references to B in DD::f() should be replaced by ::B. The reason is that the class name B is private in D and thus inaccessible in DD. (The example was probably not updated when class name injection was added.)

Proposed resolution (10/00):

Replace the example in 11.2  class.access.base paragraph 3 with:

    class B {
    public:
        int mi;                 // nonstatic member
        static int si;          // static member
    };
    class D: private B {
    };
    class DD: public D {
        void f();
    };
    void DD::f() {
        mi = 3;                 // error: mi is private in D
        si = 3;                 // error: si is private in D
        ::B b;
        b.mi = 3;               // OK (b.mi is different from this->mi)
        b.si = 3;               // OK (b.si is different from this->si)
        ::B::si = 3;            // OK
        ::B* bp1 = this;        // error: B is a private base class
        ::B* bp2 = (::B*)this;  // OK with cast
        bp2->mi = 3;            // OK: access through a pointer to B
    }



161. Access to protected nested type

Section: 11.5  class.protected     Status: TC1     Submitter: Steve Adamczyk     Date: 26 Aug 1999

11.5  class.protected paragraph 1 begins:

When a friend or a member function of a derived class references a protected nonstatic member of a base class, an access check applies in addition to those described earlier in clause 11  class.access .

This was intended to refer to nonstatic member functions and nonstatic data members. However, a protected nested type declared in a base class is, by some definition of the word, a "nonstatic" member, and therefore subject to this additional access check.

Proposed resolution (10/99): change "protected nonstatic member" in the above to "protected nonstatic member function or protected nonstatic data member" to make the intent clear.




194. Identifying constructors

Section: 12.1  class.ctor     Status: TC1     Submitter: Jamie Schmeiser     Date: 11 Jan 2000

According to 12.1  class.ctor paragraph 1, the syntax used in declaring a constructor allows at most one function-specifier. It is thus not permitted to declare a constructor both inline and explicit. This seems overly restrictive.

On a related note, there doesn't seem to be any explicit prohibition against member functions with the same name as the class. (Such a prohibition might reasonably be expected to occur in 9.2  class.mem paragraph 13, but member functions are not listed there.)

One possible interpretation would be that such member functions would violate the restrictions in 3.3.6  basic.scope.class paragraph 1, because the class name would refer to the class at some points in the class scope and to the member function at others. However, this seems a bit tenuous. Is an explicit prohibition needed?

(See also issue 147.)

Proposed resolution (10/00):

  1. Add to 9.2  class.mem paragraph 13

    • every member function of class T [Note: this restriction does not apply to constructors, which do not have names (12.1  class.ctor). ];

    immediately following the line

    • every data member of class T;
  2. Change 12.1  class.ctor paragraph 1 from

    A special declarator syntax using an optional function-specifier (7.1.2  dcl.fct.spec)...

    to

    A special declarator syntax using an optional sequence of function-specifiers (7.1.2  dcl.fct.spec)...



152. explicit copy constructors

Section: 12.3.1  class.conv.ctor     Status: TC1     Submitter: Steve Adamczyk     Date: 4 August 1999

Can a copy-constructor declared as explicit be used to copy class values implicitly? For example,

   struct X {
      X();
      explicit X(const X&);
   };
   void f(X);
   int main() { X x; f(x); }
According to 12.3.1  class.conv.ctor paragraphs 2-3,
An explicit constructor constructs objects just like non-explicit constructors, but does so only where the direct-initialization syntax (8.5  dcl.init ) or where casts (5.2.9  expr.static.cast , 5.4  expr.cast ) are explicitly used... A copy-constructor (12.8  class.copy ) is a converting constructor. An implicitly-declared copy constructor is not an explicit constructor; it may be called for implicit type conversions.
This passage would appear to indicate that the call in the example is ill-formed, since it uses neither the direct-initialization syntax nor an explicit cast. The last sentences are especially interesting in this regard, indicating that explicit and non-explicit copy constructors are handled differently.

On the other hand, 8.5  dcl.init paragraph 14, bullet 4, sub-bullet 2 says,

If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination... [the] applicable constructors are enumerated (13.3.1.3  over.match.ctor )...
The cited passage says that
The candidate functions are all the constructors of the class of the object being initialized.

Notes from 04/01 meeting:

After the issue was accepted as a DR with the proposed resolution to change 13.3.1.3  over.match.ctor paragraph 1 as described below, it was noticed that 12.3.1  class.conv.ctor paragraph 3 states that:

A copy-constructor (12.8  class.copy) is a converting constructor.

In addition to making the proposed resolution for this issue ineffectual, the wording of paragraph 3 also contradicts that of paragraph 1:

A constructor declared without the function-specifier explicit that can be called with a single parameter specifies a conversion from the type of its first parameter to the type of its class. Such a constructor is called a converting constructor.

These considerations led to the addition of the second point of the proposed resolution.

Proposed resolution (04/01):

  1. Change the first two sentences of 13.3.1.3  over.match.ctor paragraph 1 to

    When objects of class type are direct-initialized (8.5  dcl.init), or copy-initialized from an expression of the same or a derived class type (8.5  dcl.init), overload resolution selects the constructor. For direct-initialization, the candidate functions are all the constructors of the class of the object being initialized. For copy-initialization, the candidate functions are all the converting constructors (12.3.1  class.conv.ctor ) of that class.
  2. Change the first sentence of 12.3.1  class.conv.ctor paragraph 3 to read:

    A non-explicit copy constructor (12.8  class.copy) is a converting constructor.



193. Order of destruction of local automatics of destructor

Section: 12.4  class.dtor     Status: TC1     Submitter: Gerhard Menzl     Date: 7 Jan 2000

The Standard is not clear whether automatic objects in a destructor are destroyed before or after the destruction of the class's base and member subobjects. That is, given

    struct S { ~S(); };

    struct T {
        S x;
        ~T() {
            S y;
        };
    };

which will be destroyed first, x or y?

Proposed resolution (10/00):

In 12.4  class.dtor paragraph 6, change

A destructor for class X calls the destructors for X's direct members, ...
to
After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X's direct members, ...




235. Assignment vs initialization

Section: 12.6.2  class.base.init     Status: TC1     Submitter: Mike Miller     Date: 16 Sep 2000

In 12.6.2  class.base.init paragraph 4 we read:

After the call to a constructor for class X has completed, if a member of X is neither specified in the constructor's mem-initializers, nor default-initialized, nor initialized during execution of the body of the constructor, the member has indeterminate value.

Using the term "initialized" to describe setting the value of a member inside the body of a constructor is a misuse of the term: only by use of a placement new expression can a member be initialized "during the execution of the body of the constructor."

Suggested resolution: Change "initialized" to "given a value."

Proposed resolution (10/00): As suggested.




20. Some clarifications needed for 12.8 para 15

Section: 12.8  class.copy     Status: TC1     Submitter: unknown     Date: unknown

Issue 1

12.8  class.copy (From J16/99-0005 = WG21 N1182, "Proposed Resolutions for Core Language Issues 6, 14, 20, 40, and 89")

There are three related sub-issues in this issue, all dealing with the elision of copy constructors as described in 12.8  class.copy paragraph 15:

  1. The text should make clear that the requirement that the copy constructor be accessible and unambiguous is not relaxed in cases where a call to a copy constructor is elided.
  2. It is not clear from the text that the two optimizations described can be applied transitively, and, if so, the implications for the order of destruction are not spelled out.
  3. The text should exclude applying the function-return optimization if the expression names a static or volatile local object.
Analysis

After discussion in Santa Cruz, the core group decided that sub-issue #1 required no change; the necessity of an accessible and unambiguous copy constructor is made clear in 12.2  class.temporary paragraph 1 and need not be repeated in this text. The remaining two sub-issues appear to be valid criticisms and should be addressed.

Proposed Resolution (10/99):

[Note: a small portion of this wording is superseded by the resolution of issue 185.]

The paragraph in question should be rewritten as follows. In addition, references to this section should be added to the index under "temporary, elimination of," "elimination of temporary," and "copy, constructor elision."

Here the criteria for elision can be combined to eliminate two calls to the copy constructor of class Thing: the copying of the local automatic object t into the temporary object for the return value of function f() and the copying of that temporary object into object t2. Effectively, the construction of the local object t can be viewed as directly initializing the global object t2, and that object's destruction will occur at program exit. —end example]


185. "Named" temporaries and copy elision

Section: 12.8  class.copy     Status: TC1     Submitter: Bill Wade     Date: 11 Nov 1999

12.8  class.copy paragraph 15 refers only to "temporary class objects." It needs to be made clear that these provisions do not apply to temporaries that have been bound to references. For instance,

    struct A {
        mutable int value;
        explicit A(int i) : value(i) {}
        void mutate(int i) const { value = i; }
    };

    int foo() {
        A const& t = A(1);
        A n(t);          // can this copy be elided?
        t.mutate(2);
        return n.value;  // can this return 2?
    }
The current wording seems to allow an implementation not to perform the copy in A N(t) because the source object is a temporary (created explicitly by A(1)).

Proposed resolution (10/00):

Change the wording proposed in the resolution of issue 20 from

to




59. Clarification of overloading and UDC to reference type

Section: 13.3.1.4  over.match.copy     Status: TC1     Submitter: Steve Adamczyk     Date: 13 Oct 1998

Sections 13.3.1.4  over.match.copy and 13.3.1.5  over.match.conv should be clarified regarding the treatment of conversion functions which return reference types.

Proposed resolution (10/99):

In 13.3.1.4  over.match.copy paragraph 1, change

Conversion functions that return "reference to T" return lvalues of type T and are therefore considered to yield T for this process of selecting candidate functions.
to
Conversion functions that return "reference to X" return lvalues of type X and are therefore considered to yield X for this process of selecting candidate functions.
In 13.3.1.5  over.match.conv paragraph 1, change
Conversion functions that return "reference to T" return lvalues of type T and are therefore considered to yield T for this process of selecting candidate functions.
to
Conversion functions that return "reference to cv2 X" return lvalues of type "cv2 X" and are therefore considered to yield X for this process of selecting candidate functions.



51. Overloading and user-defined conversions

Section: 13.3.3  over.match.best     Status: TC1     Submitter: Steve Adamczyk     Date: 13 Oct 1998

In 13.3.3  over.match.best paragraph 1, bullet 4 of the second set of bullets, there is a cross-reference to 8.5  dcl.init and 13.3.1.5  over.match.conv . I believe it should also reference 13.3.1.6  over.match.ref . I think the phrase "initialization by user-defined conversion" was intended to refer to all initializations using user-defined conversions, and not just the case in 13.3.1.5  over.match.conv . Referring to only 13.3.1.5  over.match.conv suggests a narrower meaning of the phrase.

13.3.1.4  over.match.copy , although it does deal with initialization by user-defined conversion, does not need to be referenced because it deals with class —> class cases, and therefore there are no standard conversions involved that could be compared.




84. Overloading and conversion loophole used by auto_ptr

Section: 13.3.3.1  over.best.ics     Status: TC1     Submitter: Steve Adamczyk     Date: 10 Dec 1998

By the letter of the standard, the conversions required to make auto_ptr work should be accepted.

However, there's good reason to wonder if there isn't a bug in the standard here. Here's the issue: line 16 in the example below comes down to

copy-initialize an auto_ptr<Base> from an auto_ptr<Derived> rvalue
To do that, we first look to see whether we can convert an auto_ptr<Derived> to an auto_ptr<Base>, by enumerating the constructors of auto_ptr<Base> and the conversion functions of auto_ptr<Derived>. There's a single possible way to do the conversion, namely the conversion function

    auto_ptr<Derived>::operator auto_ptr<Base>()
(generated from the template). (The constructor auto_ptr<Base>(auto_ptr_ref<Base>) doesn't work because it requires a user-defined conversion on the argument.)

So far, so good. Now, we do the copy step:

direct-initialize an auto_ptr<Base> from an auto_ptr<Base> rvalue
This, as we've gone to great lengths to set up, is done by calling the conversion function
    auto_ptr<Base>::operator auto_ptr_ref<Base>()
(generated from the template), and then the constructor
    auto_ptr<Base>(auto_ptr_ref<Base>)
(generated from the template).

The problem with this interpretation is that it violates the long-standing common-law rule that only a single user-defined conversion will be called to do an implicit conversion. I find that pretty disturbing. (In fact, the full operation involves two conversion functions and two constructors, but "copy" constructors are generally considered not to be conversions.)

The direct-initialization second step of a copy-initialization was intended to be a simple copy — you've made a temporary, and now you use a copy constructor to copy it. Because it is defined in terms of direct initialization, however, it can exploit the loophole that auto_ptr is based on.

To switch to personal opinion for a second, I think it's bad enough that auto_ptr has to exploit a really arcane loophole of overload resolution, but in this case it seems like it's exploiting a loophole on a loophole.

    struct Base {                             //  2
       static void sink(auto_ptr<Base>);      //  3
    };                                        //  4

    struct Derived : Base {                   //  5
       static void sink(auto_ptr<Derived>);   //  6
    };                                        //  7

    auto_ptr<Derived> source() {              //  8
       auto_ptr<Derived> p(source());         //  9
       auto_ptr<Derived> pp(p);               // 10
       Derived::sink(source());               // 11
       p = pp;                                // 12
       p = source();                          // 13
       auto_ptr<Base> q(source());            // 14
       auto_ptr<Base> qp(p);                  // 15
       Base::sink(source());                  // 16
       q = pp;                                // 17
       q = source();                          // 18
       return p;                              // 19
       return source();
    }
Derek Inglis:

It seems clear to me that the result of this direct initilization must be the second standard conversion sequence in a user defined conversion sequence. Otherwise the resulting conversion sequence is not an implicit conversion sequence. By the letter of the standard, the sequence of conversions making up a copy-initialization must be an implicit conversion sequence.

Paragraph 3 of clause 4  conv:

An expression e can be implicitly converted to a type T if and only if the declaration "T t=e;" is well-formed, for some invented temporary variable t (8.5  dcl.init).

Paragraph 1 of 13.3.3.1  over.best.ics:

An implicit conversion sequence is a sequence of conversions used to convert an argument in a function call to the type of the corresponding parameter of the function being called. The sequence of conversions is an implicit conversion as defined in clause 4  conv, which means it is governed by the rules for initialization of an object or reference by a single expression (8.5  dcl.init, 8.5.3  dcl.init.ref).
Sentence 1 of paragraph 12 of 8.5  dcl.init:
The initialization that occurs in argument passing ... is called copy-initialization and is equivalent to the form
     T x = a;

For me, these sentences imply that all sequences of conversions permitted on a function argument must be valid implicit conversion sequences.

The 'loophole' can be closed by adding a sentence (or note) to the section describing the 'direct initialization second step of a copy initialization' stating that the copy initialization is ill-formed if the conversion sequence resulting from the direct initialization is not a standard conversion sequence.

(See also issue 177 and paper J16/00-0009 = WG21 N1232.)

Proposed resolution (10/00):

Change 13.3.3.1  over.best.ics paragraphs 3 and 4 from

Except in the context of an initialization by user-defined conversion (13.3.1.4  over.match.copy, 13.3.1.5  over.match.conv), a well-formed implicit conversion sequence is one of the following forms:

In the context of an initialization by user-defined conversion (i.e., when considering the argument of a user-defined conversion function; see 13.3.1.4  over.match.copy, 13.3.1.5  over.match.conv), only standard conversion sequences and ellipsis conversion sequences are allowed.

to

A well-formed implicit conversion sequence is one of the following forms:

However, when considering the argument of a user-defined conversion function that is a candidate by 13.3.1.3  over.match.ctor when invoked for the copying of the temporary in the second step of a class copy-initialization, or by 13.3.1.4  over.match.copy, 13.3.1.5  over.match.conv, or 13.3.1.6  over.match.ref in all cases, only standard conversion sequences and ellipsis conversion sequences are allowed.




83. Overloading and deprecated conversion of string literal

Section: 13.3.3.2  over.ics.rank     Status: TC1     Submitter: Steve Adamczyk     Date: 24 Jan 1999

In 13.3.3.2  over.ics.rank , we have

This does not work right with respect to the deprecated conversion from string literal to "char *". Consider
    void f(char *);
    void f(const char *);
    
    f("abc");
The two conversion sequences differ only in their qualification conversions, and the destination types are similar. The cv-qualification signature of "char *", is a proper subset of the cv-qualification signature of "const char *", so f(char *) is chosen, which is wrong. The rule should be like the one for conversion to bool — the deprecated conversion should be worse than another exact match that is not the deprecated conversion.

Proposed resolution (10/00):

Change 13.3.3.2  over.ics.rank paragraph 3 bullet 1 sub-bullet 3 from

S1 and S2 differ only in their qualification conversion and yield similar types T1 and T2 (4.4  conv.qual ), respectively, and the cv-qualification signature of type T1 is a proper subset of the cv-qualification signature of type T2.
to
S1 and S2 differ only in their qualification conversion and yield similar types T1 and T2 (4.4  conv.qual ), respectively, and the cv-qualification signature of type T1 is a proper subset of the cv-qualification signature of type T2, and S1 is not the deprecated string literal array-to-pointer conversion (4.2  conv.array ).



153. Misleading wording (rank of conversion)

Section: 13.3.3.2  over.ics.rank     Status: TC1     Submitter: Valentin Bonnard     Date: 6 Aug 1999

13.3.3.2  over.ics.rank paragraph 3 bullet 1 sub-bullet 2 says,

the rank of S1 is better than the rank of S2 (by the rules defined below)...
This wording is confusing. The word "below" refers to paragraph 4 (which may not be clear), and the bulk of paragraph 4 deals with comparing conversion sequences whose "rank" is the same.

Proposed resolution (10/00):

In 13.3.3.2  over.ics.rank paragraph 3, change

the rank of S1 is better than the rank of S2 (by the rules defined below)
to
the rank of S1 is better than the rank of S2, or S1 and S2 have the same rank and are distinguishable by the rules in the paragraph below



202. Use of overloaded function name

Section: 13.4  over.over     Status: TC1     Submitter: Steve Clamage     Date: 2 Feb 2000

13.4  over.over paragraph 1 contains a supposedly exhaustive list of contexts in which the name of an overloaded function can be used without an argument list ("...shall not be used without arguments in contexts other than those listed"). However, 14.3.2  temp.arg.nontype paragraph 5, bullet 4 gives another context: as a template nontype argument.

Suggested resolution: Add the missing case to 13.4  over.over.

Proposed resolution (10/00):

Add as the final bullet in 13.4  over.over paragraph 1:

and adjust the "or" and final period on the preceding two bullets.




250. Address of function template specialization with non-deduced template arguments

Section: 13.4  over.over     Status: TC1     Submitter: Nikolas Kauer     Date: 10 Oct 2000

13.4  over.over paragraph 2 says,

If the name is a function template, template argument deduction is done (14.8.2.2  temp.deduct.funcaddr), and if the argument deduction succeeds, the deduced template arguments are used to generate a single template function, which is added to the set of overloaded functions considered.

It is not clear whether this formulation allows explicit specification of non-deduced template arguments. For instance,

    template <int I> void f(double x[]);
    typedef void (*FPtr)(double x[]);
    FPtr fp = &f<3>;

If only deduced arguments can be used, this example is ill-formed.

Suggested resolution: Clarify 13.4  over.over paragraph 2 to allow both deduced and explicitly-specified template arguments to be used to determine the function template specialization to be added to the overload set.

(See also issues 115 and 214.)

Proposed resolution (10/00):

In 13.4  over.over paragraph 2, change

...if the argument deduction succeeds, the deduced template arguments are used to generate a single template function...

to

...if the argument deduction succeeds, the resulting template argument list is used to generate a single function template specialization...



32. Clarification of explicit instantiation of non-exported templates

Section: 14  temp     Status: TC1     Submitter: Daveed Vandevoorde     Date: 10 Jul 1998

Section 14  temp paragraph 8 says:

A non-exported template that is neither