Document number:   J16/02-0024 = WG21 N1366
Date:  10 May, 2002
Project:  Programming Language C++
Reference:  ISO/IEC IS 14882:1998(E)
Reply to:  J. Stephen Adamczyk
 jsa@edg.com


C++ Standard Core Language Active Issues, Revision 22

Committee Version


This document contains the C++ core language issues on which the Committee (J16 + WG21) has not yet acted, that is, issues issues with status "Ready," "Review," "Drafting," and "Open."

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:

The purpose of these documents is to record the disposition of issues which have come before the Core Language Working Group of the ANSI (J16) and ISO (WG21) C++ Standard Committee.

Issues represent potential defects in the ISO/IEC IS 14882:1998(E) document; they are not necessarily formal ISO Defect Reports (DRs). While some issues will eventually be elevated to DR status, others will be disposed of in other ways. (See Issue Status below.)

This set of documents exists in two slightly different versions. The Committee Version (this version) is the master version and should be made available only to committee members. The Public Version is extracted from the Committee Version by removal of sensitive and/or unnecessary information, such as drafting assignments, reflector message numbers, and the like.

Committee members with the HTML version of the IS may wish to place the issues list documents in the same directory; references to the IS in the issues can then be explored by hyperlink.

The most current public version of this document can be found at http://www.dkuug.dk/jtc1/sc22/wg21. Requests for further information about these documents should include the document number, reference ISO/IEC 14882:1998(E), and be submitted to the Information Technology Information Council (ITI), 1250 Eye Street NW, Washington, DC 20005, USA.

Information regarding how to obtain a copy of the C++ Standard, join the Standard Committee, or submit an issue can be found in the C++ FAQ at http://www.research.att.com/~austern/csc/faq.html . Public discussion of the C++ Standard and related issues occurs on newsgroup comp.std.c++.


Revision History

Issue status

Issues progress through various statuses as the Core Language Working Group and, ultimately, the full J16 and WG21 committees deliberate and act. For ease of reference, issues are grouped in these documents by their status. Issues have one of the following statuses:

Open: The issue is new or the working group has not yet formed an opinion on the issue. If a Suggested Resolution is given, it reflects the opinion of the issue's submitter, not necessarily that of the working group or the Committee as a whole.

Drafting: Informal consensus has been reached in the working group and is described in rough terms in a Tentative Resolution, although precise wording for the change is not yet available.

Review: Exact wording of a Proposed Resolution is now available for an issue on which the working group previously reached informal consensus.

Ready: The working group has reached consensus that the issue is a defect in the Standard, the Proposed Resolution is correct, and the issue is ready to forward to the full Committee for ratification as a proposed defect report.

DR: The full J16 Committee has approved the item as a proposed defect report. The Proposed Resolution in an issue with this status reflects the best judgment of the Committee at this time regarding the action that will be taken to remedy the defect; however, the current wording of the Standard remains in effect until such time as a Technical Corrigendum or a revision of the Standard is issued by ISO.

Dup: The issue is identical to or a subset of another issue, identified in a Rationale statement.

NAD: The working group has reached consensus that the issue is not a defect in the Standard. A Rationale statement describes the working group's reasoning.

Extension: The working group has reached consensus that the issue is not a defect in the Standard but is a request for an extension to the language. Under ISO rules, extensions cannot be considered for at least five years from the approval of the Standard, at which time the Standard will be open for review. The working group expresses no opinion on the merits of an issue with this status; however, the issue will be maintained on the list for possible future consideration when extension proposals will be in order.


Issues with "Ready" Status


261. When is a deallocation function "used?"

Section: 3.2  basic.def.odr     Status: ready     Submitter: Mike Miller     Date: 7 Nov 2000     Priority: 2     Drafting: Maurer

From message 8976.

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: ready     Submitter: Mike Miller     Date: 25 May 2001     Priority: 0     Drafting: Adamczyk

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:




281. inline specifier in friend declarations

Section: 7.1.2  dcl.fct.spec     Status: ready     Submitter: John Spicer     Date: 24 Apr 2001     Priority: 2     Drafting: Spicer

(From message 9134.)

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.



283. Template type-parameters are not syntactically type-names

Section: 7.1.5.2  dcl.type.simple     Status: ready     Submitter: Clark Nelson     Date: 01 May 2001     Priority: 1     Drafting: Nelson

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".




172. Unsigned int as underlying type of enum

Section: 7.2  dcl.enum     Status: ready     Submitter: Bjarne Stroustrup     Date: 26 Sep 1999     Priority: 2     Drafting: Adamczyk

From reflector messages 8332-4, 8337.

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. Steve Adamczyk is investigating this alternative.

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.




29. Linkage of locally declared functions

Section: 7.5  dcl.link     Status: ready     Submitter: Mike Ball     Date: 19 Mar 1998     Drafting: Gibbons/Adamczyk

[This was never explicitly voted to DR status, even though it was 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.]

From reflector message core-7714.

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.




295. cv-qualifiers on function types

Section: 8.3.5  dcl.fct     Status: ready     Submitter: Nathan Sidwell     Date: 29 Jun 2001     Priority: 1     Drafting: Maurer

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 in message 9423): 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.




302. Value-initialization and generation of default constructor

Section: 8.5  dcl.init     Status: ready     Submitter: Steve Adamczyk     Date: 23 Jul 2001     Priority: 1     Drafting: Adamczyk

From message 9258.

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).



273. POD classes and operator&()

Section: class     Status: ready     Submitter: Andrei Iltchenko     Date: 10 Mar 2001     Priority: 2     Drafting: Maurer

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: ready     Submitter: Mike Miller     Date: 01 May 2001     Priority: 1     Drafting: Vandevoorde

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.



296. Can conversion functions be static?

Section: 12.3.2  class.conv.fct     Status: ready     Submitter: Scott Meyers     Date: 5 Jul 2001     Priority: 3

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: ready     Submitter: John Spicer     Date: 6 Sep 2000     Priority: 1     Drafting: Merrill

From messages 8895-6.

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, in message 9325: 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, in message 9328: 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
}



162. (&C::f)() with nonstatic members

Section: 13.3.1.1  over.match.call     Status: ready     Submitter: Steve Adamczyk     Date: 26 Aug 1999     Priority: 3     Drafting: Adamczyk

From reflector messages 8289 and 8305.

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.)



60. Reference binding and valid conversion sequences

Section: 13.3.3.1.4  over.ics.ref     Status: ready     Submitter: Steve Adamczyk     Date: 13 Oct 1998     Priority: 2     Drafting: Adamczyk

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.



63. Class instantiation from pointer conversion to void*, null and self

Section: 14.7.1  temp.inst     Status: ready     Submitter: Steve Adamczyk     Date: 13 Oct 1998     Priority: 3     Drafting: Merrill

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.)




300. References to functions in template argument deduction

Section: 14.8.2.4  temp.deduct.type     Status: ready     Submitter: Andrei Iltchenko     Date: 11 Jul 2001     Priority: 0     Drafting: Adamczyk

Paragraph 9 of 14.8.2.4  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.4  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.4  temp.deduct.type replace

type(*)(T)
T(*)()
T(*)(T)

by

type(T)
T()
T(T)





Issues with "Review" Status


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

Section: 3.4.3.1  class.qual     Status: review     Submitter: Steve Adamczyk     Date: 7 Jul 2001     Priority: 2     Drafting: Nelson

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.




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

Section: 3.4.3.1  class.qual     Status: review     Submitter: John Spicer     Date: 18 Oct 2001     Priority: 0     Drafting: Spicer

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 (4/02):

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



245. Name lookup in elaborated-type-specifiers

Section: 3.4.4  basic.lookup.elab     Status: review     Submitter: Jack Rouse     Date: 14 Sep 2000     Priority: 1     Drafting: Nelson

From message 8898.

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.)

Proposed resolution (04/01):

The guiding principle is to deal only with actual name lookup issues in 3.4.4  basic.lookup.elab, and to deal with issues of conflict with prior declarations (found by name lookup) in 7.1.5.3  dcl.type.elab.

  1. Change 3.4.4  basic.lookup.elab paragraph 1:

    An elaborated-type-specifier (7.1.5.3  dcl.type.elab) may be used to refer to a previously declared class-name or enum-name even though the name has been hidden by a non-type declaration (3.3.7  basic.scope.hiding). The class-name or enum-name in the elaborated-type-specifier may be either be a simple identifer or be a qualified-id.
  2. Change 3.4.4  basic.lookup.elab paragraph 2:

    If the name in the elaborated-type-specifier is a simple identifer, and unless the elaborated-type-specifier has the following form:
    class-key identifier ;
    the identifier is looked up according to 3.4.1  basic.lookup.unqual but ignoring any non-type names that have been declared. If this name lookup finds a typedef-name, the elaborated-type-specifier is ill-formed. If the elaborated-type-specifier refers to an enum-name is introduced by the enum keyword and this lookup does not find a previously declared enum-name type-name, the elaborated-type-specifier is ill-formed. If the elaborated-type-specifier refers to an class-name is introduced by a class-key and this lookup does not find a previously declared class-name type-name, or if the elaborated-type-specifier has the form:
    class-key identifier ;
    the elaborated-type-specifier is a declaration that introduces the class-name as described in 3.3.1  basic.scope.pdecl.
  3. Change 3.4.4  basic.lookup.elab paragraph 3:

    If the name is a qualified-id, the name is looked up according its qualifications, as described in 3.4.3  basic.lookup.qual, but ignoring any non-type names that have been declared. If this name lookup finds a typedef-name, the elaborated-type-specifier is ill-formed. If this name lookup does not find a previously declared class-name or enum-name type-name, the elaborated-type-specifier is ill-formed.
  4. To eliminate redundancy, in 7.1.5.3  dcl.type.elab paragraph 2, delete:

    If name lookup does not find a declaration for the name, the elaborated-type-specifier is ill-formed unless it is of the simple form class-key identifier in which case the identifier is declared as described in 3.3.1  basic.scope.pdecl.

(This resolution assumes that the disjunction between type-names and template type-parameters described in issue 283 is resolved. It also depends on the resolution of issue 215 for the grammar of nested-name-specifers, so that all type-names and not just class-names and namespace-names can participate in the lookup.)

Additional changes (Clark Nelson, March 2002):

In 3.4.4  basic.lookup.elab paragraph 2: The grammar of elaborated-type-specifier does not allow a semicolon. Change "the elaborated-type-specifier has the [following] form" (which occurs twice) to "the elaborated-type-specifier appears in a declaration with the form".

The grammar of elaborated-type-specifier has no class-name, enum-name or qualified-id. Therefore, editorially:

Notes from the 4/02 meeting:

This will be consolidated with the changes for issue 254.




254. Definitional problems with elaborated-type-specifiers

Section: 3.4.4  basic.lookup.elab     Status: review     Submitter: Clark Nelson     Date: 26 Oct 2000     Priority: 2     Drafting: Nelson
  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.

Proposed resolution (Clark Nelson, March 2002):

GRAMMAR CHANGES

In 7.1.5.3  dcl.type.elab: Move the typename alternatives from the grammar rule for elaborated-type-specifier into a new rule named typename-specifier.

In 7.1.5  dcl.type: Add typename-specifier as a new alternative for type-specifier.

In 5.2  expr.post: Replace the typename alternatives in postfix-expression with

TEXT CHANGES

In 3.3.1  basic.scope.pdecl paragraph 5, first bullet: The grammar of elaborated-type-specifier does not allow a semicolon; therefore change "an elaborated-type-specifier of the form" to "a declaration of the form". Editorially, change "the elaborated-type-specifier declares the identifier" to "the identifier is declared".

In 3.3.1  basic.scope.pdecl paragraph 5, second bullet: Editorially, change the note to: "Other forms of elaborated-type-specifier do not declare a new name, and therefore must refer to an existing type-name. See 3.4.4  basic.lookup.elab and 7.1.5.3  dcl.type.elab."

9.1  class.name paragraph 3 is implied by 3.4.4  basic.lookup.elab, so it should be a note.

9.1  class.name paragraph 5 is implied by 7.1.3  dcl.typedef and 7.1.5.3  dcl.type.elab, so it should be a note.

In 14.6  temp.res paragraph 3, change both instances of elaborated-type-specifier to typename-specifier.

Notes from the 4/02 meeting:

This will be consolidated with the changes for issue 245.




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

Section: 3.8  basic.life     Status: review     Submitter: Mike Miller     Date: 14 Mar 2001     Priority: 0     Drafting: Vandevoorde

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:




258. using-declarations and cv-qualifiers

Section: 7.3.3  namespace.udecl     Status: review     Submitter: Liam Fitzpatrick     Date: 2 Nov 2000     Priority: 0     Drafting: Vandevoorde

From messages 8946 and 8948.

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.




262. Default arguments and ellipsis

Section: 8.3.5  dcl.fct     Status: review     Submitter: Jamie Schmeiser     Date: 13 Nov 2000     Priority: 3     Drafting: Schmeiser

From messages 8977 and 8984.

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



328. Missing requirement that class member types be complete

Section: 9.2  class.mem     Status: review     Submitter: Michiel Salters     Date: 10 Dec 2001     Priority: 0     Drafting: Vandevoorde

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 (4/02):

Add after the first bullet of the note in 3.2  basic.def.odr paragraph 4:

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 cl shall not contain a non-static member of class cl, but it can contain a pointer or reference to an object of class cl.

See also 3.9  basic.types paragraph 6, which is relevant but not changed by the Proposed Resolution.




198. Definition of "use" in local and nested classes

Section: 9.8  class.local     Status: review     Submitter: Erwin Unruh     Date: 27 Jan 2000     Drafting: Wilcox

From reflector messages 8501-2, 8507.

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 (4/02):

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 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 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).

Note: Should the above be "each occurrence of the name in an expression" so as not to rule out declarations?

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



39. Conflicting ambiguity rules

Section: 10.2  class.member.lookup     Status: review     Submitter: Neal M Gafter     Date: 20 Aug 1998     Drafting: Merrill

From reflector message core-7816.

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 (10/01):

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.

A lookup set consists of two distinct sets: a set of members named f and 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 the injected-class-name) are replaced by the types they designate. The lookup set for f in C SC 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.

If C does not contain a declaration of f, SC is initially empty. If C does not contain a declaration of f but has base classes, begin with that empty set, calculate the lookup set for f in each base class subjobject Bn, and merge each such lookup set Sn in turn into SC.

The following steps define the result of merging lookup set Si for Bi into the intermediate SC:

The result of name lookup for f in C is the set of declarations from the lookup set for f in C.

Turn 10.2  class.member.lookup paragraphs 5 and 6 into notes.

Note: This also resolves issue 306.

Notes from the 4/02 meeting:

We discussed this at length, but less productively than one would like due to the absence of Jason Merrill. A fair amount of rewording led to the following version, which we now leave to Jason to reconcile with his version. Of particular concern was the fact that it was not clear how the clauses were to be grouped given the "otherwise"s.

It was also noted that this section probably should define "ambiguous base class" but doesn't, and that it needs to deal with typedefs (they get mapped to the underlying type. See issue 306).

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, S(C), consists of two distinct sets: a set of declarations for members named f [Drafting note: anybody thinks it's ambiguous what's named f?], and 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 the injected-class-name) are replaced by the types they designate. The lookup set for f in C, S(C), S(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(C) is initially empty. If C has base classes, calculate the lookup set for f in each base class subobject Bi, and merge each such lookup set S(Bi) in turn into S(C).

The following steps define the result of merging two lookup sets Sp and Sq Si for Bi into a new lookup set S, or determine that the lookup is ambiguous and no merged lookup set S is constructed:

  1. If one of the lookup sets is empty, the other is taken as the new S.
  2. Otherwise, Remove from Sp any subobject that is a base class subobject of at least one of the subobjects in Sq. Similarly, remove from Sq any subobject that is a base class subobject of at least one of the subobjects in Sp.
  3. If one of the lookup sets has no subobjects left after this step, the other is taken as the new S.
  4. Otherwise, if Sp and Sq do not contain the same set of declarations, the lookup is ambiguous.
  5. Otherwise, consider each declaration d in the set, where d is a member of some class A. If d is a nonstatic member, compare the subobjects having type A among the subobjects members in Sp and Sq; if they do not match, the lookup is ambiguous. [Note: It is not necessary to remember which subobject having type A each member comes from, since using-declarations don't disambiguate. ]
  6. Otherwise, the new S is a lookup set with the shared set of declarations and the union of the subobject sets.

The result of name lookup for f in C is the set of declarations from the lookup set for f in C.

Turn 10.2  class.member.lookup paragraphs 5 and 6 into notes.




306. Ambiguity by class name injection

Section: 10.2  class.member.lookup     Status: review     Submitter: Clark Nelson     Date: 19 Jul 2001     Priority: 1     Drafting: Merrill

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




263. Can a constructor be declared a friend?

Section: 12.1  class.ctor     Status: review     Submitter: Martin Sebor     Date: 13 Nov 2000     Priority: 2     Drafting: Schmeiser

From messages 8978-83 and 8985-6.

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 (4/02):

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 7.1.5  dcl.type paragraph 2 as follows:

At least one typ