Document number:   J16/04-0072 = WG21 N1632
Date:  9 April, 2004
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 30


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:2003 document and corrected defects in the earlier ISO/IEC 14882:1998 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.)

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:2003, and be submitted to the InterNational Committee for Information Technology Standards (INCITS), 1250 Eye Street NW, Suite 200, 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.jamesd.demon.co.uk/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 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.

TC1: A DR issue included in Technical Corrigendum 1. TC1 is a revision to the Standard issued in 2003.

WP: A DR issue that the Committee has voted to apply to the current Working Paper. The Working Paper is a draft for a future version of the Standard.

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. 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 as an extension proposal.


Issues with "Ready" Status


381. Incorrect example of base class member lookup

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

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

Proposed Resolution (October 2003):

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




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

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

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

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

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

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

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

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

#include <vector>

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

Suggested resolution:

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

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

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

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

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

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

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

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

Suggested resolution:

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

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

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

Proposed resolution:

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




389. Unnamed types in entities with linkage

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

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

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

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

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

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

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

A related situation is the following:

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

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

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

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

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

See also issue 319.

Notes from April 2003 meeting:

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

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

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

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

In 3.5  basic.link paragraph 8, change

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

to

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

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

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

to

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

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




54. Static_cast from private base to derived class

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

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

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

Reopened September 2003:

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

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

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

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

Notes from the October 2003 meeting:

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

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

Proposed Resolution (October 2003):

Replace paragraph 5.2.9  expr.static.cast/6:

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

with two paragraphs:

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

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

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

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

Change the second sentence to read:

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

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

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



427. static_cast ambiguity: conversion versus cast to derived

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

Consider this code:

  struct B {};

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

  extern B& b;

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

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

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

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

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

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

Proposed Resolution (October 2003):

Move paragraph 5.2.9/5:

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

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

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

before paragraph 5.2.9  expr.static.cast/2.

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

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




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

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

What does this example do?

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

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

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


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

  };

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

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

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

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

Notes from October 2003 meeting:

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

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

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

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

Proposed Resolution (October 2003):

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

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

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

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



417. Using derived-class qualified name in out-of-class nested class definition

Section: 9.1  class.name     Status: ready     Submitter: Jon Caves     Date: 19 May 2003

We had a user complain that our compiler was allowing the following code:

  struct B {
    struct S;
  };

  struct D : B { };

  struct D::S {
  };

We took one look at the code and made the reasonable (I would claim) assumption that this was indeed a bug in our compiler. Especially as we had just fixed a very similar issue with the definition of static data members.

Imagine our surprise when code like this showed up in Boost and that every other compiler we tested accepts this code. So is this indeed legal (it seems like it must be) and if so is there any justification for this beyond 3.4.3.1  class.qual?

John Spicer: The equivalent case for a member function is covered by the declarator rules in 8.3  dcl.meaning paragraph 1. The committee has previously run into cases where a restriction should apply to both classes and non-classes, but fails to do so because there is no equivalent of 8.3  dcl.meaning paragraph 1 for classes.

Given that, by the letter of the standard, I would say that this case is allowed.

Notes from October 2003 meeting:

We feel this case should get an error.

Proposed Resolution (October 2003):

Note that the change here interacts with issue 432.

Add the following as a new paragraph immediately following 3.3.1  basic.scope.pdecl paragraph 2:

The point of declaration for a class first declared by a class-specifier is immediately after the identifier or template-id (if any) in its class-head (Clause 9  class). The point of declaration for an enumeration is immediately after the identifier (if any) in its enum-specifier (7.2  dcl.enum).

Change point 1 of 3.3.6  basic.scope.class paragraph 1 to read:

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

[Note that the preceding change duplicates one of the changes in the proposed resolution of issue 432.]

Change 14.7.2  temp.explicit paragraph 2 to read:

If the explicit instantiation is for a member function, a member class or a static data member of a class template specialization, the name of the class template specialization in the qualified-id for the member declarator name shall be a template-id.

Add the following as paragraph 5 of Clause 9  class:

If a class-head contains a nested-name-specifier, the class-specifier shall refer to a class that was previously declared directly in the class or namespace to which the nested-name-specifier refers (i.e., neither inherited nor introduced by a using-declaration), and the class-specifier shall appear in a namespace enclosing the previous declaration.

Delete 9.1  class.name paragraph 4 (this was added by issue 284):

When a nested-name-specifier is specified in a class-head or in an elaborated-type-specifier, the resulting qualified name shall refer to a previously declared member of the class or namespace to which the nested-name-specifier refers, and the member shall not have been introduced by a using-declaration in the scope of the class or namespace nominated by the nested-name-specifier.



436. Problem in example in 9.6 paragraph 4

Section: 9.6  class.bit     Status: ready     Submitter: Roberto Santos     Date: 10 October 2003

It looks like the example on 9.6  class.bit paragraph 4 has both the enum and function contributing the identifier "f" for the same scope.

  enum BOOL { f=0, t=1 };
  struct A {
    BOOL b:1;
  };
  A a;
  void f() {
    a.b = t;
    if (a.b == t) // shall yield true
    { /* ... */ }
  }

Proposed resolution:

Change the example at the end of 9.6  class.bit/4 from:

  enum BOOL { f=0, t=1 };
  struct A {
    BOOL b:1;
  };
  A a;
  void f() {
    a.b = t;
    if (a.b == t) // shall yield true
    { /* ... */ }
  }

To:

  enum BOOL { FALSE=0, TRUE=1 };
  struct A {
    BOOL b:1;
  };
  A a;
  void f() {
    a.b = TRUE;
    if (a.b == TRUE) // shall yield true
    { /* ... */ }
  }




385. How does protected member check of 11.5 interact with using-declarations?

Section: 11.5  class.protected     Status: ready     Submitter: Vincent Korstanje     Date: 24 Sep 2002

We consider it not unreasonable to do the following

  class A { 
    protected: 
    void g();
  }; 
  class B : public A { 
    public: 
      using A::g; // B::g is a public synonym for A::g 
  }; 

  class C: public A {
    void foo();
  };

  void C::foo() { 
    B b; 
    b.g(); 
  } 

However the EDG front-end does not like and gives the error

  #410-D: protected function "A::g" is not accessible through a "B" pointer or  object 
    b.g();
      ^

Steve Adamczyk: The error in this case is due to 11.5  class.protected of the standard, which is an additional check on top of the other access checking. When that section says "a protected nonstatic member function ... of a base class" it doesn't indicate whether the fact that there is a using-declaration is relevant. I'd say the current wording taken at face value would suggest that the error is correct -- the function is protected, even if the using-declaration for it makes it accessible as a public function. But I'm quite sure the wording in 11.5  class.protected was written before using-declarations were invented and has not been reviewed since for consistency with that addition.

Notes from April 2003 meeting:

We agreed that the example should be allowed.

Proposed resolution (April 2003, revised October 2003):

Change 11.5  class.protected paragraph 1 from

When a friend or a member function of a derived class references a protected nonstatic member function or protected nonstatic data member of a base class, an access check applies in addition to those described earlier in clause 11  class.access. [Footnote: This additional check does not apply to other members, e.g. static data members or enumerator member constants.] Except when forming a pointer to member (5.3.1  expr.unary.op), the access must be through a pointer to, reference to, or object of the derived class itself (or any class derived from that class (5.2.5  expr.ref). If the access is to form a pointer to member, the nested-name-specifier shall name the derived class (or any class derived from that class).

to

An additional access check beyond those described earlier in clause 11  class.access is applied when a nonstatic data member or nonstatic member function is a protected member of its naming class (11.2  class.access.base). [Footnote: This additional check does not apply to other members, e.g., static data members or enumerator member constants.] As described earlier, access to a protected member is granted because the reference occurs in a friend or member of some class C. If the access is to form a pointer to member (5.3.1  expr.unary.op), the nested-name-specifier shall name C or a class derived from C. All other accesses involve a (possibly implicit) object expression (5.2.5  expr.ref). In this case, the class of the object expression shall be C or a class derived from C.



416. Class must be complete to allow operator lookup?

Section: 13.3.1.2  over.match.oper     Status: ready     Submitter: Greg Comeau     Date: 22 May 2003

Normally reference semantics allow incomplete types in certain contexts, but isn't this:

  class A;

  A& operator<<(A& a, const char* msg);
  void foo(A& a)
  {
    a << "Hello";
  }

required to be diagnosed because of the op<<? The reason being that the class may actually have an op<<(const char *) in it.

What is it? un- or ill-something? Diagnosable? No problem at all?

Steve Adamczyk: I don't know of any requirement in the standard that the class be complete. There is a rule that will instantiate a class template in order to be able to see whether it has any operators. But I wouldn't think one wants to outlaw the above example merely because the user might have an operator<< in the class; if he doesn't, he would not be pleased that the above is considered invalid.

Mike Miller: Hmm, interesting question. My initial reaction is that it just uses ::operator<<; any A::operator<< simply won't be considered in overload resolution. I can't find anything in the Standard that would say any different.

The closest analogy to this situation, I'd guess, would be deleting a pointer to an incomplete class; 5.3.5  expr.delete paragraph 5 says that that's undefined behavior if the complete type has a non-trivial destructor or an operator delete. However, I tend to think that that's because it deals with storage and resource management, not just because it might have called a different function. Generally, overload resolution that goes one way when it might have gone another with more declarations in scope is considered to be not an error, cf 7.3.3  namespace.udecl paragraph 9, 14.6.3  temp.nondep paragraph 1, etc.

So my bottom line take on it would be that it's okay, it's up to the programmer to ensure that all necessary declarations are in scope for overload resolution. Worst case, it would be like the operator delete in an incomplete class -- undefined behavior, and thus not required to be diagnosed.

13.3.1.2  over.match.oper paragraph 3, bullet 1, says, "If T1 is a class type, the set of member candidates is the result of the qualified lookup of T1::operator@ (13.3.1.1.1  over.call.func)." Obviously, that lookup is not possible if T1 is incomplete. Should 13.3.1.2  over.match.oper paragraph 3, bullet 1, say "complete class type"? Or does the inability to perform the lookup mean that the program is ill-formed? 3.2  basic.def.odr paragraph 4 doesn't apply, I don't think, because you don't know whether you'll be applying a class member access operator until you know whether the operator involved is a member or not.

Notes from October 2003 meeting:

We noticed that the title of this issue did not match the body. We checked the original source and then corrected the title (so it no longer mentions templates).

We decided that this is similar to other cases like deleting a pointer to an incomplete class, and it should not be necessary to have a complete class. There is no undefined behavior.

Proposed Resolution (October 2003):

Change the first bullet of 13.3.1.2  over.match.oper paragraph 3 to read:

If T1 is a complete class type, the set of member candidates is the result of the qualified lookup of T1::operator@ (13.3.1.1.1  over.call.func); otherwise, the set of member candidates is empty.



410. Paragraph missed in changes for issue 166

Section: 14.5.3  temp.friend     Status: ready     Submitter: John Spicer     Date: 18 Apr 2003

14.5.3  temp.friend paragraph 2 was overlooked when the changes for issue 166 were made.

The friend declaration of f<>(int) is now valid.

A friend function declaration that is not a template declaration and in which the name of the friend is an unqualified template-id shall refer to a specialization of a function template declared in the nearest enclosing namespace scope. [Example:
  namespace N {
          template <class T> void f(T);
          void g(int);
          namespace M {
                  template <class T> void h(T);
                  template <class T> void i(T);
                  struct A {
                          friend void f<>(int);   // ill-formed - N::f
                          friend void h<>(int);   // OK - M::h
                          friend void g(int);     // OK - new decl of M::g
                          template <class T> void i(T);
                          friend void i<>(int);   // ill-formed - A::i
                  };
          }
  }
--end example]

Proposed Resolution (October 2003):

Remove 14.5.3  temp.friend paragraph 2:

A friend function declaration that is not a template declaration and in which the name of the friend is an unqualified template-id shall refer to a specialization of a function template declared in the nearest enclosing namespace scope. [Example:
  namespace N {
          template <class T> void f(T);
          void g(int);
          namespace M {
                  template <class T> void h(T);
                  template <class T> void i(T);
                  struct A {
                          friend void f<>(int);   // ill-formed - N::f
                          friend void h<>(int);   // OK - M::h
                          friend void g(int);     // OK - new decl of M::g
                          template <class T> void i(T);
                          friend void i<>(int);   // ill-formed - A::i
                  };
          }
  }
--end example]



409. Obsolete paragraph missed by changes for issue 224

Section: 14.6  temp.res     Status: ready     Submitter: John Spicer     Date: 18 Apr 2003

Paragraph 6 of 14.6  temp.res is obsolete as result of issue 224, and needs to be revised.

Within the definition of a class template or within the definition of a member of a class template, the keyword typename is not required when referring to the unqualified name of a previously declared member of the class template that declares a type. The keyword typename shall always be specified when the member is referred to using a qual- ified name, even if the qualifier is simply the class template name. [Example:
  template<class T> struct A {
      typedef int B;
      A::B b;                     // ill-formed: typename required before A::B
      void f(A<T>::B);            // ill-formed: typename required before A<T>::B
      typename A::B g();          // OK
  };
]

Proposed Resolution:

Change 14.6  temp.res paragraph 6 as follows

Within the definition of a class template or within the definition of a member of a class template, the keyword typename is not required when referring to the unqualified name of a previously declared member of the class template that declares a type. The keyword typename shall always be specified when the member is referred to using a qualified name, even if the qualifier is simply the class template name. [Example:
template<class T> struct A {
    typedef int B;
    B b;                      // ok, no typename required
    A::B b;                   //  ill-formed: typename required before  A::B
    void f(A<T>::B);          //  ill-formed: typename required before  A<T>::B
    typename A::B g();        //  OK
};
The keyword typename is required whether the qualified name is A or A<T> because A or A<T> are synonyms within a class template with the parameter list <T>. ]



394. identifier-list is never defined

Section: 16  cpp     Status: ready     Submitter: Nicola Musatti     Date: 16 Dec 2002

In clause 16  cpp, paragraph 1, the control-line non-terminal symbol is defined in terms of the identifier-list non-terminal, which is never defined within the standard document.

The same definition is repeated in clause A.14  gram.cpp.

I suggest that the following definition is added to clause 16  cpp, paragraph 1, after the one for replacement-list:

This should be repeated again in clause A.14  gram.cpp, again after the one for replacement-list. It might also be desirable to include a third repetition in clause 16.3  cpp.replace, paragraph 9.

Proposed Resolution (Clark Nelson, Dec 2003):

In clause 16  cpp, paragraph 1, immediately before the definition of replacement-list, add:

If the correct TROFF macros are used, the definition will appear automatically in appendix A. It doesn't need to be repeated in 16.3p9.

With respect to the question of having the preprocessor description be synchronized with C99, this would fall into the category of a justified difference. (Other justified differences include those for Boolean expressions, alternative tokens, and terminology differences.)






Issues with "Review" Status


441. Ordering of static reference initialization

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

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

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

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

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

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

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

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

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

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

Suggested resolution:

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

Proposed Resolution:

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

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



350. signed char underlying representation for objects

Section: 3.9  basic.types     Status: review     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!

Notes from October 2002 meeting:

We should do whatever C99 does. 6.5p6 of the C99 standard says "array of character type", and "character type" includes signed char (6.2.5p15), and 6.5p7 says "character type". But see also 6.2.6.1p4, which mentions (only) an array of unsigned char.

Proposed resolution (April 2003):

Change 3.8  basic.life paragraph 5 bullet 3 from

to

Change 3.8  basic.life paragraph 6 bullet 3 from

to

Change the beginning of 3.9  basic.types paragraph 2 from

For any object (other than a base-class subobject) of POD 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.

to

For any object (other than a base-class subobject) of POD 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 byte-character type.

Add the indicated text to 3.9.1  basic.fundamental paragraph 1:

Objects declared as characters (char) shall be large enough to store any member of the implementation's basic character set. If a character from this set is stored in a character object, the integral value of that character object is equal to the value of the single character literal form of that character. It is implementation-defined whether a char object can hold negative values. Characters can be explicitly declared unsigned or signed. Plain char, signed char, and unsigned char are three distinct types, called the byte-character types. A char, a signed char, and an unsigned char occupy the same amount of storage and have the same alignment requirements (3.9  basic.types); that is, they have the same object representation. For byte-character types, all bits of the object representation participate in the value representation. For unsigned byte-character types, all possible bit patterns of the value representation represent numbers. These requirements do not hold for other types. In any particular implementation, a plain char object can take on either the same values as a signed char or an unsigned char; which one is implementation-defined.

Change 3.10  basic.lval paragraph 15 last bullet from

to

Notes from October 2003 meeting:

It appears that in C99 signed char may have padding bits but no trap representation, whereas in C++ signed char has no padding bits but may have -0. A memcpy in C++ would have to copy the array preserving the actual representation and not just the value.

March 2004: The liaisons to the C committee have been asked to tell us whether this change would introduce any unnecessary incompatibilities with C.




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

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

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

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

That guarantee should be stronger. In particular, given:

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

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

Proposed resolution:

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

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

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

---end example]

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




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

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

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

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

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

See also issue 348.

Proposed resolution:

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

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



435. Change "declararation or definition" to "declaration"

Section: dcl.dcl     Status: review     Submitter: Jens Maurer     Date: 27 Oct 2003

Because a definition is also a declaration, it might make sense to change uses of "declaration or definition" to simply "declaration".

Notes from the March 2004 meeting:

Jens Maurer prepared drafting for this issue, but we find ourselves reluctant to actually make the changes. Though correct, they seemed more likely to be misread than the existing wording.

Proposed resolution:

Remove in 1.3.9  defns.parameter the indicated words:

an object or reference declared as part of a function declaration or definition, or in the catch clause of an exception handler, that acquires a value on entry to the function or handler; ...

Remove in 14.1  temp.param paragraph 10 the indicated words:

The set of default template-arguments available for use with a template declaration or definition is obtained by merging the default arguments from the definition (if in scope) and all declarations in scope in the same way default function arguments are (...).

Remove in 14.6  temp.res paragraph 2 the indicated words:

A name used in a template declaration or definition and that is dependent on a template-parameter is assumed not to name a type unless the applicable name lookup finds a type name or the name is qualified by the keyword typename.

Remove in 14.6.4.1  temp.point paragraph 1 the indicated words:

Otherwise, the point of instantiation for such a specialization immediately follows the namespace scope declaration or definition that refers to the specialization.

Remove in 14.6.4.1  temp.point paragraph 3 the indicated words:

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

Remove in 14.7.3  temp.expl.spec paragraph 21 the indicated words:

Default function arguments shall not be specified in a declaration or a definition for one of the following explicit specializations: [Note: default function arguments may be specified in the declaration or definition of a member function of a class template specialization that is explicitly specialized. ]

Remove in 14.8.2.5  temp.deduct.type paragraph 18 the indicated words:

[Note: a default template-argument cannot be specified in a function template declaration or definition; ...]

Remove in 17.4.2.1  lib.using.headers paragraph 3 the indicated words:

A translation unit shall include a header only outside of any external declaration or definition, and shall include the header lexically before the first reference to any of the entities it declares or first defines in that translation unit.



453. References may only bind to "valid" objects

Section: 8.3.2  dcl.ref     Status: review     Submitter: Gennaro Prota     Date: 18 Jan 2004

8.3.2  dcl.ref paragraph 4 says:

A reference shall be initialized to refer to a valid object or function. [Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the "object" obtained by dereferencing a null pointer, which causes undefined behavior ...]

What is a "valid" object? In particular the expression "valid object" seems to exclude uninitialized objects, but the response to Core Issue 363 clearly says that's not the intent. This is an example (overloading construction on constness of *this) by John Potter, which I think is supposed to be legal C++ though it binds references to objects that are not initialized yet:

 struct Fun {
    int x, y;
    Fun (int x, Fun const&) : x(x), y(42) { }
    Fun (int x, Fun&) : x(x), y(0) { }
  };
  int main () {
    const Fun f1 (13, f1);
    Fun f2 (13, f2);
    cout << f1.y << " " << f2.y << "\n";
  }

Proposed resolution: Changing the final part of 8.3.2  dcl.ref paragraph 4 to:

A reference shall be initialized to refer to an object or function. From its point of declaration on (see 3.3.1  basic.scope.pdecl) its name is an lvalue which refers to that object or function. The reference may be initialized to refer to an uninitialized object but, in that case, it is usable in limited ways (3.8  basic.life, paragraph 6) [Note: On the other hand, a declaration like this:
    int & ref = *(int*)0;
is ill-formed because ref will not refer to any object or function ]

I also think a "No diagnostic is required." would better be added (what about something like int& r = r; ?)

Proposed Resolution:

Change 8.3.2  dcl.ref paragraph 4 as follows:

A reference shall be initialized to refer to a valid object or function. If an lvalue to which a reference is directly bound designates neither an existing object or function of an appropriate type (8.5.3  dcl.init.ref), nor a region of memory of suitable size and alignment to contain an object of the reference's type (1.8  intro.object, 3.8  basic.life, 3.9  basic.types), the behavior is undefined. [Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the "object" obtained by dereferencing a null pointer, which causes undefined behavior. As the result of dereferencing a null pointer value cannot be used to initialize a reference. Also, as described in 9.6  class.bit, a reference cannot be bound directly to a bit-field. ]

The name of a reference shall not be used in its own initializer. Any other use of a reference before it is initialized results in undefined behavior. [Example:

  int& f(int&);
  int& g();

  extern int& ir3;
  int* ip = 0;

  int& ir1 = *ip;     // undefined behavior: null pointer
  int& ir2 = f(ir3);  // undefined behavior: ir3 not yet initialized
  int& ir3 = g();
  int& ir4 = f(ir4);  // ill-formed: ir4 used in its own initializer
---end example]

Rationale: The proposed wording goes beyond the specific concerns of the issue, primarily in response to messages 10498-10506 on the core reflector. It was noted that, while the current wording makes cases like int& r = r; ill-formed (because r in the initializer does not "refer to a valid object"), an inappropriate initialization can only be detected, if at all, at runtime and thus "undefined behavior" is a more appropriate treatment. Nevertheless, it was deemed desirable to continue to require a diagnostic for obvious compile-time cases.

It was also noted that the current Standard does not say anything about using a reference before it is initialized. It seemed reasonable to address both of these concerns in the same wording proposed to resolve this issue.




291. Overload resolution needed when binding reference to class rvalue

Section: 8.5.3  dcl.init.ref     Status: review     Submitter: Andrei Iltchenko     Date: 15 Jun 2001

There is a place in the Standard where overload resolution is implied but the way that a set of candidate functions is to be formed is omitted. See below.

According to the Standard, when initializing a reference to a non-volatile const class type (cv1 T1) with an rvalue expression (cv2 T2) where cv1 T1 is reference compatible with cv2 T2, the implementation shall proceed in one of the following ways (except when initializing the implicit object parameter of a copy constructor) 8.5.3  dcl.init.ref paragraph 5 bullet 2 sub-bullet 1:

While the first case is quite obvious, the second one is a bit unclear as it says "a constructor is called to copy the entire rvalue object into the temporary" without specifying how the temporary is created -- by direct-initialization or by copy-initialization? As stated in DR 152, this can make a difference when the copy constructor is declared as explicit. How should the set of candidate functions be formed? The most appropriate guess is that it shall proceed as per 13.3.1.3  over.match.ctor.

Another detail worth of note is that in the draft version of the Standard as of 2 December 1996 the second bullet read:

J. Stephen Adamczyk replied that the reason for changing "a copy constructor" to "a constructor" was to allow for member template converting constructors.

However, the new wording is somewhat in conflict with the footnote #93 that says that when initializing the implicit object parameter of a copy constructor an implementation must eventually choose the first alternative (binding without copying) to avoid infinite recursion. This seems to suggest that a copy constructor is always used for initializing the temporary of type "cv1 T2".

Furthermore, now that the set of candidate functions is not limited to only the copy constructors of T2, there might be some unpleasant consequences. Consider a rather contrived sample below:

    int   * pi = ::new(std::nothrow) int;
    const std::auto_ptr<int>   & ri = std::auto_ptr<int>(pi);

In this example the initialization of the temporary of type '<TT>const std::auto_ptr<int>' (to which 'ri' is meant to be subsequently bound) doesn't fail, as it would had the approach with copy constructors been retained, instead, a yet another temporary gets created as the well-known sequence:

    std::auto_ptr<int>::operator std::auto_ptr_ref<int>()
    std::auto_ptr<int>(std::auto_ptr_ref<int>)

is called (assuming, of course, that the set of candidate functions is formed as per 13.3.1.3  over.match.ctor). The second temporary is transient and gets destroyed at the end of the initialization. I doubt that this is the way that the committee wanted this kind of reference binding to go.

Besides, even if the approach restricting the set of candidates to copy constructors is restored, it is still not clear how the initialization of the temporary (to which the reference is intended to be bound) is to be performed -- using direct-initialization or copy-initialization.

Another place in the Standard that would benefit from a similar clarification is the creation of an exception object, which is delineated in 15.1  except.throw.

David Abrahams (February 2004): It appears, looking at core 291, that there may be a need to tighten up 8.5.3  dcl.init.ref/5.

Please see the attached example file, which demonstrates "move semantics" in C++98. Many compilers fail to compile test 10 because of the way 8.5.3/5 is interpreted. My problem with that interpretation is that test 20:

    typedef X const XC;
    sink2(XC(X()));
does compile. In other words, it *is* possible to construct the const temporary from the rvalue. IMO, that is the proper test.

8.5.3/5 doesn't demand that a "copy constructor" is used to copy the temporary, only that a constructor is used "to copy the temporary". I hope that when the language is tightened up to specify direct (or copy initialization), that it also unambiguously allows the enclosed test to compile. Not only is it, I believe, within the scope of reasonable interpretation of the current standard, but it's an incredibly important piece of functionality for library writers and users alike.

#include <iostream>
#include <cassert>

template <class T, class X>
struct enable_if_same
{
};

template <class X>
struct enable_if_same<X, X>
{
    typedef char type;
};

struct X
{
    static int cnt;  // count the number of Xs
    
    X()
      : id(++cnt)
      , owner(true)
    {
        std::cout << "X() #" << id << std::endl;
    }
    
    // non-const lvalue - copy ctor
    X(X& rhs)
      : id(++cnt)
      , owner(true)
    {
        std::cout << "copy #" << id << " <- #" << rhs.id << std::endl;
    }

    // const lvalue - T will be deduced as X const
    template <class T>
    X(T& rhs, typename enable_if_same<X const,T>::type = 0)
      : id(++cnt)
      , owner(true)
    {
        std::cout << "copy #" << id << " <- #" << rhs.id << " (const)" << std::endl;
    }

    ~X()
    {
        std::cout << "destroy #" << id << (owner?"":" (EMPTY)") << std::endl;
    }
    
 private:    // Move stuff
    struct ref { ref(X*p) : p(p) {} X* p; };

 public:    // Move stuff
    operator ref() {
        return ref(this);
    }

    // non-const rvalue
    X(ref rhs)
      : id(++cnt)
      , owner(rhs.p->owner)
    {
        std::cout << "MOVE #" << id << " <== #" << rhs.p->id << std::endl;
        rhs.p->owner = false;
        assert(owner);
    }
    
 private:   // Data members
    int id;
    bool owner;
};

int X::cnt;


X source()
{
    return X();
}

X const csource()
{
    return X();
}

void sink(X)
{
    std::cout << "in rvalue sink" << std::endl;
}

void sink2(X&)
{
    std::cout << "in non-const lvalue sink2" << std::endl;
}

void sink2(X const&)
{
    std::cout << "in const lvalue sink2" << std::endl;
}

void sink3(X&)
{
    std::cout << "in non-const lvalue sink3" << std::endl;
}

template <class T>
void tsink(T)
{
    std::cout << "in templated rvalue sink" << std::endl;
}

int main()
{
    std::cout << " ------ test 1, direct init from rvalue ------- " << std::endl;
#ifdef __GNUC__ // GCC having trouble parsing the extra parens
    X z2((0, X() ));
#else
    X z2((X()));
#endif 

    std::cout << " ------ test 2, copy init from rvalue ------- " << std::endl;
    X z4 = X();

    std::cout << " ------ test 3, copy init from lvalue ------- " << std::endl;
    X z5 = z4;

    std::cout << " ------ test 4, direct init from lvalue ------- " << std::endl;
    X z6(z4);
    
    std::cout << " ------ test 5, construct const ------- " << std::endl;
    X const z7;
    
    std::cout << " ------ test 6, copy init from lvalue ------- " << std::endl;
    X z8 = z7;

    std::cout << " ------ test 7, direct init from lvalue ------- " << std::endl;
    X z9(z7);
    
    std::cout << " ------ test 8, pass rvalue by-value ------- " << std::endl;
    sink(source());
    
    std::cout << " ------ test 9, pass const rvalue by-value ------- " << std::endl;
    sink(csource());

    std::cout << " ------ test 10, pass rvalue by overloaded reference ------- " << std::endl;
    // This one fails in Comeau's strict mode due to 8.5.3/5.  GCC 3.3.1 passes it.
    sink2(source());

    std::cout << " ------ test 11, pass const rvalue by overloaded reference ------- " << std::endl;
    sink2(csource());

#if 0    // These two correctly fail to compile, just as desired
    std::cout << " ------ test 12, pass rvalue by non-const reference ------- " << std::endl;
    sink3(source());

    std::cout << " ------ test 13, pass const rvalue by non-const reference ------- " << std::endl;
    sink3(csource());
#endif 

    std::cout << " ------ test 14, pass lvalue by-value ------- " << std::endl;
    sink(z5);
    
    std::cout << " ------ test 15, pass const lvalue by-value ------- " << std::endl;
    sink(z7);

    std::cout << " ------ test 16, pass lvalue by-reference ------- " << std::endl;
    sink2(z4);

    std::cout << " ------ test 17, pass const lvalue by const reference ------- " << std::endl;
    sink2(z7);

    std::cout << " ------ test 18, pass const lvalue by-reference ------- " << std::endl;
#if 0   // correctly fails to compile, just as desired
    sink3(z7);
#endif 

    std::cout << " ------ test 19, pass rvalue by value to template param ------- " << std::endl;
    tsink(source());

    std::cout << " ------ test 20, direct initialize a const A with an A ------- " << std::endl;
    typedef X const XC;
    sink2(XC(X()));
}

Proposed Resolution:

(As proposed by N1610 section 5, with editing.)

Change paragraph 5, second bullet, first sub-bullet, second sub-sub-bullet as follows:

A temporary of type "cv1 T2" [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary via copy-initialization from the entire rvalue object. The reference is bound to the temporary or to a sub-object within the temporary.

The text immediately following that is changed as follows:

The constructor that would be used to make the copy shall be callable whether or not the copy is actually done. The constructor and any conversion function that would be used in the initialization shall be callable whether or not the temporary is actually created.

Note, however, that the way the core working group is leaning on issue 391 (i.e., requiring direct binding) would make this change unnecessary.




437. Is type of class allowed in member function exception specification?

Section: 9.2  class.mem     Status: review     Submitter: Cary Coutant     Date: 10 Oct 2003

I've encountered a C++ program in which a member function wants to declare that it may throw an object of its own class, e.g.:

  class Foo {
  private:
     int val;
  public:
     Foo( int &initval ) { val = initval; };
     void throwit() throw(Foo) { throw (*this); };
  };

The compiler is complaining that Foo is an incomplete type, and can't be used in the exception specification.

My reading of the standard [basic.types] is inconclusive. Although it does state that the class declaration is considered complete when the closing brace is read, I believe it also intends that the member function declarations should not be semantically validated until the class has been completely declared.

If this isn't allowed, I don't know how else a member function could be declared to throw an object of its own class.

John Spicer: The type is considered complete within function bodies, but not in their declaration (see 9.2  class.mem paragraph 2).

Proposed Resolution:

Change 9.2  class.mem paragraph 2 as follows:

Within the class member-specification, the class is regarded as complete within function bodies, default arguments, exception-specifications, and constructor ctor-initializers (including such things in nested classes).

Rationale: Taken with 8.3.5  dcl.fct paragraph 6, the exception-specification is the only part of a function declaration/definition in which the class name cannot be used because of its putative incompleteness. There is no justification for singling out exception specifications this way; both in the function body and in a catch clause, the class type will be complete, so there is no harm in allowing the class name to be used in the exception-specification.




39. Conflicting ambiguity rules

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

The ambiguity text in 10.2  class.member.lookup may not say what we intended. It makes the following example ill-formed:

    struct A {
        int x(int);
    };
    struct B: A {
        using A::x;
        float x(float);
    };
    
    int f(B* b) {
        b->x(3);  // ambiguous
    }
This is a name lookup ambiguity because of 10.2  class.member.lookup paragraph 2:
... Each of these declarations that was introduced by a using-declaration is considered to be from each sub-object of C that is of the type containing the declaration designated by the using-declaration. If the resulting set of declarations are not all from sub-objects of the same type, or the set has a nonstatic member and includes members from distinct sub-objects, there is an ambiguity and the program is ill-formed.
This contradicts the text and example in paragraph 12 of 7.3.3  namespace.udecl .

Proposed Resolution (10/00):

  1. Replace the two cited sentences from 10.2  class.member.lookup paragraph 2 with the following:

    The resulting set of declarations shall all be from sub-objects of the same type, or there shall be a set of declarations from sub-objects of a single type that contains using-declarations for the declarations found in all other sub-object types. Furthermore, for nonstatic members, the resulting set of declarations shall all be from a single sub-object, or there shall be a set of declarations from a single sub-object that contains using-declarations for the declarations found in all other sub-objects. Otherwise, there is an ambiguity and the program is ill-formed.
  2. Replace the examples in 10.2  class.member.lookup paragraph 3 with the following:

        struct A {
            int x(int);
            static int y(int);
        };
        struct V {
            int z(int);
        };
        struct B: A, virtual V {
            using A::x;
            float x(float);
            using A::y;
            static float y(float);
            using V::z;
            float z(float);
        };
        struct C: B, A, virtual V {
        };
    
        void f(C* c) {
            c->x(3);    // ambiguous -- more than one sub-object A
            c->y(3);    // not ambiguous
            c->z(3);    // not ambiguous
        }
    

Notes from 04/01 meeting:

The following example should be accepted but is rejected by the wording above:

    struct A { static void f(); };

    struct B1: virtual A {
        using A::f;
    };

    struct B2: virtual A {
        using A::f;
    };

    struct C: B1, B2 { };

    void g() {
        C::f();        // OK, calls A::f()
    }

Notes from 10/01 meeting (Jason Merrill):

The example in the issues list:

    struct A {
        int x(int);
    };
    struct B: A {
        using A::x;
        float x(float);
    };
    
    int f(B* b) {
        b->x(3);  // ambiguous
    }
Is broken under the existing wording:
... Each of these declarations that was introduced by a using-declaration is considered to be from each sub-object of C that is of the type containing the declaration designated by the using-declaration. If the resulting set of declarations are not all from sub-objects of the same type, or the set has a nonstatic member and includes members from distinct sub-objects, there is an ambiguity and the program is ill-formed.
Since the two x's are considered to be "from" different objects, looking up x produces a set including declarations "from" different objects, and the program is ill-formed. Clearly this is wrong. The problem with the existing wording is that it fails to consider lookup context.

The first proposed solution:

The resulting set of declarations shall all be from sub-objects of the same type, or there shall be a set of declarations from sub-objects of a single type that contains using-declarations for the declarations found in all other sub-object types. Furthermore, for nonstatic members, the resulting set of declarations shall all be from a single sub-object, or there shall be a set of declarations from a single sub-object that contains using-declarations for the declarations found in all other sub-objects. Otherwise, there is an ambiguity and the program is ill-formed.
breaks this testcase:
    struct A { static void f(); };

    struct B1: virtual A {
        using A::f;
    };

    struct B2: virtual A {
        using A::f;
    };

    struct C: B1, B2 { };

    void g() {
        C::f();        // OK, calls A::f()
    }
because it considers the lookup context, but not the definition context; under this definition of "from", the two declarations found are the using-declarations, which are "from" B1 and B2.

The solution is to separate the notions of lookup and definition context. I have taken an algorithmic approach to describing the strategy.

Incidentally, the earlier proposal allows one base to have a superset of the declarations in another base; that was an extension, and my proposal does not do that. One algorithmic benefit of this limitation is to simplify the case of a virtual base being hidden along one arm and not another ("domination"); if we allowed supersets, we would need to remember which subobjects had which declarations, while under the following resolution we need only keep two lists, of subobjects and declarations.

Proposed resolution (October 2002):

Replace 10.2  class.member.lookup paragraph 2 with:

The following steps define the result of name lookup for a member name f in a class scope C.

The lookup set for f in C, called S(f,C), consists of two component sets: the declaration set, a set of members named f; and the subobject set, a set of subobjects where declarations of these members (possibly including using-declarations) were found. In the declaration set, using-declarations are replaced by the members they designate, and type declarations (including injected-class-names) are replaced by the types they designate. S(f,C) is calculated as follows.

If C contains a declaration of the name f, the declaration set contains every declaration of f in C (excluding bases), the subobject set contains C itself, and calculation is complete.

Otherwise, S(f,C) is initially empty. If C has base classes, calculate the lookup set for f in each direct base class subjobject Bi, and merge each such lookup set S(f,Bi) in turn into S(f,C).

The following steps define the result of merging lookup set S(f,Bi) into the intermediate S(f,C):

The result of name lookup for f in C is the declaration set of S(f,C). If it is an invalid set, the program is ill-formed.

[Example:

    struct A { int x; };                    // S(x,A) = {{ A::x }, { A }}
    struct B { float x; };                  // S(x,B) = {{ B::x }, { B }}
    struct C: public A, public B { };       // S(x,C) = { invalid, { A in C, B in C }}
    struct D: public virtual C { };         // S(x,D) = S(x,C)
    struct E: public virtual C { char x; }; // S(x,E) = {{ E::x }, { E }}
    struct F: public D, public E { };       // S(x,F) = S(x,E)

    int main() {
      F f;
      f.x = 0;   // OK, lookup finds { E::x }
    }
S(x,F) is unambiguous because the A and B base subobjects of D are also base subobjects of E, so S(x,D) is discarded in the first merge step. --end example]

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

Notes from October 2003 meeting:

Mike Miller raised some new issues in N1543, and we adjusted the proposed resolution as indicated in that paper.

Further information from Mike Miller (January 2004):

Unfortunately, I've become aware of a minor glitch in the proposed resolution for issue 39 in N1543, so I'd like to suggest a change that we can discuss in Sydney.

A brief review and background of the problem: the major change we agreed on in Kona was to remove detection of multiple-subobject ambiguity from class lookup (10.2  class.member.lookup) and instead handle it as part of the class member access expression. It was pointed out in Kona that 11.2  class.access.base/5 has this effect:

If a class member access operator, including an implicit "this->," is used to access a nonstatic data member or nonstatic member function, the reference is ill-formed if the left operand (considered as a pointer in the "." operator case) cannot be implicitly converted to a pointer to the naming class of the right operand.

After the meeting, however, I realized that this requirement is not sufficient to handle all the cases. Consider, for instance,

    struct B {
        int i;
    };

    struct I1: B { };
    struct I2: B { };

    struct D: I1, I2 {
        void f() {
            i = 0;    // not ill-formed per 11.2p5
        }
    };

Here, both the object expression ("this") and the naming class are "D", so the reference to "i" satisfies the requirement in 11.2  class.access.base/5, even though it involves a multiple-subobject ambiguity.

In order to address this problem, I proposed in N1543 to add a paragraph following 5.2.5  expr.ref/4:

If E2 is a non-static data member or a non-static member function, the program is ill-formed if the class of E1 cannot be unambiguously converted (10.2) to the class of which E2 is directly a member.

That's not quite right. It does diagnose the case above as written; however, it breaks the case where qualification is used to circumvent the ambiguity:

    struct D2: I1, I2 {
        void f() {
            I2::i = 0;    // ill-formed per proposal
        }
    };

In my proposed wording, the class of "this" can't be converted to "B" (the qualifier is ignored), so the access is ill-formed. Oops.

I think the following is a correct formulation, so the proposed resolution we discuss in Sydney should contain the following paragraph instead of the one in N1543:

If E2 is a nonstatic data member or a non-static member function, the program is ill-formed if the naming class (11.2) of E2 cannot be unambiguously converted (10.2) to the class of which E2 is directly a member.

This reformulation also has the advantage of pointing readers to 11.2  class.access.base, where the the convertibility requirement from the class of E1 to the naming class is located and which might otherwise be overlooked.

Notes from the March 2004 meeting:

We discussed this further and agreed with these latest recommendations. Mike Miller has produced a paper N1626 that gives just the final collected set of changes.




306. Ambiguity by class name injection

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

Is the following well-formed?

    struct A {
        struct B { };
    };
    struct C : public A, public A::B {
        B *p;
    };
The lookup of B finds both the struct B in A and the injected B from the A::B base class. Are they the same thing? Does the standard say so?

What if a struct is found along one path and a typedef to that struct is found along another path? That should probably be valid, but does the standard say so?

This is resolved by issue 39

February 2004: Moved back to "Review" status because issue 39 was moved back to "Review".






Issues with "Drafting" Status


189. Definition of operator and punctuator

Section: 2.12  lex.operators     Status: drafting     Submitter: Mike Miller     Date: 20 Dec 1999

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.



218. Specification of Koenig lookup

Section: 3.4.2  basic.lookup.koenig     Status: drafting     Submitter: Hyman Rosen     Date: 28 Mar 2000

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.

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.




225. Koenig lookup and fundamental types

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

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

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

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.




414. Multiple types found on destructor lookup

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

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

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

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




426. Identically-named variables, one internally and one externally linked, allowed?

Section: 3.5  basic.link     Status: drafting     Submitter: Steve Adamczyk     Date: 2 July 2003

An example in 3.5  basic.link paragraph 6 creates two file-scope variables with the same name, one with internal linkage and one with external.

  static void f();
  static int i = 0;                       //1
  void g() {
          extern void f();                // internal linkage
          int i;                          //2: i has no linkage
          {
                  extern void f();        // internal linkage
                  extern int i;           //3: external linkage
          }
  }

Is this really what we want? C99 has 6.2.2.7/7, which gives undefined behavior for having an identifier appear with internal and external linkage in the same translation unit. C++ doesn't seem to have an equivalent.

Notes from October 2003 meeting:

We agree that this is an error. We propose to leave the example but change the comment to indicate that line //3 has undefined behavior, and elsewhere add a normative rule giving such a case undefined behavior.




348. delete and user-written deallocation functions

Section: 3.7.3.2  basic.stc.dynamic.deallocation     Status: drafting     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).

Notes from October 2002 meeting:

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

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

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




170. Pointer-to-member conversions

Section: 4.11  conv.mem     Status: drafting     Submitter: Mike Stump     Date: 16 Sep 1999

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.


222. Sequence points and lvalue-returning operators

Section: expr     Status: drafting     Submitter: Andrew Koenig     Date: 20 Dec 1999

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.




195. Converting between function and object pointers

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

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

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

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

Several points were raised in opposition to this suggestion:


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

Martin O'Riordan suggested an alternative approach:


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

Notes from 04/00 meeting:

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

Later note:

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

Notes on the 10/01 meeting:

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

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

Proposed resolution (April 2003):

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

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

Drafting Note:

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

Notes from April 2003 meeting:

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

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




232. Is indirection through a null pointer undefined behavior?

Section: 5.3.1  expr.unary.op     Status: drafting     Submitter: Mike Miller     Date: 5 Jun 2000

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.

Notes from the October 2003 meeting:

See also issue 315, which deals with the call of a static member function through a null pointer.

We agreed that the approach in the standard seems okay: p = 0; *p; is not inherently an error. An lvalue-to-rvalue conversion would give it undefined behavior.




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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Suggested wording for p3 b2 b1

The base part:

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

The same type temporary version:

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

The never copy version:

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

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

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

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

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

// Sun C++-6.2       PFF  Ron Natalie

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

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

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

Notes from the March 2004 meeting:

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




367. throw operator allowed in constant expression?

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

The following translation unit appears to be well-formed.

int x[true?throw 4:5];

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

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

Notes from October 2002 meeting:

We should also check on new and delete.




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

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

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

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

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

It seems weird to say that:

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

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




276. Order of destruction of parameters and temporaries

Section: 6.6  stmt.jump     Status: drafting     Submitter: James Kanze     Date: 28 Mar 2001

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

Note that issue 378 picks a nit with the wording of this same paragraph.




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

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

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

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

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

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

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

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

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

Notes from April 2003 meeting:

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

Proposed resolution (April 2003):

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

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

Notes from October 2003 meeting:

We discussed ctor-initializer lists and decided that they are also part of the body. We've asked Clark Nelson to work on syntax changes to give us a syntax term for the body of a function so we can refer to it here. See also issue 452, which could use this term.




138. Friend declaration name lookup

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

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




460. Can a using-declaration name a namespace?

Section: 7.3.3  namespace.udecl     Status: drafting     Submitter: John Spicer     Date: 12 Feb 2004

Can a using-declaration be used to import a namespace?

namespace my_namespace{
  namespace my_namespace2 {
    int function_of_my_name_space(){ return 2;}
  }
}

int main (){
  using  ::my_namespace::my_namespace2;
  return my_namespace2::function_of_my_name_space();
}

Several popular compilers give an error on this, but there doesn't seem to be anything in 7.3.3  namespace.udecl that prohibits it. It should be noted that the user can get the same effect by using a namespace alias:

  namespace my_namespace2 = ::my_namespace::my_namespace2;

Notes from the March 2004 meeting:

We agree that it should be an error.




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

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

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.




374. Can explicit specialization outside namespace use qualified name?

Section: 8.3  dcl.meaning     Status: drafting     Submitter: Steve Adamczyk     Date: 23 August 2002

This case is nonstandard by 8.3  dcl.meaning paragraph 1 (there is a requirement that the specialization first be declared within the namespace before being defined outside of the namespace), but probably should be allowed:

  namespace NS1 {
    template<class T>
    class CDoor {
    public:
      int mtd() { return 1; }
    };
  }
  template<> int NS1::CDoor<char>::mtd()
  {
    return 0;
  }

Notes from October 2002 meeting:

There was agreement that we wanted to allow this.




393. Pointer to array of unknown bound in template argument list in parameter

Section: 8.3.5  dcl.fct     Status: drafting     Submitter: Mark Mitchell     Date: 12 Dec 2002

EDG rejects this code:

  template <typename T>
  struct S {};

  void f (S<int (*)[]>);
G++ accepts it.

This is another case where the standard isn't very clear:

The language from 8.3.5  dcl.fct is:

If the type of a parameter includes a type of the form "pointer to array of unknown bound of T" or "reference to array of unknown bound of T," the program is ill-formed.
Since "includes a type" is not a term defined in the standard, we're left to guess what this means. (It would be better if this were a recursive definition, the way a type theoretician would do it: )

Notes from April 2003 meeting:

We agreed that the example should be allowed.




233. References vs pointers in UDC overload resolution

Section: 8.5.3  dcl.init.ref     Status: drafting     Submitter: Matthias Meixner     Date: 9 Jun 2000

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.




391. Require direct binding of short-lived references to rvalues

Section: 8.5.3  dcl.init.ref     Status: drafting     Submitter: Raoul Gough     Date: 14 Nov 2002

After some email exchanges with Rani Sharoni, I've come up with the following proposal to allow reference binding to non-copyable rvalues in some cases. Rationale and some background appear afterwards.

---- proposal ----

Replace the section of 8.5.3  dcl.init.ref paragraph 5 that begins "If the initializer expression is an rvalue" with the following:

---- rationale ----

  1. The intention of the current wording is to provide the implementation freedom to construct an rvalue of class type at an arbitrary location and copy it zero or more times before binding any reference to it.
  2. The standard allows code to call a member function on an rvalue of class type (in 5.2.5  expr.ref, I guess). This means that the implementation can be forced to bind the reference directly, with no freedom to create any temporary copies. e.g.
       class nc {
         nc (nc const &);  // private, nowhere defined
       public:
         nc ();
         nc const &by_ref () const { return *this; }
       };
    
       void f () {
         void g (nc const &);
    
         g (nc());          // Ill-formed
         g (nc().by_ref()); // Ok - binds directly to rvalue
       }
    
    Forcing a direct binding in this way is possible wherever the lifetime of the reference does not extend beyond the containing full expression, since the reference returned by the member function remains valid for this long.
  3. As demonstrated above, existing implementations must already be capable of constructing an rvalue of class type in the "right" place the first time. Some compilers already silently allow the direct binding of references to non-copyable rvalues.
  4. The change will not break any portable user code. It would break any platform-specific user code that relies on copies being performed by the particular implementation.

---- background ----

The proposal is based on a recent discussion in this group. I originally wanted to leave the implementation free to copy the rvalue if there was a callable copy constructor, and only have to bind directly if none was callable. Unfortunately, a traditional compiler can't always tell whether a function is callable or not, e.g. if the copy constructor is declared but not defined. Rani pointed this out in an example, and suggested that maybe trivial copy constructors should still be allowed (by extension, maybe wherever the compiler can determine callability). I've gone with this version because it's simpler, and I also figure the "as if" rule gives the compiler some freedom with POD types anyway.

Notes from April 2003 meeting:

We agreed generally with the proposal. We were unsure about the need for the restriction regarding long-lived references. We will check with the proposer about that.

Jason Merrill points out that the test case in issue 86 may be a case where we do not want to require direct binding.

Further information from Rani Sharoni (April 2003):

I wasn't aware about the latest suggestion of Raoul as it appears in core issue 391. In our discussions we tried to formulate a different proposal.

The rational, as we understood, behind the implementation freedom to make an extra copying (8.5.3/5/2/12) of the rvalue is to allow return values in registers which on some architectures are not addressable. The example that Raoul and I presented shows that this implementation freedom is not always possible since we can "force" the rvalue to be addressable using additional member function (by_ref). The example only works for short lived rvalues and this is probably why Raoul narrow the suggestion.

I had different rational which was related to the implementation of conditional operator in VC. It seems that when conditional operator is involved VC does use an extra copying when the lifetime of the temporary is extended:

  struct A { /* ctor with side effect */};

  void f(A& x) {
    A const& r = cond ? A(1) : x; // VC actually make an extra copy of
                                  // the rvalue A(1)
  }

I don't know what the consideration behind the VC implementation was (I saw open bug on this issue) but it convinced me to narrow the suggestion.

IMHO such limitation seems to be too strict because it might limit the optimizer since returning class rvalues in registers might be useful (although I'm not aware about any implementation that actually does it). My suggestion was to forbid the extra copying if the ctor is not viable (e.g. A::A(A&) ). In this case the implementation "freedom" doesn't exist (since the code might not compile) and only limits the programmer freedom (e.g. Move Constructors - http://www.cuj.com/experts/2102/alexandr.htm).

Core issue 291 is strongly related to the above issue and I personally prefer to see it resolved first. It seems that VC already supports the resolution I prefer.

Notes from October 2003 meeting:

We ended up feeling that this is just one of a number of cases of optimizations that are widely done by compilers and allowed but not required by the standard. We don't see any strong reason to require compilers to do this particular optimization.

Notes from the March 2004 meeting:

After discussing issue 450, we found ourselves reconsidering this, and we are not inclined to make a change to require the direct binding in all cases, with no restriction on long-lived references. Note that such a change would eliminate the need for a change for issue 291.




434. Unclear suppression of standard conversions while binding reference to lvalue

Section: 8.5.3  dcl.init.ref     Status: drafting     Submitter: Bronek Kozicki     Date: 14 September 2003

In section 8.5.3  dcl.init.ref, paragraph 5, there is following note:

Note: the usual lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are not needed, and therefore are suppressed, when such direct bindings to lvalues are done.

I believe that this note is misleading. There should be either:

The problem:

  1. under current wording it's unclear if following code is legal, or not:
    int main()
    {
      const int ci = 10;
      int * pi = NULL;
      const int * & rpci = pi;
      rpci = &ci;
      *pi = 12; // circumvent constness of "ci"
    }
    
  2. it is also unclear what behaviour should following program expose:
    int main()
    {
      int * pi = NULL;
      const int * const & rcpci = pi; // 1
      int i = 0;
      pi = &i; // 2
      if (pi == rcpci)
        std::cout << "bound to lvalue" << std::endl;
      else
        std::cout << "bound to temporary rvalue" << std::endl;
    }
    

There has been discussion on this issue on comp.lang.c++.moderated month ago, see http://groups.google.pl/groups?threadm=9bed99bb.0308041153.1c79e882%40posting.google.com and there seems to be some confusion about it. I understand that note is not normative, but apparently even some compiler writers are misled (try above code snippets on few different compilers, and using different compilation options - notably GCC 3.2.3 with -Wall -pedantic), thus it should be cleared up.

My proposal is to change wording of discussed note to:

Note: result of every standard conversion is never an lvalue, and therefore all standard conversions (clause 4) are suppressed, when such direct bindings to lvalues are done.



450. Binding a reference to const to a cv-qualified array rvalue

Section: 8.5.3  dcl.init.ref     Status: drafting     Submitter: Steve Adamczyk     Date: 16 Jan 2004

It's unclear whether the following is valid:

const int N = 10;
const int M = 20;
typedef int T;
void f(T const (&x)[N][M]){}

struct X {
	int i[10][20];
};

X g();

int main()
{
	f(g().i);
}

When you run this through 8.5.3  dcl.init.ref, you sort of end up falling off the end of the standard's description of reference binding. The standard says in the final bullet of paragraph 5 that an array temporary should be created and copy-initialized from the rvalue array, which seems implausible.

I'm not sure what the right answer is. I think I'd be happy with allowing the binding in this case. We would have to introduce a special case like the one for class rvalues.

Notes from the March 2004 meeting:

g++ and EDG give an error. Microsoft (8.0 beta) and Sun accept the example. Our preference is to allow the direct binding (no copy). See the similar issue with class rvalues in issue 391.




413. Definition of "empty class"

Section: class     Status: drafting     Submitter: Pete Becker     Date: 30 Apr 2003

The proposal says that value is true if "T is an empty class (10)". Clause 10 doesn't define an empty class, although it has a note that says a base class may "be of zero size (clause 9)" 9/3 says "Complete objects and member subobjects of class type shall have nonzero size." This has a footnote, which says "Base class subobjects are not so constrained."

The standard uses the term "empty class" in two places (8.5.1  dcl.init.aggr), but neither of those places defines it. It's also listed in the index, which refers to the page that opens clause 9, i.e. the nonzero size stuff cited above.

So, what's the definition of "empty class" that determines whether the predicate is_empty is true?

The one place where it's used is 8.5.1  dcl.init.aggr paragraph 8, which says (roughly paraphrased) that an aggregate initializer for an empty class must be "{}", and when such an initializer is used for an aggregate that is not an empty class the members are default-initialized. In this context it's pretty clear what's meant. In the type traits proposal it's not as clear, and it was probably intended to have a different meaning. The boost implementation, after it eliminates non-class types, determines whether the trait is true by comparing the size of a class derived from T to the size of an otherwise-identical class that is not derived from T.

Howard Hinnant: is_empty was created to find out whether a type could be derived from and have the empty base class optimization successfully applied. It was created in part to support compressed_pair which attempts to optimize away the space for one of its members in an attempt to reduce spatial overhead. An example use is:

  template <class T, class Compare = std::less<T> >
  class SortedVec
  {
  public:
  ...
  private:
    T* data_;
    compressed_pair<Compare, size_type> comp_;

    Compare&       comp()       {return comp_.first();}
    const Compare& comp() const {return comp_.first();}
    size_type&     sz()         {return comp_.second();}
    size_type      sz() const   {return comp_.second();}
  };

Here the compare function is optimized away via the empty base optimization if Compare turns out to be an "empty" class. If Compare turns out to be a non-empty class, or a function pointer, the space is not optimized away. is_empty is key to making this work.

This work built on Nathan's article: http://www.cantrip.org/emptyopt.html.

Clark Nelson: I've been looking at issue 413, and I've reached the conclusion that there are two different kinds of empty class. A class containing only one or more anonymous bit-field members is empty for purposes of aggregate initialization, but not (necessarily) empty for purposes of empty base-class optimization.

Of course we need to add a definition of emptiness for purposes of aggregate initialization. Beyond that, there are a couple of questions:

  1. Should the definition of emptiness used by the is_empty predicate be defined in a language clause or a library clause?
  2. Do we need to open a new core issue pointing out the fact that the section on aggregate initialization does not currently say that unnamed bit-fields are skipped?



454. When is definition of static data member required?

Section: 9.4.2  class.static.data     Status: drafting     Submitter: Gennaro Prota     Date: 18 Jan 2004

As a result of the resolution of core issue 48, the current C++ standard is not in sync with existing practice and with user expectations as far as definitions of static data members having const integral or const enumeration type are concerned. Basically what current implementations do is to require a definition only if the address of the constant is taken. Example:

void f() {

  std::string s;
  ... 

  // current implementations don't require a definition
  if (s.find('a', 3) == std::string::npos) {
   ...
  }

To the letter of the standard, though, the above requires a definition of npos, since the expression std::string::npos is potentially evaluated. I think this problem would be easily solved with simple changes to 9.4.2  class.static.data/4, 9.4.2  class.static.data/5 and 3.2  basic.def.odr/3.

Proposed resolution:

Replace 9.4.2  class.static.data paragraph 4 with:

If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be [note1] an integral constant expression (5.19). In that case, the member can appear in integral constant expressions. No definition of the member is required, unless an lvalue expression that designates it is potentially evaluated and either used as operand to the built-in unary & operator [note 2] or directly bound to a reference.

If a definition exists, it shall be at namespace scope and shall not contain an initializer.

In 9.4.2  class.static.data paragraph 5 change

There shall be exactly one definition of a static data member that is used in a program; no diagnostic is required; see 3.2.

to

Except as allowed by 9.4.2 par. 4, there shall be exactly one definition of a static data member that is potentially evaluated (3.2) in a program; no diagnostic is required.

In 3.2  basic.def.odr paragraph 3 add, at the beginning:

Except for the omission allowed by 9.4.2, par. 4, ...

[note 1] Actually it shall be a "= followed by a constant-expression". This could probably be an editorial fix, rather than a separate DR.

[note 2] Note that this is the case when reinterpret_cast-ing to a reference, like in

struct X { static const int value = 0; };
const char & c = reinterpret_cast<const char&>(X::value);
See 5.2.10  expr.reinterpret.cast/10

More information, in response to a question about why issue 48 does not resolve the problem:

The problem is that the issue was settled in a way that solves much less than it was supposed to solve; that's why I decided to file, so to speak, a DR on a DR.

I understand this may seem a little 'audacious' on my part, but please keep reading. Quoting from the text of DR 48 (emphasis mine):

Originally, all static data members still had to be defined outside the class whether they were used or not.

But that restriction was supposed to be lifted [...]

In particular, if an integral/enum const static data member is initialized within the class, and its address is never taken, we agreed that no namespace-scope definition was required.

The corresponding resolution doesn't reflect this intent, with the definition being still required in most situations anyway: it's enough that the constant appears outside a place where constants are required (ignoring the obvious cases of sizeof and typeid) and you have to provide a definition. For instance:

  struct X {
   static const int c = 1;
  };

  void f(int n)
  {
   if (n == X::c)   // <-- potentially evaluated
    ...
  }

<start digression>

Most usages of non-enum BOOST_STATIC_COSTANTs, for instance, are (or were, last time I checked) non-conforming. If you recall, Paul Mensonides pointed out that the following template

// map_integral

template<class T, T V> struct map_integral : identity<T> {
  static const T value = V;
};

template<class T, T V> const T map_integral<T, V>::value;

whose main goal is to map the same couples (type, value) to the same storage, also solves the definition problem. In this usage it is an excellent hack (if your compiler is good enough), but IMHO still a hack on a language defect.

<end digression>

What I propose is to solve the issue according to the original intent, which is also what users expect and all compilers that I know of already do. Or, in practice, we would have a rule that exists only as words in a standard document.

PS: I've sent a copy of this to Mr. Adamczyk to clarify an important doubt that occurred to me while writing this reply:

if no definition is provided for an integral static const data member is that member an object? Paragraph 1.8/1 seems to say no, and in fact it's difficult to think it is an object without assuming/pretending that a region of storage exists for it (an object *is* a region of storage according to the standard).

I would think that when no definition is required we have to assume that it could be a non-object. In that case there's nothing in 3.2 which says what 'used' means for such an entity and the current wording would thus be defective. Also, since the name of the member is an lvalue and 3.10/2 says an lvalue refers to an object we would have another problem.

OTOH the standard could pretend it is always an object (though the compiler can optimize it away) and in this case it should probably make a special case for it in 3.2/2.

Notes from the March 2004 meeting:

We sort of like this proposal, but we don't feel it has very high priority. We're not going to spend time discussing it, but if we get drafting for wording we'll review it.




86. Lifetime of temporaries in query expressions

Section: 12.2  class.temporary     Status: drafting     Submitter: Steve Adamczyk     Date: Jan 1999

In 12.2  class.temporary paragraph 5, should binding a reference to the result of a "?" operation, each of whose branches is a temporary, extend both temporaries?

Here's an example:

    const SFileName &C = noDir ? SFileName("abc") : SFileName("bcd");

Do the temporaries created by the SFileName conversions survive the end of the full expression?

Notes from 10/00 meeting:

Other problematic examples include cases where the temporary from one branch is a base class of the temporary from the other (i.e., where the implementation must remember which type of temporary must be destroyed), or where one branch is a temporary and the other is not. Similar questions also apply to the comma operator. The sense of the core language working group was that implementations should be required to support these kinds of code.

Notes from the March 2004 meeting:

We decided that the cleanest model is one in which any "?" operation that returns a class rvalue always copies one of its operands to a temporary and returns the temporary as the result of the operation. (Note that this may involve slicing.) An implementation would be free to optimize this using the rules in 12.8  class.copy paragraph 15, and in fact we would expect that in many cases compilers would do such optimizations. For example, the compiler could construct both rvalues in the above example into a single temporary, and thus avoid a copy.

See also issue 446.




443. Wording nit in description of lifetime of temporaries

Section: 12.2  class.temporary     Status: drafting     Submitter: Matthias Hofmann     Date: 2 Dec 2003

There seems to be a typo in 12.2  class.temporary/5, which says "The temporary to which the reference is bound or the temporary that is the complete object TO a subobject OF which the TEMPORARY is bound persists for the lifetime of the reference except as specified below."

I think this should be "The temporary to which the reference is bound or the temporary that is the complete object OF a subobject TO which the REFERENCE is bound persists for the lifetime of the reference except as specified below."

I used upper-case letters for the parts I think need to be changed.




399. Destructor lookup redux

Section: 12.4  class.dtor     Status: drafting     Submitter: John Spicer     Date: 17 Jan 2003

Mark Mitchell raised a number of issues related to the resolution of issue 244 and of destructor lookup in general.

Issue 244 says:

... in a qualified-id of the form: the second class-name is looked up in the same scope as the first.

But if the reference is "p->X::~X()", the first class-name is looked up in two places (normal lookup and a lookup in the class of p). Does the new wording mean:

  1. You look up the second class-name in the scope that you found the first one.
  2. You look up the second class-name using the same kind of lookup that found the first one (normal vs. class).
  3. If you did a dual lookup for the first you do a dual lookup for the second.

This is a test case that illustrates the issue:

  struct A {
    typedef A C;
  };

  typedef A B;

  void f(B* bp) {
    bp->B::~B();  // okay B found by normal lookup
    bp->C::~C();  // okay C found by class lookup
    bp->B::~C();  // B found by normal lookup C by class -- okay?
    bp->C::~B();  // C found by class lookup B by normal -- okay?
  }

A second issue concerns destructor references when the class involved is a template class.

  namespace N {
    template <typename T> struct S {
      ~S();
    };
  }

  void f(N::S<int>* s) {
    s->N::S<int>::~S();
  }

The issue here is that the grammar uses "~class-name" for destructor names, but in this case S is a template name when looked up in N.

Finally, what about cases like:

  template <typename T> void f () {
    typename T::B x;
    x.template A<T>::template B<T>::~B();
  }

When parsing the template definition, what checks can be done on "~B"?

Sandor Mathe adds :

The standard correction for issue 244 (now in DR status) is still incomplete.

Paragraph 5 of 3.4.3  basic.lookup.qual is not applicable for p->T::~T since there is no nested-name-specifier. Section 3.4.5  basic.lookup.classref describes the lookup of p->~T but p->T::~T is still not described. There are examples (which are non-normative) that illustrate this sort of lookup but they still leave questions unanswered. The examples imply that the name after ~ should be looked up in the same scope as the name before the :: but it is not stated. The problem is that the name to the left of the :: can be found in two different scopes. Consider the following:

  struct S {
    struct C { ~C() { } };
  };

  typedef S::C D;

  int main() {
    D* p;
    p->C::~D();  // valid?
  }

Should the destructor call be valid? If there were a nested name specifier, then D should be looked for in the same scope as C. But here, C is looked for in 2 different ways. First, it is searched for in the type of the left hand side of -> and it is also looked for in the lexical context. It is found in one or if both, they must match. So, C is found in the scope of what p points at. Do you only look for D there? If so, this is invalid. If not, you would then look for D in the context of the expression and find it. They refer to the same underlying destructor so this is valid. The intended resolution of the original defect report of the standard was that the name before the :: did not imply a scope and you did not look for D inside of C. However, it was not made clear whether this was to be resolved by using the same lookup mechanism or by introducing a new form of lookup which is to look in the left hand side if that is where C was found, or in the context of the expression if that is where C was found. Of course, this begs the question of what should happen when it is found in both? Consider the modification to the above case when C is also found in the context of the expression. If you only look where you found C, is this now valid because it is in 1 of the two scopes or is it invalid because C was in both and D is only in 1?

  struct S {
    struct C { ~C() { } };
  };

  typedef S::C D;
  typedef S::C C;

  int main() {
    D* p;
    p->C::~D();  // valid?
  }

I agree that the intention of the committee is that the original test case in this defect is broken. The standard committee clearly thinks that the last name before the last :: does not induce a new scope which is our current interpretation. However, how this is supposed to work is not defined. This needs clarification of the standard.

Martin Sebor adds this example (September 2003), along with errors produced by the EDG front end:

namespace N {
    struct A { typedef A NA; };
    template <class T> struct B { typedef B NB; typedef T BT; };
    template <template <class> class T> struct C { typedef C NC; typedef T<A> CA; };
}

void foo (N::A *p)
{
    p->~NA ();
    p->NA::~NA ();
}

template <class T>
void foo (N::B<T> *p)
{
    p->~NB ();
    p->NB::~NB ();
}

template <class T>
void foo (typename N::B<T>::BT *p)
{
    p->~BT ();
    p->BT::~BT ();
}

template <template <class> class T>
void foo (N::C<T> *p)
{
    p->~NC ();
    p->NC::~NC ();
}

template <template <class> class T>
void foo (typename N::C<T>::CA *p)
{
    p->~CA ();
    p->CA::~CA ();
}

Edison Design Group C/C++ Front End, version 3.3 (Sep  3 2003 11:54:55)
Copyright 1988-2003 Edison Design Group, Inc.

"t.cpp", line 16: error: invalid destructor name for type "N::B<T>"
      p->~NB ();
          ^

"t.cpp", line 17: error: qualifier of destructor name "N::B<T>::NB" does not
          match type "N::B<T>"
      p->NB::~NB ();
              ^

"t.cpp", line 30: error: invalid destructor name for type "N::C<T>"
      p->~NC ();
          ^

"t.cpp", line 31: error: qualifier of destructor name "N::C<T>::NC" does not
          match type "N::C<T>"
      p->NC::~NC ();
              ^

4 errors detected in the compilation of "t.cpp".

John Spicer: The issue here is that we're unhappy with the destructor names when doing semantic analysis of the template definitions (not during an instantiation).

My personal feeling is that this is reasonable. After all, why would you call p->~NB for a class that you just named as N::B<T> and you could just say p->~B?




420. postfixexpression->scalar_type_dtor() inconsistent

Section: 13.5.6  over.ref     Status: drafting     Submitter: Markus Mauhart     Date: 8 June 2003

Lets start with the proposed solution. In 13.5.6  over.ref, replace line ...

postfix-expression -> id-expression
.... with the lines ...
postfix-expression -> templateopt id-expression
postfix-expression -> pseudo-destructor-name
(This then is a copy of the two lines in 5.2  expr.post covering "->dtor")

Alternatively remove the sentence "It implements class member access using ->" and the syntax line following.

Reasons:

Currently stdc++ is inconsistent when handling expressions of the form "postfixexpression->scalar_type_dtor()": If "postfixexpression" is a pointer to the scalar type, it is OK, but if "postfixexpression" referres to any smart pointer class (e.g. iterator or allocator::pointer) with class specific CLASS::operator->() returning pointer to the scalar type, then it is ill-formed; so while c++98 does allow CLASS::operator->() returning pointer to scalar type, c++98 prohibits any '->'-expression involving this overloaded operator function.

Not only is this behaviour inconsistent, but also when comparing the corresponding chapters of c++pl2 and stdc++98 it looks like an oversight and unintended result. Mapping between stdc++98 and c++pl2:

c++pl2.r.5.2 -> 5.2 [expr.post]
c++pl2.r.5.2.4 -> 5.2.4 [expr.pseudo] + 5.2.5 [expr.ref]
c++pl2.r.13.4 -> 13.3.1.2 [over.match.oper]
c++pl2.r.13.4.6 -> 13.5.6 [over.ref]
For the single line of c++pl2.r.5.2 covering "->dtor", 5.2 [expr.post] has two lines. Analogously c++pl2.r.5.2.4 has been doubled to 5.2.4 [expr.pseudo] and 5.2.5 [expr.ref]. From 13.5.6 [over.ref], the sentence forbiding CLASS::operator->() returning pointer to scalar type has been removed. Only the single line of c++pl2.r.13.4.6 (<-> c++pl2.r.5.2's single line) has not gotten its 2nd line when converted into 13.5.6 [over.ref].

Additionally GCC32 does is right (but against 13.5.6 [over.ref]).

AFAICS this would not break old code except compilers like VC7x and Comeau4301.

It does not add new functionality, cause any expression class_type->scalar_type_dtor() even today can be substituted through (*class_type).scalar_type_dtor().

Without this fix, template functions like some_allocator<T>::destroy(p) must use "(*p).~T()" or "(*p).T::~T()" when calling the destructor, otherwise the simpler versions "p->~T()" or "p->T::~T()" could be used.

Sample code, compiled with GCC32, VC7[1] and Comeau4301:

struct A {};//any class

template <class T>
struct PTR
    {
    T& operator*  () const;
    T* operator-> () const;
    };

template <class T>
void f ()
    {
        {
        T*  p               ;
        p = new T           ;
        (*p).T::~T()        ;//OK
        p = new T           ;
        (*p).~T()           ;//OK
        p = new T           ;
        p->T::~T()          ;//OK
        p = new T           ;
        p->~T()             ;//OK
        }

        {
        PTR<T> p = PTR<T>() ;
        (*p).T::~T()        ;//OK
        (*p).~T()           ;//OK
        p.operator->()      ;//OK !!!
        p->T::~T()          ;//GCC32: OK; VC7x,Com4301: OK for A; ERROR w/ int
        p->~T()             ;//GCC32: OK; VC7x,Com4301: OK for A; ERROR w/ int
        }
    }

void test ()
    {
    f <A>  ();
    f <int>();
    }



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

Section: 13.6  over.built     Status: drafting     Submitter: Scott Douglas     Date: 4 Nov 2000

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




205. Templates and static data members

Section: 14  temp     Status: drafting     Submitter: Mike Miller     Date: 11 Feb 2000

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

According to 14.1  temp.param paragraph 3, the following fragment is ill-formed:

    template <class T>
    class X{
      friend void T::foo();
    };

In the friend declaration, the T:: part is a nested-name-specifier (8  dcl.decl paragraph 4), and T must be a class-name or a namespace-name (5.1  expr.prim paragraph 7). However, according to 14.1  temp.param paragraph 3, it is only a type-name. The fragment should be well-formed, and instantiations of the template allowed as long as the actual template argument is a class which provides a function member foo. As a result of this defect, any usage of template parameters in nested names is ill-formed, e.g., in the example of 14.6  temp.res paragraph 2.

Notes from 04/00 meeting:

The discussion at the meeting revealed a self-contradiction in the current IS in the description of nested-name-specifiers. According to the grammar in 5.1  expr.prim paragraph 7, the components of a nested-name-specifier must be either class-names or namespace-names, i.e., the constraint is syntactic rather than semantic. On the other hand, 3.4.3  basic.lookup.qual paragraph 1 describes a semantic constraint: only object, function, and enumerator names are ignored in the lookup for the component, and the program is ill-formed if the lookup finds anything other than a class-name or namespace-name. It was generally agreed that the syntactic constraint should be eliminated, i.e., that the grammar ought to be changed not to use class-or-namespace-name.

A related point is the explicit prohibition of use of template parameters in elaborated-type-specifiers in 7.1.5.3  dcl.type.elab paragraph 2. This rule was the result of an explicit Committee decision and should not be unintentionally voided by the resolution of this issue.

Proposed resolution (04/01):

Change 5.1  expr.prim paragraph 7 and A.4  gram.expr from

to

This resolution depends on the resolutions for issues 245 (to change the name lookup rules in elaborated-type-specifiers to include all type-names) and 283 (to categorize template type-parameters as type-names).

Notes from 10/01 meeting:

There was some sentiment for going with simply identifier in front of the "::", and stronger sentiment for going with something with a more descriptive name if possible. See also issue 180.

Notes from April 2003 meeting:

This was partly resolved by the changes for issue 125. However, we also need to add a semantic check in 3.4.3  basic.lookup.qual to allow T::foo and we need to reword the first sentence of 3.4.3  basic.lookup.qual.




401. When is access for template parameter default arguments checked?

Section: 14.1  temp.param     Status: drafting     Submitter: Steve Adamczyk     Date: 27 Jan 2003

Is the following well-formed?

  class policy {};
  class policy_interface {};
  template <class POLICY_INTERFACE>
  class aph {
  protected:
    typedef POLICY_INTERFACE PI;
  };
  template <class POLICY, class BASE, class PI = typename BASE::PI>
  class ConcretePolicyHolder : public BASE, protected POLICY
  {};
  ConcretePolicyHolder < policy , aph < policy_interface > > foo;
  void xx() { }

The issue is whether the access to the default argument type BASE::PI is checked before or after it is known that BASE is a base class of the template. To some extent, one needs to develop the list of template arguments (and therefore evaluate the default argument) before one can instantiate the template, and one does not know what base classes the template has until it has been instantiated.

Notes from April 2003 meeting:

Shortened example:

  class B {
  protected:
    typedef int A;
  };
  template<class T, class U = typename T::A>
  class X : public T
  { };

The convincing argument here is that if we had only the declaration of the template (including the default argument), we would expect it to be usable in exactly the same way as the version with the definition. However, the special access needed is visible only when the definition is available. So the above should be an error, and information from the definition cannot affect the access of the default arguments.

Proposed Resolution (April 2003):

Add a new paragraph 16 to 14.1  temp.param after paragraph 15:

Since a default template-argument is encountered before any base-clause there is no special access to members used in a default template-argument. [Example:
  class B {};
  template <class T> class C {
  protected:
     typedef T TT;
  };

  template <class U, class V = typename U::TT>
  class D : public U {};

  D <C<B> > d;  // access error, C::TT is protected
--- end example]

Notes from October 2003 meeting:

We decided that template parameter default arguments should have their access checked in the context where they appear without special access for the entity declared (i.e., they are different than normal function default arguments). One reason: we don't know the instance of the template when we need the value. Second reason: compilers want to parse and throw away the form of the template parameter default argument, not save it and check it for each instantiation.

Class templates should be treated the same as function templates in this regard. The base class information is in the same category as friend declarations inside the class itself -- not available. If the body were used one would need to instantiate it in order to know whether one can name it.




96. Syntactic disambiguation using the template keyword

Section: 14.2  temp.names     Status: drafting     Submitter: John Spicer     Date: 16 Feb 1999

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.

Notes from the October 2003 meeting:

We reviewed John Spicer's paper N1528 and agreed with his recommendations therein.




301. Syntax for template-name

Section: 14.2  temp.names     Status: drafting     Submitter: Mark Mitchell     Date: 24 Jul 2001

The grammar for a template-name is:

That's not right; consider:

    template <class T> T operator+(const T&, const T&);
    template <> S operator+<S>(const S&, const S&);

This is ill-formed according to the standard, since operator+ is not a template-name.

Suggested resolution:

I think the right rule is

John Spicer adds that there's some question about whether conversion functions should be included, as they cannot have template argument lists.

Notes from 4/02 meeting:

If the change is made as a syntax change, we'll need a semantic restriction to avoid operator+<int> as a class. Clark Nelson will work on a compromise proposal -- not the minimal change to the syntax proposed, not the maximal change either.

Clark Nelson (April 2003):

The proposed solution (adding operator-function-id as an alternative for template-name) would have a large impact on the language described by the grammar. Specifically, for example, operator+<int> would become a syntactically valid class-name.

On the other hand, a change with (I believe) exactly the desired effect on the language accepted, would be to modify operator-function-id itself:

(Steve Adamczyk: this change was already made by issue 38 and is in TC1.)

Then there is the first sentence of 14.2  temp.names paragraph 3:

After name lookup (3.4  basic.lookup) finds that a name is a template-name, if this name is followed by a <, the < is always taken as the beginning of a template-argument-list and never as a name followed by the less-than operator.

This description seems to be adequate for names of class templates. As far as I can tell, the only ambiguity it resolves is from something that starts with new X <, in the scope of a class template X. But as far as I can tell is already inadequate for names of function templates, and is even worse for operator function templates.

Probably < should always be interpreted as introducing a template-argument-list if any member of the overload set is a function template. After all, function pointers are very rarely compared for ordering, and it's not clear what other rule might be workable.

I'm inclined to propose the simplest rule possible for operator-function-ids: if one is followed by <, then what follows is interpreted as a template-argument-list, unconditionally. Of course, if no template for that operator has been declared, then there's an error.

Also, note that if the operator in question is < or <<, it is possible to run into a problem similar to the famous >> nested template argument list closing delimiter problem. However, since in this case (at least) one of the < characters has a radically different interpretation than the other, and for other reasons as well, this is unlikely to be nearly as much of a practical problem as the >> problem.

Notes from April 2003 meeting:

We felt that the operator functions should not be special-cased. They should be treated like any other name.

September 2003:

Clark Nelson has provided the changes in N1490=03-0073.

Notes from October 2003 meeting:

We reviewed Clark Nelson's N1490. Clark will revise it and introduce a new syntax term for an identifier or the name of an operator function.




314. template in base class specifier

Section: 14.2  temp.names     Status: drafting     Submitter: Mark Mitchell     Date: 23 Aug 2001

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.




431. Defect in wording in 14.2

Section: 14.2  temp.names     Status: drafting     Submitter: Mat Marcus     Date: 10 August 2003

Consider this example:

   class Foo {
   public:
       template< typename T > T *get();
   };

   template< typename U >
   U *testFoo( Foo &foo ) {
       return foo.get< U >(); //#1
   }

I am under the impression that this should compile without requiring the insertion of the template keyword before get in the expression at //#1. This notion is supported by this note excerpted from 14.2  temp.names/5:

[Note: just as is the case with the typename prefix, the template prefix is allowed in cases where it is not strictly necessary; i.e., when the expression on the left of the -> or ., or the nested-name-specifier is not dependent on a template parameter.]

But 14.2  temp.names/4 contains this text:

When the name of a member template specialization appears after . or -> in a postfix-expression, or after nested-name-specifier in a qualified-id, and the postfix-expression or qualified-id explicitly depends on a template-parameter (14.6.2), the member template name must be prefixed by the keyword template. Otherwise the name is assumed to name a non-template.

The only way that I can read this to support my assumption above is if I assume that the phrase postfix-expression is used twice above with different meaning. That is I read the first use as referring to the full expression while the second use refers to the subexpression preceding the operator. Is this the correct determination of intent? I find this text confusing. Would it be an improvement if the second occurrence of "postfix-expression" should be replaced by "the subexpression preceding the operator". Of course that begs the question "where is subexpression actually defined in the standard?"

John Spicer: I agree that the code should work, and that we should tweak the wording.




372. Is access granted by base class specifiers available in following base class specifiers?

Section: 14.3  temp.arg     Status: drafting     Submitter: Clark Nelson     Date: 13 August 2002

I'm not really sure what the standard says about this. Personally, I'd like for it to be ill-formed, but I can't find any words that I can interpret to say so.

  template<class T>
  class X
  {
  protected:
    typedef T Type;
  };
  template<class T>
  class Y
  {
  };
  template<class T,
           template<class> class T1,
           template<class> class T2>
  class Z:
    public T2<typename T1<T>::Type>,
    public T1<T>
  {
  };
  Z<int, X, Y> z;

John Spicer: I don't think the standard really addresses this case. There is wording about access checking of things used as template arguments, but that doesn't address accessing members of the template argument type (or template) from within the template.

This example is similar, but does not use template template arguments.

  class X {
  private:
    struct Type {};
  };
  template <class T> struct A {
    typename T::Type t;
  };
  A<X> ax;

This gets an error from most compilers, though the standard is probably mute on this as well. An error makes sense -- if there is no error, there is a hole in the access checking. (The special rule about no access checks on template parameters is not a hole, because the access is checked on the type passed in as an argument. But when you look up something in the scope of a template parameter type, you need to check the access to the member found.)

The logic in the template template parameter case should be similar: anytime you look up something in a template-dependent class, the member's access must be checked, because it could be different for different template instances.

Proposed Resolution (October 2002):

Change the last sentence of 14.3  temp.arg paragraph 3 from:

For a template-argument of class type, the template definition has no special access rights to the inaccessible members of the template argument type.
to:
For a template-argument that is a class type or a class template, the template definition has no special access rights to the members of the template-argument. [Example:
  template <template <class TT> class T> class A {
    typename T<int>::S s;
  };

  template <class U> class B {
    private:
    struct S { /* ... */ };
  };

  A<B> b;   // ill-formed, A has no access to B::S
-- end example]

Daniel Frey posts on comp.std.c++ in July 2003: I just read DR 372 and I think that the problem presented is not really discussed/solved properly. Consider this example:

  class A {
  protected:
     typedef int N;
  };

  template< typename T >
  class B
  {};

  template< typename U >
  class C : public U, public B< typename U::N >
  {};

  C< A > x;

The question is: If C is derived from A as above, is it allowed to access A::N before the classes opening '{'?

The main problem is that you need to access U's protected parts in C's base-clause. This pattern is common when using policies, Andrei's Loki library was bitten by it as he tried to make some parts of the policies 'protected' but some compilers rejected the code. They were right to reject it, I think it's 11.4  class.friend/2 that applies here and prevents the code above to be legal, although it addresses a different and reasonable example. To me, it seems wrong to reject the code as it is perfectly reasonable to write such stuff. The questions are:

Steve Adamczyk: In other words, the point of the issue is over what range access derived from base class specifiers is granted, and whether any part of that range is the base specifier list itself, either the parts afterwards or the whole base specifier list. (Clark Nelson confirms this is what he was asking with the original question.) Personally, I find it somewhat disturbing that access might arrive incrementally; I'd prefer that the access happen all at once, at the opening brace of the class.

Notes from October 2003 meeting:

We decided it makes sense to delay the access checking for the base class specifiers until the opening brace of the class is seen. In other words, the base specifiers will be checked using the full access available for the class, and the order of the base classes is not significant in that determination. The implementors present all said they already had code to handle accumulation of delayed access checks, because it is already needed in other contexts.




408. sizeof applied to unknown-bound array static data member of template

Section: 14.5.1.3  temp.static     Status: drafting     Submitter: Nathan Myers     Date: 14 Apr 2003

Is this allowed?

  template<typename T> 
    struct X
    {
        static int s[];
        int c;
    };

  template<typename T>
    int X<T>::s[sizeof(X<T>)];

  int* p = X<char>::s;

I have a compiler claiming that, for the purpose of sizeof(), X<T> is an incomplete type, when it tries to instantiate X<T>::s. It seems to me that X<char> should be considered complete enough for sizeof even though the size of s isn't known yet.

John Spicer: This is a problematic construct that is currently allowed but which I think should be disallowed.

I tried this with a number of compilers. None of which did the right thing. The EDG front end accepts it, but gives X<...>::s the wrong size.

It appears that most compilers evaluate the "declaration" part of the static data member definition only once when the definition is processed. The initializer (if any) is evaluated for each instantiation.

This problem is solvable, and if it were the only issue with incomplete arrays as template static data members, then it would make sense to solve it, but there are other problems.

The first problem is that the size of the static data member is only known if a template definition of the static data member is present. This is weird to start with, but it also means that sizes would not be available in general for exported templates.

The second problem concerns the rules for specialization. An explicit specialization for a template instance can be provided up until the point that a use is made that would cause an implicit instantiation. A reference like "sizeof(X<char>::s)" is not currently a reference that would cause an implicit instantiation of X<char>::s. This means you could use such a sizeof and later specialize the static data member with a different size, meaning the earlier sizeof gave the wrong result. We could, of course, change the "use" rules, but I'd rather see us require that static data members that are arrays have a size specified in the class or have a size based on their initializer.

Notes from the October 2003 meeting:

The example provided is valid according to the current standard. A static data member must be instantiated (including the processing of its initializer, if any) if there is any reference to it. The compiler need not, however, put out a definition in that translation unit. The standard doesn't really have a concept of a "partial instantiation" for a static data member, and although we considered adding that, we decided that to get all the size information that seems to be available one needs a full instantiation in any case, so there's no need for the concept of a partial instantiation.




382. Allow typename outside of templates

Section: 14.6  temp.res     Status: drafting     Submitter: Steve Adamczyk     Date: 8 Nov 2002

P. J. Plauger, among others, has noted that typename is hard to use, because in a given context it's either required or forbidden, and it's often hard to tell which. It would make life easier for programmers if typename could be allowed in places where it is not required, e.g., outside of templates.

Notes from the April 2003 meeting:

There was unanimity on relaxing this requirement on typename. The question was how much to relax it. Everyone agreed on allowing it on all qualified names, which is an easy fix (no syntax change required). But should it be allowed other places? P.J. Plauger said he'd like to see it allowed anywhere a type name is allowed, and that it could actually be a decades-late assist for the infamous "the ice is thin here" typedef problem noted in K&R I.

Proposed resolution (April 2003):

Replace the text at the start of 14.6  temp.res paragraph 3:

A qualified-id that refers to a type and in which the nested-name-specifier depends on a template-parameter (14.6.2  temp.dep) shall be prefixed by the keyword typename to indicate that the qualified-id denotes a type, forming an elaborated-type-specifier (7.1.5.3  dcl.type.elab).

With:

The keyword typename can only be applied to a qualified-id. A qualified-id that refers to a type and in which the nested-name-specifier depends on a template-parameter (14.6.2  temp.dep) shall be prefixed by the keyword typename to indicate that the qualified-id denotes a type, forming an elaborated-type-specifier (7.1.5.3  dcl.type.elab). If a qualified-id which has been prefixed by the keyword typename does not denote a type the program is ill-formed. [ Note: The keyword is only required on a qualified-id within a template declaration or definition in which the nested-name-specifier depends on a template-parameter. ]

Remove 14.6  temp.res paragraph 5:

The keyword typename shall only be used in template declarations and definitions, including in the return type of a function template or member function template, in the return type for the definition of a member function of a class template or of a class nested within a class template, and in the type-specifier for the definition of a static member of a class template or of a class nested within a class template. The keyword typename shall be applied only to qualified names, but those names need not be dependent. The keyword typename shall be used only in contexts in which dependent names can be used. This includes template declarations and definitions but excludes explicit specialization declarations and explicit instantiation declarations. The keyword typename is not permitted in a base-specifier or in a mem-initializer; in these contexts a qualified-id that depends on a template-parameter (temp.dep) is implicitly assumed to be a type name.

Note: the claim here that a qualified name preceded by typename forms an elaborated type specifier conflicts with the changes made in issue 254 (see N1376=02-0034), which introduces typename-specifier.

Notes from October 2003 meeting:

We considered whether typename should be allowed in more places, and decided we only wanted to allow it in qualified names (for now at least).

Core issue 254 changed elaborated-type-specifier to typename-specifier. It also changed 14.6  temp.res paragraph 5, which this proposed resolution deletes. Back to Nelson for redrafting.




448. Set of template functions in call with dependent explicit argument

Section: 14.6.1  temp.local     Status: drafting     Submitter: Mark Mitchell     Date: 4 Jan 2004

Is this program valid?

  template <typename T> int g(int);
  class h{};
  template <typename T> int l(){h j; return g<T>(j);}
  template <typename T> int g(const h&);
  class j{};
  int jj(){return l<j>();}

The key issue is when "g" is looked up, i.e., whether both overloaded template "g" functions are available at the call site or only the first. Clearly, the entire postfix-expression "g<T>(j)" is dependent, but when is the set of available template functions determined?

For consistency with the rules about when the set of available overloads is determined when calling a function given by an unqualified-id, I would think that we should postpone determining the set of template functions if (and only if) any of the explicit template arguments are dependent.

John Spicer: I agree that there should be a core issue for this. The definition of "dependent name" (14.6.2  temp.dep paragraph 1) should probably be modified to cover this case. It currently only handles cases where the function name is a simple identifier.

Notes from the March 2004 meeting:

A related issue is a call with a qualified name and dependent arguments, e.g., x::y(depa, depb).




458. Hiding of member template parameters by other members

Section: 14.6.1  temp.local     Status: drafting     Submitter: Gabriel Dos Reis     Date: 2 Feb 2004

The list of cases in 14.6.1  temp.local about when a template parameter is hidden seems to be incomplete.

Consider

      // example-1
    struct S {
       int C;
       template<class> void f();
    };

    template<class C>
      void S::f()
      {
         C c;           // #1
      }

Someone asked whether line #1 is well-formed and I responded "no" based on my understanding of the rules in 14.6.1. After a second looking, I've realized that the above case is currently missing from the list.

The list in 14.6.1 covers cases like

     // example-2
   template<class T>
     struct S {
        int C;
        void f();
     };

   template<class C>
     void S<C>::f()
     {
       C c;     // ERROR: 'C' is 'S::C' not the template parameter
     }
or
     // example-3
   struct A { int C; }

   template<class C>
      struct S : A {
        C c;    // ERROR: 'C' is 'A::C', not the template parameter
      };
But the case of a 'member template' is missing. I believe it should follow the same rule as above. The reason is this.

In the case listed in 14.6.1 (having to do with members of classes), the "algorithm" seems to be this:

  1. put the "template parameter scope"[1] on the top of active scope stack. That will make the template parameter declarations the innermost bindings.
  2. Enter the class scope. That will push more scopes on the stack. In particular, any bindings from non-dependent base classes or from the class definition will hide any previous bindings, especially the template parameter declarations.
The above formulation uniformly covers paragraphs 5 and 7 of section 14.6.1 and gives a general view of how name lookup is supposed to happen.

I believe that any rule, coherent with 14.6.1/5 and 14.6.1/7, for covering the cases of member templates (example-1) will be described by the above "algorithm".

Am I missing something?

[1] of course, the standard text does not formally speak of "template parameter scope", but we all know that the template parameters "live" somewhere. I'm using that terminology to designate the declarative region of the template parameters.

Mike Miller: I have a somewhat different perspective on this question. I think your example-1 is fundamentally different from your example-2 and example-3. Looking, for instance, at your example-2, I see four nested scopes:

     namespace scope
       template scope (where the parameter is)
         class S scope
           S::f() block scope

Naturally, S::C hides the template parameter C. The same is true of your example-3, with three scopes:

     namespace scope
       template scope
         class S scope (includes 10.2 base class lookup)

Again, it's clear that the C inherited from A hides the template parameter in the containing scope.

The scopes I see in your example-1, however, are different:

     namespace scope
       struct S scope
         template scope (where the parameter is)
           S::f() block scope

Here it seems clear to me that the template parameter hides the class member.

It might help to look at the case where the function template is defined inline in the class:

     struct S {
        int C;
        template<class C> int f() {
            C c;   // #1
        }
     };

It would be pretty strange, I think, if the #1 C were the member and not the template parameter. It would also be odd if the name lookup were different between an inline definition and an out-of-line definition.

See also issue 459.

Notes from the March 2004 meeting:

Basically, the standard is okay. We think Gaby's desired cases like #1 should be ill-formed.

There is a wording problem in 14.6.1  temp.local paragraph 7. It says:

In the definition of a member of a class template that appears outside of the class template definition, the name of a member of this template hides the name of a template-parameter.

It should say "hides the name of a template-parameter of the class template (but not a template-parameter of the member, if the member is itself a template)" or words to that effect.




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: drafting     Submitter: unknown     Date: unknown
    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.




287. Order dependencies in template instantiation

Section: 14.6.4.1  temp.point     Status: drafting     Submitter: Martin Sebor     Date: 17 May 2001

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

Notes from the October 2002 meeting:

This needs work. Moved back to drafting status.




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

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




212. Implicit instantiation is not described clearly enough

Section: 14.7.1  temp.inst     Status: drafting     Submitter: Christophe de Dinechin     Date: 7 Mar 2000

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




415. Template deduction does not cause instantiation

Section: 14.8.3  temp.over     Status: drafting     Submitter: John Spicer     Date: 4 May 2003

Mike Miller: In fact, now that I've looked more closely, that appears not to be the case. (At least, it's not the error I get when I compile his example.) Here's a minimal extract (without the inflammatory using-directive :-) that illustrates what I think is going on:

  template <typename _Iterator>
  struct iterator_traits {
    typedef typename _Iterator::difference_type difference_type;
  };

  template <typename _InputIterator>
  inline typename iterator_traits<_InputIterator>::difference_type
  distance(_InputIterator, _InputIterator);

  double distance(const int&, const int&);

  void f() {
    int i = 0;
    int j = 0;
    double d = distance(i, j);
  }

What happens is that iterator_traits<int> is instantiated as part of type deduction for the function template distance, and the instantiation fails. (Note that it can't be instantiation of distance<int>, as I had originally posited, because in this case only a declaration, not a definition, of that template is in scope.)

John Spicer: Yes, I believe that is what is going on.

Mike Miller: I seem to recall that there was some discussion of questions related to this during the core meetings in Oxford. I think Steve Adamczyk said something to the effect that it's infeasible to suppress all instantiation errors during template type deduction and simply call any such errors a deduction failure. (I could be misremembering, and I could be misapplying that comment to this situation.)

John Spicer: Regardless of other conditions in which this may apply, I don't think it would be reasonable for compilers to have to do "speculative instantiations" during template argument deduction. One class instantiation could kick off a series of other instantiations, etc.

Mike Miller: I don't see anything in the Standard that tells me whether it's legitimate or not to report an error in this case. I hope John or another template expert can enlighten me on that.

John Spicer: My opinion is that, because this case is not among those enumerated that cause deduction failure (rather than being ill-formed) that reporting an error is the right thing to do.

Mike Miller: I am still interested, though, in the question of why 14.8.3  temp.over says that viable function template specializations are instantiated, even if they are not selected by overload resolution.

John Spicer: I believe the wording in 14.8.3  temp.over is incorrect. I researched this and found that a change was made during the clause 14 restructuring that was incorporated in March of 1996. The prior wording was "the deduced template arguments are used to generate a single template function". This was changed to "deduced template arguments are used to instantiate a single function template specialization". I believe this resulted from what was basically a global replace of "generate" with "instantiate" and of "template function" with "function template specialization". In this case, the substitution changed the meaning. This paragraph needs reworking.




370. Can #include <...> form be used other than for standard C++ headers?

Section: 16.2  cpp.include     Status: drafting     Submitter: Beman Dawes     Date: 01 August 2002

The motivation for this issue is a desire to write portable programs which will work with any conforming implementation.

The C++ Standard (16.2  cpp.include) provides two forms of #include directives, with the <...> form being described (16.2  cpp.include paragraph 2) as "for a header", and the "..." form (16.2  cpp.include paragraph 3) as for "the source file" identified between the delimiters. When the standard uses the term "header", it often appears to be limiting the term to apply to the Standard Library headers only. Users of the standard almost always use the term "header" more broadly, to cover all #included source files, but particularly those containing interface declarations.

Headers, including source files, can be categorized according to their origin and usage:

  1. C++ Standard Library headers (which aren't necessarily files).
  2. Other standard libraries such as the POSIX headers.
  3. Operating system API's such as windows.h.
  4. Third party libraries, such as Boost, ACE, or commercial offerings.
  5. Organization-wide "standard" header files, such as a company's config.hpp.
  6. A project's "public" header files, often shared by all developers working on the same project.
  7. A project or user's "private", "local", or "detail" headers, in the same directory or sub-directory as the compilation unit.

Existing practice varies widely, but it is fairly easy to find users advocating:

Do any of the practices A, B, or C result in programs which can be rejected by a conforming implementation?

The first defect is that readers of the standard have not been able to reach consensus on the answers to the above question.

A second possible defect is that if A, B, or C can be rejected by a conforming implementation, then the standard should be changed because would mean there is a wide variance between the standard and existing practice.

Matt Austern: I really only see two positions:

  1. Implementations have some unspecified mechanism (copying files to a magic directory, adding a magic flag,...) such that the line #include <foo> results in textual inclusion of the file foo.
  2. Implementations are not required to have any mechanism for getting #include <foo> to perform textual inclusion of an arbitrary file foo. That form is reserved for standard library headers only.

I agree that the standard should clarify which of those two is the case (I imagine it'll hinge on finding one crucual sentence that either says "implementation defined" or "unspecified"), but from the standpoint of portability I don't see much difference between the two. I claim that, with either of those two interpretations, using #include <foo> is already nonportable.

(Of course, I claim that almost anything having to do with headers, including the #include "foo" form, is also nonportable. In practice there's wide variation in how compilers handle paths, especially relative paths.)

Beman Dawes: The whole issue can be resolved by replacing "header" with "header or source file" in 16.2  cpp.include paragraph 2. That will bring the standard into alignment with existing practice by both users and implementations. The "header and/or source file" wording is used at least three other places in the standard where otherwise there might be some confusion.

John Skaller: In light of Andrew Koenig's comments, this doesn't appear to be the case, since the mapping of include names to file names is implementation defined, and therefore source file inclusion cannot be made portable within the ISO C/C++ standards (since that provision obviously cannot be removed).

A possible idea is to create a binding standard, outside the C/C++ ISO Standards, which specifies not only the path lookup mechanism but also the translation from include names to file names. Clearly that is OS dependent, encoding dependent, etc, but there is no reason not to have a binding standard for Unix, Windows, etc, and specify these bindings in such a way that copying directories from one OS to the other can result in programs working on both OS's.

Andy Koenig: An easier solution might be to specify a (presumably unbounded, or bounded only by implementation capacity) collection of header-file names that every implementation must make it possible for programs to access somehow, without specifying exactly how.

Notes from October 2002 meeting:

This was discussed at some length. While there was widespread agreement that such inclusion is inherently implementation-dependent, we agreed to try to add wording that would make it clear that implementations are permitted (but not required) to allow inclusion of files using the <...> form of #include.






Issues with "Open" Status


357. Definition of signature should include name

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

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

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

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

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

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




129. Stability of uninitialized auto variables

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

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.




369. Are new/delete identifiers or preprocessing-op-or-punc?

Section: 2.4  lex.pptoken     Status: open     Submitter: Martin v. Loewis     Date: 30 July 2002

2.4  lex.pptoken paragraph 2 specifies that there are 5 categories of tokens in phases 3 to 6. With 2.12  lex.operators paragraph 1, it is unclear whether new is an identifier or a preprocessing-op-or-punc; likewise for delete. This is relevant to answer the question whether

#define delete foo

is a well-formed control-line, since that requires an identifier after the define token.




411. Use of universal-character-name in character versus string literals

Section: 2.13.4  lex.string     Status: open     Submitter: James Kanze     Date: 23 Apr 2003

2.13.4  lex.string paragraph 5 reads

Escape sequences and universal-character-names in string literals have the same meaning as in character literals, except that the single quote ' is representable either by itself or by the escape sequence \', and the double quote " shall be preceded by a \. In a narrow string literal, a universal-character-name may map to more than one char element due to multibyte encoding.

The first sentence refers us to 2.13.2  lex.ccon, where we read in the first paragraph that "An ordinary character literal that contains a single c-char has type char [...]." Since the grammar shows that a universal-character-name is a c-char, something like '\u1234' must have type char (and thus be a single char element); in paragraph 5, we read that "A universal-character-name is translated to the encoding, in the execution character set, of the character named. If there is no such encoding, the universal-character-name is translated to an implemenation-defined encoding."

This is in obvious contradiction with the second sentence. In addition, I'm not really clear what is supposed to happen in the case where the execution (narrow-)character set is UTF-8. Consider the character \u0153 (the oe in the French word oeuvre). Should '\u0153' be a char, with an "error" value, say '?' (in conformance with the requirement that it be a single char), or an int, with the two char values 0xC5, 0x93, in an implementation defined order (in conformance with the requirement that a character representable in the execution character set be represented). Supposing the former, should "\u0153" be the equivalent of "?" (in conformance with the first sentence), or "\xC5\x93" (in conformance with the second).

Notes from October 2003 meeting:

We decided we should forward this to the C committee and let them resolve it. Sent via e-mail to John Benito on November 14, 2003.

Reply from John Benito:

I talked this over with the C project editor, we believe this was handled by the C committee before publication of the current standard.

WG14 decided there needed to be a more restrictive rule for one-to-one mappings: rather than saying "a single c-char" as C++ does, the C standard says "a single character that maps to a single-byte execution character"; WG14 fully expect some (if not many or even most) UCNs to map to multiple characters.

Because of the fundamental differences between C and C++ character types, I am not sure the C committee is qualified to answer this satisfactorily for WG21. WG14 is willing to review any decision reached for compatibility.

I hope this helps.




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

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

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

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

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



405. Unqualified function name lookup

Section: 3.4.1  basic.lookup.unqual     Status: open     Submitter: William M. Miller     Date: 14 Apr 2003

There seems to be some confusion in the Standard regarding the relationship between 3.4.1  basic.lookup.unqual (Unqualified name lookup) and 3.4.2  basic.lookup.koenig (Argument-dependent lookup). For example, 3.4.1  basic.lookup.unqual paragraph 3 says,

The lookup for an unqualified name used as the postfix-expression of a function call is described in 3.4.2  basic.lookup.koenig.

In other words, nothing in 3.4.1  basic.lookup.unqual applies to function names; the entire lookup is described in 3.4.2  basic.lookup.koenig.

3.4.2  basic.lookup.koenig does not appear to share this view of its responsibility. The closest it comes is in 3.4.2  basic.lookup.koenig paragraph 2a:

...the set of declarations found by the lookup of the function name is the union of the set of declarations found using ordinary unqualified lookup and the set of declarations found in the namespaces and classes associated with the argument types.

Presumably, "ordinary unqualified lookup" is a reference to the processing described in 3.4.1  basic.lookup.unqual, but, as noted above, 3.4.1  basic.lookup.unqual explicitly precludes applying that processing to function names. The details of "ordinary unqualified lookup" of function names are not described anywhere.

The other clauses that reference 3.4.2  basic.lookup.koenig, clauses 13  over and 14  temp, are split over the question of the relationship between 3.4.1  basic.lookup.unqual and 3.4.2  basic.lookup.koenig. 13.3.1.1.1  over.call.func paragraph 3, for instance, says

The name is looked up in the context of the function call following the normal rules for name lookup in function calls (3.4.2  basic.lookup.koenig).

I.e., this reference assumes that 3.4.2  basic.lookup.koenig is self-contained. The same is true of 13.3.1.2  over.match.oper paragraph 3, second bullet:

The set of non-member candidates is the result of the unqualified lookup of operator@ in the context of the expression according to the usual rules for name lookup in unqualified function calls (3.4.2  basic.lookup.koenig), except that all member functions are ignored.

On the other hand, however, 14.6.4.2  temp.dep.candidate paragraph 1 explicitly assumes that 3.4.1  basic.lookup.unqual and 3.4.2  basic.lookup.koenig are both involved in function name lookup and do different things:

For a function call that depends on a template parameter, if the function name is an unqualified-id but not a template-id, the candidate functions are found using the usual lookup rules (3.4.1  basic.lookup.unqual, 3.4.2  basic.lookup.koenig) except that:

Suggested resolution:

Change 3.4.1  basic.lookup.unqual paragraph 1 from

...name lookup ends as soon as a declaration is found for the name.

to

...name lookup ends with the first scope containing one or more declarations of the name.

Change the first sentence of 3.4.1  basic.lookup.unqual paragraph 3 from

The lookup for an unqualified name used as the postfix-expression of a function call is described in 3.4.2  basic.lookup.koenig.

to

An unqualified name used as the postfix-expression of a function call is looked up as described below. In addition, argument-dependent lookup (3.4.2  basic.lookup.koenig) is performed on this name to complete the resulting set of declarations.



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

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

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.


373. Lookup on namespace qualified name in using-directive

Section: 3.4.6  basic.lookup.udir     Status: open     Submitter: Clark Nelson     Date: 15 August 2002

Is this case valid? G++ compiles it.

namespace X {
  namespace Y {
    struct X {
      void f()
      {
        using X::Y;
        namespace Z = X::Y;
      }
    };
  }
}

The relevant citation from the standard is 3.4.6  basic.lookup.udir: "When looking up a namespace-name in a using-directive or namespace-alias-definition, only namespace names are considered." This statement could reasonably be interpreted to apply only to the last element of a qualified name, and that's the way EDG and Microsoft seem to interpret it.

However, since a class can't contain a namespace, it seems to me that this interpretation is, shall we say, sub optimal. If the X qualifiers in the above example are interpreted as referring to the struct X, an error of some sort is inevitable, since there can be no namespace for the qualified name to refer to. G++ apparently interprets 3.4.6  basic.lookup.udir as applying to nested-name-specifiers in those contexts as well, which makes a valid interpretation of the test possible.

I'm thinking it might be worth tweaking the words in 3.4.6  basic.lookup.udir to basically mandate the more useful interpretation. Of course a person could argue that the difference would matter only to a perverse program. On the other hand, namespaces were invented specifically to enable the building of programs that would otherwise be considered perverse. Where name clashes are concerned, one man's perverse is another man's real world.




278. External linkage and nameless entities

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

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

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?




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

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




371. Interleaving of constructor calls

Section: 3.6.2  basic.start.init     Status: open     Submitter: Matt Austern     Date: 7 August 2002

Is a compiler allowed to interleave constructor calls when performing dynamic initialization of nonlocal objects? What I mean by interleaving is: beginning to execute a particular constructor, then going off and doing something else, then going back to the original constructor. I can't find anything explicit about this in clause 3.6.2  basic.start.init.

I'll present a few different examples, some of which get a bit wild. But a lot of what this comes down to is exactly what the standard means when it talks about the order of initialization. If it says that some object x must be initialized before a particular event takes place, does that mean that x's constructor must be entered before that event, or does it mean that it must be exited before that event? If object x must be initialized before object y, does that mean that x's constructor must exit before y's constructor is entered?

(The answer to that question might just be common sense, but I couldn't find an answer in clause 3.6.2  basic.start.init. Actually, when I read 3.6.2  basic.start.init carefully, I find there are a lot of things I took for granted that aren't there.)

OK, so a few specific scenerios.

  1. We have a translation unit with nonlocal objects A and B, both of which require dynamic initialization. A comes before B. A must be initialized before B. May the compiler start to construct A, get partway through the constructor, then construct B, and then go back to finishing A?
  2. We have a translation unit with nonlocal object A and function f. Construction of A is deferred until after the first statement of main. A must be constructed before the first use of f. Is the compiler permitted to start constructing A, then execute f, then go back to constructing A?
  3. We have nonlocal objects A and B, in two different translation units. The order in which A and B are constructed is unspecified by the Standard. Is the compiler permitted to begin constructing A, then construct B, then finish A's constructor? Note the implications of a 'yes' answer. If A's and B's constructor both call some function f, then the call stack might look like this:
       <runtime gunk>
         <Enter A's constructor>
            <Enter f>
               <runtime gunk>
                  <Enter B's constructor>
                     <Enter f>
                     <Leave f>
                  <Leave B's constructor>
            <Leave f>
         <Leave A's constructor>
    
    The implication of a 'yes' answer for users is that any function called by a constructor, directly or indirectly, must be reentrant.
  4. This last example is to show why a 'no' answer to #3 might be a problem too. New scenerio: we've got one translation unit containing a nonlocal object A and a function f1, and another translation unit containing a nonlocal object B and a function f2. A's constructor calls f2. Initialization of A and B is deferred until after the first statement of main(). Someone in main calls f1. Question: is the compiler permitted to start constructing A, then go off and construct B at some point before f2 gets called, then go back and finish constructing A? In fact, is the compiler required to do that? We've got an unpleasant tension here between the bad implications of a 'yes' answer to #3, and the explicit requirement in 3.6.2  basic.start.init paragraph 3.

At this point, you might be thinking we could avoid all of this nonsense by removing compilers' freedom to defer initialization until after the beginning of main(). I'd resist that, for two reasons. First, it would be a huge change to make after the standard has been out. Second, that freedom is necessary if we want to have support for dynamic libraries. I realize we don't yet say anything about dynamic libraries, but I'd hate to make decisions that would make such support even harder.




465. May constructors of global objects call exit()?

Section: 3.6.2  basic.start.init     Status: open     Submitter: Matt Austern     Date: 26 Feb 2004

The subject line pretty much says it all. It's a possibility that hadn't ever occurred to me. I don't see any prohibition in the standard, and I also don't think the possibility introduces any logical inconsistencies. The proper behavior, presumably, would be to go through the list of already-constructed objects (not including the current one, since its constructor wouldn't have finished executing) and destroy them in reverse order. Not fundamentally hard, and I'm sure lots of existing implementations already do that.

I'm just not sure whether the standard was intended to support this, or whether it's just that nobody else thought of it either. If the former, then a non-normative note somewhere in 3.6.2  basic.start.init might be nice.




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

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

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

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

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

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

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

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

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

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

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

Proposed Resolution (10/98):

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

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




365. Storage duration and temporaries

Section: 3.7  basic.stc     Status: open     Submitter: James Kanze     Date: 24 July 2002

There are several problems with 3.7  basic.stc:

Steve Adamczyk: There may well be an issue here, but one should bear in mind the difference between storage duration and object lifetime. As far as I can see, there is no particular problem with temporaries having automatic or static storage duration, as appropriate. The point of 12.2  class.temporary is that they have an unusual object lifetime.

Notes from Ocrober 2002 meeting:

It might be desirable to shorten the storage duration of temporaries to allow reuse of them. The as-if rule allows some reuse, but such reuse requires analysis, including noting whether the addresses of such temporaries have been taken.




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

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.




419. Can cast to virtual base class be done on partially-constructed object?

Section: 3.8  basic.life     Status: open     Submitter: Judy Ward     Date: 2 June 2003

Consider

  extern "C" int printf (const char *,...);

  struct Base { Base();};
  struct Derived: virtual public Base {
     Derived() {;}
  };

  Derived d;
  extern Derived& obj = d;

  int i;

  Base::Base() {
    if ((Base *) &obj) i = 4;
    printf ("i=%d\n", i);
  }

  int main() { return 0; }

12.7  class.cdtor paragraph 2 makes this valid, but 3.8  basic.life paragraph 5 implies that it isn't valid.

Steve Adamczyk: A second issue:

  extern "C" int printf(const char *,...);
  struct A                      { virtual ~A(); int x; };
  struct B : public virtual A   { };
  struct C : public B           { C(int); };
  struct D : public C           { D(); };
 
  int main()                    { D t; printf("passed\n");return 0; }
 
  A::~A()                       {} 
  C::C(int)                     {} 
  D::D() : C(this->x)           {}

Core issue 52 almost, but not quite, says that in evaluating "this->x" you do a cast to the virtual base class A, which would be an error according to 12.7  class.cdtor paragraph 2 because the base class B constructor hasn't started yet. 5.2.5  expr.ref should be clarified to say that the cast does need to get done.

James Kanze submitted the same issue via comp.std.c++ on 11 July 2003:

Richard Smith: Nonsense. You can use "this" perfectly happily in a constructor, just be careful that (a) you're not using any members that are not fully initialised, and (b) if you're calling virtual functions you know exactly what you're doing.

In practice, and I think in intent, you are right. However, the standard makes some pretty stringent restrictions in 3.8  basic.life. To start with, it says (in paragraph 1):

The lifetime of an object is a runtime property of the object. The lifetime of an object of type T begins when: The lifetime of an object of type T ends when:
(Emphasis added.) Then when we get down to paragraph 5, it says:

Before the lifetime of an object has started but after the storage which the object will occupy has been allocated [which sounds to me like it would include in the constructor, given the text above] or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that refers to the storage location where the object will be or was located may be used but only in limited ways. [...] If the object will be or was of a non-POD class type, the program has undefined behavior if:

[...]

I can't find any exceptions for the this pointer.

Note that calling a non-static function in the base class, or even constructing the base class in initializer list, involves an implicit conversion of this to a pointer to the base class. Thus undefined behavior. I'm sure that this wasn't the intent, but it would seem to be what this paragraph is saying.




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

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.




146. Floating-point zero

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

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

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

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

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




238. Precision and accuracy constraints on floating point

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

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?




438. Possible flaw in wording for multiple accesses to object between sequence points

Section: expr     Status: open     Submitter: Jason Merrill     Date: 29 Oct 2003

Lisa Lippincott mentioned this case to me:

  A[0] = 0;
  A[A[0]] = 1;

This seems to use the old value of A[0] other than to calculate the new value, which is said to be undefined, but it also seems reasonable, since the old value is used in order to select the object to modify, so there's no ordering ambiguity.

Steve Adamczyk: the ordering rule referred to is in 5  expr paragraph 4.

Notes from the March 2004 meeting:

Clark Nelson mentions that the C committee may have done something on this.




118. Calls via pointers to virtual member functions

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

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
    }

Notes from October 2002 meeting:

This was moved back to open for lack of a champion. Martin O'Riordan is not expected to be attending meetings.




466. cv-qualifiers on pseudo-destructor type

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

5.2.4  expr.pseudo paragraph 2 says both:

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




282. Namespace for extended_type_info

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

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.




463. reinterpret_cast<T*>(0)

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

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

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

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

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

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

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

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

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

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

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

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




342. Terminology: "indirection" versus "dereference"

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

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

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




267. Alignment requirement for new-expressions

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

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

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

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

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

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

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

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



242. Interpretation of old-style casts

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

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

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

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

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

  template <int I> struct A {};

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

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

  int main()
  {
    f(1);
  }

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

Notes from the 4/02 meeting:

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

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

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

Notes from the April 2003 meeting:

Here is a better example:

  extern "C" int printf(const char *, ...);
  char f(int);
  int f(...);
  // Approach 1 -- overload resolution in template class
  // No problem
  template <class T> struct conv_int {
    static const bool value = (sizeof(f(T())) == 1);
  };
  // Approach 2 -- overload resolution in type deduction
  // Difficult
  template <int I> struct A {
    static const int value = I;
  };
  template <class T> bool conv_int2(A<sizeof(f(T()))> p) {
    return p.value == 1;
  }
  int main() {
    printf("short: %d\n", conv_int<short>::value);
    printf("int *: %d\n", conv_int<int *>::value);
    printf("short: %d\n", conv_int2<short>());
    printf("int *: %d\n", conv_int2<int *>());
  }

The core working group liked the idea of a restriction that says that expressions inside sizeof in template signature contexts must be otherwise valid as nontype template argument expressions (i.e., integer operations only, limited casts). This of course is subject to whether users can live with that restriction. This topic was brought up in full committee, but there was limited feedback from other groups.

It was also noted that if typeof (whatever it is called) is added, there may be a similar issue there.




378. Wording that says temporaries are declared

Section: 6.6  stmt.jump     Status: open     Submitter: Gennaro Prota     Date: 07 September 2002

Paragraph 6.6  stmt.jump paragraph 2 of the standard says:

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

It refers to objects "that are declared" but the text in parenthesis also mentions temporaries, which cannot be declared. I think that text should be removed.

This is related to issue 276.




467. Jump past initialization of local static variable

Section: 6.7  stmt.dcl     Status: open     Submitter: Kerch Holt     Date: 31 Mar 2004

When jumping past initialization of a local static variable the value of the static becomes indeterminate. Seems like this behavior should be illegal just as it is for local variables with automatic linkage.

Here is an example:

struct X {
    X(int i) : x(i) {}
    int x;
};
int f(int c) {
    if (c)
        goto ly;    // error here for jumping past next stmt.
    static X a = 1;
ly:
    return a.x;  // either 1 or 0 depending on implementation.
}

6.7  stmt.dcl P3 should be changed to:

A program that jumps from a point where a local variable with automatic or static storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has POD type (3.9) and is declared without an initializer (8.5).
This would imply "static X a = 1;" should be flagged as an error. Note that this behavior a may be a "quality of implementation issue" which may be covered in 6.7 P4. Paragraph 4 seems to make the choice of static/dynamic initialization indeterminate. Making this an error and thus determinate seems the correct thing to do since that is what is already required of automatic variables.

Steve Adamczyk: Some version of this may be appropriate, but it's common to have code that is executed only the first time it is reached, and to have an initialization of a static variable inside such a piece of code. In such a case, on executions after the first there is indeed a jump over the declaration, but the static variable is correctly initialized -- it was initialized the first time the routine was called.

  void f() {
    static bool first_time = true;
    if (!first_time) goto after_init;
    static int i = g();
    first_time = false;
  after_init:
    ...
  }



157. Omitted typedef declarator

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

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

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.




407. Named class with associated typedef: two names or one?

Section: 7.1.3  dcl.typedef     Status: open     Submitter: Clark Nelson     Date: 31 March 2003

Here's an example:

  typedef struct S { ... } S;
  void fs(S *x) { ... }

The big question is, to what declaration does the reference to identifier S actually refer? Is it the S that's declared as a typedef name, or the S that's declared as a class name (or in C terms, as a struct tag)? (In either case, there's clearly only one type to which it could refer, since a typedef declaration does not introduce a new type. But the debugger apparently cares about more than just the identity of the type.)

Here's a classical, closely related example:

  struct stat { ... };
  int stat();
  ... stat( ... ) ...

Does the identifier stat refer to the class or the function? Obviously, in C, you can't refer to the struct tag without using the struct keyword, because it is in a different name space, so the reference must be to the function. In C++, the reference is also to the function, but for a completely different reason.

Now in C, typedef names and function names are in the same name space, so the natural extrapolation would be that, in the first example, S refers to the typedef declaration, as it would in C. But C++ is not C. For the purposes of this discussion, there are two important differences between C and C++

The first difference is that, in C++, typedef names and class names are not in separate name spaces. On the other hand, according to section 3.3.7  basic.scope.hiding (Name hiding), paragraph 2:

A class name (9.1) or enumeration name (7.2) can be hidden by the name of an object, function, or enumerator declared in the same scope. If a class or enumeration name and an object, function, or enumerator are declared in the same scope (in any order) with the same name, the class or enumeration name is hidden wherever the object, function, or enumerator name is visible.

Please consider carefully the phrase I have highlighted, and the fact that a typedef name is not the name of an object, function or enumerator. As a result, this example:

  struct stat { ... };
  typedef int stat;

Which would be perfectly legal in C, is disallowed in C++, both implicitly (see the above quote) and explicitly (see section 7.1.3  dcl.typedef (The typedef specifier), paragraph 3):

In a given scope, a typedef specifier shall not be used to redefine the name of any type declared in that scope to refer to a different type. Similarly, in a given scope, a class or enumeration shall not be declared with the same name as a typedef-name that is declared in that scope and refers to a type other than the class or enumeration itself.

From which we can conclude that in C++ typedef names do not hide class names declared in the same scope. If they did, the above example would be legal.

The second difference is that, in C++, a typedef name that refers to a class is a class-name; see 7.1.3  dcl.typedef paragraph 4:

A typedef-name that names a class is a class-name(9.1). If a typedef-name is used following the class-key in an elaborated-type-specifier (7.1.5.3) or in the class-head of a class declaration (9), or is used as the identifier in the declarator for a constructor or destructor declaration (12.1, 12.4), the program is ill-formed.

This implies, for instance, that a typedef-name referring to a class can be used in a nested-name-specifier (i.e. before :: in a qualified name) or following ~ to refer to a destructor. Note that using a typedef-name as a class-name in an elaborated-type-specifier is not allowed. For example:

  struct X { };
  typedef struct X X2;
  X x; // legal
  X2 x2; // legal
  struct X sx; // legal
  struct X2 sx2; // illegal

The final relevant piece of the standard is 7.1.3  dcl.typedef paragraph 2:

In a given scope, a typedef specifier can be used to redefine the name of any type declared in that scope to refer to the type to which it already refers.

This of course is what allows the original example, to which let us now return:

  typedef struct S { ... } S;
  void fs(S *x) { ... }

The question, again is, to which declaration of S does the reference actually refer? In C, it would clearly be to the second, since the first would be accessible only by using the struct keyword. In C++, if typedef names hid class names declared in the same scope, the answer would be the same. But we've already seen that typedef names do not hide class names declared in the same scope.

So to which declaration does the reference to S refer? The answer is that it doesn't matter. The second declaration of S, which appears to be a declaration of a typedef name, is actually a declaration of a class name (7.1.3  dcl.typedef paragraph 4), and as such is simply a redeclaration. Consider the following example:

  typedef int I, I;
  extern int x, x;
  void f(), f();

To which declaration would a reference to I, x or f refer? It doesn't matter, because the second declaration of each is really just a redeclaration of the thing declared in the first declaration. So to save time, effort and complexity, the second declaration of each doesn't add any entry to the compiler's symbol table.




144. Position of friend specifier

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

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?


36. using-declarations in multiple-declaration contexts

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

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.

See also issues 56, 85, and 138..




386. Friend declaration of name brought in by using-declaration

Section: 7.3.3  namespace.udecl     Status: open     Submitter: Herb Sutter     Date: 8 Oct 2002

The following came up recently on comp.lang.c++.moderated (edited for brevity):

  namespace N1 {
    template<typename T> void f( T* x ) {
      // ... other stuff ...
      delete x;
    }
  }

  namespace N2 {
    using N1::f;

    template<> void f<int>( int* ); // A: ill-formed

    class Test {
      ~Test() { }
      friend void f<>( Test* x );   // B: ill-formed?
    };
  }

I strongly suspect, but don't have standardese to prove, that the friend declaration in line B is ill-formed. Can someone show me the text that allows or disallows line B?

Here's my reasoning: Writing "using" to pull the name into namespace N2 merely allows code in N2 to use the name in a call without qualification (per 7.3.3  namespace.udecl). But just as declaring a specialization must be done in the namespace where the template really lives (hence line A is ill-formed), I suspect that declaring a specialization as a friend must likewise be done using the original namespace name, not obliquely through a "using". I see nothing in 7.3.3  namespace.udecl that would permit this use. Is there?

Andrey Tarasevich: 14.5.3  temp.friend paragraph 2 seems to get pretty close: "A friend declaration that is not a template declaration and in which the name of the friend is an unqualified 'template-id' shall refer to a specialization of a function template declared in the nearest enclosing namespace scope".

Herb Sutter: OK, thanks. Then the question in this is the word "declared" -- in particular, we already know we cannot declare a specialization of a template in any other namespace but the original one.

John Spicer: This seems like a simple question, but it isn't.

First of all, I don't think the standard comments on this usage one way or the other.

A similar example using a namespace qualified name is ill-formed based on 8.3  dcl.meaning paragraph 1:

  namespace N1 {
        void f();
  }

  namespace N2 {
        using N1::f;
        class A {
                friend void N2::f();
        };
  }

Core issue 138 deals with this example:

  void foo();
  namespace A{
    using ::foo;
    class X{
      friend void foo();
    };
  }

The proposed resolution (not yet approved) for issue 138 is that the friend declares a new foo that conflicts with the using-declaration and results in an error.

Your example is different than this though because the presence of the explicit argument list means that this is not declaring a new f but is instead using a previously declared f.

One reservation I have about allowing the example is the desire to have consistent rules for all of the "declaration like" uses of template functions. Issue 275 (in DR status) addresses the issue of unqualified names in explicit instantiation and explicit specialization declarations. It requires that such declarations refer to templates from the namespace containing the explicit instantiation or explicit specialization. I believe this rule is necessary for those directives but is not really required for friend declarations -- but there is the consistency issue.

Notes from April 2003 meeting:

This is related to issue 138. John Spicer is supposed to update his paper on this topic. This is a new case not covered in that paper. We agreed that the B line should be allowed.




461. Make asm conditionally-supported

Section: 7.4  dcl.asm     Status: open     Submitter: Clark Nelson     Date: 24 May 2004

Now that the concept of "conditionally-supported" is available (see N1564), perhaps asm should not be required of every implementation.




332. cv-qualified void parameter types

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

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

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




361. Forward reference to default argument

Section: 8.3.6  dcl.fct.default     Status: open     Submitter: Steve Clamage     Date: 17 June 2002

Is this program well-formed?

  struct S {
    static int f2(int = f1()); // OK?
    static int f1(int = 2);
  };
  int main()
  {
    return S::f2();
  }

A class member function can in general refer to class members that are declared lexically later. But what about referring to default arguments of member functions that haven't yet been declared?

It seems to me that if f2 can refer to f1, it can also refer to the default argument of f1, but at least one compiler disagrees.




155. Brace initializer for scalar

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

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.




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

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




430. Ordering of expression evaluation in initializer list

Section: 8.5.1  dcl.init.aggr     Status: open     Submitter: Nathan Sidwell     Date: 23 July 2003

A recent GCC bug report ( http://gcc.gnu.org/bugzilla/show_bug.cgi?id=11633) asks about the validity of

  int count = 23;
  int foo[] = { count++, count++, count++ };
is this undefined or unspecified or something else? I can find nothing in 8.5.1  dcl.init.aggr that indicates whether the components of an initializer-list are evaluated in order or not, or whether they have sequence points between them.

6.7.8/23 of the C99 std has this to say

The order in which any side effects occur among the initialization list expressions is unspecified.
I think similar wording is needed in 8.5.1  dcl.init.aggr

Steve Adamczyk: I believe the standard is clear that each initializer expression in the above is a full-expression (1.9  intro.execution/12-13; see also issue 392) and therefore there is a sequence point after each expression (1.9  intro.execution/16). I agree that the standard does not seem to dictate the order in which the expressions are evaluated, and perhaps it should. Does anyone know of a compiler that would not evaluate the expressions left to right?




327. Use of "structure" without definition

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

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.



355. Global-scope :: in elaborated-type-specifier

Section: class     Status: open     Submitter: Clark Nelson     Date: 16 May 2002

In looking at a large handful of core issues related to elaborated-type-specifiers and the naming of classes in general, I discovered an odd fact. It turns out that there is exactly one place in the grammar where nested-name-specifier is not immediately preceded by "::opt": in class-head, which is used only for class definitions. So technically, this example is ill-formed, and should evoke a syntax error:

  struct A;
  struct ::A { };

However, all of EDG, GCC and Microsoft's compiler accept it without a qualm. In fact, I couldn't get any of them to even warn about it.

Suggested resolution:

It would simplify the grammar, and apparently better reflect existing practice, to factor the global-scope operator into the rule for nested-name-specifier.




452. Wording nit on description of this

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

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

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

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

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

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

Notes from the March 2004 meeting:

This will be resolved when issue 397 is resolved.




57. Empty unions

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

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

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




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

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?




380. Definition of "ambiguous base class" missing

Section: 10.2  class.member.lookup     Status: open     Submitter: Jason Merrill     Date: 22 Oct 2002

The term "ambiguous base class" doesn't seem to be actually defined anywhere. 10.2  class.member.lookup paragraph 7 seems like the place to do it.




230. Calls to pure virtual functions

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

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.




360. Using-declaration that reduces access

Section: 11.2  class.access.base     Status: open     Submitter: Steve Clamage     Date: 4 June 2002

I have heard a claim that the following code is valid, but I don't see why.

  struct A {
    int foo ();
  };

  struct B: A {
  private:
    using A::foo;
  };

  int main ()
  {
    return B ().foo ();
  }

It seems to me that the using declaration in B should hide the public foo in A. Then the call to B::foo should fail because B::foo is not accessible in main.

Am I missing something?

Steve Adamczyk: This is similar to the last example in 11.2  class.access.base. In prose, the rule is that if you have access to cast to a base class and you have access to the member in the base class, you are given access in the derived class. In this case, A is a public base class of B and foo is public in A, so you can access foo through a B object. The actual permission for this is in the fourth bullet in 11.2  class.access.base paragraph 4.

The wording changes for issue 9 make this clearer, but I believe even without them this example could be discerned to be valid.

See my paper J16/96-0034, WG21/N0852 on this topic.

Steve Clamage: But a using-declaration is a declaration (7.3.3  namespace.udecl). Compare with

  struct B : A {
  private:
    int foo();
  };

In this case, the call would certainly be invalid, even though your argument about casting B to an A would make it OK. Your argument basically says that an access adjustment to make something less accessible has no effect. That also doesn't sound right.

Steve Adamczyk: I agree that is strange. I do think that's what 11.2  class.access.base says, but perhaps that's not what we want it to say.




199. Order of destruction of temporaries

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

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

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?




462. Lifetime of temporaries bound to comma expressions

Section: 12.2  class.temporary     Status: open     Submitter: Steve Adamczyk     Date: ( April 2004

Split off from issue 86.

Should binding a reference to the result of a "," operation whose second operand is a temporary extend the lifetime of the temporary?

  const SFileName &C = ( f(), SFileName("abc") );

Notes from the March 2004 meeting:

We think the temporary should be extended.




464. Wording nit on lifetime of temporaries to which references are bound

Section: 12.2  class.temporary     Status: open     Submitter: Allan Odgaard     Date: 21 Feb 2004

Section 12.2  class.temporary paragraph 5 ends with this "rule":

[...] if obj2 is an object with static or automatic storage duration created after the temporary is created, the temporary shall be destroyed after obj2 is destroyed.

For the temporary to be destoyed after obj2 is destroyed, when obj2 has static storage, I would say that the reference to the temporary should also have static storage, but that is IMHO not clear from the paragraph.

Example:

    void f ()
    {
       const T1& ref = T1();
       static T2 obj2;
       ...
    }

Here the temporary would be destoyed before obj2, contrary to the rule above.

Steve Adamczyk: I agree there's a minor issue here. I think the clause quoted above meant for obj1 and obj2 to have the same storage duration. Replacing "obj2 is an object with static or automatic storage duration" by "obj2 is an object with the same storage duration as obj1" would, I believe, fix the problem.




395. Conversion operator template syntax

Section: 12.3.2  class.conv.fct     Status: open     Submitter: Daveed Vandevoorde     Date: 18 Dec 2002

A posting in comp.lang.c++.moderated prompted me to try the following code:

  struct S {
    template<typename T, int N> (&operator T())[N];
  };

The goal is to have a (deducible) conversion operator template to a reference-to-array type.

This is accepted by several front ends (g++, EDG), but I now believe that 12.3.2  class.conv.fct paragraph 1 actually prohibits this. The issue here is that we do in fact specify (part of) a return type.

OTOH, I think it is legitimate to expect that this is expressible in the language (preferably not using the syntax above ;-). Maybe we should extend the syntax to allow the following alternative?

  struct S {
    template<typename T, int N> operator (T(&)[N])();
  };

Eric Niebler: If the syntax is extended to support this, similar constructs should also be considered. For instance, I can't for the life of me figure out how to write a conversion member function template to return a member function pointer. It could be useful if you were defining a null_t type. This is probably due to my own ignorance, but getting the syntax right is tricky.

Eg.

  struct null_t {
    // null object pointer. works.
    template<typename T> operator T*() const { return 0; }
    // null member pointer. works.
    template<typename T,typename U> operator T U::*() const { return 0; }
    // null member fn ptr.  doesn't work (with Comeau online).  my error?
    template<typename T,typename U> operator T (U::*)()() const { return 0; }
  };

Martin Sebor: Intriguing question. I have no idea how to do it in a single declaration but splitting it up into two steps seems to work:

  struct null_t {
    template <class T, class U>
    struct ptr_mem_fun_t {
      typedef T (U::*type)();
    };

    template <class T, class U>
    operator typename ptr_mem_fun_t<T, U>::type () const {
      return 0;
    }
  };

Note: In the April 2003 meeting, the core working group noticed that the above doesn't actually work.




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

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

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

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

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

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

Christopher Lester, on comp.std.c++, March 19, 2004: If any of you reading this posting happen to be members of the above working group, I would like to encourage you to review the suggestion contained therein, as it seems to me that the final tenor of the submission is both (a) correct (the silence of the standard DOES mandate the omission) and (b) describes what most users would intuitively expect and desire from the C++ language as well.

The suggestion is to make it clearer that constructors for abstract base classes should not be required to provide initialisers for any virtual base classes they contain (as only the most-derived class has the job of initialising virtual base classes, and an abstract base class cannot possibly be a most-derived class).

For example:

struct A {
  A(const int i, const int j) {};
};

struct B1 : virtual public A {
  virtual void moo()=0;
  B1() {};   // (1) Look! not "B1() : A(5,6) {};"
};

struct B2 : virtual public A {
  virtual void cow()=0;
  B2() {};   // (2) Look! not "B2() : A(7,8) {};"
};

struct C : public B1, public B2 {
  C() : A(2,3) {};
  void moo() {};
  void cow() {};
};

int main() {
  C c;
  return 0;
};

I believe that, by not expressly forbidding it, the standard does (and should!) allow the above code. However, as the standard doesn't expressly allow it either (have I missed something?) there appears to be room for misunderstanding. For example, g++ version 3.2.3 (and maybe other versions as well) rejects the above code with messages like:

	In constructor `B1::B1()':
	no matching function for call to `A::A()'
	candidates are: A::A(const A&)
         	        A::A(int, int)

Fair enough, the standard is perhaps not clear enough. But it seems to be a shame that although this issue was first raised in 2000, we are still living with it today.

Note that we can work-around, and persuade g++ to compile the above by either (a) providing a default constructor A() for A, or (b) supplying default values for i and j in A(i,j), or (c) replace the construtors B1() and B2() with the forms shown in the two comments in the above example.

All three of these workarounds may at times be appropriate, but equally there are other times when all of these workarounds are particularly bad. (a) and (b) may be very bad if you are trying to enforce string contracts among objects, while (c) is just barmy (I mean why did I have to invent random numbers like 5, 6, 7 and 8 just to get the code to compile?).

So to to round up, then, my plea to the working group is: "at the very least, please make the standard clearer on this issue, but preferrably make the decision to expressly allow code that looks something like the above"




111. Copy constructors and cv-qualifiers

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

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.




418. Imperfect wording on error on multiple default arguments on a called function

Section: 13.3.3  over.match.best     Status: open     Submitter: Chris Bowler     Date: 27 May 2003

According to 13.3.3  over.match.best paragraph 4, the following program appears to be ill-formed:

  void f(int, int=0);
  void f(int=0, int);

  void g() {
    f();
  }

Though I do not expect this is the intent of this paragraph in the standard.

13.3.3  over.match.best paragraph 4:

If the best viable function resolves to a function for which multiple declarations were found, and if at least two of these declarations or the declarations they refer to in the case of using-declarations specify a default argument that made the function viable, the program is ill-formed. [Example:
namespace A {
  extern "C" void f(int = 5);
}
namespace B {
  extern "C" void f(int = 5);
}
using A::f;
using B::f;
void use() {
f(3); //OK, default argument was not used for viability
f(); //Error: found default argument twice
}
end example]



455. Partial ordering and non-deduced arguments

Section: 13.3.3  over.match.best     Status: open     Submitter: Rani Sharoni     Date: 19 Jan 2004

It's not clear how overloading and partial ordering handle non-deduced pairs of corresponding arguments. For example:

template<typename T>
struct A { typedef char* type; };

template<typename T> char* f1(T, typename A<T>::type);  // #1
template<typename T> long* f1(T*, typename A<T>::type*); // #2

long* p1 = f1(p1, 0); // #3

I thought that #3 is ambiguous but different compilers disagree on that. Comeau C/C++ 4.3.3 (EDG 3.0.3) accepted the code, GCC 3.2 and BCC 5.5 selected #1 while VC7.1+ yields ambiguity.

I intuitively thought that the second pair should prevent overloading from triggering partial ordering since both arguments are non-deduced and has different types - (char*, char**). Just like in the following:

template<typename T> char* f2(T, char*);   // #3
template<typename T> long* f2(T*, char**); // #4

long* p2 = f2(p2, 0); // #5

In this case all the compilers I checked found #5 to be ambiguous. The standard and DR 214 is not clear about how partial ordering handle such cases.

I think that overloading should not trigger partial ordering (in step 13.3.3  over.match.best/1/5) if some candidates have non-deduced pairs with different (specialized) types. In this stage the arguments are already adjusted so no need to mention it (i.e. array to pointer). In case that one of the arguments is non-deuced then partial ordering should only consider the type from the specialization:

template<typename T> struct B { typedef T type; };

template<typename T> char* f3(T, T);                   // #7
template<typename T> long* f3(T, typename B<T>::type); // #8

char* p3 = f3(p3, p3); // #9

According to my reasoning #9 should yield ambiguity since second pair is (T, long*). The second type (i.e. long*) was taken from the specialization candidate of #8. EDG and GCC accepted the code. VC and BCC found an ambiguity.

John Spicer: There may (or may not) be an issue concerning whether nondeduced contexts are handled properly in the partial ordering rules. In general, I think nondeduced contexts work, but we should walk through some examples to make sure we think they work properly.

Rani's description of the problem suggests that he believes that partial ordering is done on the specialized types. This is not correct. Partial ordering is done on the templates themselves, independent of type information from the specialization.




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

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

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.




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




468. Allow ::template outside of templates

Section: 14.2  temp.names     Status: open     Submitter: John Spicer     Date: 9 Apr 2004

For the same reasons that issue 382 proposes for relaxation of the requirements on typename, it would make sense to allow the ::template disambiguator outside of templates.

See also issues 11, 30, 96, and 109.




440. Allow implicit pointer-to-member conversion on nontype template argument

Section: 14.3  temp.arg     Status: open     Submitter: David Abrahams     Date: 13 Nov 2003

None of my compilers accept this, which surprised me a little. Is the base-to-derived member function conversion considered to be a runtime-only thing?

  template <class D>
  struct B
  {
      template <class X> void f(X) {}
      template <class X, void (D::*)(X) = &B<D>::f<X> >
      struct row {};
  };
  struct D : B<D>
  {
      void g(int);
      row<int,&D::g> r1;
      row<char*> r2;
  };

John Spicer: This is not among the permitted conversions listed in 14.3.

I'm not sure there is a terribly good reason for that. Some of the template argument rules for external entities were made conservatively because of concerns about issues of mangling template argument names.

David Abrahams: I'd really like to see that restriction loosened. It is a serious inconvenience because there appears to be no way to supply a usable default in this case. Zero would be an OK default if I could use the function pointer's equality to zero as a compile-time switch to choose an empty function implementation:

  template <bool x> struct tag {};

  template <class D>
  struct B
  {
      template <class X> void f(X) {}

      template <class X, void (D::*pmf)(X) = 0 >
      struct row {
          void h() { h(tag<(pmf == 0)>(), pmf); }
          void h(tag<1>, ...) {}
          void h(tag<0>, void (D::*q)(X)) { /*something*/}
      };
  };

  struct D : B<D>
  {
      void g(int);
      row<int,&D::g> r1;
      row<char*> r2;
  };

But there appears to be no way to get that effect either. The result is that you end up doing something like:

      template <class X, void (D::*pmf)(X) = 0 >
      struct row {
          void h() { if (pmf) /*something*/ }
      };

which invariably makes compilers warn that you're switching on a constant expression.




354. Null as nontype template argument

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

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.




229. Partial specialization of function templates

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

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

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

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?

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.




402. More on partial ordering of function templates

Section: 14.5.5.2  temp.func.order     Status: open     Submitter: Nathan Sidwell     Date: 7 Apr 2003

This was split off from issue 214 at the April 2003 meeting.

Nathan Sidwell: John Spicer's proposed resolution does not make the following well-formed.

  template <typename T> int Foo (T const *) {return 1;} //#1
  template <unsigned I> int Foo (char const (&)[I]) {return 2;} //#2

  int main ()
  {
    return Foo ("a") != 2;
  }

Both #1 and #2 can deduce the "a" argument, #1 deduces T as char and #2 deduces I as 2. However, neither is more specialized because the proposed rules do not have any array to pointer decay.

#1 is only deduceable because of the rules in 14.8.2.1  temp.deduct.call paragraph 2 that decay array and function type arguments when the template parameter is not a reference. Given that such behaviour happens in deduction, I believe there should be equivalent behaviour during partial ordering. #2 should be resolved as more specialized as #1. The following alteration to the proposed resolution of DR214 will do that.

Insert before,

the following

For the example above, this change results in deducing 'T const *' against 'char const *' in one direction (which succeeds), and 'char [I]' against 'T const *' in the other (which fails).

John Spicer: I don't consider this a shortcoming of my proposed wording, as I don't think this is part of the current rules. In other words, the resolution of 214 might make it clearer how this case is handled (i.e., clearer that it is not allowed), but I don't believe it represents a change in the language.

I'm not necessarily opposed to such a change, but I think it should be reviewed by the core group as a related change and not a defect in the proposed resolution to 214.

Notes from the October 2003 meeting:

There was some sentiment that it would be desirable to have this case ordered, but we don't think it's worth spending the time to work on it now. If we look at some larger partial ordering changes at some point, we will consider this again.




186. Name hiding and template template-parameters

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

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.


459. Hiding of template parameters by base class members

Section: 14.6.1  temp.local     Status: open     Submitter: Daveed Vandevoorde     Date: 2 Feb 2004

Currently, member of nondependent base classes hide references to template parameters in the definition of a derived class template.

Consider the following example:

   class B {
      typedef void *It;    // (1)
      // ...
    };

    class M: B {};

    template<typename> X {};

    template<typename It> struct S   // (2)
        : M, X<It> {   // (3)
      S(It, It);   // (4)
      // ...
    };

As the C++ language currently stands, the name "It" in line (3) refers to the template parameter declared in line (2), but the name "It" in line (4) refers to the typedef in the private base class (declared in line (1)).

This situation is both unintuitive and a hindrance to sound software engineering. (See also the Usenet discussion at http://tinyurl.com/32q8d .) Among other things, it implies that the private section of a base class may change the meaning of the derived class, and (unlike other cases where such things happen) there is no way for the writer of the derived class to defend the code against such intrusion (e.g., by using a qualified name).

Changing this can break code that is valid today. However, such code would have to:

  1. name a template parameter and not use it after the opening brace, and
  2. use that same name to access a base-class name within the braces.
I personally have no qualms breaking such a program.

It has been suggested to make situations like these ill-formed. That solution is unattractive however because it still leaves the writer of a derived class template without defense against accidental name conflicts with base members. (Although at least the problem would be guaranteed to be caught at compile time.) Instead, since just about everyone's intuition agrees, I would like to see the rules changed to make class template parameters hide members of the same name in a base class.

See also issue 458.

Notes from the March 2004 meeting:

We have some sympathy for a change, but the current rules fall straightforwardly out of the lookup rules, so they're not "wrong". Making private members invisible also would solve this problem. We'd be willing to look at a paper proposing that.




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

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.




447. Is offsetof type-dependent?

Section: 14.6.2.3  temp.dep.constexpr     Status: open     Submitter: Mark Mitchell     Date: 7 Jan 2004

As far as I can tell, the standard doesn't say whether "offsetof(...)" is type-dependent. In the abstract, it shouldn't be -- an "offsetof" expression is always of type "size_t". But the standard doesn't say to what the definition of the macro is, so I don't think one can deduce that it will always be considered non-dependent by a conforming compiler.

John Spicer: (1) I agree that you can't know if offsetof is dependent because you don't know what it expands to. (2) In principle, offsetof should be like sizeof -- it is value-dependent if its argument is type-dependent.

Mark Mitchell: I think we should say that: (a) offsetof is not type-dependent, and (b) offsetof is value dependent iff the first argument is type-dependent

Everyone is using slightly different builtins to implement this functionality, and I don't think that there's any guarantee that they're all behaving the same here.

Notes from the March 2004 meeting:

Note that any such requirement would be in the library section, not core.




237. Explicit instantiation and base class members

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

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

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




182. Access checking on explicit specializations

Section: 14.7.3  temp.expl.spec     Status: open     Submitter: John Spicer     Date: 8 Nov 1999

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

Notes from October 2002 meeting:

We reconsidered this and decided that the difference between the two cases (explicit specialization and explicit instantiation) is appropriate. The access rules are sometimes bent when necessary to allow naming something, as in an explicit instantiation, but explicit specialization requires not only naming the entity but also providing a definition somewhere. Closed as Not-a-defect.




264. Unusable template constructors and conversion functions

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

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

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

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.




469. Const template specializations and reference arguments

Section: 14.8.2.5  temp.deduct.type     Status: open     Submitter: Matt Austern     Date: 19 Mar 2004

Consider the following:

	template <typename T> struct X {};  // #1
	template <typename T> struct X<const T>; //#2
	template struct X<int&>; //#3

Which specialization are we instantiating in #3? The "obvious" answer is #1, because "int&" doesn't have a top level cv-qualification. However, there's also an argument saying that we should actually be instantiating #2. The argument is: int& can be taken as a match for either one (top-level cv-qualifiers are ignored on references, so they're equally good), and given two equally good matches we must choose the more specialized one.

Is this a valid argument? If so, is this behavior intentional?

John Spicer: I don't see the rationale for any choice other than #1. While it is true that if you attempt to apply const to a reference type it just gets dropped, that is very different from saying that a reference type is acceptable where a const-qualified type is required.

If the type matched both templates, the const one would be more specialized, but "int&" does not match "const T".

Nathan Sidwell: thanks for bringing this one to the committee. However this is resolved, I'd like clarification on the followup questions in the gcc bug report regarding deduced and non-deduced contexts and function templates. Here're those questions for y'all,

template <typename T> void Foo (T *); // #1
template <typename T> void Foo (T const *); // #2
void Baz ();
Foo (Baz); // which?

template <typename T> T const *Foo (T *); // #1
void Baz ();
Foo (Baz); // well formed?

template <typename T> void Foo (T *, T const * = 0);
void Baz ();
Foo (Baz); // well formed?

BTW, I didn't go trying to break things, I implemented the cv-qualifier ignoring requirements and fell over this. I could find nothing in the standard saying 'don't do this ignoring during deduction'.




388. Catching base*& from a throw of derived*

Section: 15.3  except.handle     Status: open     Submitter: John Spicer     Date: 28 Oct 2002

I have a question about exception handling with respect to derived to base conversions of pointers caught by reference.

What should the result of this program be?

  struct S             {};
  struct SS : public S {};

  int main()
  {
  	SS ss;
  	int result = 0;
  	try
  	{
  		throw &ss; // throw object has type SS*
  		           // (pointer to derived class)
  	}
  	catch (S*& rs) // (reference to pointer to base class)
  	{
  		result = 1;
  	}
  	catch (...)
  	{
  		result = 2;
  	}
  	return result;
  }

The wording of 15.3  except.handle paragraph 3 would seem to say that the catch of S*& does not match and so the catch ... would be taken.

All of the compilers I tried (EDG, g++, Sun, and Microsoft) used the catch of S*& though.

What do we think is the desired behavior for such cases?

My initial reaction is that this is a bug in all of these compilers, but the fact that they all do the same thing gives me pause.

On a related front, if the handler changes the parameter using the reference, what is caught by a subsequent handler?

  extern "C" int printf(const char *, ...);
  struct S             {};
  struct SS : public S {};
  SS ss;

  int f()
  {
  	try
  	{
  		throw &ss;
  	}
  	catch (S*& rs) // (reference to pointer to base class)
  	{
  		rs = 0;
  		throw;
  	}
  	catch (...)
  	{
  	}
  	return 0;
  }

  int main()
  {
  	try { f(); }
  	catch (S*& rs) {
  		printf("rs=%p, &ss=%p\n", rs, &ss);
  	}
  }

EDG, g++, and Sun all catch the original (unmodified) value. Microsoft catches the modified value. In some sense the EDG/g++/Sun behavior makes sense because the later catch could catch the derived class instead of the base class, which would be difficult to do if you let the catch clause update the value to be used by a subsequent catch.

But on this non-pointer case, all of the compilers later catch the modified value:

  extern "C" int printf(const char *, ...);
  int f()
  {
  	try
  	{
  		throw 1;
  	}
  	catch (int& i)
  	{
  		i = 0;
  		throw;
  	}
  	catch (...)
  	{
  	}
  	return 0;
  }

  int main()
  {
  	try { f(); }
  	catch (int& i) {
  		printf("i=%p\n", i);
  	}
  }

To summarize:

  1. Should "base*const&" be able to catch a "derived*"? The current standard seems to say "no" but parallels to how calls work, and existing practice, suggest that the answer should be "yes".
  2. Should "base*&" be able to catch a "derived*". Again, the standard seems seems to say "no". Parallels to how calls work still suggest "no", but existing practice suggests "yes".
  3. If either of the above is "yes", what happens if you modify the pointer referred to by the reference. This requires a cast to remove const for case #2.
  4. On a related front, if you catch "derived*&" when a "derived*" is thrown, what happens if you modify the pointer referred to by the reference? EDG/g++/Sun still don't modify the underlying value that would be caught by a rethrow in this case. This case seems like it should be the same as the "int&" example above, but is not on the three compilers mentioned.



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

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

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.




219. Cannot defend against destructors that throw exceptions

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

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

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

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

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.