Document number:  J16/06-0197 = WG21 N2127
Date:  2006-11-05
Project:  Programming Language C++
Reference:  ISO/IEC IS 14882:2003
Reply to:  William M. Miller
 Edison Design Group, Inc.
 wmm@edg.com


C++ Standard Core Language Closed Issues, Revision 44


This document contains the C++ core language issues for which the Committee (J16 + WG21) has decided that no action is required, that is, issues with status "NAD" ("Not A Defect"), "dup" (duplicate), and "extension."

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

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


Issues with "NAD" Status


449. Consistency in use of hyphen with names of "non" entities

Section: 1.3  intro.defs     Status: NAD     Submitter: Daveed Vandevoorde     Date: 14 Jan 2004

The standard is inconsistent in its use of a hyphen on the following: nontype vs. non-type, non-dependent vs. nondependent, non-deduced vs. nondeduced, and non-template vs. nontemplate. We should pick a preferred form.

Notes from the March 2004 meeting:

If this isn't a purely editorial issue, nothing is. We're referring this to the editor. We prefer the hyphenated forms.




50. Converting pointer to incomplete type to same type

Section: 3.2  basic.def.odr     Status: NAD     Submitter: Steve Adamczyk     Date: 13 Oct 1998

In 3.2  basic.def.odr paragraph 4 bullet 4, it's presumably the case that a conversion to T* requires that T be complete only if the conversion is from a different type. One could argue that there is no conversion (and therefore the text is accurate as it stands) if a cast does not change the type of the expression, but it's probably better to be more explicit here.

On the other hand, this text is non-normative (it's in a note).

Rationale (04/99): The relevant normative text makes this clear. Implicit conversion and static_cast are defined (in 4  conv and 5.2.9  expr.static.cast , respectively) as equivalent to declaration with initialization, which permits pointers to incomplete types, and dynamic_cast (5.2.7  expr.dynamic.cast ) explicitly prohibits pointers to incomplete types.




42. Redefining names from base classes

Section: 3.3.6  basic.scope.class     Status: NAD     Submitter: Steve Clamage     Date: 15 Sep 1998

Consider this code:

    struct Base {
        enum { a, b, c, next };
    };

    struct Derived : public Base {
        enum { d = Base::next, e, f, next };
    };

The idea is that the enumerator "next" in each class is the next available value for enumerators in further derived classes.

If we had written

    enum { d = next, e, f, next };

I think we would run afoul of 3.3.6  basic.scope.class :
A name N used in a class S shall refer to the same declaration in its context and when re-evaluated in the completed scope of S. No diagnostic is required for a violation of this rule.
But in the original code, we don't have an unqualified "next" that refers to anything but the current scope. I think the intent was to allow the code, but I don't find the wording clear on on that point.

Is there another section that makes it clear whether the original code is valid? Or am I being obtuse? Or should the quoted section say "An unqualified name N used in a class ..."?

Rationale (04/99): It is sufficiently clear that "name" includes qualified names and hence the usual lookup rules make this legal.




231. Visibility of names after using-directives

Section: 3.4.1  basic.lookup.unqual     Status: NAD     Submitter: Jörg Barfurth     Date: 31 May 2000

The wording of 3.4.1  basic.lookup.unqual paragraph 2 is misleading. It says:

The declarations from the namespace nominated by a using-directive become visible in a namespace enclosing the using-directive; see 7.3.4  namespace.udir.

According to 7.3.4  namespace.udir paragraph 1, that namespace is

the nearest enclosing namespace which contains both the using-directive and the nominated namespace.

That would seem to imply the following:

    namespace outer {
        namespace inner {
            int i;
        }
        void f() {
            using namespace inner;
        }
        int j = i;   // inner::i is "visible" in namespace outer
    }

Suggested resolution: Change the first sentence of 3.4.1  basic.lookup.unqual paragraph 2 to read:

The declarations from the namespace nominated by a using-directive become visible in the scope in which the using-directive appears after the using-directive.

Notes from the 4/02 meeting:

After a lot of discussion of possible wording changes, we decided the wording should be left alone. 3.4.1  basic.lookup.unqual paragraph 2 is not intended to be a full specification; that's in 7.3.4  namespace.udir paragraph 1. See also 3.3.5  basic.scope.namespace paragraph 1.




91. A union's associated types should include the union itself

Section: 3.4.2  basic.lookup.argdep     Status: NAD     Submitter: John Spicer     Date: 2 Feb 1999

When a union is used in argument-dependent lookup, the union's type is not an associated class type. Consequently, code like this will fail to work.

    union U {
        friend void f(U);
    };
    
    int main() {
        U u;
        f(u);  // error: no matching f — U is not an associated class
    }
Is this an error in the description of unions in argument-dependent lookup?

Also, this section is written as if unions were distinct from classes. So adding unions to the "associated classes" requires either rewriting the section so that "associated classes" can include unions, or changing the term to be more inclusive, e.g. "associated classes and unions" or "associated types".

Jason Merrill: Perhaps in both cases, the standard text was intended to only apply to anonymous unions.

Liam Fitzpatrick: One cannot create expressions of an anonymous union type.

Rationale (04/99): Unions are class types, so the example is well-formed. Although the wording here could be improved, it does not rise to the level of a defect in the Standard.




384. Argument-dependent lookup and operator functions

Section: 3.4.2  basic.lookup.argdep     Status: NAD     Submitter: Herb Sutter     Date: 18 Sept 2002

I believe the following code example should unambiguously call the member operator+. Am I right?

  //--- some library header ---
  //
  namespace N1 {
    template<class T> struct Base { };

    template<class T> struct X {
      struct Y : public Base<T> {     // here's a member operator+
        Y operator+( int _Off ) const { return Y(); }
      };

      Y f( unsigned i ) { return Y() + i; } // the "+" in question
    };
  }

  //--- some user code ---
  //
  namespace N2 {
    struct Z { };

    template<typename T>              // here's another operator+
    int* operator+( T , unsigned ) { static int i ; return &i ; }
  }

  int main() {
    N1::X< N2::Z > v;
    v.f( 0 );
  }

My expectation is that 3.4.2  basic.lookup.argdep would govern, specifically:

If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated namespaces and classes are not considered.
So I think the member should hide the otherwise-better-matching one in the associated namespace. Here's what compilers do:

Agree with me and call the member operator+: Borland 5.5, Comeau 4.3.0.1, EDG 3.0.1, Metrowerks 8.0, MSVC 6.0

Disagree with me and try to call N2::operator+: gcc 2.95.3, 3.1.1, and 3.2; MSVC 7.0

Simple so far, but someone tells me that 13.3.1.2  over.match.oper muddies the waters. There, paragraph 10 summarizes that subclause:

[Note: the lookup rules for operators in expressions are different than the lookup rules for operator function names in a function call, ...
In particular, consider the above call to "Y() + unsigned" and please help me step through 13.3.1.2  over.match.oper paragraph 3:
... for a binary operator @ with a left operand of a type whose cv-unqualified version is T1 and a right operand of a type whose cv-unqualified version is T2,
OK so far, here @ is +, and T1 is N1::X::Y.
three sets of candidate functions, designated member candidates, non-member candidates and built-in candidates, are constructed as follows:
[and later are union'd together to get the candidate list]
If T1 is a class type, the set of member candidates is the result of the qualified lookup of T1::operator@ (over.call.func); otherwise, the set of member candidates is empty.
So there is one member candidate, N1::X::Y::operator+.
The set of non-member candidates is the result of the unqualified lookup of operator@ in the context of the expression according to the usual rules for name lookup in unqualified function calls (basic.lookup.argdep) except that all member functions are ignored.

*** This is the question: What does that last phrase mean? Does it mean:

a) first apply the usual ADL rules to generate a candidate list, then ignore any member functions in that list (this is what I believe and hope it means, and in particular it means that the presence of a member will suppress names that ADL would otherwise find in the associated namespaces); or

b) something else?

In short, does N2::operator+ make it into the candidate list? I think it shouldn't. Am I right?

John Spicer: I believe that the answer is sort-of "a" above. More specifically, the unqualified lookup consists of a "normal" unqualified lookup and ADL. ADL always deals with only namespace members, so the "ignore members functions" part must affect the normal lookup, which should ignore class members when searching for an operator.

I suspect that the difference between compilers may have to do with details of argument-dependent lookup. In the example given, the argument types are "N1::X<N2::Z>::Y" and "unsigned int". In order for N2::operator+ to be a candidate, N2 must be an associated namespace.

N1::X<N2::Z>::Y is a class type, so 3.4.2  basic.lookup.argdep says that its associated classes are its direct and indirect base classes, and its namespaces are the namespaces of those classes. So, its associated namespace is just N1.

3.4.2  basic.lookup.argdep also says:

If T is a template-id, its associated namespaces and classes are the namespace in which the template is defined; for member templates, the member template's class; the namespaces and classes associated with the types of the template arguments provided for template type parameters (excluding template template parameters); the namespaces in which any template template arguments are defined; and the classes in which any member templates used as template template arguments are defined. [Note: non-type template arguments do not contribute to the set of associated namespaces. ]
First of all, there is a problem with the term "is a template-id". template-id is a syntactic constuct and you can't really talk about a type being a template-id. Presumably, this is intended to mean "If T is the type of a class template specialization ...". But does this apply to N1::X<N2::Z>::Y? Y is a class nested within a class template specialization. In addition, its base class is a class template specialization.

I think this raises two issues:

  1. Should the enclosing class(es) of a class, and their template argument lists (if any) contribute to the associated classes/namespaces for ADL?
  2. Should the template argument lists of base classes contribute to the associated classes/namespaces for ADL?

Notes from the April 2003 meeting:

The ADL rules in the standard sort of look at if they are fully recursive, but in fact they are not; in some cases, enclosing classes and base classes are considered, and in others they are not. Microsoft and g++ did fully-recursive implementations, and EDG and IBM did it the other way. Jon Caves reports that Microsoft saw no noticeable difference (e.g., no complaints from customers internal or external) when they made this change, so we believe that even if the rules are imperfect the way they are in the standard, they are clear and the imperfections are small enough that programmers will not notice them. Given that, it seemed prudent to make no changes and just close this issue.

The template-id issue is spun off as issue 403.




132. Local types and linkage

Section: 3.5  basic.link     Status: NAD     Submitter: Daveed Vandevoorde     Date: 25 June 1999

3.5  basic.link paragraph 8 says,

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.
This wording does not, but should, prohibit use of an unnamed local type in the declaration of an entity with linkage. For example,
    void f() {
        extern struct { } x;  // currently allowed
    }

Proposed resolution: Change the text in 3.5  basic.link paragraph 8 from:

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.
to:
A name with no linkage (notably, the name of a class or enumeration declared in a local scope (3.3.2  basic.scope.local)) or an unnamed type shall not be used to declare an entity with linkage.
In section 3.5  basic.link paragraph 8, add to the example, before the closing brace of function f:
extern struct {} x;    // ill-formed

Rationale (10/00): The proposed change would have introduced an incompatibility with the C language. For example, the global declaration

    static enum { A, B, C } abc;

represents an idiom that is used in C but would be prohibited under this resolution.




269. Order of initialization of multiply-defined static data members of class templates

Section: 3.6.2  basic.start.init     Status: NAD     Submitter: Andrei Iltchenko     Date: 8 Feb 2001

According to 3.2  basic.def.odr paragraph 5, it is possible for a static data member of a class template to be defined more than once in a given program provided that each such definition occurs in a different translation unit and the ODR is met.

Now consider the following example:

src1.cpp:

    #include <iostream>

    int  initializer()
    {
       static int   counter;
       return  counter++;
    }

    int   g_data1 = initializer();

    template<class T>
    struct  exp  {
       static int   m_data;
    };
    template<class T>
    int  exp<T>::m_data = initializer();

    int   g_data2 = initializer();
    extern int   g_data3;

    int  main()
    {
       std::cout << exp<char>::m_data << ", " << g_data1 << ", "
	  << g_data2 << ", " << g_data3 << std::endl;
       return  0;
    }

src2.cpp:

    extern int  initializer();
    int  g_data3 = initializer();

    template<class T>
    struct  exp  {
       static int   m_data;
    };
    template<class T>
    int  exp<T>::m_data = initializer();

    void  func()
    {
      exp<char>::m_data++;
    }

The specialization exp<char>::m_data is implicitly instaniated in both translation units, hence (14.7.1  temp.inst paragraph 1) its initialization occurs. And for both definitions of exp<T>::m_data the ODR is met. According to 3.6.2  basic.start.init paragraph 1:

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

But for exp<T>::m_data we have two definitions. Does it mean that both g_data1 and g_data3 are guaranteed to be dynamically initialized before exp<char>::m_data?

Suggested Resolution: Insert the following sentence before the last two sentences of 3.2  basic.def.odr paragraph 5:

In the case of D being a static data member of a class template the following shall also hold:

Notes from 10/01 meeting:

It was decided that this issue is not linked to issue 270 and that there is no problem, because there is only one instantiation (see 2.1  lex.phases paragraph 8).




465. May constructors of global objects call exit()?

Section: 3.6.2  basic.start.init     Status: NAD     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.

Rationale (October 2004):

There is nothing in the Standard to indicate that this usage is prohibited, so it must be presumed to be permitted.




234. Reuse of base class subobjects

Section: 3.8  basic.life     Status: NAD     Submitter: Bill Wade     Date: 28 Jun 2000

3.8  basic.life and 12.4  class.dtor discuss explicit management of object lifetime. It seems clear that most object lifetime issues apply to sub-objects (array elements, and data members) as well. The standard supports

     struct X { T t } x;
     T* pt = &x.t;
     pt->~T();
     new(pt) T;

and this kind of behavior is useful in allocators.

However the standard does not seem to prohibit the same operations on base sub-objects.

   struct D: B{ ... } d;
   B* pb = &d;
   pb->~B();
   new(pb) B;

However if B and/or D have virtual member functions or virtual bases, it is unlikely that this code will result in a well-formed D object in current implementations (note that the various lines may be in different functions).

Suggested resolution: 12.4  class.dtor should be modified so that explicit destruction of base-class sub-objects be made illegal, or legal only under some restrictive conditions.

Rationale (04/01):

Reallocation of a base class subobject is already disallowed by 3.8  basic.life paragraph 7.




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

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

Rationale (October, 2004):

7.1.5.1  dcl.type.cv paragraph 4 already forbids modifying a const member of a POD struct. The prohibition need not be repeated in 3.9  basic.types.




584. Unions and aliasing

Section: 3.10  basic.lval     Status: NAD     Submitter: comp.std.c++     Date: 10 June 2006

The C++ standard says in 3.10  basic.lval, in paragraph 15:

Note that it is a literal copy from the C standard, but this is of course not the problem.

In C, union is not defined as an aggregate type. Therefore it is appropriate to say “aggregate or union.” But things changed in C++: aggregate type includes union type now (though not all unions are aggregates), and it becomes clear that the “union” in “aggregate or union” is redundant and should be deleted.

The above cited paragraph could be changed to:

Rationale (October, 2006):

As noted in the issue, not all unions are aggregates, but those that are not aggregates still allow aliasing. That part of the specification would be lost with the suggested change.




303. Integral promotions on bit-fields

Section: 4.5  conv.prom     Status: NAD     Submitter: Kiril Avdeiv     Date: 24 Jul 2001

Paragraph 3 of section 4.5  conv.prom contains a statement saying that if a bit-field is larger than int or unsigned int, no integral promotions apply to it. This phrase needs further clarification, as it is hardly possible to fugure out what it means. See below.

Assuming a machine with a size of general-purpose register equal 32 bits (where a byte takes up 8 bits) and a C++ implementation where an int is 32 bits and a long is 64 bits. And the following snippet of code:

    struct ExternalInterface {
      long field1:36, field2:28;
    };
    int main() {
      ExternalInterface  myinstance = { 0x100000001L, 0x12,};
      if(myinstance.field1 < 0x100000002L) { //do something }
    }

Does the standard prohibit the implementation from promoting field1's value into two general purpose registers? And imposes a burden of using shift machine instructions to work with the field's value? What else could that phrase mean?

Either alternative is implementation specific, so I don't understand why the phrase "If the bit-field is larger yet, no integral promotions apply to it" made it to the standard.

Notes from 10/01 meeting:

The standard of course does not dictate what an implementation might do with regard to use of registers or shift instructions in the generated code. The phrase cited means only that a larger bit-field does not undergo integral promotions, and therefore it retains the type with which it was declared (long in the above example). The Core Working Group judged that this was sufficiently clear in the standard.

Note that 9.6  class.bit paragraph 1 indicates that any bits in excess of the size of the underlying type are padding bits and do not participate in the value representation. Therefore the field1 bit field in the above example is not capable of holding the indicated values, which require more than 32 bits.




566. Conversion of negative floating point values to integer type

Section: 4.9  conv.fpint     Status: NAD     Submitter: Seungbeom Kim     Date: 13 March 2006

Section 4.9  conv.fpint paragraph 1 states:

An rvalue of a floating point type can be converted to an rvalue of an integer type. The conversion truncates; that is, the fractional part is discarded.

Here, the concepts of “truncation” and “fractional part” seem to be used without precise definitions. When -3.14 is converted into an integer, is the truncation toward zero or away from zero? Is the fractional part -0.14 or 0.86? The standard seem to give no clear answer to these.

Suggested resolution:

  1. Replace “truncates” with “truncates toward zero.”

  2. Replace “the fractional part” with “the fractional part (where that of x is defined as x-floor(x) for nonnegative x and x-ceiling(x) for negative x);” there should be a better wording for this, or the entire statement “that is, the fractional part is discarded” can be removed, once the meaning of “truncation” becomes unambiguous as above.

Rationale (October, 2006):

The specification is clear enough: “fractional part” refers to the digits following the decimal point, so that -3.14 converted to int becomes -3.




456. Is initialized const int or const bool variable a null pointer constant?

Section: 4.10  conv.ptr     Status: NAD     Submitter: Lloyd Lewins     Date: 31 Jan 2004

In the following code, I expect both "null" and "FALSE" to be null pointer constants -- and that the code should compile and output the string "int*" twice to cout:

#include <iostream>

using namespace std;

void foo(int* p)
{
     cout << "int*" << endl;
}

int main(void)
{
     const int null = 0;
     foo(null);
     const bool FALSE = false;
     foo(FALSE);
}

ISO/IEC 14882-1998 4.10  conv.ptr states:

An integral constant expression rvalue of integer type that evaluates to zero (called a /null pointer constant/) can be converted to a pointer type.

Stroustrup appears to agree with me -- he states (3rd edition page 88):

In C, it has been popular to define a macro NULL to represent the zero pointer. Because of C++`s tighter type checking, the use of plain 0, rather than any suggested NULL macro, leads to fewer problems. If you feel you must define NULL, use:
   const int NULL = 0;

However gcc 3.3.1 rejects this code with the errors:

     bug.cc:17: error: invalid conversion from `int' to `int*'
     bug.cc:19: error: cannot convert `const bool' to `int*' for argument `1' to `
         void foo(int*)'

I have reported this as a bug (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=13867), but the gcc team states that 4.10 requires that a null pointer constant must be an rvalue -- and no implicit conversion from an lvalue to an rvalue is required (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=396):

a null pointer constant is an integral constant expression rvalue that evaluates to zero [4.10/1] in this case `null' is an lvalue. The standard does not specify that lvalue->rvalue decay happens here, so `null' is not a null pointer constant.

I disagree with the gcc teams interpretation -- I don't see why 3.10  basic.lval doesn't apply:

Whenever an lvalue appears in a context where an rvalue is expected, the lvalue is converted to an rvalue;

The insertion of the word rvalue appears to have occurred during standardization -- it is not present in either Stroustrup 2nd edition or the 3rd edition. Does the committee deliberately intend to exclude an lvalue as a null pointer constant by adding the word rvalue? If so, it leads to the rather bizarre fact that "null" is not a null pointer constant, but "null + 0" is!

Notes from the March 2004 meeting:

We think this is just a bug in gcc. The const variable does get converted to an rvalue in this context. This case is not really any different than cases like

  const int null = 0;
  int i = null;
or
  const int i = 1;
  int a[i];
(which are accepted by gcc). No one would argue that the second lines of those examples are invalid because the variables are lvalues, and yet the conversions to rvalue happen implicitly for the same reason cited above -- the contexts require an rvalue.




71. Incorrect cross reference

Section: expr     Status: NAD     Submitter: Neal Gafter     Date: 15 Oct 1998

An operator expression can, according to 5  expr paragraph 2, require transformation into function call syntax. The reference in that paragraph is to 13.5  over.oper , but it should be to 13.3.1.2  over.match.oper .

Rationale (04/99): The subsections 13.5.1  over.unary , 13.5.2  over.binary , etc. of the referenced section are in fact relevant.




31. Looking up new/delete

Section: 5.3.4  expr.new     Status: NAD     Submitter: Daveed Vandevoorde     Date: 23 Jun 1998

Section 12.5  class.free paragraph 4 says:

If a delete-expression begins with a unary :: operator, the deallocation function's name is looked up in global scope. Otherwise, if the delete-expression is used to deallocate a class object whose static type has a virtual destructor, the deallocation function is the one found by the lookup in the definition of the dynamic type's virtual destructor (12.4  class.dtor ). Otherwise, if the delete-expression is used to deallocate an object of class T or array thereof, the static and dynamic types of the object shall be identical and the deallocation function's name is looked up in the scope of T. If this lookup fails to find the name, the name is looked up in the global scope. If the result of the lookup is ambiguous or inaccessible, or if the lookup selects a placement deallocation function, the program is ill-formed.
I contrast that with 5.3.4  expr.new paragraphs 16 and 17:
If the new-expression creates an object or an array of objects of class type, access and ambiguity control are done for the allocation function, the deallocation function (12.5  class.free ), and the constructor (12.1  class.ctor ). If the new-expression creates an array of objects of class type, access and ambiguity control are done for the destructor (12.4  class.dtor ).

If any part of the object initialization described above terminates by throwing an exception and a suitable deallocation function can be found, the deallocation function is called to free the memory in which the object was being constructed, after which the exception continues to propagate in the context of the new-expression. If no unambiguous matching deallocation function can be found, propagating the exception does not cause the object's memory to be freed. [Note: This is appropriate when the called allocation function does not allocate memory; otherwise, it is likely to result in a memory leak. ]

I think nothing in the latter paragraphs implies that the deallocation function found is the same as that for a corresponding delete-expression. I suspect that may not have been intended and that the lookup should occur "as if for a delete-expression".

Rationale:

Paragraphs 16 through 18 are sufficiently correct and unambiguous as written.




130. Sequence points and new-expressions

Section: 5.3.4  expr.new     Status: NAD     Submitter: Herb Sutter     Date: 20 June 1999

Clause 5  expr paragraph 4 appears to grant an implementation the right to generate code for a function call like

    f(new T1, new T2)
in the order However, 5.3.4  expr.new paragraph 17 seems to require the deallocation of the storage for an object only if part of the initialization of that object terminates with an exception. Given the ordering above, this specification would appear to allow the memory for the T2 object to be leaked if the T1 constructor throws an exception.

Suggested resolution: either forbid the ordering above or expand the requirement for reclaiming storage to include exceptions thrown in all operations between the allocation and the completion of the constructor.

Rationale (10/99): Even in the "traditional" ordering of the calls to allocation functions and constructors, memory can still leak. For instance, if T1 were successfully constructed and then the construction of T2 were terminated by an exception, the memory for T1 would be lost. Programmers concerned about memory leaks will avoid this kind of construct, so it seems unnecessary to provide special treatment for it to avoid the memory leaks associated with one particular implementation strategy.




55. Adding/subtracting pointer and enumeration value

Section: 5.7  expr.add     Status: NAD     Submitter: Steve Adamczyk     Date: 13 Oct 1998

An expression of the form pointer + enum (see paragraph 5) is not given meaning, and ought to be, given that paragraph 2 of this section makes it valid. Presumably, the enum value should be converted to an integral value, and the rest of the processing done on that basis. Perhaps we want to invoke the integral promotions here.

[Should this apply to (pointer - enum) too?]

Rationale (04/99): Paragraph 1 invokes "the usual arithmetic conversions" for operands of enumeration type.

(It was later pointed out that the builtin operator T* operator+(T*, ptrdiff_t) (13.6  over.built paragraph 13) is selected by overload resolution. Consequently, according to 13.3.1.2  over.match.oper paragraph 7, the operand of enumeration type is converted to ptrdiff_t before being interpreted according to the rules in 5.7  expr.add .)




567. Can size_t and ptrdiff_t be larger than long?

Section: 5.7  expr.add     Status: NAD     Submitter: Nick MacLaren     Date: 14 March 2006

Code that was portable in C90 and C++98 is no longer portable with the introduction of data types longer than long; code that could previously cast size_t and ptrdiff_t to long without loss of precision (because long was the largest type) can no longer rely on that idiom.

The CWG discussed this during the Berlin (April, 2006) meeting. The general consensus was that this was unavoidable: there are valid reasons for implementations to keep long at a size less than that required for address arithmetic.

See paper J16/06-0053 = WG21 N1983, which also suggests the possibility of required diagnostics for problematic cases as an alternative to restricting the size of size_t and ptrdiff_t.

Rationale (October, 2006):

This is not an area in which the Standard should override the decisions of implementors who wish to maintain the size of long for backward compatibility but need a larger size_t to deal with expanded address spaces. Also, diagnostics of the sort described are better treated as quality of implementation issues rather than topics for standardization.




97. Use of bool constants in integral constant expressions

Section: 5.19  expr.const     Status: NAD     Submitter: Andy Koenig     Date: 18 Feb 1999

Consider:

    int* p = false;         // Well-formed?
    int* q = !1;            // What about this?
>From 3.9.1  basic.fundamental paragraph 6: "As described below, bool values behave as integral types."

From 4.10  conv.ptr paragraph 1: "A null pointer constant is an integral constant expression rvalue of integer type that evaluates to zero."

From 5.19  expr.const paragraph 1: "An integral constant-expression can involve only literals, enumerators, const variables or static members of integral or enumeration types initialized with constant expressions, ..."

In 2.13.1  lex.icon : No mention of true or false as an integer literal.

From 2.13.5  lex.bool : true and false are Boolean literals.

So the definition of q is certainly valid, but the validity of p depends on how the sentence in 5.19  expr.const is parsed. Does it mean

Or does it mean Or something else?

If the latter, then (3.0 < 4.0) is a constant expression, which I don't think we ever wanted. If the former, though, we have the anomalous notion that true and false are not constant expressions.

Now, you may argue that you shouldn't be allowed to convert false to a pointer. But what about this?

    static const bool debugging = false;
    
    // ...
    
    int table[debugging? n+1: n];
Whether the definition of table is well-formed hinges on whether false is an integral constant expression.

I think that it should be, and that failure to make it so was just an oversight.

Rationale (04/99): A careful reading of 5.19  expr.const indicates that all types of literals can appear in integral constant expressions, but floating-point literals must immediately be cast to an integral type.




487. Operator overloading in constant expressions

Section: 5.19  expr.const     Status: NAD     Submitter: Steve Adamczyk     Date: 24 Nov 2004

According to 5.19  expr.const paragraph 1,

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

Given a case like

    enum E { e };
    int operator+(int, E);
    int i[4 + e];

does this mean that the overloaded operator+ is not considered (because it can't be called), or is it selected by overload resolution, thus rendering the program ill-formed?

Rationale (April, 2005):

All expressions, including constant expressions, are subject to overload resolution. The example is ill-formed.




467. Jump past initialization of local static variable

Section: 6.7  stmt.dcl     Status: NAD     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:
    ...
  }

Rationale (October, 2004):

The CWG sees no reason to change this specification. Local static variables are different from automatic variables: automatic variables, if not explicitly initialized, can have indeterminate (“garbage”) values, including trap representations, while local static variables are subject to zero initialization and thus cannot have garbage values.

The latitude granted to implementations regarding performing dynamic initialization of local static objects as if it were static initialization is exactly parallel to namespace scope objects (3.6.2  basic.start.init), as are the restrictions on programmer assumptions.




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

Section: dcl.dcl     Status: NAD     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.

Rationale (October, 2004):

CWG felt that readers might misunderstand “declaration” as meaning “non-definition declaration.”




154. Anonymous unions in unnamed namespaces

Section: 7.1.1  dcl.stc     Status: NAD     Submitter: Greg Comeau     Date: 9 Aug 1999

9.5  class.union paragraph 3 implies that anonymous unions in unnamed namespaces need not be declared static (it only places that restriction on anonymous unions "declared in a named namespace or in the global namespace").

However, 7.1.1  dcl.stc paragraph 1 says that "global anonymous unions... shall be declared static." This could be read as prohibiting anonymous unions in unnamed namespaces, which are the preferred alternative to the deprecated use of static.

Rationale (10/99): An anonymous union in an unnamed namespace is not "a global anonymous union," i.e., it is not a member of the global namespace.




376. Class "definition" versus class "declaration"

Section: 7.1.2  dcl.fct.spec     Status: NAD     Submitter: Randy Maddox     Date: 28 August 2002

In clause 7.1.2  dcl.fct.spec, para. 3, the following sentence

A function defined within a class definition is an inline function.

should, if I am not mistaken, instead be:

A function defined within a class declaration is an inline function."

Notes from October 2002 meeting:

This is not a defect. Though there is a long history, going back to the ARM, of use of the term "class declaration" to mean the definition of the class, we believe "class definition" is clearer. We have opened issue 379 to deal with changing all other uses of "class declaration" to "class definition" where appropriate.




412. Can a replacement allocation function be inline?

Section: 7.1.2  dcl.fct.spec     Status: NAD     Submitter: Matt Austern     Date: 23 Apr 2003

A customer reports that when he attempts to replace ::operator new with a user-defined function, the standard library calls the default function by preference if the user-defined function is inline. I believe that our compiler is correct, and that such a replacement function isn't allowed to be inline, but I'm not sure there's sufficiently explicit language in the standard.

In general, of course, the definition of an inline function must be present in every translation unit where the function is called. (7.1.2  dcl.fct.spec, par 4) It could be argued that this requirement doesn't quite address replacement functions: what we're dealing with is the odd case where we've already got one definition and the user is supplying a different one. I'd like to see something specifically addressing the case of a replacement function.

So what do we have? I see discussion of requirement for a replacement ::operator new in three places: 17.4.3.4  lib.replacement.functions, 18.4.1.1  lib.new.delete.single par 2, and 3.7.3  basic.stc.dynamic par 2-3. I don't see anything explicitly saying that the replacement function may not be inline. The closest I can find is 18.4.1.1  lib.new.delete.single par 2, which says that "a C++ program may define a function with this function signature that displaces the default version defined by the C++ Standard library". One might argue that "with this function signature" rules out inline, but that strikes me as a slight stretch.

Have I missed anything?

Andrew Koenig: I think you've turned up a problem in 7.1.2  dcl.fct.spec paragraph 4. Consider:

  // Translation unit 1
  #include <iostream>

  extern void foo(void (*)());

  inline void bar() {
    std::cout << "Hello, world!" << std::endl;
  }

  int main() {
    foo(bar);
  }

  // Translation unit 2
  void foo(void (*f)()) { (*f)(); }

Are you really trying to tell me that this program is ill-formed because the definition of bar is not available in translation unit 2?

I think not. The actual words in 7.1.2  dcl.fct.spec par 4 are

An inline function shall be defined in every translation unit in which it is used...
and I think at in this context, ``used'' should be interpreted to mean that foo is used only in translation unit 1, where it is converted to a value of type void(*)().

Notes from October 2003 meeting:

We don't think Andy Koenig's comment requires any action; "used" is already defined appropriately.

We agree that this replacement should not be allowed, but we think it's a library issue (in the rules for allowed replacements). Forwarded to library group; it's issue 404 on the library issues list.




422. Is a typedef redeclaration allowed with a template type that might be the same?

Section: 7.1.3  dcl.typedef     Status: NAD     Submitter: Steve Adamczyk     Date: 18 June 2003

Is the following valid?

  template <class T> void f(T) {
    typedef int x;
    typedef T x;
  }
  int main() {
    f(1);
  }

There is an instantiation where the function is valid. Is an implementation allowed to issue an error on the template declaration because the types on the typedef are not the same (7.1.3  dcl.typedef)?

How about

  typedef T x;
  typedef T2 x;
?

It can be argued that these cases should be allowed because they aren't necessarily wrong, but it can also be argued that there's no reason to write things like the first case above, and if such a case appears it's more likely to be a mistake than some kind of intentional test that int and T are the same type.

Notes from the October 2003 meeting:

We believe that all these cases should be allowed, and that errors should be required only when an instance of the template is generated. The current standard wording does not seem to disallow such cases, so no change is required.




311. Using qualified name to reopen nested namespace

Section: 7.3.1  namespace.def     Status: NAD     Submitter: Bjarne Stroustrup     Date: 18 Sep 2001

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

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

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

namespace A {
    namespace M {
        int e;
    }
}

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

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

    int x;

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

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

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

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

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

Lawrence Crowl: Even worse is

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

Notes from April 2003 meeting:

We're closing this because it's on the Evolution working group list.




95. Elaborated type specifiers referencing names declared in friend decls

Section: 7.3.1.2  namespace.memdef     Status: NAD     Submitter: John Spicer     Date: 9 Feb 1999

A change was introduced into the language that made names first declared in friend declarations "invisible" to normal lookups until such time that the identifier was declared using a non-friend declaration. This is described in 7.3.1.2  namespace.memdef paragraph 3 and 11.4  class.friend paragraph 9 (and perhaps other places).

The standard gives examples of how this all works with friend declarations, but there are some cases with nonfriend elaborated type specifiers for which there are no examples, and which might yield surprising results.

The problem is that an elaborated type specifier is sometimes a declaration and sometimes a reference. The meaning of the following code changes depending on whether or not friend class names are injected (visibly) into the enclosing namespace scope.

    struct A;
    struct B;
    namespace N {
        class X {
            friend struct A;
            friend struct B;
        };
        struct A *p;     // N::A with friend injection, ::A without
        struct B;        // always N::B
    }
Is this the desired behavior, or should all elaborated type specifiers (and not just those of the form "class-key identifier;") have the effect of finding previously declared "invisible" names and making them visible?

Mike Miller: That's not how I would categorize the effect of "struct B;". That declaration introduces the name "B" into namespace N in exactly the same fashion as if the friend declaration did not exist. The preceding friend declaration simply stated that, if a class N::B were ever defined, it would have friendly access to the members of N::X. In other words, the lookups in both "struct A*..." and "struct B;" ignore the friend declarations.

(The standard is schizophrenic on the issue of whether such friend declarations introduce names into the enclosing namespace. 3.3  basic.scope paragraph 4 says,

while 3.3.1  basic.scope.pdecl paragraph 6 says exactly the opposite: Both of these are just notes; the normative text doesn't commit itself either way, just stating that the name is not found until actually declared in the enclosing namespace scope. I prefer the latter description; I think it makes the behavior you're describing a lot clearer and easier to understand.)

John Spicer: The previous declaration of B is not completely ignored though, because certainly changing "friend struct B;" to "friend union B;" would result in an error when B was later redeclared as a struct, wouldn't it?

Bill Gibbons: Right. I think the intent was to model this after the existing rule for local declarations of functions (which dates back to C), where the declaration is introduced into the enclosing scope but the name is not. Getting this right requires being somewhat more rigorous about things like the ODR because there may be declaration clashes even when there are no name clashes. I suspect that the standard gets this right in most places but I would expect there to be a few that are still wrong, in addition to the one Mike pointed out.

Mike Miller: Regarding would result in an error when B was later redeclared

I don't see any reason why it should. The restriction that the class-key must agree is found in 7.1.5.3  dcl.type.elab and is predicated on having found a matching declaration in a lookup according to 3.4.4  basic.lookup.elab . Since a lookup of a name declared only (up to that point) in a friend declaration does not find that name (regardless of whether you subscribe to the "does-not-introduce" or "introduces-invisibly" school of thought), there can't possibly be a mismatch.

I don't think that the Standard's necessarily broken here. There is no requirement that a class declared in a friend declaration ever be defined. Explicitly putting an incompatible declaration into the namespace where that friend class would have been defined is, to me, just making it impossible to define — which is no problem, since it didn't have to be defined anyway. The only error would occur if the same-named but unbefriended class attempted to use the nonexisting grant of friendship, which would result in an access violation.

(BTW, I couldn't find anything in the Standard that forbids defining a class with a mismatched class-key, only using one in an elaborated-type-specifier. Is this a hole that needs to be filled?)

John Spicer: This is what 7.1.5.3  dcl.type.elab paragraph 3 says:

The latter part of this paragraph (beginning "This rule also applies...") is somewhat murky to me, but I think it could be interpreted to say that

            class B;
            union B {};
and
            union B {};
            class B;
are both invalid. I think this paragraph is intended to say that. I'm not so sure it actually does say that, though.

Mike Miller: Regarding I think the intent was to model this after the existing rule for local declarations of functions (which dates back to C)

Actually, that's not the C (1989) rule. To quote the Rationale from X3.159-1989:

Regarding Getting this right requires being somewhat more rigorous

Yes, I think if this is to be made illegal, it would have to be done with the ODR; the name-lookup-based current rules clearly (IMHO) don't apply. (Although to be fair, the [non-normative] note in 3.3  basic.scope paragraph 4 sounds as if it expects friend invisible injection to trigger the multiple-declaration provisions of that paragraph; it's just that there's no normative text implementing that expectation.)

Bill Gibbons: Nor does the ODR currently disallow:

    translation unit #1    struct A;
    
    translation unit #2    union A;
since it only refers to class definitions, not declarations.

But the obvious form of the missing rule (all declarations of a class within a program must have compatible struct/class/union keys) would also answer the original question.

The declarations need not be visible. For example:

    translation unit #1    int f() { return 0; }
    
    translation unit #2:   void g() {
                               extern long f();
                           }
is ill-formed even though the second "f" is not a visible declaration.

Rationale (10/99): The main issue (differing behavior of standalone and embedded elaborated-type-specifiers) is as the Committee intended. The remaining questions mentioned in the discussion may be addressed in dealing with related issues.

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




165. Definitions of friends and block-scope externs

Section: 7.3.1.2  namespace.memdef     Status: NAD     Submitter: Derek Inglis     Date: 7 Sep 1999

7.3.1.2  namespace.memdef paragraph 2 says,

Members of a named namespace can also be defined outside that namespace by explicit qualification (3.4.3.2  namespace.qual ) of the name being defined, provided that the entity being defined was already declared in the namespace...
It is not clear whether block-scope extern declarations and friend declarations are sufficient to permit the named entities to be defined outside their namespace. For example,
    namespace NS {
       struct A { friend struct B; };
       void foo() { extern void bar(); }
    }
    struct NS::B { };   // 1) legal?
    void NS::bar() { }  // 2) legal?

Rationale (10/99): Entities whose names are "invisibly injected" into a namespace as a result of friend declarations are not "declared" in that namespace until an explicit declaration of the entity appears at namespace scope. Consequently, the definitions in the example are ill-formed.

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




553. Problems with friend allocation and deallocation functions

Section: 7.3.1.2  namespace.memdef     Status: NAD     Submitter: Dmitriy Litskalov     Date: 16 December 2005

Consider the following example:

    class C {
       public: enum E {};

       friend void* operator new(size_t, E);
       friend void  operator delete(void*, E);
    };

    void foo() {
       C::E e;
       C* ptr = new(e) C();
    }

This code, which is valid in global scope, becomes ill-formed when the class definition is moved into a namespace, and there is no way to make it valid:

    namespace N {
       class C {
          public: enum E {};

          friend void* operator new(size_t, E);
          friend void  operator delete(void*, E);
       };
    }

    void foo() {
       N::C::E e;
       N::C* ptr = new(e) N::C();
    }

The reason for this is that non-member allocation and deallocation functions are required to be members of the global scope (3.7.3.1  basic.stc.dynamic.allocation paragraph 1, 3.7.3.2  basic.stc.dynamic.deallocation paragraph 1), unqualified friend declarations declare names in the innermost enclosing namespace (7.3.1.2  namespace.memdef paragraph 3), and these functions cannot be declared in global scope at a point where the friend declarations could refer to them using qualified-ids because their second parameter is a member of the class and thus can't be named before the class containing the friend declarations is defined.

Possible solutions for this conundrum include invention of some mechanism to allow a friend declaration to designate a namespace scope other than the innermost enclosing namespace in which the friend class or function is to be declared or to relax the innermost enclosing namespace lookup restriction in 7.3.1.2  namespace.memdef paragraph 3 for friend declarations that nominate allocation and deallocation functions.

Rationale (April, 2006):

The CWG acknowledged that it is not always possible to move code from the global scope into a namespace but felt that this problem was not severe enough to warrant changing the language to accommodate it. Possible solutions include moving the enumeration outside the class or defining member allocation and deallocation functions.




109. Allowing ::template in using-declarations

Section: 7.3.3  namespace.udecl     Status: NAD     Submitter: Daveed Vandevoorde     Date: 6 Apr 1999

Daveed Vandevoorde : While reading Core issue 11 I thought it implied the following possibility:

    template<typename T>
    struct B {
       template<int> void f(int);
    };

    template<typename T>
    struct D: B<T> {
       using B<T>::template f;
       void g() { this->f<1>(0); } // OK, f is a template
    };

However, the grammar for a using-declaration reads:

and nested-name-specifier never ends in "template".

Is that intentional?

Bill Gibbons :

It certainly appears to be, since we have:

so it would be easier to specify using-declaration as: if the "template" keyword were allowed. There was a discussion about whether a dependent name specified in a using-declaration could be given an "is a type" attribute through the typename keyword; the decision was to allow this. But I don't recall if the "is a template" attribute was discussed.

Rationale (04/99): Any semantics associated with the template keyword in using-declarations should be considered an extension.

Notes from the April 2003 meeting:

See also issues 96 and 11.

We decided to make no change and to close this issue as not-a-defect. This is not needed functionality; the example above, for example, can be written with ->template. This issue has been on the issues list for years as an extension, and there has been no clamor for it.

It was also noted that knowing that something is a template is not enough; there's still the issue of knowing whether it is a class or function template.




169. template-ids in using-declarations

Section: 7.3.3  namespace.udecl     Status: NAD     Submitter: Valentin Bonnard     Date: 16 Sep 1999

7.3.3  namespace.udecl paragraph says,

A using-declaration shall not name a template-id.
It is not clear whether this prohibition applies to the entity for which the using-declaration is a synonym or to any name that appears in the using-declaration. For example, is the following code well-formed?
    template <typename T>
    struct base {
	void bar ();
    };

    struct der : base<int> 
    {
	using base<int>::bar; // ill-formed ?
    };

Rationale (10/99): 7.3.3  namespace.udecl paragraph 1 says, "A using-declaration introduces a name..." It is the name that is thus introduced that cannot be a template-id.




461. Make asm conditionally-supported

Section: 7.4  dcl.asm     Status: NAD     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.

Rationale (October, 2004):

This is covered in paper N1627. We would like to keep asm as a keyword for all implementations, however, to enhance portability by preventing programmers from inadvertently using it as an identifier.




14. extern "C" functions and declarations in different namespaces

Section: 7.5  dcl.link     Status: NAD     Submitter: Erwin Unruh     Date: unknown

Issue 1

7.5  dcl.link paragraph 6 says the following:

Is this only for linkage purposes or for both name look up and linkage purposes:
    extern "C" int f(void);
    namespace A {
         extern "C" int f(void);
    };
    using namespace A;
     
    int i = f(); // Ok because only one function f() or
                 // ill-formed
For name lookup, both declarations of f are visible and overloading cannot distinguish between them. Has the compiler to check that these functions are really the same function or is the program in error?

Rationale: These are the same function for all purposes.

Issue 2

A similar question may arise with typedefs:

    // vendor A
    typedef unsigned int size_t;
    // vendor B
    namespace std {
            typedef unsigned int size_t;
    }
    using namespace std;
    size_t something(); // error?
Is this valid because the typedef size_t refers to the same type in both namespaces?

Rationale (04/99): In 7.3.4  namespace.udir paragraph 4:

If name lookup finds a declaration for a name in two different namespaces, and the declarations do not declare the same entity and do not declare functions, the use of the name is ill-formed.
The term entity applied to typedefs refers to the underlying type or class (3  basic , paragraph 3); therefore both declarations of size_t declare the same entity and the above example is well-formed.




358. Namespaces and extern "C"

Section: 7.5  dcl.link     Status: NAD     Submitter: Steve Clamage     Date: 28 May 2002

Is this code valid:

  extern "C" void f();

  namespace N
  {
    int var; 
    extern "C" void f(){ var = 10; }
  }

The two declarations of f refer to the same external function, but is this a valid way to declare and define f?

And is the definition of f considered to be in namespace N or in the global namespace?

Notes from October 2002 meeting:

Yes, this example is valid. See 7.5  dcl.link paragraph 6, which contains a similar example with the definition in the global namespace instead. There is only one f, so the question of whether the definition is in the global namespace or the namespace N is not meaningful. The same function is found by name lookup whether it is found from the declaration in namespace N or the declaration in the global namespace, or both (7.3.4  namespace.udir paragraph 4).




333. Ambiguous use of "declaration" in disambiguation section

Section: 8.2  dcl.ambig.res     Status: NAD     Submitter: Michiel Salters     Date: 14 Jan 2002

In deciding whether a construct is an object declaration or a function declaration, 8.2  dcl.ambig.res contains the following gem: "In that context, the choice is between a function declaration [...] and an object declaration [...] Just as for the ambiguities mentioned in 6.8  stmt.ambig, the resolution is to consider any construct that could possibly be a declaration a declaration."

To what declaration do the last two "declarations" refer? Object, function, or (following from the syntax) possibly parameter declarations?

Notes from the 4/02 meeting:

This is not a defect. Section 8.2  dcl.ambig.res reads:

The ambiguity arising from the similarity between a function-style cast and a declaration mentioned in 6.8  stmt.ambig can also occur in the context of a declaration. In that context, the choice is between a function declaration with a redundant set of parentheses around a parameter name and an object declaration with a function-style cast as the initializer. Just as for the ambiguities mentioned in 6.8  stmt.ambig, the resolution is to consider any construct that could possibly be a declaration a declaration.

The wording "any construct" in the last sentence is not limited to top-level constructs. In particular, the function declaration encloses a parameter declaration, whereas the object declaration encloses an expression. Therefore, in case of ambiguity between these two cases, the declaration is parsed as a function declaration.




340. Unclear wording in disambiguation section

Section: 8.2  dcl.ambig.res     Status: NAD     Submitter: Bart v Ingen Schenau     Date: 27 Feb 2002

Consider the following program:

  struct Point
  {
    Point(int){}
  };
  struct Lattice 
  {
    Lattice(Point, Point, int){}
  };
  int main(void)
  {
    int a, b;
    Lattice latt(Point(a), Point(b), 3);   /* Line X */
  }

The problem concerns the line marked /* Line X */, which is an ambiguous declarations for either an object or a function. The clause that governs this ambiguity is 8.2  dcl.ambig.res paragraph 1, and reads:

The ambiguity arising from the similarity between a function-style cast and a declaration mentioned in 6.8  stmt.ambig can also occur in the context of a declaration. In that context, the choice is between a function declaration with a redundant set of parentheses around a parameter name and an object declaration with a function-style cast as the initializer. Just as for the ambiguities mentioned in 6.8  stmt.ambig, the resolution is to consider any construct that could possibly be a declaration a declaration. [Note: a declaration can be explicitly disambiguated by a nonfunction-style cast, by a = to indicate initialization or by removing the redundant parentheses around the parameter name. ]

Based on this clause there are two possible interpretations of the declaration in line X:

  1. The declaration of latt declares a function with a return value of the type Lattice and taking three arguments. The type of the first two arguments is Point and each of these arguments is followed by a parameter name in redundant parentheses. The type of the third argument can not be determined, because it is a literal. This will result in a syntax error.
  2. The declaration of latt declares an object, because the other option (a function declaration) would result in a syntax error.

Note that the last sentence before the "[Note:" is not much help, because both options are declarations.

Steve Adamczyk: a number of people replied to this posting on comp.std.c++ saying that they did not see a problem. The original poster replied:

I can't do anything but agree with your argumentation. So there is only one correct interpretation of clause 8.2  dcl.ambig.res paragraph 1, but I have to say that with some rewording, the clause can be made a lot clearer, like stating explicitly that the entire declaration must be taken into account and that function declarations are preferred over object declarations.

I would like to suggest the following as replacement for the current clause 8.2  dcl.ambig.res paragraph 1:

The ambiguity arising from the similarity between a functionstyle cast and a declaration mentioned in 6.8  stmt.ambig can also occur in the context of a declaration. In that context, the choice is between a function declaration with a redundant set of parentheses around a parameter name and an object declaration with a function-style cast as the initializer. The resolution is to consider any construct that could possibly be a function declaration a function declaration. [Note: To disambiguate, the whole declaration might have to be examined to determine if it is an object or a function declaration.] [Note: a declaration can be explicitly disambiguated by a nonfunction-style cast, by a = to indicate initialization or by removing the redundant parentheses around the parameter name. ]

Notes from the 4/02 meeting:

The working group felt that the current wording is clear enough.




478. May a function parameter be an array of an abstract class type?

Section: 8.3.4  dcl.array     Status: NAD     Submitter: Steve Adamczyk     Date: 28 Sep 2004

Consider the following example:

    struct S {
      virtual void v() = 0;
    };

    void f(S sa[10]);     // permitted?

8.3.4  dcl.array paragraph 1 says that a declaration like that of sa is ill-formed:

T is called the array element type; this type shall not be a reference type, the (possibly cv-qualified) type void, a function type or an abstract class type.

On the other hand, 8.3.5  dcl.fct paragraph 3 says that the type of sa is adjusted to S*, which would be permitted:

The type of each parameter is determined from its own decl-specifier-seq and declarator. After determining the type of each parameter, any parameter of type “array of T” or “function returning T” is adjusted to be “pointer to T” or “pointer to function returning T,” respectively.

It is not clear whether the parameter adjustment trumps the prohibition on declaring an array of an abstract class type or not. Implementations differ in this respect: EDG 2.4.2 and MSVC++ 7.1 reject the example, while g++ 3.3.3 and Sun Workshop 8 accept it.

Rationale (April, 2005):

The prohibition in 8.3.4  dcl.array is absolute and does not allow for exceptions. Even though such a type in a parameter declaration would decay to an allowed type, the prohibition applies to the type before the decay.

This interpretation is consistent with the resolution of issue 337, which causes template type deduction to fail if such types are deduced. It was also observed that pointer arithmetic on pointers to abstract classes is very likely to fail, and the fact that the programmer used array notation to declare the pointer type is a strong indication that he/she expected to use subscripting.




18. f(TYPE) where TYPE is void should be allowed

Section: 8.3.5  dcl.fct     Status: NAD     Submitter: unknown     Date: unknown

8.3.5  dcl.fct paragraph 2 says:

If the parameter-declaration-clause is empty, the function takes no arguments. The parameter list (void) is equivalent to the empty parameter list.
Can a typedef to void be used instead of the type void in the parameter list?

Rationale: The IS is already clear that this is not allowed.




66. Visibility of default args vs overloads added after using-declaration

Section: 8.3.6  dcl.fct.default     Status: NAD     Submitter: Mike Miller     Date: 6 Oct 1998

Paragraph 9 of says that extra default arguments added after a using-declaration but before a call are usable in the call, while 7.3.3  namespace.udecl paragraph 9 says that extra function overloads are not. This seems inconsistent, especially given the similarity of default arguments and overloads.

Rationale (10/99): The Standard accurately reflects the intent of the Committee.




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

Section: 8.5.3  dcl.init.ref     Status: NAD     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.

Rationale (April, 2005):

As acknowledged in the description of the issue, the referenced text is only a note and has no normative impact. Furthermore, the examples cited do not involve the conversions mentioned in the note, and the normative text is already sufficiently clear that the types in the examples are not reference-compatible.




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

Section: 9.4.1  class.static.mfct     Status: NAD     Submitter: Jason Merrill     Date: 7 Oct 2001

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

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

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

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

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

This is related to issue 232

Rationale (October 2003):

We agreed the example should be allowed. p->f() is rewritten as (*p).f() according to 5.2.5  expr.ref. *p is not an error when p is null unless the lvalue is converted to an rvalue (4.1  conv.lval), which it isn't here.




7. Can a class with a private virtual base class be derived from?

Section: 11.2  class.access.base     Status: NAD     Submitter: Jason Merrill     Date: unknown
    class Foo { public: Foo() {}  ~Foo() {} };
    class A : virtual private Foo { public: A() {}  ~A() {} };
    class Bar : public A { public: Bar() {}  ~Bar() {} };
~Bar() calls ~Foo(), which is ill-formed due to access violation, right? (Bar's constructor has the same problem since it needs to call Foo's constructor.) There seems to be some disagreement among compilers. Sun, IBM and g++ reject the testcase, EDG and HP accept it. Perhaps this case should be clarified by a note in the draft.

In short, it looks like a class with a virtual private base can't be derived from.

Rationale: This is what was intended.




17. Footnote 99 should discuss the naming class when describing members that can be accessed from friends

Section: 11.2  class.access.base     Status: NAD     Submitter: unknown     Date: unknown

Footnote 98 says:

As specified previously in clause 11  class.access , private members of a base class remain inaccessible even to derived classes unless friend declarations within the base class declaration are used to grant access explicitly.
This footnote does not fit with the algorithm provided in 11.2  class.access.base paragraph 4 because it does not take into account the naming class concept introduced in this paragraph.

(See also paper J16/99-0002 = WG21 N1179.)

Rationale (10/99): The footnote should be read as referring to immediately-derived classes, and is accurate in that context.




471. Conflicting inherited access specifications

Section: 11.2  class.access.base     Status: NAD     Submitter: Mike Miller     Date: 14 Jun 2004

The Standard does not appear to specify how to handle cases in which conflicting access specifications for a member are inherited from different base classes. For example,

    struct A {
    public:
      int i;
    };
    struct B : virtual public A {
    protected:
      using A::i;
    };
    struct C : virtual public A, public B {
      // "i" is protected from B, public from A
    };

This question affects both the existing wording of 11.2  class.access.base paragraph 4 (“m as a member of N is public ... m as a member of N is private ... m as a member of N is protected”) and the proposed wording for issue 385 (“when a nonstatic data member or nonstatic member function is a protected member of its naming class”).

One possible definition of “is public” would be something like, “if any visible declaration of the entity has public access.” One could also plausibly define the access of m in N to be the minimum of all the visible declarations, or even an error if the visible declarations are inconsistent.

11.2  class.access.base paragraph 1 describes the access of inherited members, so a clarifying statement resolving this issue might plausibly be inserted at the end of that paragraph.

Proposed resolution (October, 2004):

Add the following text as a new paragraph after 11.2  class.access.base paragraph 1:

If a given base class can be reached along more than one path through a derived class's sub-object lattice (10.1  class.mi), a member of that base class could have different accessibility in the derived class along different paths. In such cases, the most permissive access prevails. [Example:

    struct B { static int i; };
    class I: protected B { };
    class D1: public B, public I { };
    class D2: public I, private B { };

i is accessible as a public member of D1 and as a protected member of D2. —end example]

Rationale (03/2005): This question is already covered, in almost identical words, in 11.7  class.paths.




209. Must friend declaration names be accessible?

Section: 11.4  class.friend     Status: NAD     Submitter: Judy Ward     Date: 1 Mar 2000

11.4  class.friend, paragraph 7, says

A name nominated by a friend declaration shall be accessible in the scope of the class containing the friend declaration.

Does that mean the following should be illegal?

    class A { void f(); };
    class B { friend void A::f(); }; // Error: A::f not accessible from B

I discussed this with Bjarne in email, and he thinks it was an editorial error and this was not the committee's intention. The paragraph seems to have been added in the pre-Kona (24 Sept 1996) mailing, and I could not find anything in the previous meeting's (Stockholm) mailings which led me to believe this was intentional. The only compiler vendor which I think currently implements it is the latest release (2.43) of the EDG front end.

Proposed resolution (10/00):

Remove the first sentence of 11.4  class.friend, paragraph 7.

Rationale (04/01):

After the 10/00 vote to accept this issue as a DR with the proposed resolution, it was noted that the first two sentences of 11  class.access paragraph 3 cause the proposed change to have no effect:

Access control is applied uniformly to all names, whether the names are referred to from declarations or expressions. [Note: access control applies to names nominated by friend declarations (11.4  class.friend) and using-declarations (7.3.3  namespace.udecl). ]

In addition to the obvious editing to the text of the note, an exception to the blanket statement in the first sentence would also be required. However, discussion during the 04/01 meeting failed to produce consensus on exactly which names in the friend declaration should be exempted from the requirements of access control.

One possibility would be that only the name nominated as friend should be exempt. However, that approach would make it impossible to name a function as a friend if it used private types in its parameters or return type. Another suggestion was to ignore access for every name used in a friend declaration. That approach raised a question about access within the body of a friend function defined inline in the class body — the body is part of the declaration of a function, but references within the body of a friend function should still be subject to the usual access controls.

Other possibilities were raised, such as allowing the declaration of a friend member function if the declaration were permissible in its containing class, or taking the union of the access within the befriending class and the befriended entity. However, these suggestions would have been complex and difficult to specify correctly.

Ultimately it was decided that the original perceived defect was not sufficiently serious as to warrant the degree of complexity required to resolve it satisfactorily and the issue was consequently declared not to be a defect. It was observed that most of the problems involved with the current state of affairs result from inability to declare a particular member function as a friend; in such cases, an easy workaround is simply to befriend the entire class rather than the specific member function.




445. Wording issue on friend declarations

Section: 11.4  class.friend     Status: NAD     Submitter: Risto Lankinen     Date: 5 Dec 2003

Thus says the section 11.4  class.friend/7 in ISO 14882 C++ standard:

A name nominated by a friend declaration shall be accessible in the scope of the class containing the friend declaration.

The obvious intention of this is to allow a friend declaration to specify a function (or nested class, enum, etc.) that is declared "private" or "protected" in its enclosing class. However, literal interpretation seems to allow a broader access to the befriended function by the whole class that is declaring the friendship.

If the rule were interpreted literally as it is currently written, this would compile (when it, of course, shouldn't be allowed at all):

class C
{
private:
  static void f();
};

class D
{
  friend void C::f();  // A name nominated by friend declaration...

  D()
  {
   C::f();  // ... shall be accessible in scope of class declaring friendship
  }
};

Suggested fix is to reword "in the scope of the class containing the friend declaration" to exclude all other references from the scope of the declaring class, except the friend-declaration itself.

Notes from the March 2004 meeting:

We considered this and concluded that the standard is clear enough.




501. Visibility of friend declarations within the befriending class

Section: 11.4  class.friend     Status: NAD     Submitter: Gabriel Dos Reis     Date: 25 Jan 2005

I just received a query from a user of why line #1 in the following snippet is ill-formed:

    void g(int (*)(int));

    template<class T>
    class A {
       friend int f(int) { return 0; }

       void h() {
           g(f);          // #1
       }
    };

I believe that the special invisibility rule about friends is too complicated and makes life too complicated, especially considering that friends in templates are not templates, nor can they be conveniently rewritten with a “first declare at the namespace scope” rule. I can understand the rules when they make programming easier or prevent some obvious “silly” mistakes; but that does not seem to be the case here.

John Spicer: See two papers that discuss this issue: N0878 by Bill Gibbons, which ultimately gave rise to our current rules, and N0913 by me as an alternative to N0878.

Rationale (April, 2005):

The Standard is clear and consistent; this rule is the result of an explicit decision by the Committee.




19. Clarify protected member access

Section: 11.5  class.protected     Status: NAD     Submitter: unknown     Date: unknown

11.5  class.protected paragraph 1 says:

When a friend or a member function of a derived class references a protected nonstatic member of a base class, an access check applies in addition to ...
Instead of saying "references a protected nonstatic member of a base class", shouldn't this be rewritten to use the concept of naming class as 11.2  class.access.base paragraph 4 does?

Rationale (04/99): This rule is orthogonal to the specification in 11.2  class.access.base paragraph 4.




117. Timing of destruction of temporaries

Section: 12.2  class.temporary     Status: NAD     Submitter: Mike Miller     Date: 14 May 1999

12.2  class.temporary paragraph 4 seems self-contradictory:

the temporary that holds the result of the expression shall persist until the object's initialization is complete... the temporary is destroyed after it has been copied, before or when the initialization completes.
How can it be destroyed "before the initialization completes" if it is required to "persist until the object's initialization is complete?"

Rationale (04/00):

It was suggested that "before the initialization completes" refers to the case in which some part of the initialization terminates by throwing an exception. In that light, the apparent contradiction does not apply.




363. Initialization of class from self

Section: 12.6.1  class.expl.init     Status: NAD     Submitter: Sergey P. Derevyago     Date: 11 July 2002

Is the following code well-formed?

 struct A { /* */ };

 int main()
 {
  A a=a;
 }

Note, that { int a=a; } is pretty legal.

And if so, what is the semantics of the self-initialization of UDT? For example

 #include <stdio.h>

 struct A {
        A()           { printf("A::A() %p\n",            this);     }
        A(const A& a) { printf("A::A(const A&) %p %p\n", this, &a); }
        ~A()          { printf("A::~A() %p\n",           this);     }
 };

 int main()
 {
  A a=a;
 }

can be compiled and prints:

A::A(const A&) 0253FDD8 0253FDD8
A::~A() 0253FDD8

(on some implementations).

Notes from October 2002 meeting:

3.8  basic.life paragraph 6 indicates that the references here are valid. It's permitted to take the address of a class object before it is fully initialized, and it's permitted to pass it as an argument to a reference parameter as long as the reference can bind directly. Except for the failure to cast the pointers to void * for the %p in the printfs, these examples are standard-conforming.




307. Initialization of a virtual base class subobject

Section: 12.7  class.cdtor     Status: NAD     Submitter: Andrei Iltchenko     Date: 31 Aug 2001

The second paragraph of section 12.7  class.cdtor contains the following text:

To explicitly or implicitly convert a pointer (an lvalue) referring to an object of class X to a pointer (reference) to a direct or indirect base class B of X, the construction of X and the construction of all of its direct or indirect bases that directly or indirectly derive from B shall have started and the destruction of these classes shall not have completed, otherwise the conversion results in undefined behavior.

Now suppose we have the following piece of code:

    struct  a  {
       a() :  m_a_data(0)  {   }
       a(const a& rhsa) :  m_a_data(rhsa.m_a_data)  {   }
       int   m_a_data;
    };

    struct  b :  virtual a  {
       b() :  m_b_data(0)  {   }
       b(const b& rhsb) :  a(rhsb),  m_b_data(rhsb.m_b_data)  {   }
       int   m_b_data;
    };

    struct  c :  b  {
       c() :  m_c_data(0)  {   }
       c(const c& rhsc) :  a(rhsc),// Undefined behaviour when constru-
                                   // cting an object of type 'c'
                           b(rhsc),  m_c_data(rhsc.m_c_data)  {   }
       int   m_c_data;
    };

    int  main()
    {   c   ac1,   ac2(ac1);   }

The problem with the above snippet is that when the value 'ac2' is being created and its construction gets started, c's copy constructor has first to initialize the virtual base class subobject 'a'. Which requires that the lvalue expression 'rhsc' be converted to the type of the parameter of a's copy constructor, which is 'const a&'. According to the wording quoted above, this can be done without undefined behaviour if and only if b's construction has already started, which is not possible since 'a', being a virtual base class, has to be initialized first by a constructor of the most derived object (12.6.2  class.base.init).

The issue could in some cases be alleviated when 'c' has a user-defined copy constuctor. The constructor could default-initialize its 'a' subobject and then initialize a's members as needed taking advantage of the latitude given in paragraph 2 of 12.6.2  class.base.init.

But if 'c' ends up having the implicitly-defined copy constuctor, there's no way to evade undefined behaviour.

    struct  c :  b  {
       c() :  m_c_data(0)  {   }
       int   m_c_data;
    };

    int  main()
    {   c   ac1,   ac2(ac1);   }

Paragraph 8 of 12.8  class.copy states

The implicitly-defined copy constructor for class X performs a memberwise copy of its subobjects. The order of copying is the same as the order of initialization of bases and members in a user-defined constructor (see 12.6.2  class.base.init). Each subobject is copied in the manner appropriate to its type:

Which effectively means that the implicitly-defined copy constructor for 'c' will have to initialize its 'a' base class subobject first and that must be done with a's copy constructor, which will always require a conversion of an lvalue expression of type 'const c' to an lvalue of type 'const a&'. The situation would be the same if all the three classes shown had implicitly-defined copy constructors.

Suggested resolution:

Prepend to paragraph 2 of 12.7  class.cdtor the following:

Unless the conversion happens in a mem-initializer whose mem-initializer-id designates a virtual base class of X, to explicitly or implicitly convert ...

Notes from the 10/01 meeting:

There is no problem in this example. ac1 is fully initialized before it is used in the initialization of ac2.




26. Copy constructors and default arguments

Section: 12.8  class.copy     Status: NAD     Submitter: Daveed Vandevoorde     Date: 22 Sep 1997

The working paper is quite explicit about

    struct X {
         X(X, X const& = X());
    };
being illegal (because of the chicken & egg problem wrt copying.)

Shouldn't it be as explicit about the following?

    struct Y {
        Y(Y const&, Y = Y());
    };
Rationale: There is no need for additional wording. This example leads to a program which either fails to compile (due to resource limits on recursive inlining) or fails to run (due to unterminated recursion). In either case the implementation may generate an error when the program is compiled.


356. Wording of behavior of generated copy constructor for scalar members

Section: 12.8  class.copy     Status: NAD     Submitter: Steve Clamage     Date: 25 May 2002

Section 12.8  class.copy paragraph 8 says the compiler-generated copy constructor copies scalar elements via the built-in assignment operator. Seems inconsistent. Why not the built-in initialization?

Notes from October 2002 meeting:

The Core Working Group believes this should not be changed. The standard already mentions built-in operators and the assignment operator does clearly define what must be done for scalar types. There is currently no concept of built-in initialization.




444. Overriding and the generated copy assignment operator

Section: 12.8  class.copy     Status: NAD     Submitter: Kerch Holt     Date: 26 Nov 2003

EDG (and g++, for that matter) picks the explicit copy assignment operator, which we think is wrong in this case:

#include <stdio.h>
struct D;   // fwd declaration
struct B {
    D& operator=(D&);
};
struct D : B {
    D() {}
    D(int ii) { s = ii; }
    using B::operator=;
    int s;
};
int main() {
    D od, od1(10);
    od = od1; // implicit D::operator=(D&) called, not BASE::operator=(D&)
}
D& B::operator=(D& d) { 
    printf("B::operator called\n");
    return d;
}

If you look at 12.8  class.copy paragraph 10 it explicitly states that in such a case the "using B::operator=" will not be considered.

Steve Adamczyk: The fact that the operator= you decl