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 type-specifier that is not a cv-qualifier is required in a declaration unless it declares names a constructor, destructor, or conversion function.

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: review     Submitter: James Kanze     Date: 9 Dec 2001     Priority: 0     Drafting: Nelson

From message 9419.

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

The fix is simply to clarify that the definition in 12.1  class.ctor paragraph 5-6 applies only to default constructors, by inserting the word "default" in the appropriate spots.

However, in looking at the paragraph, I found some instances of italicized words which are not being defined. I propose also to remove the italics.

The problem of inappropriate italics also affects 12.4  class.dtor paragraph 3, in the description of a trivial destructor. The italics should also be removed from there, but as there is no actual wording change, I don't bother to illustrate the changes here.

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.




280. Access and surrogate call functions

Section: 13.3.1.1.2  over.call.object     Status: review     Submitter: Andrei Iltchenko     Date: 16 Apr 2001     Priority: 2     Drafting: Adamczyk

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.

Proposed resolution (10/01, same as suggested resolution):

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. If such a surrogate function is eventually selected as the best viable function and its corresponding conversion function is from an ambiguous (10.2  class.member.lookup) base class of T, the program is ill-formed.

Notes from 4/02 meeting:

There was some concern about whether 10.2  class.member.lookup (or anthing else, for that matter) actually defines "ambiguous base class". See issue 39. See also issue 156.




204. Exported class templates

Section: 14  temp     Status: review     Submitter: Robert Klarer     Date: 11 Feb 2000     Priority: 2     Drafting: Vandevoorde

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: review     Submitter: Daveed Vandevoorde     Date: 14 Nov 2001     Priority: 1     Drafting: Vandevoorde

From message 9386.

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

Replace 14  temp paragraphs 6, 7, and 8 by the following text:

A template-declaration may be preceded by the export keyword. 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: review     Submitter: John Spicer     Date: 30 Jan 2002     Priority: 1     Drafting: Spicer

From messages 9468, 9470.

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.




184. Default arguments in template template-parameters

Section: 14.1  temp.param     Status: review     Submitter: John Spicer     Date: 11 Nov 1999     Priority: 2     Drafting: Spicer

From reflector messages 8356-60.

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

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



226. Default template arguments for function templates

Section: 14.1  temp.param     Status: review     Submitter: Bjarne Stroustrup     Date: 19 Apr 2000     Priority: 2     Drafting: Spicer

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

  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.8  temp.fct.spec 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 must be a definition and shall be the only declaration of the function template in the translation unit.
  3. In 14.8  temp.fct.spec 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.
  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.




228. Use of template keyword with non-member templates

Section: 14.2  temp.names     Status: review     Submitter: Daveed Vandevoorde     Date: 4 May 2000     Priority: 2     Drafting: Vandevoorde

From reflector messages 8726-7.

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.



287. Order dependencies in template instantiation

Section: 14.6.4.1  temp.point     Status: review     Submitter: Martin Sebor     Date: 17 May 2001     Priority: 1     Drafting: Spicer

From messages 9163-70. The discussion did not appear to have concluded at the time of this writing, so additional messages may deal with this topic.

Implementations differ in their treatment of the following code:

    template <class T>
    struct A {
	typename T::X x;
    };

    template <class T>
    struct B {
	typedef T* X;
	A<B> a;
    };

    int main ()
    {
	B<int> b;
    }

Some implementations accept it. At least one rejects it because the instantiation of A<B<int> > requires that B<int> be complete, and it is not at the point at which A<B<int> > is being instantiated.

Erwin Unruh:

In my view the programm is ill-formed. My reasoning:

So each class needs the other to be complete.

The problem can be seen much easier if you replace the typedef with

    typedef T (*X) [sizeof(B::a)];

Now you have a true recursion. The compiler cannot easily distinguish between a true recursion and a potential recursion.

John Spicer:

Using a class to form a qualified name does not require the class to be complete, it only requires that the named member already have been declared. In other words, this kind of usage is permitted:

    class A {
        typedef int B;
        A::B ab;
    };

In the same way, once B has been declared in A, it is also visible to any template that uses A through a template parameter.

The standard could be more clear in this regard, but there are two notes that make this point. Both 3.4.3.1  class.qual and 5.1  expr.prim paragraph 7 contain a note that says "a class member can be referred to using a qualified-id at any point in its potential scope (3.3.6  basic.scope.class)." A member's potential scope begins at its point of declaration.

In other words, a class has three states: incomplete, being completed, and complete. The standard permits a qualified name to be used once a name has been declared. The quotation of the notes about the potential scope was intended to support that.

So, in the original example, class A does not require the type of T to be complete, only that it have already declared a member X.

Bill Gibbons:

The template and non-template cases are different. In the non-template case the order in which the members become declared is clear. In the template case the members of the instantiation are conceptually all created at the same time. The standard does not say anything about trying to mimic the non-template case during the instantiation of a class template.

Mike Miller:

I think the relevant specification is 14.6.4.1  temp.point paragraph 3, dealing with the point of instantiation:

For a class template specialization... if the specialization is implicitly instantiated because it is referenced from within another template specialization, if the context from which the specialization is referenced depends on a template parameter, and if the specialization is not instantiated previous to the instantiation of the enclosing template, the point of instantiation is immediately before the point of instantiation of the enclosing template. Otherwise, the point of instantiation for such a specialization immediately precedes the namespace scope declaration or definition that refers to the specialization.

That means that the point of instantiation of A<B<int> > is before that of B<int>, not in the middle of B<int> after the declaration of B::X, and consequently a reference to B<int>::X from A<B<int> > is ill-formed.

To put it another way, I believe John's approach requires that there be an instantiation stack, with the results of partially-instantiated templates on the stack being available to instantiations above them. I don't think the Standard mandates that approach; as far as I can see, simply determining the implicit instantiations that need to be done, rewriting the definitions at their respective points of instantiation with parameters substituted (with appropriate "forward declarations" to allow for non-instantiating references), and compiling the result normally should be an acceptable implementation technique as well. That is, the implicit instantiation of the example (using, e.g., B_int to represent the generated name of the B<int> specialization) could be something like

        struct B_int;

        struct A_B_int {
            B_int::X x;    // error, incomplete type
        };

        struct B_int {
            typedef int* X;
            A_B_int a;
        };

Notes from 10/01 meeting:

This was discussed at length. The consensus was that the template case should be treated the same as the non-template class case it terms of the order in which members get declared/defined and classes get completed.

Proposed resolution:

In 14.6.4.1  temp.point paragraph 3 change:

the point of instantiation is immediately before the point of instantiation of the enclosing template. Otherwise, the point of instantiation for such a specialization immediately precedes the namespace scope declaration or definition that refers to the specialization.

To:

the point of instantiation is the same as the point of instantiation of the enclosing template. Otherwise, the point of instantiation for such a specialization immediately precedes the nearest enclosing declaration. [Note: The point of instantiation is still at namespace scope but any declarations preceding the point of instantiation, even if not at namespace scope, are considered to have been seen.]

Add following paragraph 3:

If an implicitly instantiated class template specialization, class member specialization, or specialization of a class template references a class, class template specialization, class member specialization, or specialization of a class template containing a specialization reference that directly or indirectly caused the instantiation, the requirements of completeness and ordering of the class reference are applied in the context of the specialization reference.

and the following example

  template <class T> struct A {
          typename T::X x;
  };

  struct B {
          typedef int X;
          A<B> a;
  };

  template <class T> struct C {
          typedef T* X;
          A<C> a;
  };

  int main ()
  {
          C<int> c;
  }



182. Access checking on explicit specializations

Section: 14.7.3  temp.expl.spec     Status: review     Submitter: John Spicer     Date: 8 Nov 1999     Priority: 2     Drafting: Caves

From reflector messages 8348-8352.

John Spicer: Certain access checks are suppressed on explicit instantiations. 14.7.2  temp.explicit paragraph 8 says:

The usual access checking rules do not apply to names used to specify explicit instantiations. [Note: In particular, the template arguments and names used in the function declarator (including parameter types, return types and exception specifications) may be private types or objects which would normally not be accessible and the template may be a member template or member function which would not normally be accessible. ]
I was surprised that similar wording does not exist (that I could find) for explicit specializations. I believe that the two cases should be handled equivalently in the example below (i.e., that the specialization should be permitted).
    template <class T> struct C {
    void f();
    void g();
    };

    template <class T> void C<T>::f(){}
    template <class T> void C<T>::g(){}

    class A {
    class B {};
    void f();
    };

    template void C<A::B>::f();    // okay
    template <> void C<A::B>::g(); // error - A::B inaccessible

    void A::f() {
    C<B> cb;
    cb.f();
    }

Mike Miller: According to the note in 14.3  temp.arg paragraph 3,

if the name of a template-argument is accessible at the point where it is used as a template-argument, there is no further access restriction in the resulting instantiation where the corresponding template-parameter name is used.

(Is this specified anywhere in the normative text? Should it be?)

In the absence of text to the contrary, this blanket permission apparently applies to explicitly-specialized templates as well as to implicitly-generated ones (is that right?). If so, I don't see any reason that an explicit instantiation should be treated differently from an explicit specialization, even though the latter involves new program text and the former is just a placement instruction to the implementation.

Proposed Resolution (4/02):

In 14.7.2  temp.explicit delete paragraph 8:

The usual access checking rules do not apply to names used to specify explicit instantiations. [Note: In particular, the template arguments and names used in the function declarator (including parameter types, return types and exception specifications) may be private types or objects which would normally not be accessible and the template may be a member template or member function which would not normally be accessible. ]

In 14.7  temp.spec add the paragraph deleted above as paragraph 7 with the changes highlighted below:

The usual access checking rules do not apply to names used to specify explicit instantiations or explicit specializations. [Note: In particular, tThe template arguments and names used in the function declarator (including parameter types, return types and exception specifications) may be private types or objects which would normally not be accessible and the template may be a member template or member function which would not normally be accessible. ]



337. Attempt to create array of abtract type should cause deduction to fail

Section: 14.8.2  temp.deduct     Status: review     Submitter: John Spicer     Date: 30 Jan 2002     Priority: 0     Drafting: Caves

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.



322. Deduction of reference conversions

Section: 14.8.2.3  temp.deduct.conv     Status: review     Submitter: Jason Merrill     Date: 14 Nov 2001     Priority: 1     Drafting: Spicer

From message 9385. (Gabriel Dos Reis and Nathan Sidwell raised the same issue a second time in message 9444. John Spicer replied in message 9479 with the same proposed solution that Jason Merrill gave here.)

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.






Issues with "Drafting" Status


225. Koenig lookup and fundamental types

Section: 3.4.2  basic.lookup.koenig     Status: drafting     Submitter: Derek Inglis     Date: 26 Jan 2000     Priority: 1     Drafting: Spicer

In discussing issue 197, the question arose as to whether the handling of fundamental types in argument-dependent lookup is actually what is desired. This question needs further discussion.




156. Name lookup for conversion functions

Section: 3.4.5  basic.lookup.classref     Status: drafting     Submitter: Derek Inglis     Date: 18 Aug 1999     Priority: 2     Drafting: Merrill

From reflector messages 8274, 8308, and 8311-13.

Paragraph 7 of 3.4.5  basic.lookup.classref says,

If the id-expression is a conversion-function-id, its conversion-type-id shall denote the same type in both the context in which the entire postfix-expression occurs and in the context of the class of the object expression (or the class pointed to by the pointer expression).
Does this mean that the following example is ill-formed?
    struct A { operator int(); } a;
    void foo() {
      typedef int T;
      a.operator T(); // 1) error T is not found in the context
		      // of the class of the object expression?
    }
The second bullet in paragraph 1 of 3.4.3.1  class.qual says,
a conversion-type-id of an operator-function-id is looked up both in the scope of the class and in the context in which the entire postfix-expression occurs and shall refer to the same type in both contexts
How about:
    struct A { typedef int T; operator T(); };
    struct B : A { operator T(); } b;
    void foo() {
      b.A::operator T(); // 2) error T is not found in the context
			 // of the postfix-expression?
    }
Is this interpretation correct? Or was the intent for this to be an error only if T was found in both scopes and referred to different entities?

If the intent was for these to be errors, how do these rules apply to template arguments?

    template <class T1> struct A { operator T1(); }
    template <class T2> struct B : A<T2> {
      operator T2();
      void foo() {
	T2 a = A<T2>::operator T2(); // 3) error? when instantiated T2 is not
				     // found in the scope of the class
	T2 b = ((A<T2>*)this)->operator T2(); // 4) error when instantiated?
      }
    }

(Note bullets 2 and 3 in paragraph 1 of 3.4.3.1  class.qual refer to postfix-expression. It would be better to use qualified-id in both cases.)

Erwin Unruh: The intent was that you look in both contexts. If you find it only once, that's the symbol. If you find it in both, both symbols must be "the same" in some respect. (If you don't find it, its an error).

Mike Miller: What's not clear to me in these examples is whether what is being looked up is T or int. Clearly the T has to be looked up somehow, but the "name" of a conversion function clearly involves the base (non-typedefed) type, not typedefs that might be used in a definition or reference (cf 3  basic paragraph 7 and 12.3  class.conv paragraph 5). (This is true even for types that must be written using typedefs because of the limited syntax in conversion-type-ids — e.g., the "name" of the conversion function in the following example

    typedef void (*pf)();
    struct S {
	operator pf();
    };
is S::operator void(*)(), even though you can't write its name directly.)

My guess is that this means that in each scope you look up the type named in the reference and form the canonical operator name; if the name used in the reference isn't found in one or the other scope, the canonical name constructed from the other scope is used. These names must be identical, and the conversion-type-id in the canonical operator name must not denote different types in the two scopes (i.e., the type might not be found in one or the other scope, but if it's found in both, they must be the same type).

I think this is all very vague in the current wording.




305. Name lookup in destructor call

Section: 3.4.5  basic.lookup.classref     Status: drafting     Submitter: Mark Mitchell     Date: 19 May 2001     Priority: 1     Drafting: Merrill

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 tyepdef for the desired type.

See also issue 244.




222. Sequence points and lvalue-returning operators

Section: expr     Status: drafting     Submitter: Andrew Koenig     Date: 20 Dec 1999     Priority: 2     Drafting: Crowl

From messages 8395, 8826-32, 8834, 8837, 8840-43, 8845, 8847, and 9297.

I believe that the committee has neglected to take into account one of the differences between C and C++ when defining sequence points. As an example, consider

    (a += b) += c;

where a, b, and c all have type int. I believe that this expression has undefined behavior, even though it is well-formed. It is not well-formed in C, because += returns an rvalue there. The reason for the undefined behavior is that it modifies the value of `a' twice between sequence points.

Expressions such as this one are sometimes genuinely useful. Of course, we could write this particular example as

    a += b; a += c;

but what about

    void scale(double* p, int n, double x, double y) {
        for (int i = 0; i < n; ++i) {
            (p[i] *= x) += y;
        }
    }

All of the potential rewrites involve multiply-evaluating p[i] or unobvious circumlocations like creating references to the array element.

One way to deal with this issue would be to include built-in operators in the rule that puts a sequence point between evaluating a function's arguments and evaluating the function itself. However, that might be overkill: I see no reason to require that in

    x[i++] = y;

the contents of `i' must be incremented before the assignment.

A less stringent alternative might be to say that when a built-in operator yields an lvalue, the implementation shall not subsequently change the value of that object as a consequence of that operator.

I find it hard to imagine an implementation that does not do this already. Am I wrong? Is there any implementation out there that does not `do the right thing' already for (a += b) += c?

5.17  expr.ass paragraph 1 says,

The result of the assignment operation is the value stored in the left operand after the assignment has taken place; the result is an lvalue.

What is the normative effect of the words "after the assignment has taken place"? I think that phrase ought to mean that in addition to whatever constraints the rules about sequence points might impose on the implementation, assignment operators on built-in types have the additional constraint that they must store the left-hand side's new value before returning a reference to that object as their result.

One could argue that as the C++ standard currently stands, the effect of x = y = 0; is undefined. The reason is that it both fetches and stores the value of y, and does not fetch the value of y in order to compute its new value.

I'm suggesting that the phrase "after the assignment has taken place" should be read as constraining the implementation to set y to 0 before yielding the value of y as the result of the subexpression y = 0.

Note that this suggestion is different from asking that there be a sequence point after evaluation of an assignment. In particular, I am not suggesting that an order constraint be imposed on any side effects other than the assignment itself.

Francis Glassborow:

My understanding is that for a single variable:

  1. Multiple read accesses without a write are OK
  2. A single read access followed by a single write (of a value dependant on the read, so that the read MUST happen first) is OK
  3. A write followed by an actual read is undefined behaviour
  4. Multiple writes have undefined behaviour

It is the 3) that is often ignored because in practice the compiler hardly ever codes for the read because it already has that value but in complicated evaluations with a shortage of registers, that is not always the case. Without getting too close to the hardware, I think we both know that a read too close to a write can be problematical on some hardware.

So, in x = y = 0;, the implementation must NOT fetch a value from y, instead it has to "know" what that value will be (easy because it has just computed that in order to know what it must, at some time, store in y). From this I deduce that computing the lvalue (to know where to store) and the rvalue to know what is stored are two entirely independent actions that can occur in any order commensurate with the overall requirements that both operands for an operator be evaluated before the operator is.

Erwin Unruh:

C distinguishes between the resulting value of an assignment and putting the value in store. So in C a compiler might implement the statement x=y=0; either as x=0;y=0; or as y=0;x=0; In C the statement (x += 5) += 7; is not allowed because the first += yields an rvalue which is not allowed as left operand to +=. So in C an assignment is not a sequence of write/read because the result is not really "read".

In C++ we decided to make the result of assignment an lvalue. In this case we do not have the option to specify the "value" of the result. That is just the variable itself (or its address in a different view). So in C++, strictly speaking, the statement x=y=0; must be implemented as y=0;x=y; which makes a big difference if y is declared volatile.

Furthermore, I think undefined behaviour should not be the result of a single mentioning of a variable within an expression. So the statement (x +=5) += 7; should NOT have undefined behaviour.

In my view the semantics could be:

  1. if the result of an assignment is used as an rvalue, its value is that of the variable after assignment. The actual store takes place before the next sequence point, but may be before the value is used. This is consistent with C usage.
  2. if the result of an assignment is used as an lvalue to store another value, then the new value will be stored in the variable before the next sequence point. It is unspecified whether the first assigned value is stored intermediately.
  3. if the result of an assignment is used as an lvalue to take an address, that address is given (it doesn't change). The actual store of the new value takes place before the next sequence point.

Jerry Schwarz:

My recollection is different from Erwin's. I am confident that the intention when we decided to make assignments lvalues was not to change the semantics of evaluation of assignments. The semantics was supposed to remain the same as C's.

Ervin seems to assume that because assignments are lvalues, an assignment's value must be determined by a read of the location. But that was definitely not our intention. As he notes this has a significant impact on the semantics of assignment to a volatile variable. If Erwin's interpretation were correct we would have no way to write a volatile variable without also reading it.

Lawrence Crowl:

For x=y=0, lvalue semantics implies an lvalue to rvalue conversion on the result of y=0, which in turn implies a read. If y is volatile, lvalue semantics implies both a read and a write on y.

The standard apparently doesn't state whether there is a value dependence of the lvalue result on the completion of the assignment. Such a statement in the standard would solve the non-volatile C compatibility issue, and would be consistent with a user-implemented operator=.

Another possible approach is to state that primitive assignment operators have two results, an lvalue and a corresponding "after-store" rvalue. The rvalue result would be used when an rvalue is required, while the lvalue result would be used when an lvalue is required. However, this semantics is unsupportable for user-defined assignment operators, or at least inconsistent with all implementations that I know of. I would not enjoy trying to write such two-faced semantics.

Erwin Unruh:

The intent was for assignments to behave the same as in C. Unfortunately the change of the result to lvalue did not keep that. An "lvalue of type int" has no "int" value! So there is a difference between intent and the standard's wording.

So we have one of several choices:

I think the last one has the least impact on existing programs, but it is an ugly solution.

Andrew Koenig:

Whatever we may have intended, I do not think that there is any clean way of making

    volatile int v;
    int i;

    i = v = 42;
have the same semantics in C++ as it does in C. Like it or not, the subexpression v = 42 has the type ``reference to volatile int,'' so if this statement has any meaning at all, the meaning must be to store 42 in v and then fetch the value of v to assign it to i.

Indeed, if v is volatile, I cannot imagine a conscientious programmer writing a statement such as this one. Instead, I would expect to see

    v = 42;
    i = v;
if the intent is to store 42 in v and then fetch the (possibly changed) value of v, or
    v = 42;
    i = 42;
if the intent is to store 42 in both v and i.

What I do want is to ensure that expressions such as ``i = v = 42'' have well-defined semantics, as well as expressions such as (i = v) = 42 or, more realistically, (i += v) += 42 .

I wonder if the following resolution is sufficient:

Append to 5.17  expr.ass paragraph 1:

There is a sequence point between assigning the new value to the left operand and yielding the result of the assignment expression.

I believe that this proposal achieves my desired effect of not constraining when j is incremented in x[j++] = y, because I don't think there is a constraint on the relative order of incrementing j and executing the assignment. However, I do think it allows expressions such as (i += v) += 42, although with different semantics from C if v is volatile.

Notes on 10/01 meeting:

There was agreement that adding a sequence point is probably the right solution.

Notes from the 4/02 meeting:

The working group reaffirmed the sequence-point solution, but we will look for any counter-examples where efficiency would be harmed.

For drafting, we note that ++x is defined in 5.3.2  expr.pre.incr as equivalent to x+=1 and is therefore affected by this change. x++ is not affected. Also, we should update any list of all sequence points.




125. Ambiguity in friend declaration syntax

Section: 5.1  expr.prim     Status: drafting     Submitter: Martin von Loewis     Date: 7 June 1999     Drafting: Nelson

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 in message 9377:

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 in message 9378:

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.




118. Calls via pointers to virtual member functions

Section: 5.2.2  expr.call     Status: drafting     Submitter: Martin O'Riordan     Date: 17 May 1999     Drafting: O'Riordan

From reflector messages 8063, 8067-8071, 8080, 8082-8090.

Martin O'Riordan: Having gone through all the relevant references in the IS, it is not conclusive that a call via a pointer to a virtual member function is polymorphic at all, and could legitimately be interpreted as being static.

Consider 5.2.2  expr.call paragraph 1:

The function called in a member function call is normally selected according to the static type of the object expression (clause 10  class.derived ), but if that function is virtual and is not specified using a qualified-id then the function actually called will be the final overrider (10.3  class.virtual ) of the selected function in the dynamic type of the object expression.
Here it is quite specific that you get the polymorphic call only if you use the unqualified syntax. But, the address of a member function is "always" taken using the qualified syntax, which by inference would indicate that call with a PMF is static and not polymorphic! Not what was intended.

Yet other references such as 5.5  expr.mptr.oper paragraph 4:

If the dynamic type of the object does not contain the member to which the pointer refers, the behavior is undefined.
indicate that the opposite may have been intended, by stating that it is the dynamic type and not the static type that matters. Also, 5.5  expr.mptr.oper paragraph 6:
If the result of .* or ->* is a function, then that result can be used only as the operand for the function call operator (). [Example:
        (ptr_to_obj->*ptr_to_mfct)(10);
calls the member function denoted by ptr_to_mfct for the object pointed to by ptr_to_obj. ]
which also implies that it is the object pointed to that determines both the validity of the expression (the static type of 'ptr_to_obj' may not have a compatible function) and the implicit (polymorphic) meaning. Note too, that this is stated in the non-normative example text.

Andy Sawyer: Assuming the resolution is what I've assumed it is for the last umpteen years (i.e. it does the polymorphic thing), then the follow on to that is "Should there also be a way of selecting the non-polymorphic behaviour"?

Mike Miller: It might be argued that the current wording of 5.2.2  expr.call paragraph 1 does give polymorphic behavior to simple calls via pointers to members. (There is no qualified-id in obj.*pmf, and the IS says that if the function is not specified using a qualified-id, the final overrider will be called.) However, it clearly says the wrong thing when the pointer-to-member itself is specified using a qualified-id (obj.*X::pmf).

Bill Gibbons: The phrase qualified-id in 5.2.2  expr.call paragraph 1 refers to the id-expression and not to the "pointer-to-member expression" earlier in the paragraph:

For a member function call, the postfix expression shall be an implicit (9.3.1  class.mfct.nonstatic , 9.4  class.static ) or explicit class member access (5.2.5  expr.ref ) whose id-expression is a function member name, or a pointer-to-member expression (5.5  expr.mptr.oper ) selecting a function member.

Mike Miller: To be clear, here's an example:

    struct S {
	virtual void f();
    };
    void (S::*pmf)();
    void g(S* sp) {
	sp->f();         // 1: polymorphic
	sp->S::f();      // 2: non-polymorphic
	(sp->S::f)();    // 3: non-polymorphic
	(sp->*pmf)();    // 4: polymorphic
	(sp->*&S::f)();  // 5: polymorphic
    }



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

Section: 5.3.1  expr.unary.op     Status: drafting     Submitter: Alasdair Grant     Date: 27 Nov 2001     Priority: 0     Drafting: Adamczyk

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.




138. Friend declaration name lookup

Section: 7.3.1.2  namespace.memdef     Status: drafting     Submitter: Martin von Loewis     Date: 14 Jul 1999     Priority: 1     Drafting: Spicer

From reflector messages 8206-8213, initiated via a message to comp.std.c++.

7.3.1.2  namespace.memdef paragraph 3 says,

If a friend declaration in a non-local class first declares a class or function the friend class or function is a member of the innermost enclosing namespace... 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.
It is not clear from this passage how to determine whether an entity is "first declared" in a friend declaration. One question is whether a using-declaration influences this determination. For instance:
    void foo();
    namespace A{
      using ::foo;
      class X{
	friend void foo();
      };
    }
Is the friend declaration a reference to ::foo or a different foo?

Part of the question involves determining the meaning of the word "synonym" in 7.3.3  namespace.udecl paragraph 1:

A using-declaration introduces a name into the declarative region in which the using-declaration appears. That name is a synonym for the name of some entity declared elsewhere.
Is "using ::foo;" the declaration of a function or not?

More generally, the question is how to describe the lookup of the name in a friend declaration.

John Spicer: When a declaration specifies an unqualified name, that name is declared, not looked up. There is a mechanism in which that declaration is linked to a prior declaration, but that mechanism is not, in my opinion, via normal name lookup. So, the friend always declares a member of the nearest namespace scope regardless of how that name may or may not already be declared there.

Mike Miller: 3.4.1  basic.lookup.unqual paragraph 7 says:

A name used in the definition of a class X outside of a member function body or nested class definition shall be declared in one of the following ways:... [Note: when looking for a prior declaration of a class or function introduced by a friend declaration, scopes outside of the innermost enclosing namespace scope are not considered.]
The presence of this note certainly implies that this paragraph describes the lookup of names in friend declarations.

John Spicer: It most certainly does not. If that section described the friend lookup it would yield the incorrect results for the friend declarations of f and g below. I don't know why that note is there, but it can't be taken to mean that that is how the friend lookup is done.

    void f(){}
    void g(){}
    class B {
        void g();
    };
    class A : public B {
        void f();
        friend void f(); // ::f not A::f
        friend void g(); // ::g not B::g
    };

Mike Miller: If so, the lookups for friend functions and classes behave differently. Consider the example in 3.4.4  basic.lookup.elab paragraph 3:

    struct Base {
        struct Data;         // OK: declares nested Data
        friend class Data;   // OK: nested Data is a friend
    };

If the friend declaration is not a reference to ::foo, there is a related but separate question: does the friend declaration introduce a conflicting (albeit "invisible") declaration into namespace A, or is it simply a reference to an as-yet undeclared (and, in this instance, undeclarable) A::foo? Another part of the example in 3.4.4  basic.lookup.elab paragraph 3 is related:

    struct Data {
        friend struct Glob;  // OK: Refers to (as yet) undeclared Glob
                             // at global scope.
    };

John Spicer: You can't refer to something that has not yet been declared. The friend is a declaration of Glob, it just happens to declare it in a such a way that its name cannot be used until it is redeclared.

(A somewhat similar question has been raised in connection with issue 36. Consider:

    namespace N {
        struct S { };
    }
    using N::S;
    struct S;          // legal?

According to 9.1  class.name paragraph 2,

A declaration consisting solely of class-key identifier ; is either a redeclaration of the name in the current scope or a forward declaration of the identifier as a class name.

Should the elaborated type declaration in this example be considered a redeclaration of N::S or an invalid forward declaration of a different class?)

(See also issues 95, 136, 139, 143, 165, and 166, as well as paper J16/00-0006 = WG21 N1229.)

Mike Miller and John Spicer will write a paper for the October, 2000 meeting with proposed wording to implement the recommendations of the preceding paper.




341. extern "C" namespace member function versus global variable

Section: 7.5  dcl.link     Status: drafting     Submitter: Steve Adamczyk     Date: 1 Mar 2002     Priority: 1     Drafting: Crowl

From message 9488.

Here's an interesting case:

  int f;
  namespace N {
    extern "C" void f () {}
  }
As far as I can tell, this is not precluded by the ODR section (3.2  basic.def.odr) or the extern "C" section (7.5  dcl.link). However, I believe many compilers do not do name mangling on variables and (more-or-less by definition) on extern "C" functions. That means the variable and the function in the above end up having the same name at link time. EDG's front end, g++, and the Sun compiler all get essentially the same error, which is a compile-time assembler-level error because of the duplicate symbols (in other words, they fail to check for this, and the assembler complains). MSVC++ 7 links the program without error, though I'm not sure how it is interpreted.

Do we intend for this case to be valid? If not, is it a compile time error (required), or some sort of ODR violation (no diagnostic required)? If we do intend for it to be valid, are we forcing many implementations to break binary compatibility by requiring them to mangle variable names?

Personally, I favor a compile-time error, and an ODR prohibition on such things in separate translation units.

Notes from the 4/02 meeting:

The working group agreed with the proposal. We feel a diagnostic should be required for declarations within one translation unit. We also noted that if the variable in global scope in the above example were declared static we would still expect an error.

Relevant sections in the standard are 7.5  dcl.link paragraph 6 and 3.5  basic.link paragraph 9. We feel that the definition should be written such that the entities in conflict are not "the same entity" but merely not allowed together.




86. Lifetime of temporaries in query expressions

Section: 12.2  class.temporary     Status: drafting     Submitter: Steve Adamczyk     Date: Jan 1999     Priority: 2     Drafting: O'Riordan

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.




115. Address of template-id

Section: 13.4  over.over     Status: drafting     Submitter: John Spicer     Date: 7 May 1999     Priority: 1     Drafting: Adamczyk

(Issue reopened in messages 9099-9101.)

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

In 13.4  over.over paragraph 1 change:

A function template name is considered to name a set of overloaded functions in such contexts.
to:
A function template name is considered to name a set of overloaded functions in such contexts, unless it is followed by an explicit template argument specification (14.8.1  temp.arg.explicit) that identifies a single function template specialization.

Add just before the first example of 14.8.1  temp.arg.explicit paragraph 2:

If a template argument list is specified and it, along with any default template arguments, identifies a single function template specialization, then the resulting template-id is an lvalue of function type (which is subject to function-to-pointer conversion (4.3  conv.func)).

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 an
                            // lvalue but an overload set
  }

Note: this interacts with the resolution of issue 226.

Notes from the 4/02 meeting:

The proposed resolution is not quite right. We would like to defer decay to a single function until the template-id is not called and does not have its address taken in the presence of a destination type.




205. Templates and static data members

Section: 14  temp     Status: drafting     Submitter: Mike Miller     Date: 11 Feb 2000     Priority: 2     Drafting: Vandevoorde

Static data members of template classes and of nested classes of template classes are not themselves templates but receive much the same treatment as template. For instance, 14  temp paragraph 1 says that templates are only "classes or functions" but implies that "a static data member of a class template or of a class nested within a class template" is defined using the template-declaration syntax.

There are many places in the clause, however, where static data members of one sort or another are overlooked. For instance, 14  temp paragraph 6 allows static data members of class templates to be declared with the export keyword. I would expect that static data members of (non-template) classes nested within class templates could also be exported, but they are not mentioned here.

Paragraph 8, however, overlooks static data members altogether and deals only with "templates" in defining the effect of the export keyword; there is no description of the semantics of defining a static data member of a template to be exported.

These are just two instances of a systematic problem. The entire clause needs to be examined to determine which statements about "templates" apply to static data members, and which statements about "static data members of class templates" also apply to static data members of non-template classes nested within class templates.

(The question also applies to member functions of template classes; see issue 217, where the phrase "non-template function" in 8.3.6  dcl.fct.default paragraph 4 is apparently intended not to include non-template member functions of template classes. See also issue 108, which would benefit from understanding nested classes of class templates as templates. Also, see issue 249, in which the usage of the phrase "member function template" is questioned.)

Notes from the 4/02 meeting:

Daveed Vandevoorde will propose appropriate terminology.




215. Template parameters are not allowed in nested-name-specifiers

Section: 14.1  temp.param     Status: drafting     Submitter: Martin von Loewis     Date: 13 Mar 2000     Drafting: Nelson

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.




301. Syntax for template-name

Section: 14.2  temp.names     Status: drafting     Submitter: Mark Mitchell     Date: 24 Jul 2001     Priority: 2     Drafting: Nelson

From message 9259.

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.




314. template in base class specifier

Section: 14.2  temp.names     Status: drafting     Submitter: Mark Mitchell     Date: 23 Aug 2001     Priority: 2     Drafting: Nelson

The EDG front-end accepts:

template <typename T>
struct A {
  template <typename U>
  struct B {};
};

template <typename T>
struct C : public A<T>::template B<T> {
};

It rejects this code if the base-specifier is spelled A<T>::B<T>.

However, the grammar for a base-specifier does not allow the template keyword.

Suggested resolution:

It seems to me that a consistent approach to the solution that looks like it will be adopted for issue 180 (which deals with the typename keyword in similar contexts) would be to assume that B is a template if it is followed by a "<". After all, an expression cannot appear in this context.

Notes from the 4/02 meeting:

We agreed that template must be allowed in this context. The syntax needs to be changed. We also opened the related issue 343.




197. Issues with two-stage lookup of dependent names

Section: 14.6.4.2  temp.dep.candidate     Status: drafting     Submitter: Derek Inglis     Date: 26 Jan 2000     Drafting: Merrill

From reflector message 8500.

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




336. Explicit specialization examples are still incorrect

Section: 14.7.3  temp.expl.spec     Status: drafting     Submitter: Jason Shirk     Date: 29 Jan 2002     Priority: 0     Drafting: Spicer

From message 9467.

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 





Issues with "Open" Status


129. Stability of uninitialized auto variables

Section: 1.9  intro.execution     Status: open     Submitter: Nathan Myers     Date: 26 June 1999     Priority: 3

From reflector messages 8111-19, 8122, 8124-26, 8129-31, 8139-40, 8142-46, and 8159-8165.

Does the Standard require that an uninitialized auto variable have a stable (albeit indeterminate) value? That is, does the Standard require that the following function return true?

    bool f() {
        unsigned char i;  // not initialized
        unsigned char j = i;
        unsigned char k = i;
        return j == k;    // true iff "i" is stable
    }
3.9.1  basic.fundamental paragraph 1 requires that uninitialized unsigned char variables have a valid value, so the initializations of j and k are well-formed and required not to trap. The question here is whether the value of i is allowed to change between those initializations.

Mike Miller: 1.9  intro.execution paragraph 10 says,

An instance of each object with automatic storage duration (3.7.2  basic.stc.auto ) is associated with each entry into its block. Such an object exists and retains its last-stored value during the execution of the block and while the block is suspended...
I think that the most reasonable way to read this is that the only thing that is allowed to change the value of an automatic (non-volatile?) value is a "store" operation in the abstract machine. There are no "store" operations to i between the initializations of j and k, so it must retain its original (indeterminate but valid) value, and the result of the program is well-defined.

The quibble, of course, is whether the wording "last-stored value" should be applied to a "never-stored" value. I think so, but others might differ.

Tom Plum: 7.1.5.1  dcl.type.cv paragraph 8 says,

[Note: volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation. See 1.9  intro.execution for detailed semantics. In general, the semantics of volatile are intended to be the same in C++ as they are in C. ]
>From this I would infer that non-volatile means "shall not be changed by means undetectable by an implementation"; that the compiler is entitled to safely cache accesses to non-volatile objects if it can prove that no "detectable" means can modify them; and that therefore i shall maintain the same value during the example above.

Nathan Myers: This also has practical code-generation consequences. If the uninitialized auto variable lives in a register, and its value is really unspecified, then until it is initialized that register can be used as a temporary. Each time it's "looked at" the variable has the value that last washed up in that register. After it's initialized it's "live" and cannot be used as a temporary any more, and your register pressure goes up a notch. Fixing the uninit'd value would make it "live" the first time it is (or might be) looked at, instead.

Mike Ball: I agree with this. I also believe that it was certainly never my intent that an uninitialized variable be stable, and I would have strongly argued against such a provision. Nathan has well stated the case. And I am quite certain that it would be disastrous for optimizers. To ensure it, the frontend would have to generate an initializer, because optimizers track not only the lifetimes of variables, but the lifetimes of values assigned to those variables. This would put C++ at a significant performance disadvantage compared to other languages. Not even Java went this route. Guaranteeing defined behavior for a very special case of a generally undefined operation seems unnecessary.




189. Definition of operator and punctuator

Section: 2.12  lex.operators     Status: open     Submitter: Mike Miller     Date: 20 Dec 1999     Priority: 2     Drafting: Crowl

From reflector message 8398.

The nonterminals operator and punctuator in 2.6  lex.token are not defined. There is a definition of the nonterminal operator in 13.5  over.oper paragraph 1, but it is apparent that the two nonterminals are not the same: the latter includes keywords and multi-token operators and does not include the nonoverloadable operators mentioned in paragraph 3.

There is a definition of preprocessing-op-or-punc in 2.12  lex.operators , with the notation that

Each preprocessing-op-or-punc is converted to a single token in translation phase 7 (2.1).
However, this list doesn't distinguish between operators and punctuators, it includes digraphs and keywords (can a given token be both a keyword and an operator at the same time?), etc.

Suggested resolution:


  1. Change 13.5  over.oper to use the term overloadable-operator.
  2. Change 2.6  lex.token to use the term operator-token instead of operator (since there are operators that are keywords and operators that are composed of more than one token).
  3. Change 2.12  lex.operators to define the nonterminals operator-token and punctuator.



309. Linkage of entities whose names are not simply identifiers, in introduction

Section: basic     Status: open     Submitter: Mike Miller     Date: 17 Sep 2001     Priority: 3

basic paragraph 8, while not incorrect, does not allow for linkage of operators and conversion functions. It says:

An identifier used in more than one translation unit can potentially refer to the same entity in these translation units depending on the linkage (3.5  basic.link) of the identifier specified in each translation unit.



191. Name lookup does not handle complex nesting

Section: 3.4.1  basic.lookup.unqual     Status: open     Submitter: Alan Nash     Date: 29 Dec 1999     Priority: 2

From reflector message 8422.

The current description of unqualified name lookup in 3.4.1  basic.lookup.unqual paragraph 8 does not correctly handle complex cases of nesting. The Standard currently reads,

A name used in the definition of a function that is a member function (9.3) of a class X shall be declared in one of the following ways:
In particular, this formulation does not handle the following example:
    struct outer {
        static int i;
        struct inner {
            void f() {
                struct local {
                    void g() {
                        i = 5;
                    }
                };
            }
        };
    };
Here the reference to i is from a member function of a local class of a member function of a nested class. Nothing in the rules allows outer::i to be found, although intuitively it should be found.

A more comprehensive formulation is needed that allows traversal of any combination of blocks, local classes, and nested classes. Similarly, the final bullet needs to be augmented so that a function need not be a (direct) member of a namespace to allow searching that namespace when the reference is from a member function of a class local to that function. That is, the current rules do not allow the following example:

    int j;    // global namespace
    struct S {
        void f() {
            struct local2 {
                void g() {
                    j = 5;
                }
            };
        }
    };



192. Name lookup in parameters

Section: 3.4.1  basic.lookup.unqual     Status: open     Submitter: Alan Nash     Date: 6 Jan 2000     Priority: 2

From reflector messages 8429-32.

The description of name lookup in the parameter-declaration-clause of member functions in 3.4.1  basic.lookup.unqual paragraphs 7-8 is flawed in at least two regards.

First, both paragraphs 7 and 8 apply to the parameter-declaration-clause of a member function definition and give different rules for the lookup. Paragraph 7 applies to names "used in the definition of a class X outside of a member function body...," which includes the parameter-declaration-clause of a member function definition, while paragraph 8 applies to names following the function's declarator-id (see the proposed resolution of issue 41), including the parameter-declaration-clause.

Second, paragraph 8 appears to apply to the type names used in the parameter-declaration-clause of a member function defined inside the class definition. That is, it appears to allow the following code, which was not the intent of the Committee:

    struct S {
        void f(I i) { }
        typedef int I;
    };



218. Specification of Koenig lookup

Section: 3.4.2  basic.lookup.koenig     Status: open     Submitter: Hyman Rosen     Date: 28 Mar 2000     Priority: 1     Drafting: Spicer

From reflector messages 8636-49, 8651-2, 8654, 8665-8, 8670-84, 8686-8715, and 8718.

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.koenig 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.koenig. 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.koenig 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. Mike Miller and John Spicer volunteered to provide wording implementing the different positions.

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.

Bjarne Stroustrup suggested that we table this discussion pending input from Andrew Koenig, who was not able to attend the Redmond meeting. The Core Working Group agreed.




321. Associated classes and namespaces for argument-dependent lookup

Section: 3.4.2  basic.lookup.koenig     Status: open     Submitter: Andrei Iltchenko     Date: 12 Nov 2001     Priority: 3

The last bullet of the second paragraph of section 3.4.2  basic.lookup.koenig says that:

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.

The first problem with this wording is that it is misleading, since one cannot get such a function argument whose type would be a template-id. The bullet should be speaking about template specializations instead.

The second problem is owing to the use of the word "defined" in the phrases "are the namespace in which the template is defined", "in which any template template arguments are defined", and "as template template arguments are defined". The bullet should use the word "declared" instead, since scenarios like the one below are possible:

namespace  A  {

   template<class T>
   struct  test  {

      template<class U>
      struct  mem_templ  {   };

   };

   // declaration in namespace 'A'
   template<> template<>
   struct  test<int>::mem_templ<int>;

   void  foo(test<int>::mem_templ<int>&)
   {   }

}

// definition in the global namespace
template<> template<>
struct  A::test<int>::mem_templ<int>  {
};

int  main()
{
   A::test<int>::mem_templ<int>   inst;
   // According to the current definition of 3.4.2
   // foo is not found.
   foo(inst);
}

In addition, the bullet doesn't make it clear whether a T which is a class template specialization must also be treated as a class type, i.e. if the contents of the second bullet of the second paragraph of section 3.4.2  basic.lookup.koenig.

must apply to it or not. The same stands for a T which is a function template specialization. This detail can make a difference in an example such as the one below:
template<class T>
struct  slist_iterator  {
   friend bool  operator==(const slist_iterator& x, const slist_iterator& y)
   {   return  true;   }
};

template<class T>
struct  slist  {
   typedef slist_iterator<T>   iterator;
   iterator  begin()
   {   return  iterator();   }
   iterator  end()
   {   return  iterator();   }
};

int  main()
{
   slist<int>   my_list;
   slist<int>::iterator   mi1 = my_list.begin(),  mi2 = my_list.end();
   // Must the the friend function declaration
   // bool  operator==(const slist_iterator<int>&, const slist_iterator<int>&);
   // be found through argument dependent lookup? I.e. is the specialization
   // 'slist<int>' the associated class of the arguments 'mi1' and 'mi2'. If we
   // apply only the contents of the last bullet of 3.4.2/2, then the type
   // 'slist_iterator<int>' has no associated classes and the friend declaration
   // is not found.
   mi1 == mi2;
}

Suggested resolution:

Replace the last bullet of the second paragraph of section 3.4.2  basic.lookup.koenig

with

Replace the second bullet of the second paragraph of section 3.4.2  basic.lookup.koenig

with




141. Non-member function templates in member access expressions

Section: 3.4.5  basic.lookup.classref     Status: open     Submitter: fvali     Date: 31 July 1999     Priority: 3

3.4.5  basic.lookup.classref paragraph 1 says,

In a class member access expression (5.2.5  expr.ref ), if the . or -> token is immediately followed by an identifier followed by a <, the identifier must be looked up to determine whether the < is the beginning of a template argument list (14.2  temp.names ) or a less-than operator. The identifier is first looked up in the class of the object expression. If the identifier is not found, it is then looked up in the context of the entire postfix-expression and shall name a class or function template.
There do not seem to be any circumstances in which use of a non-member template function would be well-formed as the id-expression of a class member access expression.


278. External linkage and nameless entities

Section: 3.5  basic.link     Status: open     Submitter: Daveed Vandevoorde     Date: 12 Apr 2000     Priority: 3

(From messages 9119, 9131-3.)

It is unclear to what extent entities without names match across translation units. For example,

    struct S {
       int :2;
       enum { a, b, c } x;
       static class {} *p;
    };

If this declaration appears in multiple translation units, are all these members "the same" in each declaration?

A similar question can be asked about non-member declarations:

    // Translation unit 1:
    extern enum { d, e, f } y;

    // Translation unit 2:
    extern enum { d, e, f } y;

    // Translation unit 3:
    enum { d, e, f } y;

Is this valid C++? Is it valid C?

James Kanze: S::p cannot be defined, because to do so requires a type specifier and the type cannot be named. ::y is valid C because C only requires compatible, not identical, types. In C++, it appears that there is a new type in each declaration, so it would not be valid. This differs from S::x because the unnamed type is part of a named type — but I don't know where or if the Standard says that.

John Max Skaller: It's not valid C++, because the type is a synthesised, unique name for the enumeration type which differs across translation units, as if:

    extern enum _synth1 { d,e,f} y;
    ..
    extern enum _synth2 { d,e,f} y;

had been written.

However, within a class, the ODR implies the types are the same:

    class X { enum { d } y; };

in two translation units ensures that the type of member y is the same: the two X's obey the ODR and so denote the same class, and it follows that there's only one member y and one type that it has.

(See also issues 132 and 216.)




279. Correspondence of "names for linkage purposes"

Section: 3.5  basic.link     Status: open     Submitter: Daveed Vandevoorde     Date: 4 Apr 2001     Priority: 3

(From message 9120.)

The standard says that an unnamed class or enum definition can be given a "name for linkage purposes" through a typedef. E.g.,

    typedef enum {} E;
    extern E *p;

can appear in multiple translation units.

How about the following combination?

    // Translation unit 1:
    struct S;
    extern S *q;

    // Translation unit 2:
    typedef struct {} S;
    extern S *q;

Is this valid C++?

Also, if the answer is "yes", consider the following slight variant:

    // Translation unit 1:
    struct S {};  // <<-- class has definition
    extern S *q;

    // Translation unit 2:
    typedef struct {} S;
    extern S *q;

Is this a violation of the ODR because two definitions of type S consist of differing token sequences?




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

Section: 3.5  basic.link     Status: open     Submitter: Clark Nelson     Date: 29 Oct 2001     Priority: 2

From message 9371.

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



338. Enumerator name with linkage used as class name in other translation unit

Section: 3.5  basic.link     Status: open     Submitter: Daveed Vandevoorde     Date: 26 Feb 2002     Priority: 2

From messages 9480, 9481, 9483, 9485, 9486.

The following declarations are allowed within a translation unit:

  struct S;
  enum { S };

However, 3.5  basic.link paragraph 9 seems to say these two declarations cannot appear in two different translation units. That also would mean that the inclusion of a header containing the above in two different translation units is not valid C++.

I suspect this is an oversight and that users should be allowed to have the declarations above appear in different translation units. (It is a fairly common thing to do, I think.)

Mike Miller: I think you meant "enum E { S };" -- enumerators only have external linkage if the enumeration does (3.5  basic.link paragraph 4), and 3.5  basic.link paragraph 9 only applies to entities with external linkage.

I don't remember why enumerators were given linkage; I don't think it's necessary for mangling non-type template arguments. In any event, I can't think why cross-TU name collisions between enumerators and other entities would cause a problem, so I guess a change here would be okay. I can think of three changes that would have that effect:

  1. Saying that enumerators do not have linkage.
  2. Removing enumerators from the list of entities in the first sentence of 3.5  basic.link paragraph 9.
  3. Saying that it's okay for an enumerator in one TU to have the same name as a class type in another TU only if the enumerator hides that same class type in both TUs (the example you gave).

Daveed Vandevoorde: I don't think any of these are sufficient in the sense that the problem isn't limited to enumerators. E.g.:

  struct X;
  extern void X();
shouldn't create cross-TU collisions either.

Mike Miller: So you're saying that cross-TU collisions should only be prohibited if both names denote entities of the same kind (both functions, both objects, both types, etc.), or if they are both references (regardless of what they refer to, presumably)?

Daveed Vandevoorde: Not exactly. Instead, I'm saying that if two entities (with external linkage) can coexist when they're both declared in the same translation unit (TU), then they should also be allowed to coexist when they're declared in two different translation units.

For example:

  int i;
  void i();  // Error
This is an error within a TU, so I don't see a reason to make it valid across TUs.

However, "tag names" (class/struct/union/enum) can sometimes coexist with identically named entities (variables, functions & enumerators, but not namespaces, templates or type names).




28. 'exit', 'signal' and static object destruction

Section: 3.6.3  basic.start.term     Status: open     Submitter: Martin J. O'Riordan     Date: 19 Oct 1997     Priority: 2     Drafting: O'Riordan

From reflector message core-7686.

The C++ standard has inherited the definition of the 'exit' function more or less unchanged from ISO C.

However, when the 'exit' function is called, objects of static extent which have been initialised, will be destructed if their types posses a destructor.

In addition, the C++ standard has inherited the definition of the 'signal' function and its handlers from ISO C, also pretty much unchanged.

The C standard says that the only standard library functions that may be called while a signal handler is executing, are the functions 'abort', 'signal' and 'exit'.

This introduces a bit of a nasty turn, as it is not at all unusual for the destruction of static objects to have fairly complex destruction semantics, often associated with resource release. These quite commonly involve apparently simple actions such as calling 'fclose' for a FILE handle.

Having observed some very strange behaviour in a program recently which in handling a SIGTERM signal, called the 'exit' function as indicated by the C standard.

But unknown to the programmer, a library static object performed some complicated resource deallocation activities, and the program crashed.

The C++ standard says nothing about the interaction between signals, exit and static objects. My observations, was that in effect, because the destructor called a standard library function other than 'abort', 'exit' or 'signal', while transitively in the execution context of the signal handler, it was in fact non-compliant, and the behaviour was undefined anyway.

This is I believe a plausible judgement, but given the prevalence of this common programming technique, it seems to me that we need to say something a lot more positive about this interaction.

Curiously enough, the C standard fails to say anything about the analogous interaction with functions registered with 'atexit' ;-)

(Also see reflector messages 7687 7688 7689 7691)

Proposed Resolution (10/98):

The current Committee Draft of the next version of the ISO C standard specifies that the only standard library function that may be called while a signal handler is executing is 'abort'. This would solve the above problem.

[This issue should remain open until it has been decided that the next version of the C++ standard will use the next version of the C standard as the basis for the behavior of 'signal'.]




312. "use" of invalid pointer value not defined

Section: 3.7.3.2  basic.stc.dynamic.deallocation     Status: open     Submitter: Martin von Loewis     Date: 20 Sep 2001     Priority: 2

3.7.3.2  basic.stc.dynamic.deallocation paragraph 4 mentions that the effect of using an invalid pointer value is undefined. However, the standard never says what it means to 'use' a value.

There are a number of possible interpretations, but it appears that each of them leads to undesired conclusions:

  1. A value is 'used' in a program if a variable holding this value appears in an expression that is evaluated. This interpretation would render the sequence
       int *x = new int(0);
       delete x;
       x = 0;
    
    into undefined behaviour. As this is a common idiom, this is clearly undesirable.
  2. A value is 'used' if an expression evaluates to that value. This would render the sequence
       int *x = new int(0);
       delete x;
       x->~int();
    
    into undefined behaviour; according to 5.2.4  expr.pseudo, the variable x is 'evaluated' as part of evaluating the pseudo destructor call. This, in turn, would mean that all containers (23  lib.containers) of pointers show undefined behaviour, e.g. 23.2.2.3  lib.list.modifiers requires to invoke the destructor as part of the clear() method of the container.

If any other meaning was intended for 'using an expression', that meaning should be stated explicitly.




348. delete and user-written deallocation functions

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

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



290. Should memcpy be allowed into a POD with a const member?

Section: 3.9  basic.types     Status: open     Submitter: Garry Lancaster     Date: 12 Jun 2001     Priority: 2

Following the definition in 9  class paragraph 4 the following is a valid POD (actually a POD-struct):

    struct test
    {
        const int i;
    };

The legality of PODs with const members is also implied by the text of 5.3.4  expr.new paragraph 15 bullet 1, sub-bullet 2 and 12.6.2  class.base.init paragraph 4 bullet 2.

3.9  basic.types paragraph 3 states that

For any POD type T, if two pointers to T point to distinct 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.

[Note: this text was changed by TC1, but the essential point stays the same.]

This implies that the following is required to work:

    test obj1 = { 1 };
    test obj2 = { 2 };
    memcpy( &obj2, &obj1, sizeof(test) );

The memcpy of course changes the value of the const member, surely something that shouldn't be allowed.

Suggested resolution:

It is recommended that 3.9  basic.types paragraph 3 be reworded to exclude PODs which contain (directly or indirectly) members of const-qualified type.




350. signed char underlying representation for objects

Section: 3.9  basic.types     Status: open     Submitter: Noah Stein     Date: 16 April 2002

Sent in by David Abrahams:

Yes, and to add to this tangent, 3.9.1  basic.fundamental paragraph 1 states "Plain char, signed char, and unsigned char are three distinct types." Strangely, 3.9  basic.types paragraph 2 talks about how "... the underlying bytes 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." I guess there's no requirement that this copying work properly with signed chars!




146. Floating-point zero

Section: 3.9.1  basic.fundamental     Status: open     Submitter: Andy Sawyer     Date: 23 Jul 1999     Priority: 3

From reflector message 8234.

3.9.1  basic.fundamental does not impose a requirement on the floating point types that there be an exact representation of the value zero. This omission is significant in 4.12  conv.bool paragraph 1, in which any non-zero value converts to the bool value true.

Suggested resolution: require that all floating point types have an exact representation of the value zero.




251. How many signed integer types are there?

Section: 3.9.1  basic.fundamental     Status: open     Submitter: Beman Dawes     Date: 18 Oct 2000     Priority: 3

From messages 8932-3 and 8936-7.

3.9.1  basic.fundamental paragraph 2 says that

There are four signed integer types: "signed char", "short int", "int", and "long int."

This would indicate that const int is not a signed integer type.




240. Uninitialized values and undefined behavior

Section: 4.1  conv.lval     Status: open     Submitter: Mike Miller     Date: 8 Aug 2000     Priority: 2

From messages 8866, 8869-71, 8875-6, 8878, and 8886-7.

4.1  conv.lval paragraph 1 says,

If the object to which the lvalue refers is not an object of type T and is not an object of a type derived from T, or if the object is uninitialized, a program that necessitates this conversion has undefined behavior.

I think there are at least three related issues around this specification:

  1. Presumably assigning a valid value to an uninitialized object allows it to participate in the lvalue-to-rvalue conversion without undefined behavior (otherwise the number of programs with defined behavior would be vanishingly small :-). However, the wording here just says "uninitialized" and doesn't mention assignment.

  2. There's no exception made for unsigned char types. The wording in 3.9.1  basic.fundamental was carefully crafted to allow use of unsigned char to access uninitialized data so that memcpy and such could be written in C++ without undefined behavior, but this statement undermines that intent.

  3. It's possible to get an uninitialized rvalue without invoking the lvalue-to-rvalue conversion. For instance:

            struct A {
                int i;
                A() { } // no init of A::i
            };
            int j = A().i;  // uninitialized rvalue
    

    There doesn't appear to be anything in the current IS wording that says that this is undefined behavior. My guess is that we thought that in placing the restriction on use of uninitialized objects in the lvalue-to-rvalue conversion we were catching all possible cases, but we missed this one.

In light of the above, I think the discussion of uninitialized objects ought to be removed from 4.1  conv.lval paragraph 1. Instead, something like the following ought to be added to 3.9  basic.types paragraph 4 (which is where the concept of "value" is introduced):

Any use of an indeterminate value (5.3.4  expr.new, 8.5  dcl.init, 12.6.2  class.base.init) of any type other than char or unsigned char results in undefined behavior.

John Max Skaller:

A().i had better be an lvalue; the rules are wrong. Accessing a member of a structure requires it be converted to an lvalue, the above calculation is 'as if':

    struct A {
        int i;
        A *get() { return this; }
    };
    int j = (*A().get()).i;

and you can see the bracketed expression is an lvalue.

A consequence is:

    int &j= A().i; // OK, even if the temporary evaporates

j now refers to a 'destroyed' value. Any use of j is an error. But the binding at the time is valid.




330. Qualification conversions and pointers to arrays of pointers

Section: 4.4  conv.qual     Status: open     Submitter: Roger Orr     Date: 2 Jan 2002     Priority: 3

Section 4.4  conv.qual covers the case of multi-level pointers, but does not appear to cover the case of pointers to arrays of pointers. The effect is that arrays are treated differently from simple scalar values.

Consider for example the following code: (from the thread "Pointer to array conversion question" begun in comp.lang.c++.moderated)

  int main()
  {
     double *array2D[2][3];
  
     double       *       (*array2DPtr1)[3] = array2D;     // Legal
     double       * const (*array2DPtr2)[3] = array2DPtr1; // Legal
     double const * const (*array2DPtr3)[3] = array2DPtr2; // Illegal
  }
and compare this code with:-
  int main()
  {
     double *array[2];
  
     double       *       *ppd1 = array; // legal
     double       * const *ppd2 = ppd1;  // legal
     double const * const *ppd3 = ppd2;  // certainly legal (4.4/4)
  }

The problem appears to be that the pointed to types in example 1 are unrelated since nothing in the relevant section of the standard covers it - 4.4  conv.qual does not mention conversions of the form "cv array of N pointer to T" into "cv array of N pointer to cv T"

It appears that reinterpret_cast is the only way to perform the conversion.

Suggested resolution:

Artem Livshits proposed a resolution :-

"I suppose if the definition of "similar" pointer types in 4.4  conv.qual paragraph 4 was rewritten like this:

T1 is cv1,0 P0 cv1,1 P1 ... cv1,n-1 Pn-1 cv1,n T

and

T2 is cv1,0 P0 cv1,1 P1 ... cv1,n-1 Pn-1 cv1,n T

where Pi is either a "pointer to" or a "pointer to an array of Ni"; besides P0 may be also a "reference to" or a "reference to an array of N0" (in the case of P0 of T2 being a reference, P0 of T1 may be nothing).

it would address the problem.

In fact I guess Pi in this notation may be also a "pointer to member", so 4.4  conv.qual/{4,5,6,7} would be nicely wrapped in one paragraph."




170. Pointer-to-member conversions

Section: 4.11  conv.mem     Status: open     Submitter: Mike Stump     Date: 16 Sep 1999     Priority: 2     Drafting: Nelson

From reflector messages 8324 and 8343.

The descriptions of explicit (5.2.9  expr.static.cast paragraph 9) and implicit (4.11  conv.mem paragraph 2) pointer-to-member conversions differ in two significant ways:

  1. In a static_cast, a conversion in which the class in the target pointer-to-member type is a base of the class in which the member is declared is permitted and required to work correctly, as long as the resulting pointer-to-member is eventually dereferenced with an object whose dynamic type contains the member. That is, the class of the target pointer-to-member type is not required to contain the member referred to by the value being converted. The specification of implicit pointer-to-member conversion is silent on this question.

    (This situation cannot arise in an implicit pointer-to-member conversion where the source value is something like &X::f, since you can only implicitly convert from pointer-to-base-member to pointer-to-derived-member. However, if the source value is the result of an explicit "up-cast," the target type of the conversion might still not contain the member referred to by the source value.)

  2. The target type in a static_cast is allowed to be more cv-qualified than the source type; in an implicit conversion, however, the cv-qualifications of the two types are required to be identical.
The first difference seems like an oversight. It is not clear whether the latter difference is intentional or not.


238. Precision and accuracy constraints on floating point

Section: expr     Status: open     Submitter: Christophe de Dinechin     Date: 31 Jul 2000     Priority: 3

From messages 8833, 8835-6, 8838-9, 8844, 8846, 8849, 8851-3, and 8856-62.

It is not clear what constraints are placed on a floating point implementation by the wording of the Standard. For instance, is an implementation permitted to generate a "fused multiply-add" instruction if the result would be different from what would be obtained by performing the operations separately? To what extent does the "as-if" rule allow the kinds of optimizations (e.g., loop unrolling) performed by FORTRAN compilers?




351. Sequence point error: unspecified or undefined?

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

From message 9526.

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?




282. Namespace for extended_type_info

Section: 5.2.8  expr.typeid     Status: open     Submitter: Jens Maurer     Date: 01 May 2001     Priority: 3

The original proposed resolution for issue 160 included changing extended_type_info (5.2.8  expr.typeid paragraph 1, footnote 61) to std::extended_type_info. There was no consensus on whether this name ought to be part of namespace std or in a vendor-specific namespace, so the question was moved into a separate issue.




195. Converting between function and object pointers

Section: 5.2.10  expr.reinterpret.cast     Status: open     Submitter: Steve Clamage     Date: 12 Jan 2000     Priority: 2     Drafting: Salters

From reflector messages 8439-86 (!).

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. This discussion occurred on the -ext reflector in messages 3905-16, 3919-21, 3929-33, 3935, and 3937-8. The thread may not be complete at the time of this writing, so additional messages may exist on this topic. There appeared to be a solid, if not unanimous, consensus behind Andy's suggestion about relaxing the requirement for a diagnostic.

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.




342. Terminology: "indirection" versus "dereference"

Section: 5.3  expr.unary     Status: open     Submitter: Jason Merrill     Date: 7 Oct 2001     Priority: 3

From message 9324.

Split off from issue 315.

Incidentally, another thing that ought to be cleaned up is the inconsistent use of "indirection" and "dereference". We should pick one.




203. Type of address-of-member expression

Section: 5.3.1  expr.unary.op     Status: open     Submitter: Lisa Lippincott     Date: 8 Feb 2000     Priority: 3

5.3.1  expr.unary.op paragraph 2 indicates that the type of an address-of-member expression reflects the class in which the member was declared rather than the class identified in the nested-name-specifier of the qualified-id. This treatment is unintuitive and can lead to strange code and unexpected results. For instance, in

    struct B { int i; };
    struct D1: B { };
    struct D2: B { };

    int (D1::* pmD1) = &D2::i;   // NOT an error
More seriously, template argument deduction can give surprising results:
    struct A {
       int i;
       virtual void f() = 0;
    };

    struct B : A {
       int j;
       B() : j(5)  {}
       virtual void f();
    };

    struct C : B {
       C() { j = 10; }
    };

    template <class T>
    int DefaultValue( int (T::*m) ) {
       return T().*m;
    }

    ... DefaultValue( &B::i )    // Error: A is abstract
    ... DefaultValue( &C::j )    // returns 5, not 10.

Suggested resolution: 5.3.1  expr.unary.op should be changed to read,

If the member is a nonstatic member (perhaps by inheritance) of the class nominated by the nested-name-specifier of the qualified-id having type T, the type of the result is "pointer to member of class nested-name-specifier of type T."
and the comment in the example should be changed to read,
// has type int B::*

Notes from 04/00 meeting:

The rationale for the current treatment is to permit the widest possible use to be made of a given address-of-member expression. Since a pointer-to-base-member can be implicitly converted to a pointer-to-derived-member, making the type of the expression a pointer-to-base-member allows the result to initialize or be assigned to either a pointer-to-base-member or a pointer-to-derived-member. Accepting this proposal would allow only the latter use.

Additional notes:

Another problematic example has been mentioned:

    class Base {
    public:
      int func() const;
    };

    class Derived : public Base {
    };

    template<class T>
    class Templ {
    public:
      template<class S>
      Templ(S (T::*ptmf)() const);
    };

    void foo()
    {
      Templ<Derived> x(&Derived::func);    // ill-formed
    }

In this example, even though the conversion of &Derived::func to int (Derived::*)() const is permitted, the initialization of x cannot be done because template argument deduction for the constructor fails.

If the suggested resolution were adopted, the amount of code broken by the change might be reduced by adding an implicit conversion from pointer-to-derived-member to pointer-to-base-member for appropriate address-of-member expressions (not for arbitrary pointers to members, of course).

(See also issue 247.)




232. Is indirection through a null pointer undefined behavior?

Section: 5.3.1  expr.unary.op     Status: open     Submitter: Mike Miller     Date: 5 Jun 2000     Priority: 2

See messages 8740-61.

At least a couple of places in the IS state that indirection through a null pointer produces undefined behavior: 1.9  intro.execution paragraph 4 gives "dereferencing the null pointer" as an example of undefined behavior, and 8.3.2  dcl.ref paragraph 4 (in a note) uses this supposedly undefined behavior as justification for the nonexistence of "null references."

However, 5.3.1  expr.unary.op paragraph 1, which describes the unary "*" operator, does not say that the behavior is undefined if the operand is a null pointer, as one might expect. Furthermore, at least one passage gives dereferencing a null pointer well-defined behavior: 5.2.8  expr.typeid paragraph 2 says

If the lvalue expression is obtained by applying the unary * operator to a pointer and the pointer is a null pointer value (4.10  conv.ptr), the typeid expression throws the bad_typeid exception (18.5.3  lib.bad.typeid).

This is inconsistent and should be cleaned up.

Bill Gibbons:

At one point we agreed that dereferencing a null pointer was not undefined; only using the resulting value had undefined behavior.

For example:

    char *p = 0;
    char *q = &*p;

Similarly, dereferencing a pointer to the end of an array should be allowed as long as the value is not used:

    char a[10];
    char *b = &a[10];   // equivalent to "char *b = &*(a+10);"

Both cases come up often enough in real code that they should be allowed.

Mike Miller:

I can see the value in this, but it doesn't seem to be well reflected in the wording of the Standard. For instance, presumably *p above would have to be an lvalue in order to be the operand of "&", but the definition of "lvalue" in 3.10  basic.lval paragraph 2 says that "an lvalue refers to an object." What's the object in *p? If we were to allow this, we would need to augment the definition to include the result of dereferencing null and one-past-the-end-of-array.

Tom Plum:

Just to add one more recollection of the intent: I was very happy when (I thought) we decided that it was only the attempt to actually fetch a value that creates undefined behavior. The words which (I thought) were intended to clarify that are the first three sentences of the lvalue-to-rvalue conversion, 4.1  conv.lval:

An lvalue (3.10  basic.lval) of a non-function, non-array type T can be converted to an rvalue. If T is an incomplete type, a program that necessitates this conversion is ill-formed. If the object to which the lvalue refers is not an object of type T and is not an object of a type derived from T, or if the object is uninitialized, a program that necessitates this conversion has undefined behavior.

In other words, it is only the act of "fetching", of lvalue-to-rvalue conversion, that triggers the ill-formed or undefined behavior. Simply forming the lvalue expression, and then for example taking its address, does not trigger either of those errors. I described this approach to WG14 and it may have been incorporated into C 1999.

Mike Miller:

If we admit the possibility of null lvalues, as Tom is suggesting here, that significantly undercuts the rationale for prohibiting "null references" -- what is a reference, after all, but a named lvalue? If it's okay to create a null lvalue, as long as I don't invoke the lvalue-to-rvalue conversion on it, why shouldn't I be able to capture that null lvalue as a reference, with the same restrictions on its use?

I am not arguing in favor of null references. I don't want them in the language. What I am saying is that we need to think carefully about adopting the permissive approach of saying that it's all right to create null lvalues, as long as you don't use them in certain ways. If we do that, it will be very natural for people to question why they can't pass such an lvalue to a function, as long as the function doesn't do anything that is not permitted on a null lvalue.

If we want to allow &*(p=0), maybe we should change the definition of "&" to handle dereferenced null specially, just as typeid has special handling, rather than changing the definition of lvalue to include dereferenced nulls, and similarly for the array_end+1 case. It's not as general, but I think it might cause us fewer problems in the long run.




267. Alignment requirement for new-expressions

Section: 5.3.4  expr.new     Status: open     Submitter: James Kuyper     Date: 4 Dec 2000     Priority: 3

Requirements for the alignment of pointers returned by new-expressions are given in 5.3.4  expr.new paragraph 10:

For arrays of char and unsigned char, the difference between the result of the new-expression and the address returned by the allocation function shall be an integral multiple of the most stringent alignment requirement (3.9  basic.types) of any object type whose size is no greater than the size of the array being created.

The intent of this wording is that the pointer returned by the new-expression will be suitably aligned for any data type that might be placed into the allocated storage (since the allocation function is constrained to return a pointer to maximally-aligned storage). However, there is an implicit assumption that each alignment requirement is an integral multiple of all smaller alignment requirements. While this is probably a valid assumption for all real architectures, there's no reason that the Standard should require it.

For example, assume that int has an alignment requirement of 3 bytes and double has an alignment requirement of 4 bytes. The current wording only requires that a buffer that is big enough for an int or a double be aligned on a 4-byte boundary (the more stringent requirement), but that would allow the buffer to be allocated on an 8-byte boundary — which might not be an acceptable location for an int.

Suggested resolution: Change "of any object type" to "of every object type."

A similar assumption can be found in 5.2.10  expr.reinterpret.cast paragraph 7:

...converting an rvalue of type "pointer to T1" to the type "pointer to T2" (where ... the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value...

Suggested resolution: Change the wording to

...converting an rvalue of type "pointer to T1" to the type "pointer to T2" (where ... the alignment requirements of T1 are an integer multiple of those of T2) and back to its original type yields the original pointer value...

The same change would also be needed in paragraph 9.




292. Deallocation on exception in new before arguments evaluated

Section: 5.3.4  expr.new     Status: open     Submitter: Andrei Iltchenko     Date: 26 Jun 2001     Priority: 3

According to the C++ Standard section 5.3.4  expr.new paragraph 21 it is unspecified whether the allocation function is called before evaluating the constructor arguments or after evaluating the constructor arguments but before entering the constructor.

On top of that paragraph 17 of the same section insists that

If any part of the object initialization described above [Footnote: This may include evaluating a new-initializer and/or calling a constructor.] terminates by throwing an exception and a suitable deallocation function is found, the deallocation function is called to free the memory in which the object was being constructed... If no unambiguous matching deallocation function can be found, propagating the exception does not cause the object's memory to be freed...

Now suppose we have:

  1. An implementation that always evaluates the constructor arguments first (for a new-expression that creates an object of a class type and has a new-initializer) and calls the allocation function afterwards.
  2. A class like this:
        struct  copy_throw  {
           copy_throw(const copy_throw&)
           {   throw  std::logic_error("Cannot copy!");   }
           copy_throw(long, copy_throw)
           {   }
           copy_throw()
           {   }
        };
    
  3. And a piece of code that looks like the one below:
        int  main()
        try  {
           copy_throw   an_object,     /* undefined behaviour */
              * a_pointer = ::new copy_throw(0, an_object);
           return  0;
        }
        catch(const std::logic_error&)
        {   }
    

Here the new-expression '::new copy_throw(0, an_object)' throws an exception when evaluating the constructor's arguments and before the allocation function is called. However, 5.3.4  expr.new paragraph 17 prescribes that in such a case the implementation shall call the deallocation function to free the memory in which the object was being constructed, given that a matching deallocation function can be found.

So a call to the Standard library deallocation function '::operator delete(void*)' shall be issued, but what argument is an implementation supposed to supply to the deallocation function? As per 5.3.4  expr.new paragraph 17 - the argument is the address of the memory in which the object was being constructed. Given that no memory has yet been allocated for the object, this will qualify as using an invalid pointer value, which is undefined behaviour by virtue of 3.7.3.2  basic.stc.dynamic.deallocation paragraph 4.

Suggested resolution:

Change the first sentence of 5.3.4  expr.new paragraph 17 to read:

If the memory for the object being created has already been successfully allocated and any part of the object initialization described above...



299. Conversion on array bound expression in new

Section: 5.3.4  expr.new     Status: open     Submitter: Mark Mitchell     Date: 19 Jul 2001     Priority: 2

From message 9257.

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




313. Class with single conversion function to integral as array size in new

Section: 5.3.4  expr.new     Status: open     Submitter: Bill Gibbons     Date: 22 Oct 2001     Priority: 2

Should it be allowed to use an object of a class type having a single conversion function to an integral type as an array size in the first bound of the type in an array new?

  struct A {
    operator int();
  } a;
  int main () {
    new int[a];
  }

There are similar accommodations for the expression in a delete (5.3.5  expr.delete paragraph 1) and in a switch (6.4.2  stmt.switch paragraph 2). There is also widespread existing practice on this (g++, EDG, MSVC++, and Sun accept it, and even cfront 3.0.2).




196. Arguments to deallocation functions

Section: 5.3.5  expr.delete     Status: open     Submitter: Matt Austern     Date: 20 Jan 2000     Priority: 3

From reflector messages 8487, 8490-99.

5.3.4  expr.new paragraph 10 says that the result of an array allocation function and the value of the array new-expression from which it was invoked may be different, allowing for space preceding the array to be used for implementation purposes such as saving the number of elements in the array. However, there is no corresponding description of the relationship between the operand of an array delete-expression and the argument passed to its deallocation function.

3.7.3.2  basic.stc.dynamic.deallocation paragraph 3 does state that

the value supplied to operator delete[](void*) in the standard library shall be one of the values returned by a previous invocation of either operator new[](std::size_t) or operator new[](std::size_t, const std::nothrow_t&) in the standard library.

This statement might be read as requiring an implementation, when processing an array delete-expression and calling the deallocation function, to perform the inverse of the calculation applied to the result of the allocation function to produce the value of the new-expression. (5.3.5  expr.delete paragraph 2 requires that the operand of an array delete-expression "be the pointer value which resulted from a previous array new-expression.") However, it is not completely clear whether the "shall" expresses an implementation requirement or a program requirement (or both). Furthermore, there is no direct statement about user-defined deallocation functions.

Suggested resolution: A note should be added to 5.3.5  expr.delete to clarify that any offset added in an array new-expression must be subtracted in the array delete-expression.




265. Destructors, exceptions, and deallocation

Section: 5.3.5  expr.delete     Status: open     Submitter: Mike Miller     Date: 21 Nov 2000     Priority: 2

From message 8988.

Does the Standard require that the deallocation function will be called if the destructor throws an exception? For example,

    struct S {
        ~S() { throw 0; }
    };
    void f() {
        try {
            delete new S;
        }
        catch(...) { }
    }

The question is whether the memory for the S object will be freed or not. It doesn't appear that the Standard answers the question, although most people would probably assume that it will be freed.

Notes from 04/01 meeting:

There is a widespread feeling that it is a poor programming practice to allow destructors to terminate with an exception (see issue 219). This question is thus viewed as a tradeoff between efficiency and supporting "bad code." It was observed that there is no way in the current language to protect against a throwing destructor, since the throw might come from a virtual override.

It was suggested that the resolution to the issue might be to make it implementation-defined whether the storage is freed if the destructor throws. Others suggested that the Standard should require that the storage be freed, with the understanding that implementations might have a flag to allow optimizing away the overhead. Still others thought that both this issue and issue 219 should be resolved by forbidding a destructor to exit via an exception. No consensus was reached.




288. Misuse of "static type" in describing pointers

Section: 5.3.5  expr.delete     Status: open     Submitter: James Kuyper     Date: 19 May 2001     Priority: 2

For delete expressions, 5.3.5  expr.delete paragraph 1 says

The operand shall have a pointer type, or a class type having a single conversion function to a pointer type.

However, paragraph 3 of that same section says:

if the static type of the operand is different from its dynamic type, the static type shall be a base class of the operand's dynamic type and the static type shall have a virtual destructor or the behavior is undefined.

Since the operand must be of pointer type, its static type is necessarily the same as its dynamic type. That clause is clearly referring to the object being pointed at, and not to the pointer operand itself.

Correcting the wording gets a little complicated, because dynamic and static types are attributes of expressions, not objects, and there's no sub-expression of a delete-expression which has the relevant types.

Suggested resolution:

then there is a static type and a dynamic type that the hypothetical expression (* const-expression) would have. If that static type is different from that dynamic type, then that static type shall be a base class of that dynamic type, and that static type shall have a virtual destructor, or the behavior is undefined.

There's precedent for such use of hypothetical constructs: see 5.10  expr.eq paragraph 2, and 8.1  dcl.name paragraph 1.

10.3  class.virtual paragraph 3 has a similar problem. It refers to

the type of the pointer or reference denoting the object (the static type).

The type of the pointer is different from the type of the reference, both of which are different from the static type of '*pointer', which is what I think was actually intended. Paragraph 6 contains the exact same wording, in need of the same correction. In this case, perhaps replacing "pointer or reference" with "expression" would be the best fix. In order for this fix to be sufficient, pointer->member must be considered equivalent to (*pointer).member, in which case the "expression" referred to would be (*pointer).

12.5  class.free paragraph 4 says that
if a delete-expression is used to deallocate a class object whose static type has...

This should be changed to

if a delete-expression is used to deallocate a class object through a pointer expression whose dereferenced static type would have...

The same problem occurs later, when it says that the

static and dynamic types of the object shall be identical

In this case you could replace "object" with "dereferenced pointer expression".

Footnote 104 says that

5.3.5  expr.delete requires that ... the static type of the delete-expression's operand be the same as its dynamic type.

This would need to be changed to

the delete-expression's dereferenced operand



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

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

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;
}



242. Interpretation of old-style casts

Section: 5.4  expr.cast     Status: open     Submitter: Mike Miller     Date: 30 Aug 2000     Priority: 3

From message 8888.

The meaning of an old-style cast is described in terms of const_cast, static_cast, and reinterpret_cast in 5.4  expr.cast paragraph 5. Ignoring const_cast for the moment, it basically says that if the conversion performed by a given old-style cast is one of those performed by static_cast, the conversion is interpreted as if it were a static_cast; otherwise, it's interpreted as if it were a reinterpret_cast, if possible. The following example is given in illustration:

    struct A {};
    struct I1 : A {};
    struct I2 : A {};
    struct D : I1, I2 {};
    A *foo( D *p ) {
	return (A*)( p ); // ill-formed static_cast interpretation
    }

The obvious intent here is that a derived-to-base pointer conversion is one of the conversions that can be performed using static_cast, so (A*)(p) is equivalent to static_cast<A*>(p), which is ill-formed because of the ambiguity.

Unfortunately, the description of static_cast in 5.2.9  expr.static.cast does NOT support this interpretation. The problem is in the way 5.2.9  expr.static.cast lists the kinds of casts that can be performed using static_cast. Rather than saying something like "All standard conversions can be performed using static_cast," it says

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.

Given the declarations above, the hypothetical declaration

    A* t(p);

is NOT well-formed, because of the ambiguity. Therefore the old-style cast (A*)(p) is NOT one of the conversions that can be performed using static_cast, and (A*)(p) is equivalent to reinterpret_cast<A*>(p), which is well-formed under 5.2.10  expr.reinterpret.cast paragraph 7.

Other situations besides ambiguity which might raise similar questions include access violations, casting from virtual base to derived, and casting pointers-to-members when virtual inheritance is involved.




236. Explicit temporaries and integral constant expressions

Section: 5.19  expr.const     Status: open     Submitter: Mike Miller     Date: 19 Jul 2000     Priority: 3

From messages 8816-19.

Does an explicit temporary of an integral type qualify as an integral constant expression? For instance,

    void* p = int();    // well-formed?

It would appear to be, since int() is an explicit type conversion according to 5.2.3  expr.type.conv (at least, it's described in a section entitled "Explicit type conversion") and type conversions to integral types are permitted in integral constant expressions (5.19  expr.const). However, this reasoning is somewhat tenuous, and some at least have argued otherwise.




339. Overload resolution in operand of sizeof in constant expression

Section: 5.19  expr.const     Status: open     Submitter: Steve Adamczyk     Date: 11 Mar 2002     Priority: 1

I've seen some pieces of code recently that put complex expressions involving overload resolution inside sizeof operations in constant expressions in templates.

5.19  expr.const paragraph 1 implies that some kinds of nonconstant expressions are allowed inside a sizeof in a constant expression, but it's not clear that this was intended to extend all the way to things like overload resolution. Allowing such things has some hidden costs. For example, name mangling has to be able to represent all operators, including calls, and not just the operators that can appear in constant expressions.

  template <int I> struct A {};

  char xxx(int);
  char xxx(float);

  template <class T> A<sizeof(xxx((T)0))> f(T){}

  int main()
  {
    f(1);
  }

If complex expressions are indeed allowed, it should be because of an explicit committee decision rather than because of some looseness in this section of the standard.

Notes from the 4/02 meeting:

Any argument for restricting such expressions must involve a cost/benefit ratio: a restriction would be palatable only if it causes minimum hardship for users and allows a substantial reduction in implementation cost. If we propose a restriction, it must be one that library writers can live with.

Lots of these cases fail with current compilers, so there can't be a lot of existing code using them. We plan to find out what cases there are in libraries like Loki and Boost.

We noted that in many cases one can move the code into a class to get the same result. The implementation problem comes up when the expression-in-sizeof is in a template deduction context or part of a template signature. The problem cases are ones where an error causes deduction to fail, as opposed to contexts where an error causes a diagnostic. The latter contexts are easier to handle; however, there are situations where "fail deduction" may be the desired behavior.




276. Order of destruction of parameters and temporaries

Section: 6.6  stmt.jump     Status: open     Submitter: James Kanze     Date: 28 Mar 2001     Priority: 2     Drafting: Adamczyk

According to 6.6  stmt.jump paragraph 2,

On exit from a scope (however accomplished), destructors (12.4  class.dtor) are called for all constructed objects with automatic storage duration (3.7.2  basic.stc.auto) (named objects or temporaries) that are declared in that scope, in the reverse order of their declaration.

This wording is problematic for temporaries and for parameters. First, temporaries are not "declared," so this requirement does not apply to them, in spite of the assertion in the quoted text that it does.

Second, although the parameters of a function are declared in the called function, they are constructed and destroyed in the calling context, and the order of evaluation of the arguments is unspecified (cf 5.2.2  expr.call paragraphs 4 and 8). The order of destruction of the parameters might, therefore, be different from the reverse order of their declaration.

Notes from 04/01 meeting:

Any resolution of this issue should be careful not to introduce requirements that are redundant or in conflict with those of other parts of the IS. This is especially true in light of the pending issues with respect to the destruction of temporaries (see issues 86, 124, 199, and 201). If possible, the wording of a resolution should simply reference the relevant sections.

It was also noted that the temporary for a return value is also destroyed "out of order." Steve Adamczyk will investigate.




157. Omitted typedef declarator

Section: dcl.dcl     Status: open     Submitter: Daveed Vandevoorde     Date: 19 Aug 1999     Priority: 3

From reflector messages 8275-80, 8272-3.

dcl.dcl paragraph 3 reads,

In a simple-declaration, the optional init-declarator-list can be omitted only when... the decl-specifier-seq contains either a class-specifier, an elaborated-type-specifier with a class-key (9.1  class.name ), or an enum-specifier. In these cases and whenever a class-specifier or enum-specifier is present in the decl-specifier-seq, the identifiers in those specifiers are among the names being declared by the declaration... In such cases, and except for the declaration of an unnamed bit-field (9.6  class.bit ), the decl-specifier-seq shall introduce one or more names into the program, or shall redeclare a name introduced by a previous declaration. [Example:
    enum { };           // ill-formed
    typedef class { };  // ill-formed
—end example]
In the absence of any explicit restrictions in 7.1.3  dcl.typedef , this paragraph appears to allow declarations like the following:
    typedef struct S { };    // no declarator
    typedef enum { e1 };     // no declarator
In fact, the final example in 7  dcl.dcl paragraph 3 would seem to indicate that this is intentional: since it is illustrating the requirement that the decl-specifier-seq must introduce a name in declarations in which the init-declarator-list is omitted, presumably the addition of a class name would have made the example well-formed.

On the other hand, there is no good reason to allow such declarations; the only reasonable scenario in which they might occur is a mistake on the programmer's part, and it would be a service to the programmer to require that such errors be diagnosed.




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

Section: 7.1.2  dcl.fct.spec     Status: open     Submitter: Steve Clamage     Date: 14 Oct 2001     Priority: 2

From messages 9338-9342.

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.




144. Position of friend specifier

Section: 7.1.5.3  dcl.type.elab     Status: open     Submitter: Daveed Vandevoorde     Date: 22 Jul 1999     Priority: 3

From reflector message 8232.

7.1.5.3  dcl.type.elab paragraph 1 seems to impose an ordering constraint on the elements of friend class declarations. However, the general rule is that declaration specifiers can appear in any order. Should

    class C friend;
be well-formed?


311. Using qualified name to reopen nested namespace

Section: 7.3.1  namespace.def     Status: open     Submitter: Bjarne Stroustrup     Date: 18 Sep 2001     Priority: 2

From messages 9309, 9312, 9313, 9315.

I received an inquiry/complaint that you cannot re-open a namespace using a qualified name. For example, the following program is ok, but if you uncomment the commented lines you get an error:

namespace A {
    namespace N {
	int a;
    }
    int b;
    namespace M {
	int c;
    }
}

//namespace A::N {
//    int d;
//}

namespace A {
    namespace M {
        int e;
    }
}

int main()
{
    A::N::a = 1;
    A::b = 2;
    A::M::c = 3;
//  A::N::d = 4;
    A::M::e = 5;
}

Andrew Koenig: There's a name lookup issue lurking here. For example:

    int x;

    namespace A {
	int x;
	namespace N {
	   int y;
	};
    }

    namespace A::N {
        int* y = &x;  // which x?
    }

Jonathan Caves: I would assume that any rule would state that:

namespace A::B {
would be equivalent to:
namespace A {
   namespace B {
so in your example 'x' would resolve to A::x

BTW: we have received lots of bug reports about this "oversight".

Lawrence Crowl: Even worse is

    int x;
    namespace A {
      int x;
    }
    namespace B {
      int x;
      namespace ::A {
         int* y = &x;
      }
    }
I really don't think that the benefits of qualied names here is worth the cost.




36. using-declarations in multiple-declaration contexts

Section: 7.3.3  namespace.udecl     Status: open     Submitter: Andrew Koenig     Date: 20 Aug 1998     Priority: 3

From reflector message core-7811.

Section 7.3.3  namespace.udecl paragraph 8 says:

A using-declaration is a declaration and can therefore be used repeatedly where (and only where) multiple declarations are allowed.
It contains the following example:
    namespace A {
            int i;
    }
    
    namespace A1 {
            using A::i;
            using A::i;             // OK: double declaration
    }
    
    void f()
    {
            using A::i;
            using A::i;             // error: double declaration
    }
However, if "using A::i;" is really a declaration, and not a definition, it is far from clear that repeating it should be an error in either context. Consider:
    namespace A {
            int i;
            void g();
    }
    
    void f() {
            using A::g;
            using A::g;
    }
Surely the definition of f should be analogous to
    void f() {
            void g();
            void g();
    }
which is well-formed because "void g();" is a declaration and not a definition.

Indeed, if the double using-declaration for A::i is prohibited in f, why should it be allowed in namespace A1?

Proposed Resolution (04/99): Change the comment "// error: double declaration" to "// OK: double declaration". (This should be reviewed against existing practice.)

Notes from 04/00 meeting:

The core language working group was unable to come to consensus over what kind of declaration a using-declaration should emulate. In a straw poll, 7 members favored allowing using-declarations wherever a non-definition declaration could appear, while 4 preferred to allow multiple using-declarations only in namespace scope (the rationale being that the permission for multiple using-declarations is primarily to support its use in multiple header files, which are seldom included anywhere other than namespace scope). John Spicer pointed out that friend declarations can appear multiple times in class scope and asked if using-declarations would have the same property under the "like a declaration" resolution.

As a result of the lack of agreement, the issue was returned to "open" status. Because of its small potential impact on code, it was given priority 3.

See also issues 56, 85, and 138..




332. cv-qualified void parameter types

Section: 8.3.5  dcl.fct     Status: open     Submitter: Michiel Salters     Date: 9 Jan 2002     Priority: 3

8.3.5  dcl.fct/2 restricts the use of void as parameter type, but does not mention CV qualified versions. Since void f(volatile void) isn't a callable function anyway, 8.3.5  dcl.fct should also ban cv-qualified versions. (BTW, this follows C)

Suggested resolution:

A possible resolution would be to add (cv-qualified) before void in

The parameter list (void) is equivalent to the empty parameter list. Except for this special case, (cv-qualified) void shall not be a parameter type (though types derived from void, such as void*, can).



325. When are default arguments parsed?

Section: 8.3.6  dcl.fct.default     Status: open     Submitter: Nathan Sidwell     Date: 27 Nov 2001     Priority: 2

The standard is not precise enough about when the default arguments of member functions are parsed. This leads to confusion over whether certain constructs are legal or not, and the validity of certain compiler implementation algorithms.

8.3.6  dcl.fct.default paragraph 5 says "names in the expression are bound, and the semantic constraints are checked, at the point where the default argument expression appears"

However, further on at paragraph 9 in the same section there is an example, where the salient parts are

  int b;
  class X {
    int mem2 (int i = b); // OK use X::b
    static int b;
  };
which appears to contradict the former constraint. At the point the default argument expression appears in the definition of X, X::b has not been declared, so one would expect ::b to be bound. This of course appears to violate 3.3.6  basic.scope.class paragraph 1(2) "A name N used in a class S shall refer to the same declaration in its context and when reevaluated in the complete scope of S. No diagnostic is required."

Furthermore 3.3.6  basic.scope.class paragraph 1(1) gives the scope of names declared in class to "consist not only of the declarative region following the name's declarator, but also of .. default arguments ...". Thus implying that X::b is in scope in the default argument of X::mem2 previously.

That previous paragraph hints at an implementation technique of saving the token stream of a default argument expression and parsing it at the end of the class definition (much like the bodies of functions defined in the class). This is a technique employed by GCC and, from its behaviour, in the EDG front end. The standard leaves two things unspecified. Firstly, is a default argument expression permitted to call a static member function declared later in the class in such a way as to require evaluation of that function's default arguments? I.e. is the following well formed?

  class A {
    static int Foo (int i = Baz ());
    static int Baz (int i = Bar ());
    static int Bar (int i = 5);
 };
If that is well formed, at what point does the non-sensicalness of
  class B {
    static int Foo (int i = Baz ());
    static int Baz (int i = Foo());
  };
become detected? Is it when B is complete? Is it when B::Foo or B::Baz is called in such a way to require default argument expansion? Or is no diagnostic required?

The other problem is with collecting the tokens that form the default argument expression. Default arguments which contain template-ids with more than one parameter present a difficulty in determining when the default argument finishes. Consider,

  template <int A, typename B> struct T { static int i;};
  class C {
    int Foo (int i = T<1, int>::i);
  };
The default argument contains a non-parenthesized comma. Is it required that this comma is seen as part of the default argument expression and not the beginning of another of argument declaration? To accept this as part of the default argument would require name lookup of T (to determine that the '<' was part of a template argument list and not a less-than operator) before C is complete. Furthermore, the more pathological
  class D {
    int Foo (int i = T<1, int>::i);
    template T<int A, typename B> struct T {static int i;};
  };
would be very hard to accept. Even though T is declared after Foo, T is in scope within Foo's default argument expression.

Suggested resolution:

Append the following text to 8.3.6  dcl.fct.default paragraph 8.

The default argument expression of a member function declared in the class definition consists of the sequence of tokens up until the next non-parenthesized, non-bracketed comma or close parenthesis. Furthermore such default argument expressions shall not require evaluation of a default argument of a function declared later in the class.

This would make the above A, B, C and D ill formed and is in line with the existing compiler practice that I am aware of.




155. Brace initializer for scalar

Section: 8.5  dcl.init     Status: open     Submitter: Steve Clamage     Date: 12 Aug 1999     Priority: 3

From reflector messages 8265-6.

It is not clear whether the following declaration is well-formed:

    struct S { int i; } s = { { 1 } };
According to 8.5.1  dcl.init.aggr paragraph 2, a brace-enclosed initializer is permitted for a subaggregate of an aggregate; however, i is a scalar, not an aggregate. 8.5  dcl.init paragraph 13 says that a standalone declaration like
    int i = { 1 };
is permitted, but it is not clear whether this says anything about the form of initializers for scalar members of aggregates.

This is (more) clearly permitted by the C89 Standard.

Notes from 10/00 meeting: Priority lowered to 3.




253. Why must empty or fully-initialized const objects be initialized?

Section: 8.5  dcl.init     Status: open     Submitter: Mike Miller     Date: 11 Jul 2000     Priority: 3

From messages 8804-8806.

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.

What if a const POD object has no non-static data members? This wording requires an empty initializer for such cases:

    struct Z {
        // no data members
        operator int() const { return 0; }
    };

    void f() {
        const Z z1;         // ill-formed: no initializer
        const Z z2 = { };   // well-formed
    }

Similar comments apply to a non-POD const object, all of whose non-static data members and base class subobjects have default constructors. Why should the class of such an object be required to have a user-declared default constructor?

(See also issue 78.)




233. References vs pointers in UDC overload resolution

Section: 8.5.3  dcl.init.ref     Status: open     Submitter: Matthias Meixner     Date: 9 Jun 2000     Priority: 2     Drafting: Adamczyk

There is an inconsistency in the handling of references vs pointers in user defined conversions and overloading. The reason for that is that the combination of 8.5.3  dcl.init.ref and 4.4  conv.qual circumvents the standard way of ranking conversion functions, which was probably not the intention of the designers of the standard.

Let's start with some examples, to show what it is about:

    struct Z { Z(){} };

    struct A {
       Z x;

       operator Z *() { return &x; }
       operator const Z *() { return &x; }
    };

    struct B {
       Z x;

       operator Z &() { return x; }
       operator const Z &() { return x; }
    };

    int main()
    {
       A a;
       Z *a1=a;
       const Z *a2=a; // not ambiguous

       B b;
       Z &b1=b;
       const Z &b2=b; // ambiguous
    }

So while both classes A and B are structurally equivalent, there is a difference in operator overloading. I want to start with the discussion of the pointer case (const Z *a2=a;): 13.3.3  over.match.best is used to select the best viable function. Rule 4 selects A::operator const Z*() as best viable function using 13.3.3.2  over.ics.rank since the implicit conversion sequence const Z* -> const Z* is a better conversion sequence than Z* -> const Z*.

So what is the difference to the reference case? Cv-qualification conversion is only applicable for pointers according to 4.4  conv.qual. According to 8.5.3  dcl.init.ref paragraphs 4-7 references are initialized by binding using the concept of reference-compatibility. The problem with this is, that in this context of binding, there is no conversion, and therefore there is also no comparing of conversion sequences. More exactly all conversions can be considered identity conversions according to 13.3.3.1.4  over.ics.ref paragraph 1, which compare equal and which has the same effect. So binding const Z* to const Z* is as good as binding const Z* to Z* in terms of overloading. Therefore const Z &b2=b; is ambiguous. [13.3.3.1.4  over.ics.ref paragraph 5 and 13.3.3.2  over.ics.rank paragraph 3 rule 3 (S1 and S2 are reference bindings ...) do not seem to apply to this case]

There are other ambiguities, that result in the special treatment of references: Example:

    struct A {int a;};
    struct B: public A { B() {}; int b;};

    struct X {
       B x;
       operator A &() { return x; }
       operator B &() { return x; }
    };

    main()
    {
       X x;
       A &g=x; // ambiguous
    }

Since both references of class A and B are reference compatible with references of class A and since from the point of ranking of implicit conversion sequences they are both identity conversions, the initialization is ambiguous.

So why should this be a defect?

So overall I think this was not the intention of the authors of the standard.

So how could this be fixed? For comparing conversion sequences (and only for comparing) reference binding should be treated as if it was a normal assignment/initialization and cv-qualification would have to be defined for references. This would affect 8.5.3  dcl.init.ref paragraph 6, 4.4  conv.qual and probably 13.3.3.2  over.ics.rank paragraph 3.

Another fix could be to add a special case in 13.3.3  over.match.best paragraph 1.




291. Overload resolution needed when binding reference to class rvalue

Section: 8.5.3  dcl.init.ref     Status: open     Submitter: Andrei Iltchenko     Date: 15 Jun 2001     Priority: 2

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.




327. Use of "structure" without definition

Section: class     Status: open     Submitter: James Kanze     Date: 9 Dec 2001     Priority: 3

From message 9419.

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.



315. Is call of static member function through null pointer undefined?

Section: 9.4.1  class.static.mfct     Status: open     Submitter: Jason Merrill     Date: 7 Oct 2001     Priority: 2

From message 9324.

Another instance to consider is that of invoking a member function from a null pointer:

  struct A { void f () { } };
  int main ()
  {
    A* ap = 0;
    ap->f ();
  }

Which is explicitly noted as undefined in 9.3.1  class.mfct.nonstatic, even though one could argue that since f() is empty, there is no lvalue->rvalue conversion.

If f is static, however, there seems to be no such rule, and the call is only undefined if the dereference implicit in the -> operator is undefined. IMO it should be.

Incidentally, another thing that ought to be cleaned up is the inconsistent use of "indirection" and "dereference". We should pick one. (This terminology issue has been broken out as issue 342.)

This is related to issue 232




57. Empty unions

Section: 9.5  class.union     Status: open     Submitter: Steve Adamczyk     Date: 13 Oct 1998     Priority: 3

There doesn't seem to be a prohibition in 9.5  class.union against a declaration like

    union { int : 0; } x;
Should that be valid? If so, 8.5  dcl.init paragraph 5 third bullet, which deals with default-initialization of unions, should say that no initialization is done if there are no data members.

What about:

    union { } x;
    static union { };
If the first example is well-formed, should either or both of these cases be well-formed as well?

(See also the resolution for issue 151.)

Notes from 10/00 meeting: The resolution to issue 178, which was accepted as a DR, addresses the first point above (default initialization). The other questions have not yet been decided, however.




58. Signedness of bit fields of enum type

Section: 9.6  class.bit     Status: open     Submitter: Steve Adamczyk     Date: 13 Oct 1998     Priority: 3

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

Notes from 10/00 meeting: Priority lowered to 3.




347. Use of derived class name in defining base class nested class

Section: 9.7  class.nest     Status: open     Submitter: Jason Shirk     Date: 21 March 2002

From message 9494.

9.3  class.mfct paragraph 5 says this about member functions defined lexically outside the class:

the member function name shall be qualified by its class name using the :: operator

9.4.2  class.static.data paragraph 2 says this about static data members:

In the definition at namespace scope, the name of the static data member shall be qualified by its class name using the :: operator

I would have expected similar wording in 9.7  class.nest paragraph 3 for nested classes. Without such wording, the following seems to be legal (and is allowed by all the compilers I have):

  struct base {
    struct nested;
  };

  struct derived : base {};
  struct derived::nested {};

Is this just an oversight, or is there some rationale for this behavior?




230. Calls to pure virtual functions

Section: 10.4  class.abstract     Status: open     Submitter: Jim Hill     Date: 4 May 2000     Priority: 3

According to 10.4  class.abstract paragraph 6,

Member functions can be called from a constructor (or destructor) of an abstract class; the effect of making a virtual call (10.3  class.virtual) to a pure virtual function directly or indirectly for the object being created (or destroyed) from such a constructor (or destructor) is undefined.

This prohibition is unnecessarily restrictive. It should not apply to cases in which the pure virtual function has been defined.

Currently the "pure" specifier for a virtual member function has two meanings that need not be related:

  1. A pure virtual function need not be defined.
  2. A pure virtual function must be overridden in any concrete derived class.

The prohibition of virtual calls to pure virtual functions arises from the first meaning and unnecessarily penalizes those who only need the second.

For example, consider a scenario such as the following. A class B is defined containing a (non-pure) virtual function f that provides some initialization and is thus called from the base class constructor. As time passes, a number of classes are derived from B and it is noticed that each needs to override f, so it is decided to make B::f pure to enforce this convention while still leaving the original definition of B::f to perform its needed initialization. However, the act of making B::f pure means that every reference to f that might occur during the execution of one of B's constructors must be tracked down and edited to be a qualified reference to B::f. This process is tedious and error-prone: needed edits might be overlooked, and calls that actually should be virtual when the containing function is called other than during construction/destruction might be incorrectly changed.

Suggested resolution: Allow virtual calls to pure virtual functions if the function has been defined.




331. Allowed copy constructor signatures

Section: 12.1  class.ctor     Status: open     Submitter: Richard Smith     Date: 8 Jan 2002     Priority: 1

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.

Suggested resolution:

Change the wording of 12.1  class.ctor paragraph 10 to

A copy constructor for a class X is a constructor with a first parameter of type X &, const X &, volatile X & or const volatile X &. [Note: see 12.8  class.copy for more information on copy constructors.]



199. Order of destruction of temporaries

Section: 12.2  class.temporary     Status: open     Submitter: Alan Nash     Date: 27 Jan 2000     Priority: 3

From reflector messages 8505-6, 8508, 8510, and 8515.

12.2  class.temporary paragraph 3 simply states the requirement that temporaries created during the evaluation of an expression

are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created.
There is nothing said about the relative order in which these temporaries are destroyed.

Paragraph 5, dealing with temporaries bound to references, says

the temporaries created during the evaluation of the expression initializing the reference, except the temporary to which the reference is bound, are destroyed at the end of the full-expression in which they are created and in the reverse order of the completion of their construction.
Is this difference intentional? May temporaries in expressions other than those initializing references be deleted in non-LIFO order?

Notes from 04/00 meeting:

Steve Adamczyk expressed concern about constraining implementations that are capable of fine-grained parallelism -- they may be unable to determine the order of construction without adding undesirable overhead.




320. Question on copy constructor elision example

Section: 12.2  class.temporary     Status: open     Submitter: Steve Clamage     Date: 2 Nov 2001     Priority: 2

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?




344. Naming destructors

Section: 12.4  class.dtor     Status: open     Submitter: Jamie Schmeiser     Date: 25 April 2002

Note that destructors suffer from similar problems as those of constructors dealt with in issue 194 and in 263 (constructors as friends). Also, the wording in 12.4  class.dtor, paragraph 1 does not permit a destructor to be defined outside of the memberlist.

Change 12.4  class.dtor, paragraph 1 from

...A special declarator syntax using an optional function-specifier (7.1.2  dcl.fct.spec) followed by ~ followed by the destructor's class name followed by an empty parameter list is used to declare the destructor in a class definition. In such a declaration, the ~ followed by the destructor's class name can be enclosed in optional parentheses; such parentheses are ignored....

to

...A special declarator syntax using an optional sequence of function-specifiers (7.1.2  dcl.fct.spec), an optional friend keyword, an optional sequence of function-specifiers (7.1.2  dcl.fct.spec) followed by an optional :: scope-resolution-operator followed by an optional nested-name-specifier followed by ~ followed by the destructor's class name followed by an empty parameter list is used to declare the destructor. The optional nested-name-specifier shall not be specified in the declaration of a destructor within the member-list of the class of which the destructor is a member. In such a declaration, the optional :: scope-resolution-operator followed by an optional nested-name-specifier followed by ~ followed by the destructor's class name can be enclosed in optional parentheses; such parentheses are ignored....



255. Placement deallocation functions and lookup ambiguity

Section: 12.5  class.free     Status: open     Submitter: Mike Miller     Date: 26 Oct 2000     Priority: 3

Paragraph 4 of 12.5  class.free speaks of looking up a deallocation function. While it is an error if a placement deallocation function alone is found by this lookup, there seems to be an assumption that a placement deallocation function and a usual deallocation function can both be declared in a given class scope without creating an ambiguity. The normal mechanism by which ambiguity is avoided when functions of the same name are declared in the same scope is overload resolution; however, there is no mention of overload resolution in the description of the lookup. In fact, there appears to be nothing in the current wording that handles this case. That is, the following example appears to be ill-formed, according to the current wording:

    struct S {
        void operator delete(void*);
        void operator delete(void*, int);
    };
    void f(S* p) {
        delete p;    // ill-formed: ambiguous operator delete
    }

Suggested resolution (Mike Miller, March 2002):

I think you might get the right effect by replacing the last sentence of 12.5  class.free paragraph 4 with something like:

After removing all placement deallocation functions, the result of the lookup shall contain an unambiguous and accessible deallocation function.



257. Abstract base constructors and virtual base initialization

Section: 12.6.2  class.base.init     Status: open     Submitter: Mike Miller     Date: 1 Nov 2000     Priority: 3

From messages 8946 and 8948.

Must a constructor for an abstract base class provide a mem-initializer for each virtual base class from which it is directly or indirectly derived? Since the initialization of virtual base classes is performed by the most-derived class, and since an abstract base class can never be the most-derived class, there would seem to be no reason to require constructors for abstract base classes to initialize virtual base classes.

It is not clear from the Standard whether there actually is such a requirement or not. The relevant text is found in 12.6.2  class.base.init paragraph 6:

All sub-objects representing virtual base classes are initialized by the constructor of the most derived class (1.8  intro.object). If the constructor of the most derived class does not specify a mem-initializer for a virtual base class V, then V's default constructor is called to initialize the virtual base class subobject. If V does not have an accessible default constructor, the initialization is ill-formed. A mem-initializer naming a virtual base class shall be ignored during execution of the constructor of any class that is not the most derived class.

This paragraph requires only that the most-derived class's constructor have a mem-initializer for virtual base classes. Should the silence be construed as permission for constructors of classes that are not the most-derived to omit such mem-initializers?




111. Copy constructors and cv-qualifiers

Section: 12.8  class.copy     Status: open     Submitter: Jack Rouse     Date: 4 May 1999     Priority: 3

From reflector messages 8038-8040.

Jack Rouse: In 12.8  class.copy paragraph 8, the standard includes the following about the copying of class subobjects in such a constructor:

But there can be multiple copy constructors declared by the user with differing cv-qualifiers on the source parameter. I would assume overload resolution would be used in such cases. If so then the passage above seems insufficient.

Mike Miller: I'm more concerned about 12.8  class.copy paragraph 7, which lists the situations in which an implicitly-defined copy constructor can render a program ill-formed. Inaccessible and ambiguous copy constructors are listed, but not a copy constructor with a cv-qualification mismatch. These two paragraphs taken together could be read as requiring the calling of a copy constructor with a non-const reference parameter for a const data member.




260. User-defined conversions and built-in operator=

Section: 13.6  over.built     Status: open     Submitter: Scott Douglas     Date: 4 Nov 2000     Priority: 2     Drafting: Adamczyk

According to the Standard (although not implemented this way in most implementations), the following code exhibits non-intuitive behavior:

  struct T {
    operator short() const;
    operator int() const;
  };

  short s;

  void f(const T& t) {
    s = t;  // surprisingly calls T::operator int() const
  }

The reason for this choice is 13.6  over.built paragraph 18:

For every triple (L, VQ, R), where L is an arithmetic type, VQ is either volatile or empty, and R is a promoted arithmetic type, there exist candidate operator functions of the form

Because R is a "promoted arithmetic type," the second argument to the built-in assignment operator is int, causing the unexpected choice of conversion function.

Suggested resolution: Provide built-in assignment operators for the unpromoted arithmetic types.

Related to the preceding, but not resolved by the suggested resolution, is the following problem. Given:

    struct T {
	 operator int() const;
	 operator double() const;
    };

I believe the standard requires the following assignment to be ambiguous (even though I expect that would surprise the user):

    double x;
    void f(const T& t) { x = t; }

The problem is that both of these built-in operator=()s exist (13.6  over.built paragraph 18):

    double& operator=(double&, int);
    double& operator=(double&, double);

Both are an exact match on the first argument and a user conversion on the second. There is no rule that says one is a better match than the other.

The compilers that I have tried (even in their strictest setting) do not give a peep. I think they are not following the standard. They pick double& operator=(double&, double) and use T::operator double() const.

I hesitate to suggest changes to overload resolution, but a possible resolution might be to introduce a rule that, for built-in operator= only, also considers the conversion sequence from the second to the first type. This would also resolve the earlier question.

It would still leave x += t etc. ambiguous -- which might be the desired behavior and is the current behavior of some compilers.

Notes from the 04/01 meeting:

The difference between initialization and assignment is disturbing. On the other hand, promotion is ubiquitous in the language, and this is the beginning of a very slippery slope (as the second report above demonstrates). Steve Adamczyk will investigate.




110. Can template functions and classes be declared in the same scope?

Section: 14  temp     Status: open     Submitter: John Spicer     Date: 28 Apr 1999     Priority: 3

From reflector messages 8025-8031.

According to 14  temp paragraph 5,

Except that a function template can be overloaded either by (non-template) functions with the same name or by other function templates with the same name (14.8.3  temp.over ), a template name declared in namespace scope or in class scope shall be unique in that scope.
3.3.7  basic.scope.hiding paragraph 2 agrees that only functions, not function templates, can hide a class name declared in the same scope:
A class name (9.1  class.name ) or enumeration name (7.2  dcl.enum ) can be hidden by the name of an object, function, or enumerator declared in the same scope.
However, 3.3  basic.scope paragraph 4 treats functions and template functions together in this regard:
Given a set of declarations in a single declarative region, each of which specifies the same unqualified name,

John Spicer: You should be able to take an existing program and replace an existing function with a function template without breaking unrelated parts of the program. In addition, all of the compilers I tried allow this usage (EDG, Sun, egcs, Watcom, Microsoft, Borland). I would recommend that function templates be handled exactly like functions for purposes of name hiding.

Martin O'Riordan: I don't see any justification for extending the purview of what is decidedly a hack, just for the sake of consistency. In fact, I think we should go further and in the interest of consistency, we should deprecate the hack, scheduling its eventual removal from the C++ language standard.

The hack is there to allow old C programs and especially the 'stat.h' file to compile with minimum effort (also several other Posix and X headers). People changing such older programs have ample opportunity to "do it right". Indeed, if you are adding templates to an existing program, you should probably be placing your templates in a 'namespace', so the issue disappears anyway. The lookup rules should be able to provide the behaviour you need without further hacking.




96. Syntactic disambiguation using the template keyword

Section: 14.2  temp.names     Status: open     Submitter: John Spicer     Date: 16 Feb 1999     Priority: 1     Drafting: Spicer

The following is the wording from 14.2  temp.names paragraphs 4 and 5 that discusses the use of the "template" keyword following . or -> and in qualified names.

The whole point of this feature is to say that the "template" keyword is needed to indicate that a "<" begins a template parameter list in certain contexts. The constraints in paragraph 5 leave open to debate certain cases.

First, I think it should be made more clear that the template name must be followed by a template argument list when the "template" keyword is used in these contexts. If we don't make this clear, we would have to add several semantic clarifications instead. For example, if you say "p->template f()", and "f" is an overload set containing both templates and nontemplates: a) is this valid? b) are the nontemplates in the overload set ignored? If the user is forced to write "p->template f<>()" it is clear that this is valid, and it is equally clear that nontemplates in the overload set are ignored. As this feature was added purely to provide syntactic guidance, I think it is important that it otherwise have no semantic implications.

I propose that paragraph 5 be modified to:

(See also issue 30 and document J16/00-0008 = WG21 N1231.)

Notes from 04/00 meeting:

The discussion of this issue revived interest in issues 11 and 109. John Spicer will revise his paper and proposal in light of the discussion.




343. Make template optional in contexts that require a type

Section: 14.2  temp.names     Status: open     Submitter: Steve Adamczyk     Date: 23 April 2002

By analogy with typename, the keyword template used to indicate that a dependent name will be a template name should be optionsl in contexts where a type is required, e.g., base class lists. We could also consider member and parameter declarations.

This was suggested by issue 314.




354. Null as nontype template argument

Section: 14.3.2  temp.arg.nontype     Status: open     Submitter: John Spicer     Date: 2 May 2002

From messages 9527-9528.

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.




329. Evaluation of friends of templates

Section: 14.5.3  temp.friend     Status: open     Submitter: John Spicer     Date: 19 Dec 2001     Priority: 1

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.




229. Partial specialization of function templates

Section: 14.5.4  temp.class.spec     Status: open     Submitter: Dave Abrahams     Date: 1 Apr 2000     Priority: 2

Library issue 225 poses the following questions:

  1. How can a 3rd party library implementor (lib1) write a version of a standard algorithm which is specialized to work with his own class template?
  2. How can another library implementor (lib2) write a generic algorithm which will take advantage of the specialized algorithm in lib1?

For example, a programmer might want to provide a version of std::swap that would be used for any specialization of a particular class template. It is possible to do that for specific types, but not for all specializations of a template.

The problem is due to the fact that programmers are forbidden to add overloads to namespace std, although specializations are permitted. One suggested solution would be to allow partial specialization of function templates, analogous to partial specialization of class templates.

Library issue 225 contains a detailed proposal for adding partial specialization of function templates (not reproduced here in the interest of space and avoiding multiple-copy problems). This Core issue is being opened to provide for discussion of the proposal within the core language working group.

Notes from 10/00 meeting:

A major concern over the idea of partial specialization of function templates is that function templates can be overloaded, unlike class templates. Simply naming the function template in the specialization, as is done for class specialization, is not adequate to identify the template being specialized.

In view of this problem, the library working group is exploring the other alternative, permitting overloads to be added to functions in namespace std, as long as certain restrictions (to be determined) are satisfied.

(See also documents N1295 and N1296 and issue 285.)

Notes from 10/01 meeting:

The Core Working Group decided to ask the Library Working Group for guidance on whether this feature is still needed to resolve a library issue. The answer at present is "we don't know."




310. Can function templates differing only in parameter cv-qualifiers be overloaded?

Section: 14.5.5.1  temp.over.link     Status: open     Submitter: Andrei Iltchenko     Date: 29 Aug 2001     Priority: 2

I get the following error diagnostic [from the EDG front end]:

line 8: error: function template "example<T>::foo<R,A>(A)" has
          already been declared
     R  foo(const A);
        ^
when compiling this piece of code:
struct  example  {
   template<class R, class A>   // 1-st member template
   R  foo(A);
   template<class R, class A>   // 2-nd member template
   const R  foo(A&);
   template<class R, class A>   // 3-d  member template
   R  foo(const A);
};

/*template<> template<>
int  example<char>::foo(int&);*/


int  main()
{
   int  (example<char>::* pf)(int&) =
      &example<char>::foo;
}

The implementation complains that

   template<class R, class A>   // 1-st member template
   R  foo(A);
   template<class R, class A>   // 3-d  member template
   R  foo(const A);
cannot be overloaded and I don't see any reason for it since it is function template specializations that are treated like ordinary non-template functions, meaning that the transformation of a parameter-declaration-clause into the corresponding parameter-type-list is applied to specializations (when determining its type) and not to function templates.

What makes me think so is the contents of 14.5.5.1  temp.over.link and the following sentence from 14.8.2.1  temp.deduct.call "If P is a cv-qualified type, the top level cv-qualifiers of P are ignored for type deduction". If the transformation was to be applied to function templates, then there would be no reason for having that sentence in 14.8.2.1  temp.deduct.call.

14.8.2.2  temp.deduct.funcaddr, which my example is based upon, says nothing about ignoring the top level cv-qualifiers of the function parameters of the function template whose address is being taken.

As a result, I expect that template argument deduction will fail for the 2-nd and 3-d member templates and the 1-st one will be used for the instantiation of the specialization.




23. Some questions regarding partial ordering of function templates

Section: 14.5.5.2  temp.func.order     Status: open     Submitter: unknown     Date: unknown     Priority: 3

(Previously numbered 934.)

Issue 1:

14.5.5.2  temp.func.order paragraph 2 says:

Given two overloaded function templates, whether one is more specialized than another can be determined by transforming each template in turn and using argument deduction (14.8.2  temp.deduct ) to compare it to the other.
14.8.2  temp.deduct now has 4 subsections describing argument deduction in different situations. I think this paragraph should point to a subsection of 14.8.2  temp.deduct .

Rationale:

This is not a defect; it is not necessary to pinpoint cross-references to this level of detail.

Issue 2:

14.5.5.2  temp.func.order paragraph 4 says:

Using the transformed function parameter list, perform argument deduction against the other function template. The transformed template is at least as specialized as the other if, and only if, the deduction succeeds and the deduced parameter types are an exact match (so the deduction does not rely on implicit conversions).
In "the deduced parameter types are an exact match", the terms exact match do not make it clear what happens when a type T is compared to the reference type T&. Is that an exact match?

John Spicer will propose a resolution, which may or may not require a change to the standard.

Issue 3:

14.5.5.2  temp.func.order paragraph 5 says:

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.
What happens in this case:
    template<class T> void f(T,int);
    template<class T> void f(T, T);
    void f(1,1);
For the first function template, there is no type deduction for the second parameter. So the rules in this clause seem to imply that the second function template will be chosen.

Rationale:

This is not a defect; the standard unambiguously makes the above example ill-formed due to ambiguity.




214. Partial ordering of function templates is underspecified

Section: 14.5.5.2  temp.func.order     Status: open     Submitter: Martin von Loewis/Martin Sebor     Date: 13 Mar 2000     Priority: 1     Drafting: Spicer

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

Notes from 04/00 meeting:

John Spicer volunteered to draft examples illustrating possible choices, so that the group can agree on desired outcomes. This will facilitate formulation of general rules.

Nathan Sidwell, in message 9445:

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.




345. Misleading comment on example in templates chapter

Section: 14.6  temp.res     Status: open     Submitter: Jason Shirk     Date: 18 March 2002

From message 9493.

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.




186. Name hiding and template template-parameters

Section: 14.6.1  temp.local     Status: open     Submitter: John Spicer     Date: 11 Nov 1999     Priority: 3

From reflector message 8361.

The standard prohibits a class template from having the same name as one of its template parameters (14.6.1  temp.local paragraph 4). This prohibits

    template <class X> class X;
for the reason that the template name would hide the parameter, and such hiding is in general prohibited.

Presumably, we should also prohibit

    template <template <class T> class T> struct A;
for the same reason.


334. Is a comma-expression dependent if its first operand is?

Section: 14.6.2.2  temp.dep.expr     Status: open     Submitter: John Spicer     Date: 10 Jan 2002     Priority: 2

Is the comma expression in the following dependent?

  template <class T> static void f(T)
  {
  }
  template <class T> void g(T)
  {
    f((T::x, 0));
  }
  struct A {
    static int x;
  };
  void h()
  {
    g(A());
  }

According to the standard, it is, because 14.6.2.2  temp.dep.expr says that an expression is dependent if any of its sub-expressions is dependent, but there is a question about whether the language should say something different. The type and value of the expression are not really dependent, and similar cases (like casting T::x to int) are not dependent.




2. How can dependent names be used in member declarations that appear outside of the class template definition?

Section: 14.6.4  temp.dep.res     Status: open     Submitter: unknown     Date: unknown     Priority: 1     Drafting: Spicer

(Previously numbered 737.)

    template <class T> class Foo {
    
       public:
       typedef int Bar;
       Bar f();
    };
    template <class T> typename Foo<T>::Bar Foo<T>::f() { return 1;}
                       --------------------
In the class template definition, the declaration of the member function is interpreted as:
   int Foo<T>::f();
In the definition of the member function that appears outside of the class template, the return type is not known until the member function is instantiated. Must the return type of the member function be known when this out-of-line definition is seen (in which case the definition above is ill-formed)? Or is it OK to wait until the member function is instantiated to see if the type of the return type matches the return type in the class template definition (in which case the definition above is well-formed)?

Suggested resolution: (John Spicer)

My opinion (which I think matches several posted on the reflector recently) is that the out-of-class definition must match the declaration in the template. In your example they do match, so it is well formed.

I've added some additional cases that illustrate cases that I think either are allowed or should be allowed, and some cases that I don't think are allowed.

    template <class T> class A { typedef int X; };
    
    
    template <class T> class Foo {
     public:
       typedef int Bar;
       typedef typename A<T>::X X;
       Bar f();
       Bar g1();
       int g2();
       X h();
       X i();
       int j();
     };
    
     // Declarations that are okay
     template <class T> typename Foo<T>::Bar Foo<T>::f()
                                                     { return 1;}
     template <class T> typename Foo<T>::Bar Foo<T>::g1()
                                                     { return 1;}
     template <class T> int Foo<T>::g2() { return 1;}
     template <class T> typename Foo<T>::X Foo<T>::h() { return 1;}
    
     // Declarations that are not okay
     template <class T> int Foo<T>::i() { return 1;}
     template <class T> typename Foo<T>::X Foo<T>::j() { return 1;}
In general, if you can match the declarations up using only information from the template, then the declaration is valid.

Declarations like Foo::i and Foo::j are invalid because for a given instance of A<T>, A<T>::X may not actually be int if the class is specialized.

This is not a problem for Foo::g1 and Foo::g2 because for any instance of Foo<T> that is generated from the template you know that Bar will always be int. If an instance of Foo is specialized, the template member definitions are not used so it doesn't matter whether a specialization defines Bar as int or not.

John Spicer will propose a clarification.




212. Implicit instantiation is not described clearly enough

Section: 14.7.1  temp.inst     Status: open     Submitter: Christophe de Dinechin     Date: 7 Mar 2000     Priority: 2     Drafting: Spicer

From reflector messages 8580-94 and 8601.

Three points have been raised where the wording in 14.7.1  temp.inst may not be sufficiently clear.

  1. In paragraph 4, the statement is made that
    A class template specialization is implicitly instantiated... if the completeness of the class type affects the semantics of the program...

    It is not clear what it means for the "completeness... [to affect] the semantics." Consider the following example:

            template<class T> struct A;
            extern A<int> a;
    
            void *foo() { return &a; }
    
            template<class T> struct A
            {
            #ifdef OPTION
                    void *operator &() { return 0; }
            #endif
            };
    

    The question here is whether it is necessary for template class A to declare an operator & for the semantics of the program to be affected. If it does not do so, the meaning of &a will be the same whether the class is complete or not and thus arguably the semantics of the program are not affected.

    Presumably what was intended is whether the presence or absence of certain member declarations in the template class might be relevant in determining the meaning of the program. A clearer statement may be desirable.

  2. Paragraph 5 says,
    If the overload resolution process can determine the correct function to call without instantiating a class template definition, it is unspecified whether that instantiation actually takes place.

    The intent of this wording, as illustrated in the example in that paragraph, is to allow a "smart" implementation not to instantiate class templates if it can determine that such an instantiation will not affect the result of overload resolution, even though the algorithm described in clause 13  over requires that all the viable functions be enumerated, including functions that might be found as members of specializations.

    Unfortunately, the looseness of the wording allowing this latitude for implementations makes it unclear what "the overload resolution process" is — is it the algorithm in 13  over or something else? — and what "the correct function" is.

  3. According to 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.

    Here, it is not clear what conditions "require" an implicit instantiation. From the context, it would appear that the intent is to refer to the conditions in paragraph 4 that cause a specialization to be instantiated.

    This interpretation, however, leads to different treatment of template and non-template incomplete classes. For example, by this interpretation,

        class A;
        template <class T> struct TA;
        extern A a;
        extern TA<int> ta;
    
        void f(A*);
        void f(TA<int>*);
    
        int main()
        {
            f(&a);    // well-formed; undefined if A
                      // has operator &() member
            f(&ta);   // ill-formed: cannot instantiate
        }
    

    A different approach would be to understand "required" in paragraph 6 to mean that a complete type is required in the expression. In this interpretation, if an incomplete type is acceptable in the context and the class template definition is not visible, the instantiation is not attempted and the program is well-formed.

    The meaning of "required" in paragraph 6 must be clarified.

(See also issues 204 and 63.)

Notes on 10/01 meeting:

It was felt that item 1 is solved by addition of the word "might" in the resolution for issue 63; item 2 is not much of a problem; and item 3 could be solved by changing "required" to "required to be complete".




237. Explicit instantiation and base class members

Section: 14.7.2  temp.explicit     Status: open     Submitter: Christophe de Dinechin     Date: 28 Jul 2000     Priority: 2

From message 8822.

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.



293. Syntax of explicit instantiation/specialization too permissive

Section: 14.7.2  temp.explicit     Status: open     Submitter: Mark Mitchell     Date: 27 Jun 2001     Priority: 2

14.7.2  temp.explicit defines an explicit instantiation as

Syntactically, that allows things like:

    template int S<int>::i = 5, S<int>::j = 7;

which isn't what anyone actually expects. As far as I can tell, nothing in the standard explicitly forbids this, as written. Syntactically, this also allows:

    template namespace N { void f(); }

although perhaps the surrounding context is enough to suggest that this is invalid.

Suggested resolution:

I think we should say:

[Steve Adamczyk: presumably, this should have template at the beginning.]

and then say that:

There are similar problems in 14.7.3  temp.expl.spec:

Here, I think we want:

with similar restrictions as above.

[Steve Adamczyk: This also needs to have template <> at the beginning, possibly repeated.]

John Spicer points out that the problem of limiting these to a single declarator, addressed in the suggested solution by replacing declaration by decl-specifier-seq followed by declarator, is solved for similar template cases by a non-syntactic constraint, e.g., 14  temp paragraph 3, and perhaps a similar approach should be taken here.




264. Unusable template constructors and conversion functions

Section: 14.8.1  temp.arg.explicit     Status: open     Submitter: John Spicer     Date: 17 Nov 2000     Priority: 3

From message 8987.

The note in paragraph 5 of 14.8.1  temp.arg.explicit makes clear that explicit template arguments cannot be supplied in invocations of constructors and conversion functions because they are called without using a name. However, there is nothing in the current wording of the Standard that makes declaring a constructor or conversion operator that is unusable because of nondeduced parameters (i.e., that would need to be specified explicitly) ill-formed. It would be a service to the programmer to diagnose this useless construct as early as possible.




271. Explicit instantiation and template argument deduction

Section: 14.8.2  temp.deduct     Status: open     Submitter: John Spicer     Date: 20 Feb 2001     Priority: 2

(From message 9092.)

Nicolai Josuttis sent me an example like the following:

    template <typename RET, typename T1, typename T2>
    const RET& min (const T1& a, const T2& b)
    {
	return (a < b ? a : b);
    }
    template const int& min<int>(const int&,const int&);  // #1
    template const int& min(const int&,const int&);       // #2

Among the questions was whether explicit instantiation #2 is valid, where deduction is required to determine the type of RET.

The first thing I realized when researching this is that the standard does not really spell out the rules for deduction in declarative contexts (friend declarations, explicit specializations, and explicit instantiations). For explicit instantiations, 14.7.2  temp.explicit paragraph 2 does mention deduction, but it doesn't say which set of deduction rules from 14.8.2  temp.deduct should be applied.

Second, Nicolai pointed out that 14.7.2  temp.explicit paragraph 6 says

A trailing template-argument can be left unspecified in an explicit instantiation provided it can be deduced from the type of a function parameter (14.8.2  temp.deduct).

This prohibits cases like #2, but I believe this was not considered in the wording as there is no reason not to include the return type in the deduction process.

I think there may have been some confusion because the return type is excluded when doing deduction on a function call. But there are contexts where the return type is included in deduction, for example, when taking the address of a function template specialization.

Suggested resolution:

  1. Update 14.8.2  temp.deduct to include a section "Deducing template arguments from a declaration" that describes how deduction is done when finding a template that matches a declaration. This should, I believe, include the return type.
  2. Update 14.7.2  temp.explicit to make reference to the new rules in 14.8.2  temp.deduct and remove the description of the deduction details from 14.7.2  temp.explicit paragraph 6.



297. Which template does an explicit specialization specialize?

Section: 14.8.2  temp.deduct     Status: open     Submitter: Andrei Iltchenko     Date: 7 Jul 2001     Priority: 2

Andrei Iltchenko points out that the standard has no wording that defines how to determine which template is specialized by an explicit specialization of a function template. He suggests "template argument deduction in such cases proceeds in the same way as when taking the address of a function template, which is described in 14.8.2.2  temp.deduct.funcaddr."

John Spicer points out that the same problem exists for all similar declarations, i.e., friend declarations and explicit instantiation directives. Finding a corresponding placement operator delete may have a similar problem.

John Spicer: There are two aspects of "determining which template" is referred to by a declaration: determining the function template associated with the named specialization, and determining the values of the template arguments of the specialization.

    template <class T> void f(T);  #1
    template <class T> void f(T*); #2
    template <> void f(int*);

In other words, which f is being specialized (#1 or #2)? And then, what are the deduced template arguments?

14.5.5.2  temp.func.order does say that partial ordering is done in contexts such as this. Is this sufficient, or do we need to say more about the selection of the function template to be selected?

14.8.2  temp.deduct probably needs a new section to cover argument deduction for cases like this.




352. Nondeduced contexts

Section: 14.8.2.1  temp.deduct.call     Status: open     Submitter: Andrei Iltchenko     Date: 24 April 2002

The current definition of the C++ language speaks about nondeduced contexts only in terms of deducing template arguments from a type 14.8.2.4  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.4  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
}

John Spicer:

Two notes:

  1. The standard actually prohibits what he is trying to do (14.8.2.4  temp.deduct.type paragraph 16). I think this rule is strange and conflicts with most other deduction cases that simply cause a function to be discarded for deduction purposes.
  2. I'm not sure that treating this as a nondeduced context is correct, but it should be discussed. A nondeduced context inherits the template arguments deduced elsewhere. Other nondeduced contexts are a result of a certain form of parameter, not argument.

I think some kind of change should be made here. At least, I think we should remove the error required by 14.8.2.4  temp.deduct.type paragraph 16. In addition, we should consider whether a change like the one suggested is desired.




349. Template argument deduction for conversion functions and qualification conversions

Section: 14.8.2.3  temp.deduct.conv     Status: open     Submitter: John Spicer     Date: 16 April 2002

From message 9502.

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.




92. Should exception specifications be part of the type system?

Section: 15.4  except.spec     Status: open     Submitter: Jonathan Schilling     Date: 2 Feb 1999     Priority: 2

It was tentatively agreed at the Santa Cruz meeting that exception specifications should fully participate in the type system. This change would address gaps in the current static checking of exception specifications such as

    void (*p)() throw(int);
    void (**pp)() throw() = &p;   // not currently an error

This is such a major change that it deserves to be a separate issue.

See also issues 25, 87, and 133.




346. Typo in 15.4

Section: 15.4  except.spec     Status: open     Submitter: Lois Goldthwaite     Date: 18 March 2002

15.4  except.spec paragraph 13 contains the following text. I believe 'implicitLY' marked below should be replaced with 'implicit.'

An implicitly declared special member function (clause 12  special) shall have an exception-specification. If f is an implicitly declared default constructor, copy constructor, destructor, or copy assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f's implicitly definition; f shall allow all exceptions if any function it directly invokes allows all exceptions, and f shall allow no exceptions if every function it directly invokes allows no exceptions. [Example:

    struct A {
        A();
        A(const A&) throw();
        ~A() throw(X);
    };
    struct B {
        B() throw();
        B(const B&) throw();
        ~B() throw(Y);
    };
    struct D : public A, public B {
            //  Implicit declaration of  D::D();
            //  Implicit declaration of  D::D(const   D&)   throw();
            //  Implicit declaration of  D::~D()   throw   (X,Y);
    };

Furthermore, if A::~A() or B::~B() were virtual, D::~D() would not be as restrictive as that of A::~A, and the program would be ill-formed since a function that overrides a virtual function from a base class shall have an exception-specification at least as restrictive as that in the base class. ]

The example code shows structs whose destructors have exception specifications which throw certain types. There is no defect here, but it doesn't sit well with our general advice elsewhere that destructors should not throw. I wish I could think of some other way to illustrate this section.




219. Cannot defend against destructors that throw exceptions

Section: 15.5.1  except.terminate     Status: open     Submitter: Herb Sutter     Date: 31 Mar 2000     Priority: 2

From reflector messages 8650, 8655-64, and 8669.

Destructors that throw can easily cause programs to terminate, with no possible defense. Example: Given

    struct XY { X x; Y y; };

Assume that X::~X() is the only destructor in the entire program that can throw. Assume further that Y construction is the only other operation in the whole program that can throw. Then XY cannot be used safely, in any context whatsoever, period — even simply declaring an XY object can crash the program:

    XY xy; // construction attempt might terminate program:
	   //   1. construct x -- succeeds
	   //   2. construct y -- fails, throws exception
	   //   3. clean up by destroying x -- fails, throws exception,
	   //      but an exception is already active, so call 
	   //      std::terminate() (oops)
	   // there is no defense
So it is highly dangerous to have even one destructor that could throw.

Suggested Resolution:

Fix the above problem in one of the following two ways. I prefer the first.

  1. We already have text that specifies that any destructor operation in the standard library (presumably including the destructors of UDTs used in containers or as predicates, etc.) may not throw. There is good reason to widen this injunction to specify that destructors may never throw at all. (I realize this would render existing programs nonconforming if they did do this, but it's unsafe anyway.)
  2. Specify what happens in the above case so that std::terminate() won't be called.

Fergus Henderson: I disagree. Code using XY may well be safe, if X::~X() only throws if std::uncaught_exception() is false.

I think the current exception handling scheme in C++ is certainly flawed, but the flaws are IMHO design flaws, not minor technical defects, and I don't think they can be solved by minor tweaks to the existing design. I think that at this point it is probably better to keep the standard stable, and learn to live with the existing flaws, rather than trying to solve them via TC.

Bjarne Stroustrup: I strongly prefer to have the call to std::terminate() be conforming. I see std::terminate() as a proper way to blow away "the current mess" and get to the next level of error handling. I do not want that escape to be non-conforming — that would imply that programs relying on a error handling based on serious errors being handled by terminating a process (which happens to be a C++ program) in std::terminate() becomes non-conforming. In many systems, there are — and/or should be — error-handling and recovery mechanisms beyond what is offered by a single C++ program.

Andy Koenig: If we were to prohibit writing a destructor that can throw, how would I solve the following problem?

I want to write a class that does buffered output. Among the other properties of that class is that destroying an object of that class writes the last buffer on the output device before freeing memory.

What should my class do if writing that last buffer indicates a hardware output error? My user had the option to flush the last buffer explicitly before destroying the object, but didn't do so, and therefore did not anticipate such a problem. Unfortunately, the problem happened anyway. Should I be required to suppress this error indication anyway? In all cases?

In practice, I would rather thrown an exception, even at the risk of crashing the program if we happen to be in the middle of stack unwinding. The reason is that the program would crash only if a hardware error occurred in the middle of cleaning up from some other error that was in the process of being handled. I would rather have such a bizarre coincidence cause a crash, which stands a chance of being diagnosed later, than to be ignored entirely and leave the system in a state where the ignore error could cause other trouble later that is even harder to diagnose.

If I'm not allowed to throw an exception when I detect this problem, what are my options?

Herb Sutter: I understand that some people might feel that "a failed dtor during stack unwinding is preferable in certain cases" (e.g., when recovery can be done beyond the scope of the program), but the problem is "says who?" It is the application program that should be able to decide whether or not such semantics are correct for it, and the problem here is that with the status quo a program cannot defend itself against a std::terminate() — period. The lower-level code makes the decision for everyone. In the original example, the mere existence of an XY object puts at risk every program that uses it, whether std::terminate() makes sense for that program or not, and there is no way for a program to protect itself.

That the "it's okay if the process goes south should a rare combination of things happen" decision should be made by lower-level code (e.g., X dtor) for all apps that use it, and which doesn't even understand the context of any of the hundreds of apps that use it, just cannot be correct.

(See also issue 265.)




268. Macro name suppression in rescanned replacement text

Section: 16.3.4  cpp.rescan     Status: open     Submitter: Bjarne Stroustrup     Date: 18 Jan 2001     Priority: 3

(From messages 9033-40.)

It is not clear from the Standard what the result of the following example should be:

    #define NIL(xxx) xxx
    #define G_0(arg) NIL(G_1)(arg)
    #define G_1(arg) NIL(arg)
    G_0(42)

The relevant text from the Standard is found in 16.3.4  cpp.rescan paragraph 2:

If the name of the macro being replaced is found during this scan of the replacement list (not including the rest of the source file's preprocessing tokens), it is not replaced. Further, if any nested replacements encounter the name of the macro being replaced, it is not replaced. These nonreplaced macro name preprocessing tokens are no longer available for further replacement even if they are later (re)examined in contexts in which that macro name preprocessing token would otherwise have been replaced.

The sequence of expansion of G0(42) is as follows:

G0(42)
NIL(G_1)(42)
G_1(42)
NIL(42)

The question is whether the use of NIL in the last line of this sequence qualifies for non-replacement under the cited text. If it does, the result will be NIL(42). If it does not, the result will be simply 42.

The original intent of the J11 committee in this text was that the result should be 42, as demonstrated by the original pseudo-code description of the replacement algorithm provided by Dave Prosser, its author. (This was cited in message core-9035 and came from document X3J11/86-196.) The English description, however, omits some of the subtleties of the pseudo-code and thus arguably gives an incorrect answer for this case.

Suggested resolution (Mike Miller): Replace the cited paragraph with the following:

As long as the scan involves only preprocessing tokens from a given macro's replacement list, or tokens resulting from a replacement of those tokens, an occurrence of the macro's name will not result in further replacement, even if it is later (re)examined in contexts in which that macro name preprocessing token would otherwise have been replaced.

Once the scan reaches the preprocessing token following a macro's replacement list — including as part of the argument list for that or another macro — the macro's name is once again available for replacement. [Example:

    #define NIL(xxx) xxx
    #define G_0(arg) NIL(G_1)(arg)
    #define G_1(arg) NIL(arg)
    G_0(42)                         // result is 42, not NIL(42)

The reason that NIL(42) is replaced is that (42) comes from outside the replacement list of NIL(G_1), hence the occurrence of NIL within the replacement list for NIL(G_1) (via the replacement of G_1(42)) is not marked as nonreplaceable. —end example]

(Note: The resolution of this issue must be coordinated with J11/WG14.)




223. The meaning of deprecation

Section: depr     Status: open     Submitter: Mike Miller     Date: 19 Apr 2000     Priority: 3

During the discussion of issues 167 and 174, it became apparent that there was no consensus on the meaning of deprecation. Some thought that deprecating a feature reflected an intent to remove it from the language. Others viewed it more as an encouragement to programmers not to use certain constructs, even though they might be supported in perpetuity.

There is a formal-sounding definition of deprecation in Annex D  depr paragraph 2:

deprecated is defined as: Normative for the current edition of the Standard, but not guaranteed to be part of the Standard in future revisions.
However, this definition would appear to say that any non-deprecated feature is "guaranteed to be part of the Standard in future revisions." It's not clear that that implication was intended, so this definition may need to be amended.

This issue is intended to provide an avenue for discussing and resolving those questions, after which the original issues may be reopened if that is deemed desirable.




248. Identifier characters

Section: extendid     Status: open     Submitter: John Spicer     Date: 6 Oct 2000     Priority: 2

From messages 8914-15 and 8918.

The list of identifier characters specified in the C++ standard annex E  extendid and the C99 standard annex D are different. The C99 standard includes more characters.

The C++ standard says that the characters are from "ISO/IEC PDTR 10176" while the C99 standard says "ISO/IEC TR 10176". I'm guessing that the PDTR is an earlier draft of the TR.

Should the list in the C++ standard be updated?

Tom Plum: In my opinion, the "identifier character" issue has not been resolved with certainty within SC22.

One critical difference in C99 was the decision to allow a compiler to accept more characters than are given in the annex. This allows for future expansion.

The broader issue concerns the venue in which the "identifier character" issue will receive ongoing resolution.

Notes from 10/00 meeting:

The core language working group expressed a strong preference (13/0/5 in favor/opposed/abstaining) that the list of identifier characters should be extensible, as is the case in C99. However, the fact that this topic is under active discussion by other bodies was deemed sufficient reason to defer any changes to the C++ specification until the situation is more stable.