Document number:  J16/07-0343 = WG21 N2473
Date:  2007-12-10
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 Active Issues, Revision 52


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

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

Section references in this document reflect the section numbering of document J16/07-0331 = WG21 N2461.

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

Some issues represent potential defects in the ISO/IEC IS 14882:2003 document and corrected defects in the earlier ISO/IEC 14882:1998 document; others refer to text in the working draft for the next revision of the C++ language, commonly known as C++0x, and not to any Standard text. Issues are not necessarily formal ISO Defect Reports (DRs). While some issues will eventually be elevated to DR status, others will be disposed of in other ways. (See Issue Status below.)

The most current public version of this document can be found at http://www.open-std.org/jtc1/sc22/wg21. Requests for further information about these documents should include the document number, reference ISO/IEC 14882:2003, and be submitted to the InterNational Committee for Information Technology Standards (INCITS), 1250 Eye Street NW, Suite 200, Washington, DC 20005, USA.

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


Revision History

Issue status

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

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

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

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

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

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

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

WP: A DR issue whose resolution is reflected in the current Working Paper. The Working Paper is a draft for a future version of the Standard.

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

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

Extension: The working group has reached consensus that the issue is not a defect in the Standard but is a request for an extension to the language. The working group expresses no opinion on the merits of an issue with this status; however, the issue will be maintained on the list for possible future consideration as an extension proposal.


Issues with "Ready" Status




Issues with "Review" Status


542. Value initialization of arrays of POD-structs

Section: 12.6  [class.init]     Status: review     Submitter: Alisdair Meredith     Date: 27 October 2005

12.6 [class.init] paragraph 2 says,

When an array of class objects is initialized (either explicitly or implicitly), the constructor shall be called for each element of the array, following the subscript order;

That implies that, given

    struct POD {
      int x;
    };

    POD data[10] = {};

this should call the implicitly declared default ctor 10 times, leaving 10 uninitialized ints, rather than value initialize each member of data, resulting in 10 initialized ints (which is required by 8.5.1 [dcl.init.aggr] paragraph 7).

I suggest rephrasing along the lines:

When an array is initialized (either explicitly or implicitly), each element of the array shall be initialized in turn, following the subscript order;

This would allow for PODs and other classes with a dual nature under value/default initialization, and cover copy initialization for arrays too.

Proposed resolution (October, 2006):

Change 12.6 [class.init] paragraph 3 as follows:

When an array of class objects is initialized (either explicitly or implicitly) and the elements are initialized by constructor, the constructor shall be called for each element of the array, following the subscript order; see 8.3.4 [dcl.array].



495. Overload resolution with template and non-template conversion functions

Section: 13.3.3  [over.match.best]     Status: review     Submitter: Nathan Sidwell     Date: 20 Dec 2004

The overload resolution rules for ranking a template against a non-template function differ for conversion functions in a surprising way. 13.3.3 [over.match.best] lists four checks, the last three concern this report. For the non-conversion operator case, checks 2 and 3 are applicable, whereas for the conversion operator case checks 3 and 4 are applicable. Checks 2 and 4 concern the ranking of argument and return value conversion sequences respectively. Check 3 concerns only the templatedness of the functions being ranked, and will prefer a non-template to a template. Notice that this check happens after argument conversion sequence ranking, but before return value conversion sequence ranking. This has the effect of always selecting a non-template conversion operator, as the following example shows:

    struct C
    {
      inline operator int () { return 1; }
      template <class T> inline operator T () { return 0; }
    };

    inline long f (long x) { return x; }

    int
    main (int argc, char *argv[])
    {
      return f (C ());
    }

The non-templated C::operator int function will be selected, rather than the apparently better C::operator long<long> instantiation. This is a surprise, and resulted in a bug report where the user expected the template to be selected. In addition some C++ compilers have implemented the overload ranking as if checks 3 and 4 were transposed.

Is this ordering accidental, or is there a rationale?

Notes from the April, 2005 meeting:

The CWG agreed that the template/non-template distinction should be the final tie-breaker.

Proposed resolution (March, 2007):

In the second bulleted list of 13.3.3 [over.match.best] paragraph 1, move the second and third bullets to the end of the list, to read as follows:




603. Type equivalence and unsigned overflow

Section: 14.4  [temp.type]     Status: review     Submitter: James Widman     Date: 3 November 2006

One of the requirements for two template-ids to refer to the same class or function (14.4 [temp.type] paragraph 1) is that

If we have some template of the form

  template <unsigned char c> struct A;

does this imply that A<'\001'> and A<257> (for an eight-bit char) refer to different specializations?

Jens Maurer: Looks like it should say something like, “their corresponding converted non-type template arguments of integral or enumeration type have identical values.”

Proposed resolution (April, 2007):

The change to 14.4 [temp.type] paragraph 1 shown in document J16/07-0118 = WG21 N2258, in which the syntactic non-terminal template-argument is changed to the English term “template argument” is sufficient to remove the confusion about whether the value before or after conversion is used in matching template-ids.




588. Searching dependent bases of classes local to function templates

Section: 14.6.2  [temp.dep]     Status: review     Submitter: James Widman     Date: 21 June 2006

14.6.2 [temp.dep] paragraph 3 reads,

In the definition of a class template or a member of a class template, if a base class of the class template depends on a template-parameter, the base class scope is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member.

This wording applies only to definitions of class templates and members of class templates. That would make the following program ill-formed (but it probably should be well-formed):

    struct B{ void f(int); };

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

    template<class T> void g() {
       struct B{ void f(); };
       struct A: D<T> {
           B m;
       };
       A a;
       a.m.f(); // Presumably, we want ::g()::B::f(), not ::B::f(int)
    }

    int main () {
       g<int>();
       return 0;
    }

I suspect the wording should be something like

In the definition of a class template or a class defined (directly or indirectly) within the scope of a class template or function template, if a base class...

That should also include deeply nested classes in templates, local classes of non-template member functions of member classes of class templates, etc.

Proposed resolution (October, 2006):

Change 14.6.2 [temp.dep] paragraph 3 as follows:

In the definition of a class or class template or a member of a class template, if a base class of the class template depends on a template-parameter, the base class scope is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member.



488. Local types, overload resolution, and template argument deduction

Section: 14.8.2  [temp.deduct]     Status: review     Submitter: Mark Mitchell     Date: 24 Nov 2004

It is not clear how to handle the following example:

    struct S {
        template <typename T> S(const T&);
    };
    void f(const S&);
    void f(int);
    void g() {
        enum E { e };
        f(e);    // ill-formed?
    }

Three possibilities suggest themselves:

  1. Fail during overload resolution. In order to perform overload resolution for the call to f, the declaration of the required specialization of the S constructor must be instantiated. This instantiation uses a local type and is thus ill-formed (14.3.1 [temp.arg.type] paragraph 2), rendering the example as a whole ill-formed, as well.

  2. Treat this as a type-deduction failure. Although it is not listed currently among the causes of type-deduction failure in 14.8.2 [temp.deduct] paragraph 2, it could plausibly be argued that instantiating a function declaration with a local type as a template type-parameter falls under the rubric of “If a substitution in a template parameter or in the function type of the function template results in an invalid type” and thus should be a type-deduction failure. The result would be that the example is well-formed because f(const S&) would be removed from the list of viable functions.

  3. Fail only if the function selected by overload resolution requires instantiation with a local type. This approach would require that the diagnostic resulting from the instantiation of the function type during overload resolution be suppressed and either regenerated or regurgitated once overload resolution is complete. (The example would be well-formed under this approach because f(int) would be selected as the best match.)

(See also issue 489.)

Notes from the April, 2005 meeting:

The question in the original example was whether there should be an error, even though the uninstantiable template was not needed for calling the best-matching function. The broader issue is whether a user would prefer to get an error or to call a “worse” non-template function in such cases. For example:

    template<typename T> void f(T);
    void f(int);
    void g() {
        enum E { e };
        f(e);    // call f(int) or get an error?
    }

It was observed that the type deduction rules are intended to model, albeit selectively, the other rules of the language. This would argue in favor of the second approach, a type-deduction failure, and the consensus of the group was that the incremental benefit of other approaches was not enough to outweigh the additional complexity of specification and implementation.

Proposed resolution (October, 2005):

Add a new sub-bullet following bullet 3, sub-bullet 7 ("Attempting to give an invalid type to a non-type template parameter") of 14.8.2 [temp.deduct] paragraph 2:

Additional note (December, 2005):

The Evolution Working Group is currently considering an extension that would effectively give linkage to some (but perhaps not all) types that currently have no linkage. If the proposed resolution above is adopted and then later a change along the lines that the EWG is considering were also adopted, the result would be a silent change in the result of overload resolution, because the newly-acceptable specializations would become part of the overload set. It is not clear whether that possibility is sufficient reason to delay adoption of this resolution or not.

Notes from the April, 2007 meeting:

The Evolution Working Group is now actively pursuing an extension that would allow local and/or nameless types to be used as template arguments, so this resolution will be held in abeyance until the outcome of that proposal is known.




575. Criteria for deduction failure

Section: 14.8.2  [temp.deduct]     Status: review     Submitter: James Widman     Date: 19 April 2006

The last two sentences of 14.8.2 [temp.deduct] paragraph 5 read:

When all template arguments have been deduced or obtained from default template arguments, all uses of template parameters in non-deduced contexts are replaced with the corresponding deduced or default argument values. If the substitution results in an invalid type, as described above, type deduction fails.

Shouldn't the substitution occur for all uses of the parameters, so that any of them could result in deduction failure?

Proposed resolution (October, 2006):

Change 14.8.2 [temp.deduct] paragraph 5 as follows:

...When all template arguments have been deduced or obtained from default template arguments, all uses of template parameters in non-deduced contexts the function type are replaced with the corresponding deduced or default argument values. If the substitution results in an invalid type, as described above, type deduction fails.



499. Throwing an array of unknown size

Section: 15.1  [except.throw]     Status: review     Submitter: Mike Miller     Date: 19 Jan 2005

According to 15.1 [except.throw] paragraph 3,

The type of the throw-expression shall not be an incomplete type, or a pointer to an incomplete type other than (possibly cv-qualified) void.

This disallows cases like the following, because str has an incomplete type (an array of unknown size):

    extern const char str[];
    void f() {
        throw str;
    }

The array-to-pointer conversion is applied to the operand of throw, so there's no problem creating the exception object, which is the reason for the restriction on incomplete types. I believe this case should be permitted.

Notes from the April, 2005 meeting:

The CWG agreed that the example should be permitted. Note that the reference to throw-expression in the cited text is incorrect; a throw-expression includes the throw keyword and is always of type void. This wording problem is addressed in the proposed resolution for issue 475.

Proposed resolution (October, 2006)

Change 15.1 [except.throw] paragraph 3 as indicated:

...The type of the throw-expression shall not If the type of the exception object would be an incomplete type, or a pointer to an incomplete type other than (possibly cv-qualified) void the program is ill-formed...



592. Exceptions during construction of local static objects

Section: 15.2  [except.ctor]     Status: review     Submitter: Alisdair Meredith     Date: 30 August 2006

According to 15.2 [except.ctor] paragraph 2,

An object that is partially constructed or partially destroyed will have destructors executed for all of its fully constructed subobjects, that is, for subobjects for which the principal constructor (12.6.2 [class.base.init]) has completed execution and the destructor has not yet begun execution. Similarly, if the non-delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object's destructor will be invoked. Should a constructor for an element of an automatic array throw an exception, only the constructed elements of that array will be destroyed.

The requirement for destruction of array elements explicitly applies only to automatic arrays, and one might conclude from the context that only automatic class objects are in view as well, although that is not explicitly stated. What about local static arrays and class objects? Are they intended also to be subject to the requirement that fully-constructed subobjects are to be destroyed?

Proposed resolution (October, 2006):

Change 15.2 [except.ctor] paragraph 2 as follows:

An object that is partially constructed or partially destroyed will have destructors executed for all of its fully constructed subobjects, that is, for subobjects for which the principal constructor (12.6.2 [class.base.init]) has completed execution and the destructor has not yet begun execution. Similarly, if the non-delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object’s destructor will be invoked. Should a constructor for an element of an automatic array throw an exception, only the constructed elements of that array will be destroyed. If the object or array was allocated in a new-expression, the matching deallocation function (3.7.3.2 [basic.stc.dynamic.deallocation], 5.3.4 [expr.new], 12.5 [class.free]), if any, is called to free the storage occupied by the object.



533. Special treatment for C-style header names

Section: 16.2  [cpp.include]     Status: review     Submitter: Jens Maurer     Date: 4 October 2005

In language imported directly from the C Standard, 16.2 [cpp.include] paragraph 5 says,

The implementation provides unique mappings for sequences consisting of one or more nondigits (2.10 [lex.name]) followed by a period (.) and a single nondigit.

This is clearly intended to support C header names like stdio.h. However, C++ has header names like cstdio that do not conform to this pattern but still presumably require “unique mappings.”

Proposed resolution (April, 2006):

Change 16.2 [cpp.include] paragraph 5 as indicated:

The implementation provides unique mappings between the delimited sequence and the external source file name for sequences consisting of one or more nondigits or digits (2.10 [lex.name]), optionally followed by a period (.) and a single nondigit...

(Clark Nelson will discuss this revision with WG14.)

Additional notes (October, 2006):

WG14 takes no position on this proposed change.




572. Standard conversions for non-built-in types

Section: 4  [conv]     Status: review     Submitter: Jens Maurer     Date: 6 April 2006

4 [conv] paragraph 1 says,

Standard conversions are implicit conversions defined for built-in types.

However, enumeration types (which take part in the integral promotions) and class types (which take part in the lvalue-to-rvalue conversion) are not “built-in” types, so the definition of “standard conversions” is wrong.

Proposed resolution (October, 2006):

Change 4 [conv] paragraph 1 as follows:

Standard conversions are implicit conversions defined for built-in types with built-in meaning...



342. Terminology: "indirection" versus "dereference"

Section: 5.3  [expr.unary]     Status: review     Submitter: Jason Merrill     Date: 7 Oct 2001

Split off from issue 315.

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

Proposed resolution (October, 2006):

  1. Change 5.3.1 [expr.unary.op] paragraph 1 as follows:

  2. The unary * operator performs indirection dereferencing: the expression to which it is applied shall be a pointer...
  3. Change 8.3.4 [dcl.array] paragraph 8 as follows:

  4. The results are added and indirection dereferencing applied to yield an array (of five integers), which in turn is converted to a pointer to the first of the integers.
  5. Change 8.3.5 [dcl.fct] paragraph 9 as follows:

  6. The binding of *fpi(int) is *(fpi(int)), so the declaration suggests, and the same construction in an expression requires, the calling of a function fpi, and then using indirection dereferencing through the (pointer) result to yield an integer. In the declarator (*pif)(const char*, const char*), the extra parentheses are necessary to indicate that indirection dereferencing through a pointer to a function yields a function, which is then called.
  7. Change Table 45 in 20.4.7.4 [lib.meta.trans.ptr] as follows:

  8. The member typedef type shall be the same as T, except any top level indirection dereferencing has been removed.
  9. Change the index for * and “dereferencing” no longer to refer to “indirection.”

[Drafting note: 26.5.9.2 [lib.indirect.array.assign] requires no change. Many more places in the current wording use “dereferencing” than “indirection.”]




288. Misuse of "static type" in describing pointers

Section: 5.3.5  [expr.delete]     Status: review     Submitter: James Kuyper     Date: 19 May 2001

For delete expressions, 5.3.5 [expr.delete] paragraph 1 says

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

However, paragraph 3 of that same section says:

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

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

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

Suggested resolution:

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

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

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

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

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

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

This should be changed to

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

The same problem occurs later, when it says that the

static and dynamic types of the object shall be identical

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

Footnote 104 says that

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

This would need to be changed to

the delete-expression's dereferenced operand

Proposed resolution (October, 2006):

  1. Change 5.3.5 [expr.delete] paragraph 3 as follows:

  2. In the first alternative (delete object), if the static type of the dereferenced (5.3.1 [expr.unary.op]) operand is different from its dynamic type, the static type shall be a base class of the dereferenced operand’s dynamic type and the static type shall have a virtual destructor or the behavior is undefined. In the second alternative (delete array) if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined.
  3. Change the footnote in 12.5 [class.free] paragraph 4 as follows:

  4. A similar provision is not needed for the array version of operator delete because 5.3.5 [expr.delete] requires that in this situation, the static type of the delete-expression’s operand object to be deleted be the same as its dynamic type.
  5. Change the footnote in 12.5 [class.free] paragraph 5 as follows:

  6. If the static type in the delete-expression of the object to be deleted is different from the dynamic type and the destructor is not virtual the size might be incorrect, but that case is already undefined; see 5.3.5 [expr.delete].

[Drafting notes: No change is required for 10.3 [class.virtual] paragraph 7 because “the type of the pointer” includes the pointed-to type. No change is required for 12.5 [class.free] paragraph 4 because “...used to deallocate a class object whose static type...” already refers to the object (and not the operand expression).]




569. Spurious semicolons at namespace scope should be allowed

Section: 7  [dcl.dcl]     Status: review     Submitter: Matt Austern     Date: 20 March 2006

The grammar in 7 [dcl.dcl] paragraph 1 says that a declaration-seq is either declaration or declaration-seq declaration. Some declarations end with semicolons and others (e.g. function definitions and namespace declarations) don't. This means that users who put a semicolon after every declaration are technically writing ill-formed code. The trouble is that in this respect the standard is out of sync with reality. It's convenient to allow semicolons after every declaration, and there's no implementation difficulty in doing so. All existing compilers accept this, except in extra-pedantic mode. When all implementations disagree with the standard, it's time for the standard to change.

Suggested resolution:

In the grammar in 7 [dcl.dcl] paragraph 11, change the second line in the definition of declaration-seq to

Proposed resolution (October, 2006):

  1. Add the indicated lines to the grammar definitions in 7 [dcl.dcl] paragraph 1:

  2. declaration:

    ...

    static_assert-declaration:


    empty-declaration:
      ;
  3. Add the following as a new paragraph after 7 [dcl.dcl] paragraph 4:

  4. An empty-declaration has no effect.



482. Qualified declarators in redeclarations

Section: 8.3  [dcl.meaning]     Status: review     Submitter: Daveed Vandevoorde     Date: 03 Nov 2004

According to 8.3 [dcl.meaning] paragraph 1,

A declarator-id shall not be qualified except for the definition of a member function (9.3 [class.mfct]) or static data member (9.4 [class.static]) outside of its class, the definition or explicit instantiation of a function or variable member of a namespace outside of its namespace, or the definition of a previously declared explicit specialization outside of its namespace, or the declaration of a friend function that is a member of another class or namespace (11.4 [class.friend]). When the declarator-id is qualified, the declaration shall refer to a previously declared member of the class or namespace to which the qualifier refers...

This restriction prohibits examples like the following:

    void f();
    void ::f();        // error: qualified declarator

    namespace N {
      void f();
      void N::f() { }  // error: qualified declarator
    }

There doesn't seem to be any good reason for disallowing such declarations, and a number of implementations accept them in spite of the Standard's prohibition. Should the Standard be changed to allow them?

Notes from the April, 2006 meeting:

In discussing issue 548, the CWG agreed that the prohibition of qualified declarators inside their namespace should be removed.

Proposed resolution (October, 2006):

Remove the indicated words from 8.3 [dcl.meaning] paragraph 1:

...An unqualified-id occurring in a declarator-id shall be a simple identifier except for the declaration of some special functions (12.3 [class.conv], 12.4 [class.dtor], 13.5 [over.oper]) and for the declaration of template specializations or partial specializations (). A declarator-id shall not be qualified except for the definition of a member function (9.3 [class.mfct]) or static data member (9.4 [class.static]) outside of its class, the definition or explicit instantiation of a function or variable member of a namespace outside of its namespace, or the definition of a previously declared explicit specialization outside of its namespace, or the declaration of a friend function that is a member of another class or namespace (11.4 [class.friend]). When the declarator-id is qualified, the declaration shall refer to a previously declared member of the class or namespace to which the qualifier refers, and the member shall not have been introduced by a using-declaration in the scope of the class or namespace nominated by the nested-name-specifier of the declarator-id...

[Drafting note: The omission of “outside of its class” here does not give permission for redeclaration of class members; that is still prohibited by 9.2 [class.mem] paragraph 1. The removal of the enumeration of the kinds of declarations in which a qualified-id can appear does allow a typedef declaration to use a qualified-id, which was not permitted before; if that is undesirable, the prohibition can be reinstated here.]






Issues with "Drafting" Status


612. Requirements on a conforming implementation

Section: 1.9  [intro.execution]     Status: drafting     Submitter: Clark Nelson     Date: 23 January 2007

The execution requirements on a conforming implementation are described twice in the Standard, once in 1.9 [intro.execution] paragraphs 5-6 and again in paragraph 11. These descriptions differ in at least a couple of important ways:

The most significant discrepancy has to do with the way output is described. In paragraph 11, the least requirements are described in terms of data written at program termination, clearly allowing arbitrary buffering, whereas in paragraph 6, the observable behavior is described in terms of calls to I/O functions. For example, there are compilers which transform a call to printf with a single argument into a call to fputs. That's valid under paragraph 11, but not under paragraph 6.

Also, in paragraph 6, volatile accesses and I/O operations are included in a single sequence, suggesting that they are equally constrained by sequencing requirements, whereas in paragraph 11, they are clearly not.

There are also editorial discrepancies that should be cleaned up.




639. What makes side effects “different” from one another?

Section: 1.9  [intro.execution]     Status: drafting     Submitter: James Widman     Date: 26 July 2007

Is the behavior undefined in the following example?

    void f() {
         int n = 0;
         n = --n;
    }

1.9 [intro.execution] paragraph 16 says,

If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.

It's not clear to me whether the two side-effects in n=--n are “different.” As far as I can tell, it seems that both side-effects involve the assignment of -1 to n, which in a sense makes them non-“different.” But I don't know if that's the intent. Would it be better to say “another” instead of “a different?”

On a related note, can we include this example to illustrate?

    void f( int, int );
    void g( int a ) { f( a = -1, a = -1 ); } // Undefined?



608. Determining the final overrider of a virtual function

Section: 10.3  [class.virtual]     Status: drafting     Submitter: Mike Miller     Date: 7 December 2006

According to 10.3 [class.virtual] paragraph 2:

Then in any well-formed class, for each virtual function declared in that class or any of its direct or indirect base classes there is a unique final overrider that overrides that function and every other overrider of that function. The rules for member lookup (10.2 [class.member.lookup]) are used to determine the final overrider for a virtual function in the scope of a derived class but ignoring names introduced by using-declarations.

I think that description is wrong on at least a couple of counts. First, consider the following example:

    struct A { virtual void f(); };
    struct B: A { };
    struct C: A { void f(); };
    struct D: B, C { };

What is the “unique final overrider” of A::f() in D? According to 10.3 [class.virtual] paragraph 2, we determine that by looking up f in D using the lookup rules in 10.2 [class.member.lookup]. However, that lookup determines that f in D is ambiguous, so there is no “unique final overrider” of A::f() in D. Consequently, because “any well-formed class” must have such an overrider, D must be ill-formed.

Of course, we all know that D is not ill-formed. In fact, 10.2 [class.member.lookup] paragraph 10 contains an example that illustrates exactly this point:

struct A {
    virtual void f();
};
struct B1 : A {     // note non-virtual derivation
    void f();
};
struct B2 : A {
    void f();
};
struct D : B1, B2 { // D has two separate A subobjects
};

In class D above there are two occurrences of class A and hence two occurrences of the virtual member function A::f. The final overrider of B1::A::f is B1::f and the final overrider of B2::A::f is B2::f.

It appears that the requirement for a “unique final overrider” in 10.3 [class.virtual] paragraph 2 needs to say something about sub-objects. Whatever that “something” is, you can't just say “look up the name in the derived class using 10.2 [class.member.lookup].”

There's another problem with using the 10.2 [class.member.lookup] lookup to specify the final overrider: name lookup just looks up the name, while the overriding relationship is based not only on the name but on a matching parameter-type-list and cv-qualification. To illustrate this point:

    struct X {
        virtual void f();
    };
    struct Y: X {
        void f(int);
    };
    struct Z: Y { };

What is the “unique final overrider” of X::f() in A? Again, 10.3 [class.virtual] paragraph 2 says you're supposed to look up f in Z to find it; however, what you find is Y::f(int), not X::f(), and that's clearly wrong.




580. Access in template-parameters of member and friend definitions

Section: 11  [class.access]     Status: drafting     Submitter: John Spicer     Date: 16 May 2006

The resolution of issue 372 leaves unclear whether the following are well-formed or not:

    class C {
        typedef int I;                // private
        template <int> struct X;
        template <int> friend struct Y;
    }

    template <C::I> struct C::X { };  // C::I accessible to member?

    template <C::I> struct Y { };     // C::I accessible to friend?

Presumably the answer to both questions is “yes,”, but the new wording does not address template-parameters.




585. Friend template template parameters

Section: 11.4  [class.friend]     Status: drafting     Submitter: James Widman     Date: 15 June 2006

After the adoption of the wording for extended friend declarations, we now have this new paragraph in 11.4 [class.friend]:

A friend declaration that does not declare a function shall have one of the following forms:

But what about friend class templates? Should the following examples compile in C++0x?

    template< template <class> class T >
         struct A{ friend T; };

    template< class > struct C;
    struct B{ friend C; };



472. Casting across protected inheritance

Section: 11.5  [class.protected]     Status: drafting     Submitter: Mike Miller     Date: 16 Jun 2004

Does the restriction in 11.5 [class.protected] apply to upcasts across protected inheritance, too? For instance,

    struct B {
        int i;
    };
    struct I: protected B { };
    struct D: I {
        void f(I* ip) {
            B* bp = ip;    // well-formed?
            bp->i = 5;     // aka "ip->i = 5;"
        }
    };

I think the rationale for the 11.5 [class.protected] restriction applies equally well here — you don't know whether ip points to a D object or not, so D::f can't be trusted to treat the protected B subobject consistently with the policies of its actual complete object type.

The current treatment of “accessible base class” in 11.2 [class.access.base] paragraph 4 clearly makes the conversion from I* to B* well-formed. I think that's wrong and needs to be fixed. The rationale for the accessibility of a base class is whether “an invented public member” of the base would be accessible at the point of reference, although we obscured that a bit in the reformulation; it seems to me that the invented member ought to be considered a non-static member for this purpose and thus subject to 11.5 [class.protected].

(See also issues 385 and 471.).

Notes from October 2004 meeting:

The CWG tentatively agreed that casting across protective inheritance should be subject to the additional restriction in 11.5 [class.protected].




462. Lifetime of temporaries bound to comma expressions

Section: 12.2  [class.temporary]     Status: drafting     Submitter: Steve Adamczyk     Date: April 2004

Split off from issue 86.

Should binding a reference to the result of a "," operation whose second operand is a temporary extend the lifetime of the temporary?

  const SFileName &C = ( f(), SFileName("abc") );

Notes from the March 2004 meeting:

We think the temporary should be extended.

Proposed resolution (October, 2004):

Change 12.2 [class.temporary] paragraph 2 as indicated:

... In all these cases, the temporaries created during the evaluation of the expression initializing the reference, except the temporary that is the overall result of the expression [Footnote: For example, if the expression is a comma expression (5.18 [expr.comma]) and the value of its second operand is a temporary, the reference is bound to that temporary.] and to which the reference is bound, are destroyed at the end of the full-expression in which they are created and in the reverse order of the completion of their construction...

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

Notes from the April, 2005 meeting:

The CWG suggested a different approach from the 10/2004 resolution, leaving 12.2 [class.temporary] unchanged and adding normative wording to 5.18 [expr.comma] specifying that, if the result of the second operand is a temporary, that temporary is the result of the comma expression as well.




650. Order of destruction for temporaries bound to the returned value of a function

Section: 12.2  [class.temporary]     Status: drafting     Submitter: Mike Miller     Date: 14 Aug 2007

In describing the order of destruction of temporaries, 12.2 [class.temporary] paragraphs 4-5 say,

There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression...

The second context is when a reference is bound to a temporary... A temporary bound to the returned value in a function return statement (6.6.3 [stmt.return]) persists until the function exits.

The following example illustrates the issues here:

    struct S {
        ~S();
    };

    S& f() {
        S s;            // #1
        return
            (S(),       // #2
             S());      // #3
    }

If the return type of f() were simply S instead of S&, the two temporaries would be destroyed at the end of the full-expression in the return statement in reverse order of their construction, followed by the destruction of the variable s at block-exit, i.e., the order of destruction of the S objects would be #3, #2, #1.

Because the temporary #3 is bound to the returned value, however, its lifetime is extended beyond the end of the full-expression, so that S object #2 is destroyed before #3.

There are two problems here. First, it is not clear what “until the function exits” means. Does it mean that the temporary is destroyed as part of the normal block-exit destructions, as described in 6.6 [stmt.jump] paragraph 2:

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

Or is the point of destruction for #3 after the destruction of the “constructed objects... that are declared [emphasis mine] in that scope” (because temporary #3 was not “declared”)? I.e., should #3 be destroyed before or after #1?

The other problem is that, according to the recollection of one of the participants responsible for this wording, the intent was not to extend the lifetime of #3 but simply to emphasize that its lifetime ended before the function returned, i.e., that the result of f() could not be used without causing undefined behavior. This is also consistent with the treatment of this example by many implementations; MSVC++, g++, and EDG all destroy #3 before #2.

Suggested resolution:

Change 12.2 [class.temporary] paragraph 5 as indicated:

A The lifetime of a temporary bound to the returned value in a function return statement (6.6.3 [stmt.return]) persists until the function exits is not extended; it is destroyed at the end of the full-expression in the return statement.



399. Destructor lookup redux

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

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

Issue 244 says:

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

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

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

This is a test case that illustrates the issue:

  struct A {
    typedef A C;
  };

  typedef A B;

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

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

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

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

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

Finally, what about cases like:

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

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

Sandor Mathe adds :

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

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

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

  typedef S::C D;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Additional note (September, 2004)

The resolution for issue 244 removed the discussion of p->N::~S, where N is a namespace-name. However, the resolution did not make this construct ill-formed; it simply left the semantics undefined. The meaning should either be defined or the construct made ill-formed.

See also issues 305 and 466.




604. Argument list for overload resolution in copy-initialization

Section: 13.3.1.3  [over.match.ctor]     Status: drafting     Submitter: Dawn Perchik     Date: 4 November 2006

According to 13.3.1.3 [over.match.ctor],

When objects of class type are direct-initialized (8.5 [dcl.init]), or copy-initialized from an expression of the same or a derived class type (8.5 [dcl.init])... [the] argument list is the expression-list within the parentheses of the initializer.

However, in copy initialization (using the “=” notation), there need be no parentheses. What is the argument list in that case?




641. Overload resolution and conversion-to-same-type operators

Section: 13.3.2  [over.match.viable]     Status: drafting     Submitter: Nathan Sidwell     Date: 2 Aug 2007

12.3.2 [class.conv.fct] paragraph 1 says,

A conversion function is never used to convert a (possibly cv-qualified) object to the (possibly cv-qualified) same object type (or a reference to it), to a (possibly cv-qualified) base class of that type (or a reference to it), or to (possibly cv-qualified) void.

At what point is this enforced, and how is it enforced?

  1. Does such a user-declared conversion operator participate in overload resolution? Or is it never entered into the overload set?
  2. If it does participate in overload resolution, what happens if it is selected? Is the program ill-formed (and diagnostic required), or is it silently ignored? The above wording doesn't really make it clear.

Consider this test case:

    struct abc;

    struct xyz {
       xyz();

       xyz(xyz &);

       operator xyz& (); // #1
       operator abc& (); // #2
    };

    struct abc : xyz {};

    void foo(xyz &);

    void bar() {
             foo (xyz ());
    }

If such conversion functions are part of the overload set, #1 is a better conversion than #2 to convert the temporary xyz object to a non-const reference required for foo's operand. If such conversion functions are not part of the overload set, then #2 would be selected, and AFAICT the program would be well formed.

If the conversion functions are not part of the overload set, then it would seem one cannot take their address. For instance, adding the following line to the above test case would find no suitable function:

    xyz &(xyz::*ptr) () = &xyz::operator xyz &;

Notes from the October, 2007 meeting:

The intent of 12.3.2 [class.conv.fct] paragraph 1 is that overload resolution not be attempted at all for the listed cases; that is, if the target type is void, the object's type, or a base of the object's type, the conversion is done directly without considering any conversion functions. Consequently, the questions about whether the conversion function is part of the overload set or not are moot. The wording will be changed to make this clearer.




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

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

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

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

  short s;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Notes from the 04/01 meeting:

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




205. Templates and static data members

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

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

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

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

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

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

Notes from the 4/02 meeting:

Daveed Vandevoorde will propose appropriate terminology.




96. Syntactic disambiguation using the template keyword

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

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

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

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

I propose that paragraph 5 be modified to:

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

Notes from 04/00 meeting:

The discussion of this issue revived interest in issues 11 and 109.

Notes from the October 2003 meeting:

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




314. template in base class specifier

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

The EDG front-end accepts:

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

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

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

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

Suggested resolution:

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

Notes from the 4/02 meeting:

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




431. Defect in wording in 14.2

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

Consider this example:

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

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

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

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

But 14.2 [temp.names]/4 contains this text:

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

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

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




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

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

Is this allowed?

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

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

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

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

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

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

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

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

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

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

Notes from the October 2003 meeting:

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

Note (June, 2006):

Mark Mitchell suggested the following example:

    template <int> void g();

    template <typename T>
    struct S {
      static int i[];
      void f();
    };

    template <typename T>
    int S<T>::i[] = { 1 };

    template <typename T>
    void S<T>::f() {
      g<sizeof (i) / sizeof (int)>();
    }

    template <typename T>
    int S<int>::i[] = { 1, 2 };

Which g is called from S<int>::f()?

If the program is valid, then surely one would expect g<2> to be called.

If the program is valid, does S<T>::i have a non-dependent type in S<T>::f? If so, is it incomplete, or is it int[1]? (Here, int[1] would be surprising, since S<int>::i actually has type int[2].)

If the program is invalid, why?

For a simpler example, consider:

    template <typename T>
    struct S {
      static int i[];
      const int N = sizeof (i);
    };

This is only valid if the type of i is dependent, meaning that the sizeof expression isn't evaluated until the class is instantiated.




549. Non-deducible parameters in partial specializations

Section: 14.5.5.1  [temp.class.spec.match]     Status: drafting     Submitter: Martin Sebor     Date: 18 November 2005

In the following example, the template parameter in the partial specialization is non-deducible:

    template <class T> struct A { typedef T U; };
    template <class T> struct C { };
    template <class T> struct C<typename A<T>::U> { };

Several compilers issue errors for this case, but there appears to be nothing in the Standard that would make this ill-formed; it simply seems that the partial specialization will never be matched, so the primary template will be used for all specializations. Should it be ill-formed?

Notes from the April, 2006 meeting:

It was noted that there are similar issues for constructors and conversion operators with non-deducible parameters, and that they should probably be dealt with similarly.




532. Member/nonmember operator template partial ordering

Section: 14.5.6.2  [temp.func.order]     Status: drafting     Submitter: Nathan Sidwell     Date: 16 September 2005

The Standard does not specify how member and nonmember function templates are to be ordered. This question arises with an example like the following:

    struct A {
        template<class T> void operator<<(T&);
    };

    template<class T> struct B { };
    template<class T> void operator<<(A&, B<T>&);

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

The two candidates for “a << b” are:

  1. A::operator<< <B<A> >(B<A>&)
  2. ::operator<< <A>(A&, B<A>&)

How should we treat the implicit this parameter of #1 and the explicit first parameter of #2?

The difference between option 1 and option 2 can be seen in the following example:

    struct A { };

    template<class T> struct B {
        template<typename R> int operator*(R&);   // #1
    };

    template <typename T> int operator*(T&, A&);  // #2

    int main() {
        A a;
        B<A> b;
        b * a;
    }

Should this select #1, select #2, or be ambiguous? Option 1 will select #2, because “A&” is more specialized than “T&”. Option 2 will make this example ambiguous, because “B<A>&” is more specialized than “T&”.

If one were considering two non-member templates,

    template <typename T> int operator*(T&, A&);                 // #2
    template <typename T, typename R> int operator*(B<A>&, R&);  // #3

the current rules would make these unordered. Option 2 thus seems more consistent with this existing behavior.

Notes from the April, 2006 meeting:

The group favored option 2.




560. Use of the typename keyword in return types

Section: 14.6  [temp.res]     Status: drafting     Submitter: Greg Comeau     Date: 11 February 2006

Consider the following example:

    template <class T> struct Outer {
        struct Inner {
            Inner* self();
        };
    };
    template <class T> Outer<T>::Inner*
        Outer<T>::Inner::self() { return this; }

According to 14.6 [temp.res] paragraph 3 (before the salient wording was inadvertently removed, see issue 559),

A qualified-id that refers to a type and in which the nested-name-specifier depends on a template-parameter (14.6.2 [temp.dep]) but does not refer to a member of the current instantiation (14.6.2.1 [temp.dep.type]) shall be prefixed by the keyword typename to indicate that the qualified-id denotes a type, forming a typename-specifier.

Because Outer<T>::Inner is a member of the current instantiation, the Standard does not currently require that it be prefixed with typename when it is used in the return type of the definition of the self() member function. However, it is difficult to parse this definition correctly without knowing that the return type is, in fact, a type, which is what the typename keyword is for. Should the Standard be changed to require typename in such contexts?




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

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

Is this program valid?

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

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

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

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

Notes from the March 2004 meeting:

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




458. Hiding of member template parameters by other members

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

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

Consider

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

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

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

The list in 14.6.1 covers cases like

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

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

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

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

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

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

Am I missing something?

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

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

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

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

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

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

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

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

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

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

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

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

See also issue 459.

Notes from the March 2004 meeting:

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

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

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

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




541. Dependent function types

Section: 14.6.2.2  [temp.dep.expr]     Status: drafting     Submitter: Daveed Vandevoorde     Date: 22 October 2005

14.6.2.2 [temp.dep.expr] paragraph 3 says,

An id-expression is type-dependent if it contains:

This treatment seems inadequate with regard to id-expressions in function calls:

  1. According to 14.6.2.1 [temp.dep.type] paragraph 6,

    A type is dependent if it is
    • ...
    • a compound type constructed from any dependent type...

    This would apply to the type of a member function of a class template if any of its parameters are dependent, even if the return type is not dependent. However, there is no need for a call to such a function to be a type-dependent expression because the type of the expression is known at definition time.

  2. This wording does not handle the case of overloaded functions, some of which might have dependent types (however defined) and others not.




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

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

Suggested resolution: (John Spicer)

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

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

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