Document number:  PL22.16/08-0198 = WG21 N2688
Date:  2008-06-29
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 56


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 PL22.16/08-0981 = WG21 N2588.

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


637. Sequencing rules and example disagree

Section: 1.9  [intro.execution]     Status: ready     Submitter: Ofer Porat     Date: 2 June 2007

In 1.9 [intro.execution] paragraph 16, the following expression is still listed as an example of undefined behavior:

    i = ++i + 1;

However, it appears that the new sequencing rules make this expression well-defined:

  1. The assignment side-effect is required to be sequenced after the value computations of both its LHS and RHS (5.17 [expr.ass] paragraph 1).

  2. The LHS (i) is an lvalue, so its value computation involves computing the address of i.

  3. In order to value-compute the RHS (++i + 1), it is necessary to first value-compute the lvalue expression ++i and then do an lvalue-to-rvalue conversion on the result. This guarantees that the incrementation side-effect is sequenced before the computation of the addition operation, which in turn is sequenced before the assignment side effect. In other words, it yields a well-defined order and final value for this expression.

It should be noted that a similar expression

    i = i++ + 1;

is still not well-defined, since the incrementation side-effect remains unsequenced with respect to the assignment side-effect.

It's unclear whether making the expression in the example well-defined was intentional or just a coincidental byproduct of the new sequencing rules. In either case either the example should be fixed, or the rules should be changed.

Clark Nelson: In my opinion, the poster's argument is perfectly correct. The rules adopted reflect the CWG's desired outcome for issue 222. At the Portland meeting, I presented (and still sympathize with) Tom Plum's case that these rules go a little too far in nailing down required behavior; this is a consequence of that.

One way or another, a change needs to be made, and I think we should seriously consider weakening the resolution of issue 222 to keep this example as having undefined behavior. This could be done fairly simply by having the sequencing requirements for an assignment expression depend on whether it appears in an lvalue context.

James Widman: How's this for a possible re-wording?

In all cases, the side effect of the assignment expression is sequenced after the value computations of the right and left operands. Furthermore, if the assignment expression appears in a context where an lvalue is required, the side effect of the assignment expression is sequenced before its value computation.

Notes from the February, 2008 meeting:

There was no real support in the CWG for weakening the resolution of issue 222 and returning the example to having undefined behavior. No one knew of an implementation that doesn't already do the (newly) right thing for such an example, so there was little motivation to go out of our way to increase the domain of undefined behavior. So the proposed resolution is to change the example to one that definitely does have undependable behavior in existing practice, and undefined behavior under the new rules.

Also, the new formulation of the sequencing rules approved in Oxford contained the wording that by and large resolved issue 222, so with the resolution of this issue, we can also close issue 222.

Proposed resolution (March, 2008):

Change the example in 1.9 [intro.execution] paragraph 16 as follows:

    i = v[i++];             // the behavior is undefined
    i = 7, i++, i++;        // i becomes 9
    i = ++i i++ + 1;        // the behavior is undefined
    i = i + 1;              // the value of i is incremented



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

Section: 1.9  [intro.execution]     Status: ready     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?

Proposed resolution (March, 2008):

Change 1.9 [intro.execution] paragraph 16 as follows:

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

    void f(int, int);
    void g(int i, int* v) {
        i = v[i++];         // the behavior is undefined
        i = 7, i++, i++;    // i becomes 9

        i = ++i + 1;        // the behavior is undefined
        i = i + 1;          // the value of i is incremented

        f(i = -1, i = -1);  // the behavior is undefined
    }

end example] When calling...




677. Deleted operator delete and virtual destructors

Section: 12.4  [class.dtor]     Status: ready     Submitter: Mike Miller     Date: 15 February, 2008

Deallocation functions can't be virtual because they are static member functions; however, according to 12.5 [class.free] paragraph 7, they behave like virtual functions when the class's destructor is virtual:

Since member allocation and deallocation functions are static they cannot be virtual. [Note: however, when the cast-expression of a delete-expression refers to an object of class type, because the deallocation function actually called is looked up in the scope of the class that is the dynamic type of the object, if the destructor is virtual, the effect is the same.

Because the intent is to make any use of a deleted function diagnosable at compile time, a virtual deleted function can neither override nor be overridden by a non-deleted function, as described in 10.3 [class.virtual] paragraph 14:

A function with a deleted definition (8.4 [dcl.fct.def]) shall not override a function that does not have a deleted definition. Likewise, a function that does not have a deleted definition shall not override a function with a deleted definition.

One would assume that a similar kind of prohibition is needed for deallocation functions in a class hierarchy with virtual destructors, but it's not clear that the current specification says that. 8.4 [dcl.fct.def] paragraph 10 says,

A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed.

Furthermore, the deallocation function is looked up at the point of definition of a virtual destructor (12.4 [class.dtor] paragraph 11), and the function found by this lookup is considered to be “used” (3.2 [basic.def.odr] paragraph 2). However, it's not completely clear that this “use” constitutes a “reference” in the sense of 8.4 [dcl.fct.def] paragraph 10, especially in a program in which an object of a type that would call that deallocation function is never deleted.

Suggested resolution:

Augment the list of lookup results from a virtual destructor that render a program ill-formed in 12.4 [class.dtor] paragraph 10 to include a deleted function:

If the result of this lookup is ambiguous or inaccessible, or if the lookup selects a placement deallocation function or a function with a deleted definition (8.4 [dcl.fct.def]), the program is ill-formed.

Proposed resolution (June, 2008):

Change 12.4 [class.dtor] paragraph 10 as follows:

If the result of this lookup is ambiguous or inaccessible, or if the lookup selects a placement deallocation function or a function with a deleted definition (8.4 [dcl.fct.def]), the program is ill-formed.



679. Equivalence of template-ids and operator function templates

Section: 14.4  [temp.type]     Status: ready     Submitter: Richard Corden     Date: 1 March, 2008

In order for two template-ids to refer to the same function, 14.4 [temp.type] paragraph 1, bullet 1 requires that

This makes it impossible for two template-ids referring to operator function templates to be equivalent, because only simple-template-ids have a template-name, and a template-id referring to an operator function template is not a simple-template-id (14.2 [temp.names] paragraph 1).

Suggested resolution:

Change 14.4 [temp.type] paragraph 1, bullet 1 to read,

Proposed resolution (June, 2008):

Change 14.4 [temp.type] paragraph 1, first bullet, as follows:




649. Optionally ill-formed extended alignment requests

Section: 3.11  [basic.align]     Status: ready     Submitter: Mike Miller     Date: 12 Aug 2007

The requirements on an implementation when presented with an alignment-specifier not supported by that implementation in that context are contradictory: 3.11 [basic.align] paragraph 9 says,

If a request for a specific extended alignment in a specific context is not supported by an implementation, the implementation may reject the request as ill-formed. The implementation may also silently ignore the requested alignment.

In contrast, 7.1.7 [dcl.align] paragraph 2, bullet 4 says simply,

with no provision to “silently ignore” the requested alignment. These two passages need to be reconciled.

If the outcome of the reconciliation is to grant implementations the license to accept and ignore extended alignment requests, the specification should be framed in terms of mechanisms that already exist in the Standard, such as undefined behavior and/or conditionally-supported constructs; “ill-formed” is a category that is defined by the Standard, not something that an implementation can decide.

Notes from the February, 2008 meeting:

The consensus was that such requests should be ill-formed and require a diagnostic. However, it was also observed that an implementation need not reject an ill-formed program; the only requirement is that it issue a diagnostic. It would thus be permissible for an implementation to “noisily ignore” (as opposed to “silently ignoring”) an unsupported alignment request.

Proposed resolution (June, 2008):

Change 3.11 [basic.align] paragraph 9 as follows:

If a request for a specific extended alignment in a specific context is not supported by an implementation, the implementation may reject the request as program is ill-formed. The implementation may also silently ignore the requested alignment. [Note: aAdditionally, a request for runtime allocation of dynamic memory storage for which the requested alignment cannot be honored may shall be treated as an allocation failure. end note]



594. Coordinating issues 119 and 404 with delegating constructors

Section: 3.8  [basic.life]     Status: ready     Submitter: Tom Plum     Date: 30 August 2006

In ISO/IEC 14882:2003, the second bullet of 3.8 [basic.life] paragraph 1 reads,

if T is a class type with a non-trivial constructor (12.1 [class.ctor]), the constructor call has completed.

Issue 119 pointed out that aggregate initialization can be used with some classes with a non-trivial implicitly-declared default constructor, and that in such cases there is no call to the object's constructor. The resolution for that issue was to change the previously-cited wording to read,

If T is a class type with a non-trivial constructor (12.1 [class.ctor], the initialization is complete.

Later (but before the WP was revised with the wording from the resolution of issue 119), issue 404 changed the 2003 wording to read,

If T is a class type and the constructor invoked to create the object is non-trivial (12.1 [class.ctor]), the constructor call has completed.

thus reversing the effect of issue 119, whose whole purpose was to cover objects with non-trivial constructors that are not invoked.

Through an editorial error, the post-Redmond draft (N1905) still contained the original 2003 wording that should have been replaced by the resolution of issue 119, in addition to the new wording from the resolution:

if T is a class type and the constructor invoked to create the object is non-trivial (12.1 [class.ctor]), the constructor call has completed. the initialization is complete.

Finally, during the application of the edits for delegating constructors (N1986), this editing error was “fixed” by retaining the original 2003 wording (which was needed for the application of the change specified in N1986), so that the current draft (N2009) reads,

if T is a class type and the constructor invoked to create the object is non-trivial (12.1 [class.ctor]), the principal constructor call 12.6.2 [class.base.init]) has completed.

Because the completion of the call to the principal constructor corresponds to the point at which the object is “fully constructed” (15.2 [except.ctor] paragraph 2), i.e., its initialization is complete, I believe that the exact wording of the issue 119 resolution would be correct and should be restored verbatim.

Proposed resolution (June, 2008):

Change 3.8 [basic.life] paragraph 1 as follows:

The lifetime of an object is a runtime property of the object. An object is said to have non-trivial initialization if it is of a class or aggregate type and it or one of its members is initialized by a constructor other than a trivial default constructor. [Note: Initialization by a trivial copy constructor is non-trivial initialization. —end note] The lifetime of an object of type T begins when:

The lifetime of an object of type T ends when...




634. Conditionally-supported behavior for non-POD objects passed to ellipsis redux

Section: 5.2.2  [expr.call]     Status: ready     Submitter: Howard Hinnant     Date: 6 Jun 2007

Issue 506 changed passing a non-POD class type to an ellipsis from undefined behavior to conditionally-supported behavior. As a result, an implementation could conceivably reject code like the following:

    struct two {char _[2];};

    template <class From, class To>
    struct is_convertible
    {
    private:
            static From f;

            template <class U> static char test(const U&);
            template <class U> static two test(...);
    public:
            static const bool value = sizeof(test<To>(f)) == 1;
    };

    struct A
    {
         A();
    };

    int main()
    {
         const bool b = is_convertible<A,int>::value;  // b == false
    }

This technique has become popular in template metaprogramming, and no non-POD object is actually passed at runtime. Concepts will eliminate much (perhaps not all) of the need for this kind of programming, but legacy code will persist.

Perhaps this technique should be officially supported by allowing implementations to reject passing a non-POD type to ellipsis only if it appears in a potentially-evaluated expression?

Notes from the July, 2007 meeting:

The CWG agreed with the suggestion to allow such calls in unevaluated contexts.

Proposed resolution (September, 2007):

Change 5.2.2 [expr.call] paragraph 7 as follows:

...Passing an a potentially-evaluated argument of non-trivial class type (clause 9 [class]) with no corresponding parameter is conditionally-supported, with implementation-defined semantics...



671. Explicit conversion from a scoped enumeration type to integral type

Section: 5.2.9  [expr.static.cast]     Status: ready     Submitter: Daveed Vandevoorde     Date: 22 December 2007

There appears to be no provision in the Standard for explicit conversion of a value of a scoped enumeration type to an integral type, even though the inverse conversion is permitted. That is,

    enum class E { e };
    static_cast<E>(0);    // #1: OK
    static_cast<int>(e);  // #2: error

This is because values of scope enumeration types (intentionally) cannot be implicitly converted to integral types (4.5 [conv.prom] and 4.7 [conv.integral]) and 5.2.9 [expr.static.cast] was not updated to permit #2, although #1 is covered by paragraph 8.

Proposed resolution (June, 2008):

Add the following as a new paragraph following 5.2.9 [expr.static.cast] paragraph 8:

A value of a scoped enumeration type (7.2 [dcl.enum]) can be explicitly converted to an integral type. The value is unchanged if the original value can be represented by the specified type. Otherwise, the resulting value is unspecified.



659. Alignment of function types

Section: 5.3.6  [expr.alignof]     Status: ready     Submitter: Alisdair Meredith     Date: 7 November 2007

The specification for the alignof operator (5.3.6 [expr.alignof]) does not forbid function types as operands, although it probably should.

Proposed resolution (March, 2008):

The issue, as described, is incorrect. The requirement in 5.3.6 [expr.alignof] is for “a complete object type,” so a function type is already forbidden. However, the existing text does have a problem in this requirement in that it does not allow a reference type, as anticipated by paragraph 3. Consequently, the proposal is to change 5.3.6 [expr.alignof] paragraph 1 as indicated:

An alignof expression yields the alignment requirement of its operand type. The operand shall be a type-id representing a complete object type or a reference to a complete object type.



647. Non-constexpr instances of constexpr constructor templates

Section: 7.1.5  [dcl.constexpr]     Status: ready     Submitter: Mike Miller     Date: 12 Aug 2007

According to 7.1.5 [dcl.constexpr] paragraph 5,

If the instantiated template specialization of a constexpr function template would fail to satisfy the requirements for a constexpr function, the constexpr specifier is ignored and the specialization is not a constexpr function.

One would expect to see a similar provision for an instantiated constructor template (because the requirements for a constexpr function [paragraph 3] are different from the requirements for a constexpr constructor [paragraph 4]), but there is none; constexpr constructor templates are not mentioned.

Suggested resolution:

Change the wording of 7.1.5 [dcl.constexpr] paragraph 5 as indicated:

If the instantiated template specialization of a constexpr function template would fail to satisfy the requirements for a constexpr function or constexpr constructor, as appropriate to the function template, the constexpr specifier is ignored and the specialization is not a constexpr function or constexpr constructor.

Proposed resolution (June, 2008):

[Drafting note: This resolution goes beyond the problem described in the issue discussion, which is one aspect of the general failure of the existing wording to deal consistently with the distinctions between constexpr functions and constexpr constructors. The wording below attempts to rectify that problem systematically.]

  1. Change 7.1.5 [dcl.constexpr] paragraph 2 as follows:

  2. A constexpr specifier used in a function declaration the declaration of a function that is not a constructor declares that function to be a constexpr function. Similarly, a constexpr specifier used in a constructor declaration declares that constructor to be a constexpr constructor. Constexpr functions and constexpr constructors are implicitly inline (7.1.2 [dcl.fct.spec]). A constexpr function shall not be virtual (10.3).
  3. Change 7.1.5 [dcl.constexpr] paragraph 3 as follows:

  4. The definition of a constexpr function shall satisfy the following constraints:

    [Example:...

  5. Change 7.1.5 [dcl.constexpr] paragraph 4 as follows:

  6. The definition of a constexpr constructor shall satisfy the following constraints:

    A trivial copy constructor is also a constexpr constructor. [Example: ...

  7. Change 7.1.5 [dcl.constexpr] paragraph 5 as follows:

  8. If the instantiated template specialization of a constexpr function template would fail to satisfy the requirements for a constexpr function or constexpr constructor, the constexpr specifier is ignored and the specialization is not a constexpr function.
  9. Change 7.1.5 [dcl.constexpr] paragraph 6 as follows:

  10. A constexpr specifier used in for a non-static member function definition that is not a constructor declares that member function to be const (9.3.1 [class.mfct.non-static]). [Note: ...



648. Constant expressions in constexpr initializers

Section: 7.1.5  [dcl.constexpr]     Status: ready     Submitter: Mike Miller     Date: 12 Aug 2007

The current wording of 7.1.5 [dcl.constexpr] paragraph 7 seems not quite correct. It reads,

A constexpr specifier used in an object declaration declares the object as const. Such an object shall be initialized, and every expression that appears in its initializer (8.5 [dcl.init]) shall be a constant expression.

The phrase “every expression” is intended to cover multiple arguments to a constexpr constructor and multiple expressions in an aggregate initializer. However, it could be read (incorrectly) as saying that non-constant expressions cannot appear as subexpressions in such initializers, even in places where they do not render the full-expression non-constant (i.e., as unevaluated operands and in the unselected branches of &&, ||, and ?:). Perhaps this problem could be remedied by replacing “every expression” with “every full-expression?”

Proposed resolution (June, 2008):

Change 7.1.5 [dcl.constexpr] paragraph 7 as follows:

A constexpr specifier used in an object declaration declares the object as const. Such an object shall be initialized, and every expression that appears in its initializer (8.5) initialized. If it is initialized by a constructor call, the constructor shall be a constexpr constructor and every argument to the constructor shall be a constant expression. Otherwise, every full-expression that appears in its initializer shall be a constant expression. Every implicit conversion used...



651. Problems in decltype specification and examples

Section: 7.1.6.2  [dcl.type.simple]     Status: ready     Submitter: Daveed Vandevoorde     Date: 16 Aug 2007

The second bullet of 7.1.6.2 [dcl.type.simple] paragraph 4 reads,

The reference to “that function” is imprecise; it is not the actual function called at runtime but the statically chosen function (ignoring covariant return types in virtual functions).

Also, the examples in this paragraph have errors:

  1. The declaration of struct A should end with a semicolon.

  2. The lines of the form decltype(...); are ill-formed; they need a declarator.

Proposed Resolution (October, 2007):

Change 7.1.6.2 [dcl.type.simple] paragraph 4 as follows:

The type denoted by decltype(e) is defined as follows:

The operand of the decltype specifier is an unevaluated operand (clause 5 [expr]).

[Example:

    const int&& foo();
    int i;
    struct A { double x; };
    const A* a = new A();
    decltype(foo()) x1;      // type is const int&&
    decltype(i) x2;          // type is int
    decltype(a->x) x3;       // type is double
    decltype((a->x)) x4;     // type is const double&

end example]




660. Unnamed scoped enumerations

Section: 7.2  [dcl.enum]     Status: ready     Submitter: Daveed Vandevoorde     Date: 15 November 2007

The current specification of scoped enumerations does not appear to forbid an example like the following, even though the enumerator e cannot be used:

    enum class { e };

This might be covered by 7 [dcl.dcl] paragraph 3,

In a simple-declaration, the optional init-declarator-list can be omitted only when declaring a class (clause 9 [class]) or enumeration (7.2 [dcl.enum]), that is, when the decl-specifier-seq contains either a class-specifier, an elaborated-type-specifier with a class-key (9.1 [class.name]), or an enum-specifier. In these cases and whenever a class-specifier or enum-specifier is present in the decl-specifier-seq, the identifiers in these specifiers are among the names being declared by the declaration (as class-names, enum-names, or enumerators, depending on the syntax). In such cases, and except for the declaration of an unnamed bit-field (9.6 [class.bit]), the decl-specifier-seq shall introduce one or more names into the program, or shall redeclare a name introduced by a previous declaration.

which, when combined with paragraph 2,

A declaration occurs in a scope (3.3 [basic.scope]); the scope rules are summarized in 3.4 [basic.lookup]. A declaration that declares a function or defines a class, namespace, template, or function also has one or more scopes nested within it. These nested scopes, in turn, can have declarations nested within them. Unless otherwise stated, utterances in clause 7 [dcl.dcl] about components in, of, or contained by a declaration or subcomponent thereof refer only to those components of the declaration that are not nested within scopes nested within the declaration.

appears to rule out the similar class definition,

    struct { int m; };

However, a scoped enumeration is not listed in paragraph 2 among the constructs containing a nested scope (although 3.3.7 [basic.scope.enum] does describe “enumeration scope”); furthermore, an enumerator-definition is not formally a “nested declaration.” If unusable scoped enumeration definitions are to be banned, these shortcomings in 7 [dcl.dcl] paragraph 2 must be addressed. (A note in 7.2 [dcl.enum] mentioning that unnamed scoped enumerations are not allowed would also be helpful.)

Notes from the February, 2008 meeting:

The consensus was to require that the identifier be present in an enum-specifier unless the enum-key is enum.

Proposed resolution (June, 2008):

Change 7.2 [dcl.enum] paragraph 2 as follows:

...The enum-keys enum class and enum struct are semantically equivalent; an enumeration type declared with one of these is a scoped enumeration, and its enumerators are scoped enumerators. The optional identifier shall not be omitted in the declaration of a scoped enumeration. The type-specifier-seq of an enum-base...



686. Type declarations/definitions in type-specifier-seqs and type-ids

Section: 8.1  [dcl.name]     Status: ready     Submitter: Jens Maurer     Date: 21 March, 2008

The restrictions on declaring and/or defining classes inside type-specifier-seqs and type-ids are inconsistent throughout the Standard. This is probably due to the fact that nearly all of the sections that deal with them attempt to state the restriction afresh. There are three cases:

  1. 5.3.4 [expr.new], 6.4 [stmt.select], and 12.3.2 [class.conv.fct] prohibit “declarations” of classes and enumerations. That means that

        while (struct C* p = 0) ;
    

    is ill-formed unless a prior declaration of C has been seen. These appear to be cases that should have been fixed by issue 379, changing “class declaration” to “class definition,” but were overlooked.

  2. 5.1.1 [expr.prim.lambda], 7 [dcl.dcl], and 8.3.5 [dcl.fct] (late-specified return types) do not contain any restriction at all.

  3. All the remaining cases prohibit “type definitions,” apparently referring to classes and enumerations.

Suggested resolution:

Add something like, “A class or enumeration shall not be defined in a type-specifier-seq or in a type-id,” to a single place in the Standard and remove all other mentions of that restriction (allowing declarations via elaborated-type-specifier).

Mike Miller:

An alias-declaration is just a different syntax for a typedef declaration, which allows definitions of a class in the type; I would expect the same to be true of an alias-declaration. I don't have any particularly strong attachment to allowing a class definition in an alias-declaration. My only concern is introducing an irregularity into what are currently exact-match semantics with typedefs.

There's a parallel restriction in many (but not all?) of these places on typedef declarations.

Jens Maurer:

Those are redundant, as typedef is not a type-specifier, and should be removed as well.

Proposed resolution (March, 2008):

  1. Delete the indicated words from 5.2.7 [expr.dynamic.cast] paragraph 1:

  2. ...Types shall not be defined in a dynamic_cast....
  3. Delete the indicated words from 5.2.8 [expr.typeid] paragraph 4:

  4. ...Types shall not be defined in the type-id....
  5. Delete the indicated words from 5.2.9 [expr.static.cast] paragraph 1:

  6. ...Types shall not be defined in a static_cast....
  7. Delete the indicated words from 5.2.10 [expr.reinterpret.cast] paragraph 1:

  8. ...Types shall not be defined in a reinterpret_cast....
  9. Delete the indicated words from 5.2.11 [expr.const.cast] paragraph 1:

  10. ...Types shall not be defined in a const_cast....
  11. Delete paragraph 5 of 5.3.3 [expr.sizeof]:

  12. Types shall not be defined in a sizeof expression.
  13. Delete paragraph 5 of 5.3.4 [expr.new]:

  14. The type-specifier-seq shall not contain class declarations, or enumeration declarations.
  15. Delete paragraph 4 of 5.3.6 [expr.alignof]:

  16. A type shall not be defined in an alignof expression.
  17. Delete paragraph 3 of 5.4 [expr.cast]:

  18. Types shall not be defined in casts.
  19. Delete the indicated words from 6.4 [stmt.select] paragraph 2:

  20. ...The type-specifier-seq shall not contain typedef and shall not declare a new class or enumeration....
  21. Add the indicated words to 7.1.6 [dcl.type] paragraph 3:

  22. At least one type-specifier that is not a cv-qualifier is required in a declaration unless it declares a constructor, destructor or conversion function. [Footnote: ... ] A type-specifier-seq shall not define a class or enumeration unless it appears in the type-id of an alias-declaration (7.1.3 [dcl.typedef]).
  23. Delete the indicated words from 12.3.2 [class.conv.fct] paragraph 1:

  24. ...Classes, enumerations, and typedef-names shall not be declared in the type-specifier-seq....
  25. Delete the indicated words from 15.3 [except.handle] paragraph 1:

  26. ...Types shall not be defined in an exception-declaration.
  27. Delete paragraph 6 of 15.4 [except.spec]:

  28. Types shall not be defined in exception-specifications.

[Drafting note: no changes are required to 5.1.1 [expr.prim.lambda], 7.1.3 [dcl.typedef], 7.1.7 [dcl.align], 7.2 [dcl.enum], 8.3.5 [dcl.fct], 14.1 [temp.param], or 14.2 [temp.names].]






Issues with "Review" Status


608. Determining the final overrider of a virtual function

Section: 10.3  [class.virtual]     Status: review     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.3 [class.virtual] 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.

Proposed Resolution (December, 2006):

Change 10.3 [class.virtual] paragraph 2 as follows:

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-declaration s. A virtual member function vf of a class C is a final overrider unless the most derived class (1.8 [intro.object]) of which C is a base class (if any) declares or inherits another member function that overrides vf. In a derived class, if a virtual member function of a base class subobject has more than one final overrider, the program is ill-formed.



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

Section: 11  [class.access]     Status: review     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.

Proposed resolution (June, 2008):

Change 11 [class.access] paragraph 6 as follows:

...For purposes of access control, the base-specifiers of a class, the template-parameters of a template-declaration, and the definitions of class members that appear outside of the class definition are considered to be within the scope of that class...



585. Friend template template parameters

Section: 11.4  [class.friend]     Status: review     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; };

Proposed resolution (June, 2008):

Change 11.4 [class.friend] paragraph 3 as follows:

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

In the last alternative, the identifier shall name a template template-parameter. [Note: a friend declaration may be the declaration in a template-declaration (clause 14 [temp], 14.5.4 [temp.friend]). —end note] If the type specifier in a friend declaration designates a (possibly cv-qualified) class type or a class template, that class or template is declared as a friend; otherwise, the friend declaration is ignored. [Example:...




462. Lifetime of temporaries bound to comma expressions

Section: 12.2  [class.temporary]     Status: review     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.

Proposed Resolution (November, 2006):

Add the indicated wording to 5.18 [expr.comma] paragraph 1:

... The type and value of the result are the type and value of the right operand; the result is an lvalue if its right operand is an lvalue, and is a bit-field if its right operand is an lvalue and a bit-field. If the value of the right operand is a temporary (12.2 [class.temporary]), the result is that temporary.



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

Section: 12.2  [class.temporary]     Status: review     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.

Proposed resolution (June, 2008):

Change 12.2 [class.temporary] paragraph 5 as follows (converting the running text into a bulleted list and making the indicated edits to the wording):

... The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except: as specified below.

The destruction of a temporary whose lifetime is not extended...




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



257. Abstract base constructors and virtual base initialization

Section: 12.6.2  [class.base.init]     Status: review     Submitter: Mike Miller     Date: 1 Nov 2000

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

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

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

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

Christopher Lester, on comp.std.c++, March 19, 2004: If any of you reading this posting happen to be members of the above working group, I would like to encourage you to review the suggestion contained therein, as it seems to me that the final tenor of the submission is both (a) correct (the silence of the standard DOES mandate the omission) and (b) describes what most users would intuitively expect and desire from the C++ language as well.

The suggestion is to make it clearer that constructors for abstract base classes should not be required to provide initialisers for any virtual base classes they contain (as only the most-derived class has the job of initialising virtual base classes, and an abstract base class cannot possibly be a most-derived class).

For example:

struct A {
  A(const int i, const int j) {};
};

struct B1 : virtual public A {
  virtual void moo()=0;
  B1() {};   // (1) Look! not "B1() : A(5,6) {};"
};

struct B2 : virtual public A {
  virtual void cow()=0;
  B2() {};   // (2) Look! not "B2() : A(7,8) {};"
};

struct C : public B1, public B2 {
  C() : A(2,3) {};
  void moo() {};
  void cow() {};
};

int main() {
  C c;
  return 0;
};

I believe that, by not expressly forbidding it, the standard does (and should!) allow the above code. However, as the standard doesn't expressly allow it either (have I missed something?) there appears to be room for misunderstanding. For example, g++ version 3.2.3 (and maybe other versions as well) rejects the above code with messages like:

	In constructor `B1::B1()':
	no matching function for call to `A::A()'
	candidates are: A::A(const A&)
         	        A::A(int, int)

Fair enough, the standard is perhaps not clear enough. But it seems to be a shame that although this issue was first raised in 2000, we are still living with it today.

Note that we can work-around, and persuade g++ to compile the above by either (a) providing a default constructor A() for A, or (b) supplying default values for i and j in A(i,j), or (c) replace the construtors B1() and B2() with the forms shown in the two comments in the above example.

All three of these workarounds may at times be appropriate, but equally there are other times when all of these workarounds are particularly bad. (a) and (b) may be very bad if you are trying to enforce string contracts among objects, while (c) is just barmy (I mean why did I have to invent random numbers like 5, 6, 7 and 8 just to get the code to compile?).

So to to round up, then, my plea to the working group is: "at the very least, please make the standard clearer on this issue, but preferrably make the decision to expressly allow code that looks something like the above"

Proposed resolution (March, 2008):

  1. Add the indicated text (moved from paragraph 6) to the end of 12.6.2 [class.base.init] paragraph 3:

  2. ...The initialization of each base and member constitutes a full-expression. Any expression in a mem-initializer is evaluated as part of the full-expression that performs the initialization. A mem-initializer where the mem-initializer-id names a virtual base class is ignored during execution of a constructor of any class that is not the most derived class.
  3. Change 12.6.2 [class.base.init] paragraph 4 as follows:

  4. If a given non-static data member or base class is not named by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer) and the entity is not a virtual base class of an abstract class, then

    [Note: An abstract base class (10.4 [class.abstract]) is never a most derived class, thus its constructors never initialize virtual base classes, therefore the corresponding mem-initializers may be omitted. —end note] After the call to a constructor for class X has completed, if a member of X is neither specified in the constructor's mem-initializers, nor default-initialized, nor value-initialized, nor given a value during execution of the compound-statement of the body of the constructor, the member has indeterminate value.

  5. Change 12.6.2 [class.base.init] paragraph 5 as follows:

  6. Initialization shall proceeds in the following order:

    [Note: the declaration order is mandated to ensure that base and member subobjects are destroyed in the reverse order of initialization. —end note]

    [Drafting note: The “shall” clauses above were rewritten to accord with the usual phrasing throughout the rest of the Standard.]

  7. Remove all the normative text in 12.6.2 [class.base.init] paragraph 6, keeping the example:

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



655. Initialization not specified for forwarding constructors

Section: 12.6.2  [class.base.init]     Status: review     Submitter: Alisdair Meredith     Date: 17 October 2007

The changes for delegating constructors overlooked the need to change 12.6.2 [class.base.init] paragraph 3:

The expression-list in a mem-initializer is used to initialize the base class or non-static data member subobject denoted by the mem-initializer-id. The semantics of a mem-initializer are as follows:

The initialization of each base and member constitutes a full-expression. Any expression in a mem-initializer is evaluated as part of the full-expression that performs the initialization.

This paragraph deals only with subobjects; it needs to be made more general to apply to the complete object as well when the mem-initializer-id designates the constructor's class.

Proposed resolution (June, 2008):

Change 12.6.2 [class.base.init] paragraph 3 as follows:

The expression-list in a mem-initializer is used to initialize the base class or non-static data member subobject denoted by the mem-initializer-id. The semantics of a mem-initializer are A mem-initializer in which the mem-initializer-id names the constructor's class initializes the object by invoking the selected target constructor with the mem-initializer's expression-list. A mem-initializer in which the mem-initializer-id names a base class or non-static data member initializes the designated subobject as follows:

...

The initialization of each base and member performed by each mem-initializer constitutes a full-expression. Any expression...




111. Copy constructors and cv-qualifiers

Section: 12.8  [class.copy]     Status: review     Submitter: Jack Rouse     Date: 4 May 1999

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

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

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

Proposed Resolution (November, 2006):

This issue is resolved by the proposed resolution for issue 535.




535. Copy construction without a copy constructor

Section: 12.8  [class.copy]     Status: review     Submitter: Mike Miller     Date: 7 October 2005

Footnote 112 (12.8 [class.copy] paragraph 2) says,

Because a template constructor is never a copy constructor, the presence of such a template does not suppress the implicit declaration of a copy constructor. Template constructors participate in overload resolution with other constructors, including copy constructors, and a template constructor may be used to copy an object if it provides a better match than other constructors.

However, many of the stipulations about copy construction are phrased to refer only to “copy constructors.” For example, 12.8 [class.copy] paragraph 14 says,

A program is ill-formed if the copy constructor... for an object is implicitly used and the special member function is not accessible (clause 11 [class.access]).

Does that mean that using an inaccessible template constructor to copy an object is permissible, because it is not a “copy constructor?” Obviously not, but each use of the term “copy constructor” in the Standard should be examined to determine if it applies strictly to copy constructors or to any constructor used for copying. (A similar issue applies to “copy assignment operators,” which have the same relationship to assignment operator function templates.)

Proposed Resolution (February, 2008):

  1. Change 3.2 [basic.def.odr] paragraph 2 as follows:

  2. ... [Note: this covers calls to named functions (5.2.2 [expr.call]), operator overloading (clause 13 [over]), user-defined conversions (12.3.2 [class.conv.fct]), allocation function for placement new (5.3.4 [expr.new]), as well as non-default initialization (8.5 [dcl.init]). A copy constructor selected to copy class objects is used even if the call is actually elided by the implementation (12.8 [class.copy]). —end note] ... A copy-assignment function for a class An assignment operator function in a class is used by an implicitly-defined copy-assignment function for another class as specified in 12.8 [class.copy]...
  3. Delete 12.1 [class.ctor] paragraphs 10 and 11:

  4. A copy constructor (12.8 [class.copy]) is used to copy objects of class type.

    A union member shall not be of a class type (or array thereof) that has a non-trivial constructor.

  5. Replace the “example” in 12.2 [class.temporary] paragraph 1 with a note as follows:

  6. [Example: even if the copy constructor is not called, all the semantic restrictions, such as accessibility (clause 11 [class.access]), shall be satisfied. —end example] [Note: This includes accessibility (clause 11 [class.access]) for the constructor selected. —end note]
  7. Change 12.8 [class.copy] paragraph 7 as follows:

  8. A non-user-provided copy constructor is implicitly defined if it is used to initialize an object of its class type from a copy of an object of its class type or of a class type derived from its class type (3.2 [basic.def.odr]). [Footnote: See 8.5 [dcl.init] for more details on direct and copy initialization. —end footnote] [Note: the copy constructor is implicitly defined even if the implementation elided its use (12.2 [class.temporary]) the copy operation (12.8 [class.copy]). —end note] A program is ill-formed if the class for which a copy constructor is implicitly defined or explicitly defaulted has:

    • a non-static data member of class type (or array thereof) with an inaccessible or ambiguous copy constructor, or

    • a base class with an inaccessible or ambiguous copy constructor.

    Before the non-user-provided copy constructor for a class is implicitly defined...

  9. Change 12.8 [class.copy] paragraph 8 as follows:

  10. ...Each subobject is copied in the manner appropriate to its type:

    [Drafting note: 8.5 [dcl.init] paragraph 15 requires “unambiguous” and 13.3 [over.match] paragraph 3 requires “accessible,” thus no need for normative text here.]

  11. Change 12.8 [class.copy] paragraph 12 as follows:

  12. A non-user-provided copy assignment operator is implicitly defined when an object of its class type is assigned a value of its class type or a value of a class type derived from its class type it is used (3.2 [basic.def.odr]). A program is ill-formed if the class for which a copy assignment operator is implicitly defined or explicitly defaulted has: a non-static data member of const or reference type.

    • a non-static data member of const type, or

    • a non-static data member of reference type, or

    • a non-static data member of class type (or array thereof) with an inaccessible copy assignment operator, or

    • a base class with an inaccessible copy assignment operator.

  13. Change 12.8 [class.copy] paragraph 13 as follows:

  14. ... Each subobject is assigned in the manner appropriate to its type:

  15. Delete 12.8 [class.copy] paragraph 14:

  16. A program is ill-formed if the copy constructor or the copy assignment operator for an object is implicitly used and the special member function is not accessible (clause 11 [class.access]). [Note: Copying one object into another using the copy constructor or the copy assignment operator does not change the layout or size of either object. —end note]
  17. Change 12.8 [class.copy] paragraph 15 as follows:

  18. When certain criteria are met, an implementation is allowed to omit the copy construction of a class object, even if the copy constructor selected for the copy operation and/or the destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization. [Footnote: Because only one object is destroyed instead of two, and one copy constructor is not executed, there is still one object destroyed for each one constructed. —end footnote] This elision...
  19. Change 13.3.3.1.2 [over.ics.user] paragraph 4 as follows:

  20. A conversion of an expression of class type to the same class type is given Exact Match rank, and a conversion of an expression of class type to a base class of that type is given Conversion rank, in spite of the fact that a copy constructor (i.e., a user-defined conversion function) is called for those cases.
  21. Change 15.1 [except.throw] paragraph 3 as follows:

  22. A throw-expression initializes a temporary object, called the exception object, the type of which by copy-initialization (8.5 [dcl.init]). The type of that temporary object is determined...
  23. Change 15.1 [except.throw] paragraph 5 as follows:

  24. When the thrown object is a class object, the copy constructor selected for the copy-initialization and the destructor shall be accessible, even if the copy operation is elided (12.8 [class.copy]).
  25. Change 15.3 [except.handle] paragraphs 16-17 as follows:

  26. When the exception-declaration specifies a class type, a copy constructor copy-initialization (8.5 [dcl.init]) is used to initialize either the object declared in the exception-declaration or, if the exception-declaration does not specify a name, a temporary object of that type. The object shall not have an abstract class type. The object is destroyed when the handler exits, after the destruction of any automatic objects initialized within the handler. The copy constructor selected for the copy-initialization and the destructor shall be accessible in the context of the handler, even if the copy operation is elided (12.8 [class.copy]). If the copy constructor and destructor are implicitly declared (12.8 [class.copy]), such a use in the handler causes these functions to be implicitly defined; otherwise, the program shall provide a definition for these functions.

    The copy constructor and destructor associated with the object shall be accessible even if the copy operation is elided (12.8 [class.copy]).

  27. Change the footnote in 15.5.1 [except.terminate] paragraph 1 as follows:

  28. [Footnote: For example, if the object being thrown is of a class with a copy constructor type, std::terminate() will be called if that copy constructor the constructor selected to copy the object exits with an exception during a throw. —end footnote]

(This resolution also resolves issue 111.)

[Drafting note: The following do not require changes: 5.17 [expr.ass] paragraph 4; 9 [class] paragraph 5; 9.5 [class.union] paragraph 1; 12.2 [class.temporary] paragraph 2; 12.8 [class.copy] paragraphs 1-2; 15.4 [except.spec] paragraph 14.]

Notes from February, 2008 meeting:

These changes overlap those that will be made when concepts are added. This issue will be maintained in “review” status until the concepts proposal is adopted and any conflicts will be resolved at that point.




574. Definition of “copy assignment operator”

Section: 12.8  [class.copy]     Status: review     Submitter: Steve Adamczyk     Date: 15 April 2006

Is the following a “copy assignment operator?”

    struct A {
        const A& operator=(const A&) volatile;
    };

12.8 [class.copy] paragraph 9 doesn't say one way or the other whether cv-qualifiers on the function are allowed. (A similar question applies to the const case, but I avoided that example because it seems so wrong one tends to jump to a conclusion before seeing what the standard says.)

Since the point of the definition of “copy assignment operator” is to control whether the compiler generates a default version if the user doesn’t, I suspect the correct answer is that neither const nor volatile cv-qualification on operator= should be allowed for a “copy assignment operator.” A user can write an operator= like that, but it doesn't affect whether the compiler generates the default one.

Proposed Resolution (November, 2006):

Change 12.8 [class.copy] paragraph 9 as follows:

A user-declared copy assignment operator X::operator= is a non-static non-template non-volatile non-const member function of class X with exactly one parameter of type X, X&, const X&, volatile X& or const volatile X&.

[Drafting note: If a user-declared volatile operator= prevented the implicit declaration of the copy assignment operator, all assignments for objects of the given class (even to non-volatile objects) would pay the penalty for volatile write accesses in the user-declared operator=, despite not needing it.]




653. Copy assignment of unions

Section: 12.8  [class.copy]     Status: review     Submitter: Jens Maurer     Date: 3 October 2007

How does copy assignment for unions work? For example,

  union U {
    int a;
    float b;
  };

  void f() {
    union U u = { 5 };
    union U v;
    v = u;    // what happens here?
  }

9.5 [class.union] is silent on the issue, therefore it seems that 12.8 [class.copy] applies. There is no special case for unions, thus paragraph 13 (memberwise assignment of subobjects) seems to apply. That would seem to imply these actions in the compiler-generated copy assignment operator:

  v.a = u.a;
  v.b = u.b;

And this is just wrong. For example, the lifetime of v.a ends once the second assignment reuses the memory of v.a.

We should probably prescribe “memcpy” copying for unions (both for the copy constructor and the assignment operator) unless the user provided his own special member function.

Proposed resolution (March, 2008):

  1. Change 12.8 [class.copy] paragraph 8 as follows:

  2. The implicitly-defined or explicitly-defaulted copy constructor for a non-union class X performs a memberwise copy of its subobjects...
  3. Add a new paragraph after 12.8 [class.copy] paragraph 8:

  4. The implicitly-defined or explicitly-defaulted copy constructor for a union X where all members have a trivial copy constructor copies the object representation (3.9 [basic.types]) of X. [Note: The behavior is undefined if X is not a trivial type. —end note]
  5. Change 12.8 [class.copy] paragraph 13 as follows:

  6. The implicitly-defined or explicitly-defaulted copy assignment operator for a non-union class X performs memberwise assignment of its subobjects...
  7. Add a new paragraph after 12.8 [class.copy] paragraph 13:

  8. The implicitly-defined or explicitly-defaulted copy assignment operator for a union X where all members have a trivial copy assignment operator copies the object representation (3.9 [basic.types]) of X. [Note: The behavior is undefined if X is not a trivial type. —end note]



667. Trivial special member functions that cannot be implicitly defined

Section: 12.8  [class.copy]     Status: review     Submitter: James Widman     Date: 14 December 2007

Should the following class have a trivial copy assignment operator?

    struct A {
        int& m;
        A();
        A(const A&);
    };

12.8 [class.copy] paragraph 11 does not mention whether the presence of reference members (or cv-qualifiers, etc.) should affect triviality. Should it?

One reason why this matters is that implementations have to make the builtin type trait operator __has_trivial_default_ctor(T) work so that they can support the type trait template std::has_trivial_default_constructor.

Assuming the answer is “yes,” it looks like we probably need similar wording for trivial default and trivial copy ctors.

Notes from the February, 2008 meeting:

Deleted special member functions are also not trivial. Resolution of this issue should be coordinated with the concepts proposal.

Notes from the June, 2008 meeting:

It appears that this issue will be resolved by the concepts proposal directly. The issue is in “review” status to check if that is indeed the case in the final version of the proposal.




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

Section: 13.3.2  [over.match.viable]     Status: review     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.

Proposed Resolution (October, 2007):

Change the footnote in 12.3.2 [class.conv.fct] paragraph 1 as follows:

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. [Footnote: These conversions are considered as standard conversions for the purposes of overload resolution (13.3.3.1 [over.best.ics], 13.3.3.1.4 [over.ics.ref]) and therefore initialization (8.5 [dcl.init]) and explicit casts (5.2.9 [expr.static.cast]). A conversion to void does not invoke any conversion function (5.2.9 [expr.static.cast]). Even though never directly called to perform a conversion, such conversion functions can be declared and can potentially be reached through a call to a virtual conversion function in a base class —end footnote]

Additional note (March, 2008):

A slight change to the example above indicates that there is a need for a normative change as well as the clarification of the rationale in the October, 2007 proposed resolution. If the declaration of foo were changed to

    void foo(const xyz&);

with the current wording, the call foo(xyz()) would be interpreted as foo(xyz().operator abc&()) instead of binding the parameter directly to the rvalue, which is clearly wrong.

Proposed resolution (March, 2008):

  1. Change the footnote in 12.3.2 [class.conv.fct] paragraph 1 as described in the October, 2007 proposed resolution.

  2. Change 8.5.3 [dcl.init.ref] paragraph 5 as follows:

  3. A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:

    [Drafting note: this resolution makes the example in the issue description ill-formed.]




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.



621. Template argument deduction from function return types

Section: 14.7.3  [temp.expl.spec]     Status: review     Submitter: Richard Corden     Date: 16 February 2007

It does not appear that the following example is well-formed, although most compilers accept it:

    template <typename T> T foo();
    template <> int foo();

The reason is that 14.7.3 [temp.expl.spec] paragraph 11 only allows trailing template-arguments to be omitted if they “can be deduced from the function argument type,” and there are no function arguments in this example.

14.7.3 [temp.expl.spec] should probably say “function type” instead of “function argument type.” Also, a subsection should probably be added to 14.8.2 [temp.deduct] to cover “Deducing template arguments from declarative contexts” or some such. It would be essentially the same as 14.8.2.2 [temp.deduct.funcaddr] except that the function type from the declaration would be used as the type of P.

Proposed resolution (March, 2008):

  1. Insert the following as a new subsection after 14.8.2.5 [temp.deduct.type]:

  2. 14.8.2.6 Deducing template arguments in a declaration that names a specialization of a function template [temp.deduct.funcdecl]

    Template arguments can be deduced from the function type specified when declaring a specialization of a function template. [Note: this can occur in the context of an explicit specialization, an explicit instantiation, or a friend declaration. —end note] The function template's function type and the declared type are used as the types of P and A, and the deduction is done as described in 14.8.2.5 [temp.deduct.type].

  3. Change 14.7.3 [temp.expl.spec] paragraph 11 as follows:

  4. A trailing template-argument can be left unspecified in the template-id naming an explicit function template specialization provided it can be deduced from the function argument type (14.8.2.6 [temp.deduct.funcdecl])...



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.



692. Partial ordering of variadic class template partial orderings

Section: 14.8.2.5  [temp.deduct.type]     Status: review     Submitter: Doug Gregor     Date: 16 April, 2008

14.8.2.5 [temp.deduct.type] paragraph 22 describes how we cope with partial ordering between two function templates that differ because one has a function parameter pack while the other has a normal function parameter. However, this paragraph was meant to apply to template parameter packs as well, e.g., to help with partial ordering of class template partial specializations:

   template <class T1, class ...Z> class S; // #1
   template <class T1, class ...Z> class S<T1, const Z&...> {}; // #2
   template <class T1, class T2> class S<T1, const T2&> {};; // #3
   S<int, const int&> s; // both #2 and #3 match; #3 is more specialized

Suggested resolution:

Change 14.8.2.5 [temp.deduct.type] paragraphs 9-10 as follows (and add the example above to paragraph 9):

If P has a form that contains <T> or <i>, then each argument Pi of the respective template argument list P is compared with the corresponding argument Ai of the corresponding template argument list of A. If the template argument list of P contains a pack expansion that is not the last template argument, the entire template argument list is a non-deduced context. If Pi is a pack expansion, then the pattern of Pi is compared with each remaining argument in the template argument list of A. Each comparison deduces template arguments for subequent positions in the template parameter packs expanded by Pi. During partial ordering (14.8.2.4 [temp.deduct.partial]), if Ai was originally a pack expansion and Pi is not a pack expansion, or if P does not contain a template argument corresponding to Ai, argument deduction fails.

Similarly, if P has a form that contains (T), then each parameter type Pi of the respective parameter-type-list of P is compared with the corresponding parameter type Ai of the corresponding parameter-type-list of A. If the parameter-declaration corresponding to Pi is a function parameter pack, then the type of its declarator-id is compared with each remaining parameter type in the parameter-type-list of A. Each comparison deduces template arguments for subsequent positions in the template parameter packs expanded by the function parameter pack. During partial ordering (14.8.2.4 [temp.deduct.partial]), if Ai was originally a function parameter pack and Pi is not a function parameter pack, or if P does not contain a function parameter type corresponding to Ai, argument deduction fails. [Note: A function parameter pack can only occur at the end of a parameter-declaration-list (8.3.5 [dcl.fct]). —end note]




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.



668. Throwing an exception from the destructor of a local static object

Section: 15.5.1  [except.terminate]     Status: review     Submitter: Daniel Krügler     Date: 16 December 2007

The destruction of local static objects occurs at the same time as that of non-local objects (3.6.3 [basic.start.term] paragraph 1) and the execution of functions registered with std::atexit (paragraph 3). According to 15.5.1 [except.terminate] paragraph 1, std::terminate is called if a destructor for a non-local object or a function registered with std::atexit exits via an exception, but the Standard is silent about the result of throwing an exception from a destructor for a local static object. Presumably this is an oversight and the same rules should apply to destruction of local static objects.

Proposed resolution (March, 2008):

Change 15.5.1 [except.terminate] paragraph 1, fourth bullet as indicated, and add an additional bullet to follow it:




601. Type of literals in preprocessing expressions

Section: 16.1  [cpp.cond]     Status: review     Submitter: Daveed Vandevoorde     Date: 23 October 2006

The description of preprocessing expressions in 16.1 [cpp.cond] paragraph 4 says,

The resulting tokens comprise the controlling constant expression which is evaluated according to the rules of 5.19 using arithmetic that has at least the ranges specified in 18.2 [support.limits], except that all signed and unsigned integer types act as if they have the same representation as, respectively, intmax_t or uintmax_t (18.3.2).

However, this does not address the type implicitly assigned to integral literals. For example, in an implementation where int is 32 bits and long long is 64 bits, is a literal like 0xffffffff signed or unsigned? WG14 adopted DR 265 to deal with this issue in the essentially-identical wording in C99; we should probably follow suit for C++.

Proposed Resolution (November, 2006):

Change 16.1 [cpp.cond] paragraph 4 as follows:

...and then each preprocessing token is converted into a token. The resulting tokens comprise the controlling constant expression which is evaluated according to the rules of 5.19 [expr.const] using arithmetic that has at least the ranges specified in 18.2 [support.limits], except that. For the purposes of that token conversion and evaluation all signed and unsigned integer types act as if they have the same representation as, respectively, intmax_t or uintmax_t (18.3.2 [stdinth])[Footnote: Thus on an implementation where std::numeric_limits<int>::max() is 0x7FFF and std::numeric_limits<unsigned int>::max() is 0xFFFF, the integer literal 0x8000 is signed and positive within a #if expression even though it is unsigned in translation phase 7 (2.1 [lex.phases]). —end footnote]. This includes interpreting character literals...



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.




626. Preprocessor string literals

Section: 16.3.2  [cpp.stringize]     Status: review     Submitter: Gennaro Prota     Date: 12 September 2006

Clause 16 [cpp] refers in several places to “character string literals” without specifying whether they are narrow or wide strings. For instance, what kind of string does the # operator (16.3.2 [cpp.stringize]) produce?

16.4 [cpp.line] paragraph 1 says,

The string literal of a #line directive, if present, shall be a character string literal.

Is “character string literal” intended to mean a narrow string literal? (Also, there is no string-literal mentioned in the grammatical descriptions of #line; paragraph 4 reads,

which is apparently intended to suggest a string literal but does not use the term.)

16.8 [cpp.predefined] should also specify what kind of character string literals are produced by the various string-valued predefined macros.

Notes from the July, 2007 meeting:

The CWG affirmed that all the string literals mentioned in Clause 16 [cpp] are intended to be narrow strings.

Proposed resolution (September, 2007)

  1. Change the footnote in 16 [cpp] paragraph 1 as follows:

  2. Thus, preprocessing directives are commonly called “lines.” These “lines” have no other syntactic significance, as all white space is equivalent except in certain situations during preprocessing (see the # character string literal creation operator in 16.3.2 [cpp.stringize], for example).
  3. Change 16.3.2 [cpp.stringize] paragraph 2 as follows:

  4. If, in the replacement list, a parameter is immediately preceded by a # preprocessing token, both are replaced by a single character narrow string literal preprocessing token that contains the spelling of the preprocessing token sequence for the corresponding argument... Otherwise, the original spelling of each preprocessing token in the argument is retained in the character narrow string literal, except for special handling for producing the spelling of string literals and character literals: a \ character is inserted before each " and \ character of a character literal or string literal (including the delimiting " characters). If the replacement that results is not a valid character narrow string literal, the behavior is undefined. The character narrow string literal corresponding to an empty argument is "". The order of evaluation of # and ## operators is unspecified.
  5. Change 16.3.5 [cpp.scope] paragraph 6 as follows:

  6. To illustrate the rules for creating character narrow string literals and concatenating tokens, the sequence... or, after concatenation of the character narrow string literals...
  7. Change 16.4 [cpp.line] paragraph 1 as follows:

  8. The string literal of a #line directive, if present, shall be a character narrow string literal.
  9. Change 16.4 [cpp.line] paragraph 4 as follows:

  10. ...and changes the presumed name of the source file to be the contents of the character narrow string literal.
  11. Change 16.8 [cpp.predefined] paragraph 1 as follows:

  12. __DATE__

    __FILE__

    ...

    __TIME__




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

Section: 3  [basic]     Status: review     Submitter: Mike Miller     Date: 17 Sep 2001

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

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

Proposed Resolution (November, 2006):

This issue is resolved by the proposed resolution of issue 485.




633. Specifications for variables that should also apply to references

Section: 3  [basic]     Status: review     Submitter: Alisdair Meredith     Date: 17 May 2007

There are a number of specifications in the Standard that should also apply to references. For example:

A number of other examples could be cited. A thorough review is needed to make sure that references are completely specified.

Proposed resolution (March, 2008):

  1. Change 2.1 [lex.phases] paragraph 1, number 9 as follows:

    1. All external object and function entity references are resolved. Library components are linked to satisfy external references to functions and objects entities not defined in the current translation...

  2. Change 3.3 [basic.scope] paragraph 4, bullet 2 as follows:

  3. Change 3.3.1 [basic.scope.pdecl] paragraph 9 as follows:

  4. Function declarations at block scope and object or reference declarations with the extern specifier at block scope refer to declarations that are members of an enclosing namespace...
  5. Change 3.3.8 [basic.scope.hiding] paragraph 2 as follows:

  6. A class name (9.1 [class.name]) or enumeration name (7.2 [dcl.enum]) can be hidden by the name of an object, reference, function, or enumerator declared in the same scope. If a class or enumeration name and an object, reference, function, or enumerator are declared in the same scope (in any order) with the same name, the class or enumeration name is hidden wherever the object, reference, function, or enumerator name is visible.
  7. Change 3.4.1 [basic.lookup.unqual] paragraph 14 as follows:

  8. If a variable or reference member of a namespace is defined outside of the scope of its namespace then any name used that appears in the definition of the variable member (after the declarator-id) is looked up as if the definition of the variable member occurred in its namespace...
  9. Change 3.4.3 [basic.lookup.qual] paragraph 1 as follows:

  10. ...During the lookup for a name preceding the :: scope resolution operator, object, reference, function, and enumerator names are ignored...
  11. Change 3.4.3.2 [namespace.qual] paragraph 5 as follows:

  12. During the lookup of a qualified namespace member name, if the lookup finds more than one declaration of the member, and if one declaration introduces a class name or enumeration name and the other declarations either introduce the same object, the same reference, the same enumerator or a set of functions, the non-type name hides the class or enumeration name if and only if the declarations are from the same namespace; otherwise (the declarations are from different namespaces), the program is ill-formed.
  13. Change 3.5 [basic.link] paragraph 6 as follows:

  14. The name of a function declared in block scope, and the name of an object or reference declared by a block scope extern declaration, have linkage...
  15. Change 3.5 [basic.link] paragraph 8 as follows:

  16. ...A type without linkage shall not be used as the type of a variable, reference, or function with linkage, unless the variable or function that entity has extern "C" linkage...
  17. Change 3.5 [basic.link] paragraph 10 as follows:

  18. ...the types specified by all declarations referring to a given object, reference, or function shall be identical...
  19. Change 3.6.1 [basic.start.main] paragraph 1 as follows:

  20. ...Dynamic initialization of an object or reference is either ordered or unordered. Definitions of explicitly specialized class template static data members have ordered initialization. Other class template static data members (i.e., implicitly or explicitly instantiated specializations) have unordered initialization. Other objects and references defined in namespace scope have ordered initialization. Objects and references defined within a single translation unit and with ordered initialization shall be initialized in the order of their definitions in the translation unit. The order of initialization is unspecified for objects and references with unordered initialization and for objects and references defined in different translation units. An unordered initialization is indeterminately sequenced with respect to every other dynamic initialization. [Note: 8.5.1 [dcl.init.aggr] describes the order in which aggregate members are initialized. The initialization of local static objects and references is described in 6.7 [stmt.dcl]. —end note]
  21. Change 3.6.1 [basic.start.main] paragraph 3 as follows:

  22. It is implementation-defined whether or not the dynamic initialization (8.5 [dcl.init], 9.4 [class.static], 12.1 [class.ctor], 12.6.1 [class.expl.init]) of an object or reference of namespace scope is done before the first statement of main. If the initialization is deferred to some point in time after the first statement of main, it shall occur before the first use of any function, or object, or reference defined in the same translation unit as the object or reference to be initialized. [Footnote: An object or reference defined in namespace scope having initialization with side-effects must be initialized even if it is not used (3.7.1). —end footnote]
  23. Change 3.7.1 [basic.stc.static] paragraph 3 as follows:

  24. The keyword static can be used to declare a local variable or reference with static storage duration. [Note: 6.7 [stmt.dcl] describes the their initialization of local static variables; 3.6.3 [basic.start.term] describes the their destruction of local static variables. —end note]
  25. Change 5.1 [expr.prim] paragraph 4 as follows:

  26. ...The result is an lvalue if the entity is a function, or variable, or reference... [Note: the use of :: allows a type, an object, a function, an enumerator, or a namespace an entity declared in the global namespace to be referred to even if its identifier name has been hidden (3.4.3 [basic.lookup.qual]). —end note]
  27. Change 5.1 [expr.prim] paragraph 7 as follows:

  28. ...The result is an lvalue if the entity is a function, variable, reference, or data member.
  29. Change 5.1 [expr.prim] paragraph 8 as follows:

  30. ...The result is an lvalue if the member is a function, or a variable, or reference.
  31. Change 6.5.1 [stmt.while] paragraph 2 as follows:

  32. ...The object or reference created in a condition is destroyed and created with each iteration of the loop...
  33. Change 6.7 [stmt.dcl] paragraph 2 as follows:

  34. Variables and references with automatic storage duration (3.7.2 [basic.stc.auto]) are initialized each time their declaration-statement is executed...
  35. Change 6.7 [stmt.dcl] paragraph 3 as follows:

  36. ...A program that jumps from a point where a local variable or reference with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has it is a variable with trivial type (3.9 [basic.types]) and is declared without an initializer (8.5 [dcl.init])...
  37. Change 6.7 [stmt.dcl] paragraph 4 as follows:

  38. The zero-initialization (8.5 [dcl.init]) of all local objects with static storage duration (3.7.1 [basic.stc.static]) is performed before any other initialization takes place. When initialized with a constant expression, a local reference with static storage duration or a A local object of trivial or literal type (3.9 [basic.types]) with static storage duration initialized with constant-expressions is initialized before its block is first entered. An implementation is permitted to perform early initialization of other local objects with static storage duration under the same conditions that an implementation is permitted to statically initialize an object with static storage duration in namespace scope (3.6.2 [basic.start.init]). Otherwise such an object or reference is initialized the first time control passes through its declaration; such an object or reference is considered initialized upon the completion of its initialization. If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration. If control re-enters the declaration (recursively) while the object or reference is being initialized, the behavior is undefined...
  39. Change 7.1.1 [dcl.stc] paragraphs 2-7 as follows:

  40. The register specifier shall be applied only to names of objects and references declared in a block (6.3 [stmt.block]) or to function parameters (8.4 [dcl.fct.def]). It specifies that the named object or reference has automatic storage duration (3.7.2 [basic.stc.auto]). An object or reference declared without a storage-class-specifier at block scope or declared as a function parameter has automatic storage duration by default.

    A register specifier is a hint to the implementation that the object or reference so declared will be heavily used. [Note: the hint can be ignored and in most implementations it will be ignored if the address of the object is taken. —end note]

    The static specifier can be applied only to names of objects, references, and functions and to anonymous unions (9.5 [class.union]). There can be no static function declarations within a block, nor any static function parameters. A static specifier used in the declaration of an object or reference declares the object entity to have static storage duration (3.7.1 [basic.stc.static]). A static specifier can be used in declarations of class members; 9.4 [class.static] describes its effect. For the linkage of a name declared with a static specifier, see 3.5 [basic.link].

    The extern specifier can be applied only to the names of objects, references, and functions. The extern specifier cannot be used in the declaration of class members or function parameters. For the linkage of a name declared with an extern specifier, see 3.5 [basic.link]. [Note: The extern keyword can also be used in explicit-instantiations and linkage-specifications, but it is not a storage-class-specifier in such contexts. —end note]

    A name declared in a namespace scope without a storage-class-specifier has external linkage unless it has internal linkage because of a previous declaration and provided it is not declared const. Objects declared const and not explicitly declared extern have internal linkage.

    The linkages implied by successive declarations for a given entity shall agree. That is, within a given scope, each declaration declaring the same object or reference name or the same overloading of a function name shall imply the same linkage. Each function in a given set of overloaded functions can have a different linkage, however...

  41. Change 7.1.6.4 [dcl.spec.auto] paragraph 1 as follows:

  42. The auto type-specifier signifies that the type of an object or reference being declared shall be deduced from its initializer...
  43. Change 7.1.6.4 [dcl.spec.auto] paragraph 3 as follows:

  44. Otherwise, the type of the object or reference is deduced from its initializer. The name of the object entity being declared shall not appear in the initializer expression. This use of auto is allowed when declaring objects and references in a block (6.3 [stmt.block]), in namespace scope (3.3.5 [basic.scope.namespace]), and in a for-init-statement (6.5.3 [stmt.for]).
  45. Change 7.1.6.4 [dcl.spec.auto] paragraph 4 as follows:

  46. The auto type-specifier can also be used in declaring an object or reference in the condition of a selection statement...
  47. Change 7.1.6.4 [dcl.spec.auto] paragraphs 6-7 as follows:

  48. Once the type of a declarator-id has been determined according to 8.3 [dcl.meaning], the type of the declared variable or reference using the declarator-id is determined from the type of its initializer using the rules for template argument deduction. Let T be the type that has been determined for a variable or reference identifier d. Obtain P from T by replacing the occurrences of auto with a new invented type template parameter U. Let A be the type of the initializer expression for d. The type deduced for the variable d is then the deduced type determined using the rules of template argument deduction from a function call (14.8.2.1 [temp.deduct.call]), where P is a function template parameter type and A is the corresponding argument type. If the deduction fails, the declaration is ill-formed.

    If the list of declarators contains more than one declarator, the type of each declared variable entity is determined as described above...

  49. Change 7.3.1.1 [namespace.unnamed] paragraph 2 as follows:

  50. The use of the static keyword is deprecated when declaring objects and references in a namespace scope (see annex D [depr]); the unnamed-namespace provides a superior alternative.
  51. Change 7.3.4 [namespace.udir] paragraph 6 as follows:

  52. ...[Note: in particular, the name of an object, reference, function or enumerator does not hide the name of a class or enumeration declared in a different namespace...
  53. Change 8 [dcl.decl] paragraph 1 as follows:

  54. A declarator declares a single object, reference, function, or type, within a declaration...
  55. Change 8 [dcl.decl] paragraph 2 as follows:

  56. ...The specifiers indicate the type, storage class or other properties of the objects, functions or typedefs entities being declared. The declarators specify the names of these objects, functions or typedefs, entities and (optionally) modify the type of the specifiers with operators such as * (pointer to) and () (function returning)...
  57. Change 8.1 [dcl.name] paragraph 1 as follows:

  58. ...This can be done with a type-id, which is syntactically a declaration for an object, reference, or function of that type that omits the name of the object or function entity...
  59. Change 8.5 [dcl.init] paragraph 2 as follows:

  60. Automatic, register, static, and external variables and references of namespace scope can be initialized by arbitrary expressions involving literals and previously declared variables and functions...
  61. Change 8.5 [dcl.init] paragraph 4 as follows:

  62. The order of initialization of static objects and references is described in 3.6 [basic.start] and 6.7 [stmt.dcl].
  63. Delete the last bullet of 8.5 [dcl.init] paragraph 4, first list (zero-initialization) and replace the semicolon with a period in the preceding bullet:

  64. Change 8.5.3 [dcl.init.ref] paragraph 1 as follows:

  65. A variable An entity declared to be a T& or T&&, that is, “reference to type T” (8.3.2 [dcl.ref]), shall be initialized by an object, or function, of type T or by an object that can be converted into a T...
  66. Change 9.1 [class.name] paragraph 2 as follows:

  67. ...If a class name is declared in a scope where an object, reference, function, or enumerator of the same name is also declared, then when both declarations are in scope, the class can be referred to only using an elaborated-type-specifier (3.4.4 [basic.lookup.elab])...
  68. Change 9.4.2 [class.static.data] paragraph 6 as follows:

  69. Static data members are initialized and destroyed exactly like non-local objects and references (3.6.2 [basic.start.init], 3.6.3 [basic.start.term]).
  70. Change 9.8 [class.local] paragraph 1 as follows:

  71. ...Declarations in a local class can use only type names, static variables and references with static storage duration, extern variables and functions, and enumerators from the enclosing scope...
  72. Change 10.2 [class.member.lookup] paragraph 4 as follows:

  73. ...[Note: Looking up a name in an elaborated-type-specifier (3.4.4 [basic.lookup.elab]) or base-specifier (clause 10 [class.derived]), for instance, ignores all non-type declarations, while looking up a name in a nested-name-specifier (3.4.3 [basic.lookup.qual]) ignores function, object, reference, and enumerator declarations...
  74. Change 14 [temp] paragraph 5 as follows:

  75. A class template shall not have the same name as any other template, class, function, object, reference, enumeration, enumerator, namespace, or type in the same scope...
  76. Change 14.8 [temp.fct.spec] paragraph 2 as follows:

  77. Each function template specialization instantiated from a template has its own copy of any static variable or reference...

[Drafting notes: This resolution depends on the part of the resolution for issue 485 that adds references to the list of “entities.” It is also partly resolved by the proposed resolution for issue 570. No change is proposed to the text in 7.5 [dcl.link], hence reference names continue to have no language linkage, and prohibitions against conflicting linkage specifications do not apply to reference declarations.]




570. Are references subject to the ODR?

Section: 3.2  [basic.def.odr]     Status: review     Submitter: Dave Abrahams     Date: 2 April 2006

3.2 [basic.def.odr] paragraph 1 says,

No translation unit shall contain more than one definition of any variable, function, class type, enumeration type or template.

This says nothing about references. Is it permitted to define a reference more than once in a single translation unit? (The list in paragraph 5 of things that can have definitions in multiple translation units does not include references.)

Proposed resolution (March, 2008):

  1. Change 3.2 [basic.def.odr] paragraph 1 as follows:

  2. No translation unit shall contain more than one definition of any variable, reference, function, class type, enumeration type or template.
  3. Change 3.2 [basic.def.odr] paragraph 2 as follows:

  4. ...An object, reference, or non-overloaded function whose name appears as a potentially-evaluated expression is used unless it is an object that satisfies the requirements for appearing in a constant expression...
  5. Change 3.2 [basic.def.odr] paragraph 3 as follows:

  6. Every program shall contain exactly one definition of every non-inline function, or object, or reference that is used in that program...

(Note: this resolution also resolves part of issue 633.)




554. Definition of “declarative region” and “scope”

Section: 3.3  [basic.scope]     Status: review     Submitter: Gabriel Dos Reis     Date: 29 December 2005

The various uses of the term “declarative region” throughout the Standard indicate that the term is intended to refer to the entire block, class, or namespace that contains a given declaration. For example, 3.3 [basic.scope] paragraph 2 says, in part:

[Example: in

    int j = 24;
    int main()
    {
        int i = j, j;
        j = 42;
    }

The declarative region of the first j includes the entire example... The declarative region of the second declaration of j (the j immediately before the semicolon) includes all the text between { and }...

However, the actual definition given for “declarative region” in 3.3 [basic.scope] paragraph 1 does not match this usage:

Every name is introduced in some portion of program text called a declarative region, which is the largest part of the program in which that name is valid, that is, in which that name may be used as an unqualified name to refer to the same entity.

Because (except in class scope) a name cannot be used before it is declared, this definition contradicts the statement in the example and many other uses of the term throughout the Standard. As it stands, this definition is identical to that of the scope of a name.

The term “scope” is also misused. The scope of a declaration is defined in 3.3 [basic.scope] paragraph 1 as the region in which the name being declared is valid. However, there is frequent use of the phrase “the scope of a class,” not referring to the region in which the class's name is valid but to the declarative region of the class body, and similarly for namespaces, functions, exception handlers, etc. There is even a mention of looking up a name “in the scope of the complete postfix-expression” (3.4.5 [basic.lookup.classref] paragraph 3), which is the exact inverse of the scope of a declaration.

This terminology needs a thorough review to make it logically consistent. (Perhaps a discussion of the scope of template parameters could also be added to section 3.3 [basic.scope] at the same time, as all other kinds of scopes are described there.)

Proposed resolution (November, 2006):

  1. Change 3.3 [basic.scope] paragraph 1 as follows:

  2. Every name is introduced in some portion of program text called a declarative region, which is the largest part of the program in which that name is valid, that is, in which that name may be used as an unqualified name to refer to the same entity a statement, block, function declarator, function-definition, class, handler, template-declaration, template-parameter-list of a template template-parameter, or namespace. In general, each particular name is valid may be used as an unqualified name to refer to the entity of its declaration or to the label only within some possibly discontiguous portion of program text called its scope. To determine the scope of a declaration...
  3. Change 3.3 [basic.scope] paragraph 3 as follows:

  4. The names declared by a declaration are introduced into the scope in which the declaration occurs declarative region that directly encloses the declaration, except that declaration-statements, function parameter names in the declarator of a function-definition, exception-declarations (3.3.2 [basic.scope.local]), the presence of a friend specifier (11.4 [class.friend]), certain uses of the elaborated-type-specifier (7.1.6.3 [dcl.type.elab]), and using-directives (7.3.4 [namespace.udir]) alter this general behavior.
  5. Change 3.3.2 [basic.scope.local] paragraphs 1-3 and add a new paragraph 4 before the existing paragraph 4 as follows:

  6. A name declared in a block (6.3 [stmt.block]) is local to that block. Its potential scope begins at its point of declaration (3.3.1 [basic.scope.pdecl]) and ends at the end of its declarative region. The declarative region of a name declared in a declaration-statement is the directly enclosing block (6.3 [stmt.block]). Such a name is local to the block.

    The potential scope declarative region of a function parameter name (including one appearing in the declarator of a function-definition or in a lambda-parameter-declaration-clause) or of a function-local predefined variable in a function definition (8.4 [dcl.fct.def]) begins at its point of declaration. If the function has a function-try-block the potential scope of a parameter or of a function-local predefined variable ends at the end of the last associated handler, otherwise it ends at the end of the outermost block of the function definition. A parameter name is the entire function definition or lambda-expression. Such a name is local to the function definition and shall not be redeclared in the any outermost block of the function definition nor in the outermost block of any handler associated with a function-try-block function-body (including handlers of a function-try-block) or lambda-expression.

    The name in a catch exception-declaration The declarative region of a name declared in an exception-declaration is its entire handler. Such a name is local to the handler and shall not be redeclared in the outermost block of the handler.

    The potential scope of any local name begins at its point of declaration (3.3.1 [basic.scope.pdecl]) and ends at the end of its declarative region.

  7. Change 3.3.4 [basic.funscope] as indicated:

  8. Labels (6.1 [stmt.label]) have function scope and may be used anywhere in the function in which they are declared except in members of local classes (9.8 [class.local]) of that function. Only labels have function scope.
  9. Change 6.7 [stmt.dcl] paragraph 1 as follows:

  10. A declaration statement introduces one or more new identifiers names into a block; it has the form

    [Note: If an identifier a name introduced by a declaration was previously declared in an outer block, the outer declaration is hidden for the remainder of the block, after which it resumes its force (3.3.8 [basic.scope.hiding]). end note]

[Drafting notes: This resolution deals almost exclusively with the unclear definition of “declarative region.” I've left the ambiguous use of “scope” alone for now. However sections 3.3.x all have headings reading “xxx scope,” but they don't mean the scope of a declaration but the different kinds of declarative regions and their effects on the scope of declarations contained therein. To me, it looks like most of 3.4 should refer to “declarative region” and not to “scope.”

The change to 6.7 fixes an “identifier” misuse (e.g., extern T operator+(T,T); at block scope introduces a name but not an identifier) and removes normative redundancy.]




642. Definition and use of “block scope” and “local scope”

Section: 3.3.2  [basic.scope.local]     Status: review     Submitter: Alisdair Meredith     Date: 6 Aug 2007

The Standard uses the terms “block scope” and “local scope” interchangeably, but the former is never formally defined. Would it be better to use only one term consistently? “Block scope” seems to be more frequently used.

Notes from the October, 2007 meeting:

The CWG expressed a preference for the term “local scope.”

Proposed resolution (February, 2008):

  1. Change the note in 3.3.1 [basic.scope.pdecl] paragraph 9 as follows:

  2. [Note: friend declarations refer to functions or classes that are members of the nearest enclosing namespace, but they do not introduce new names into that namespace (7.3.1.2 [namespace.memdef]). Function declarations at block local scope and object declarations with the extern specifier at block local scope refer to declarations that are members of an enclosing namespace, but they do not introduce new names into that scope. —end note]
  3. Change the example in 3.4.1 [basic.lookup.unqual] paragraph 6 as follows:

  4. ...
    // 1) outermost block local scope of A::n::f, before the use of i
    ...
  5. Change the example in 3.4.1 [basic.lookup.unqual] paragraph 8 as follows:

  6. ...
    // 1) outermost block local scope of M::N::X::f, before the use of i
    ...
  7. Change 3.4.1 [basic.lookup.unqual] paragraph 11 as follows:

  8. During the lookup for a name used as a default argument (8.3.6 [dcl.fct.default]) in a function parameter-declaration-clause or used in the expression of a mem-initializer for a constructor (12.6.2 [class.base.init]), the function parameter names are visible and hide the names of entities declared in the block local, class or namespace scopes containing the function declaration...
  9. Change 3.4.1 [basic.lookup.unqual] paragraph 12 as follows:

  10. During the lookup of a name used in the constant-expression of an enumerator-definition, previously declared enumerators of the enumeration are visible and hide the names of entities declared in the block local, class, or namespace scopes containing the enum-specifier.
  11. Change 3.4.2 [basic.lookup.argdep] paragraph 3 as follows:

  12. Let X be the lookup set produced by unqualified lookup (3.4.1 [basic.lookup.unqual]) and let Y be the lookup set produced by argument dependent lookup (defined as follows). If X contains

    then Y is empty. Otherwise...

  13. Change 3.5 [basic.link] paragraph 6 as follows:

  14. The name of a function declared in block local scope, and the name of an object declared by a block local scope extern declaration, have linkage. If there is a visible declaration of an entity with linkage having the same name and type, ignoring entities declared outside the innermost enclosing namespace scope, the block local scope declaration declares that same entity and receives the linkage of the previous declaration. If there is more than one such matching entity, the program is ill-formed. Otherwise, if no matching entity is found, the block local scope entity receives external linkage...
  15. Change 3.5 [basic.link] paragraph 7 as follows:

  16. When a block local scope declaration of an entity with linkage is not found to refer to some other declaration, then that entity is a member of the innermost enclosing namespace...
  17. Change 3.6.3 [basic.start.term] paragraph 1 as follows:

  18. Destructors (12.4 [class.dtor]) for initialized objects of static storage duration (declared at block local scope or at namespace scope) are called as a result...
  19. Change 7.1.1 [dcl.stc] paragraph 2 as follows:

  20. ...An object declared without a storage-class-specifier at block local scope or declared as a function parameter has automatic storage duration by default.
  21. Change 7.1.2 [dcl.fct.spec] paragraph 3 as follows (cf 7.1.6.4 [dcl.spec.auto] paragraph 3):

  22. ...The inline specifier shall not appear on a block scope function declaration when declaring a function in a block...
  23. Change 9.5 [class.union] paragraph 3 as follows:

  24. Anonymous unions declared in a named namespace or in the global namespace shall be declared static. Anonymous unions declared at block scope in a block shall be declared with any storage class allowed for a block local-scope variable, or with no storage class...
  25. Change 20.6.11 [unique.ptr] paragraph 1 as follows:

  26. Template The class template unique_ptr stores a pointer to an object and deletes that object using the associated deleter when it is itself destroyed (such as when leaving block local scope (6.7 [stmt.dcl])).
  27. Change 30.3.3 [thread.lock] paragraph 1 as follows:

  28. A lock is an object that holds a reference to a mutex and may unlock the mutex during the lock's destruction (such as when leaving block local scope)...
  29. Change Appendix B [implimits] paragraph 2, bullet 8 as follows:

  30. Change C.1.7 [diff.class], reference to 9.1 [class.name] as follows:

  31. ...If the hidden name is at block local scope, either the type or the struct tag has to be renamed.
  32. Change D.9.1 [auto.ptr] paragraph 1 as follows:

  33. Template auto_ptr stores a pointer to an object obtained via new and deletes that object when it itself is destroyed (such as when leaving block local scope 6.7 [stmt.dcl]).



555. Pseudo-destructor name lookup

Section: 3.4  [basic.lookup]     Status: review     Submitter: Krzysztof Zelechowski     Date: 26 January 2006

The Standard does not completely specify how to look up the type-name(s) in a pseudo-destructor-name (5.2 [expr.post] paragraph 1, 5.2.4 [expr.pseudo]), and what information it does have is incorrect and/or in the wrong place. Consider, for instance, 3.4.5 [basic.lookup.classref] paragraphs 2-3:

If the id-expression in a class member access (5.2.5 [expr.ref]) is an unqualified-id, and the type of the object expression is of a class type C (or of pointer to a class type C), the unqualified-id is looked up in the scope of class C. If the type of the object expression is of pointer to scalar type, the unqualified-id is looked up in the context of the complete postfix-expression.

If the unqualified-id is ~type-name, and the type of the object expression is of a class type C (or of pointer to a class type C), the type-name is looked up in the context of the entire postfix-expression and in the scope of class C. The type-name shall refer to a class-name. If type-name is found in both contexts, the name shall refer to the same class type. If the type of the object expression is of scalar type, the type-name is looked up in the scope of the complete postfix-expression.

There are at least three things wrong with this passage with respect to pseudo-destructors:

  1. A pseudo-destructor call (5.2.4 [expr.pseudo]) is not a “class member access”, so the statements about scalar types in the object expressions are vacuous: the object expression in a class member access is required to be a class type or pointer to class type (5.2.5 [expr.ref] paragraph 2).

  2. On a related note, the lookup for the type-name(s) in a pseudo-destructor name should not be described in a section entitled “Class member access.”

  3. Although the class member access object expressions are carefully allowed to be either a class type or a pointer to a class type, paragraph 2 mentions only a “pointer to scalar type” (disallowing references) and paragraph 3 deals only with a “scalar type,” presumably disallowing pointers (although it could possibly be a very subtle way of referring to both non-class pointers and references to scalar types at once).

The other point at which lookup of pseudo-destructors is mentioned is 3.4.3 [basic.lookup.qual] paragraph 5:

If a pseudo-destructor-name (5.2.4 [expr.pseudo]) contains a nested-name-specifier, the type-names are looked up as types in the scope designated by the nested-name-specifier.

Again, this specification is in the wrong location (a pseudo-destructor-name is not a qualified-id and thus should not be treated in the “Qualified name lookup” section).

Finally, there is no place in the Standard that describes the lookup for pseudo-destructor calls of the form p->T::~T() and r.T::~T(), where p and r are a pointer and reference to scalar, respectively. To the extent that it gives any guidance at all, 3.4.5 [basic.lookup.classref] deals only with the case where the ~ immediately follows the . or ->, and 3.4.3 [basic.lookup.qual] deals only with the case where the pseudo-destructor-name contains a nested-name-specifier that designates a scope in which names can be looked up.

See document J16/06-0008 = WG21 N1938 for further discussion of this and related issues, including 244, 305, 399, and 414.

Proposed resolution (June, 2008):

  1. Add a new paragraph following 5.2 [expr.post] paragraph 2 as follows:

  2. When a postfix-expression is followed by a dot . or arrow -> operator, the interpretation depends on the type T of the expression preceding the operator. If the operator is ., T shall be a scalar type or a complete class type; otherwise, T shall be a pointer to a scalar type or a pointer to a complete class type. When T is a (pointer to) a scalar type, the postfix-expression to which the operator belongs shall be a pseudo-destructor call (5.2.4 [expr.pseudo]); otherwise, it shall be a class member access (5.2.5 [expr.ref]).
  3. Change 5.2.4 [expr.pseudo] paragraph 2 as follows:

  4. The left-hand side of the dot operator shall be of scalar type. The left-hand side of the arrow operator shall be of pointer to scalar type. This scalar type The type of the expression preceding the dot operator, or the type to which the expression preceding the arrow operator points, is the object type...
  5. Change 5.2.5 [expr.ref] paragraph 2 as follows:

  6. For the first option (dot) the type of the first expression (the object expression) shall be “class object” (of a complete type) is a class type. For the second option (arrow) the type of the first expression (the pointer expression) shall be “pointer to class object” (of a complete type) is a pointer to a class type. In these cases, the id-expression shall name a member of the class or of one of its base classes.
  7. Add a new paragraph following 3.4 [basic.lookup] paragraph 2 as follows:

  8. In a pseudo-destructor-name that does not include a nested-name-specifier, the type-names are looked up as types in the context of the complete expression.
  9. Delete the last sentence of 3.4.5 [basic.lookup.classref] paragraph 2:

  10. If the id-expression in a class member access (5.2.5 [expr.ref]) is an unqualified-id, and the type of the object expression is of a class type C, the unqualified-id is looked up in the scope of class C. If the type of the object expression is of pointer to scalar type, the unqualified-id is looked up in the context of the complete postfix-expression.



598. Associated namespaces of overloaded functions and function templates

Section: 3.4.2  [basic.lookup.argdep]     Status: review     Submitter: Mike Miller     Date: 27 September 2006

The resolution of issue 33 added the following wording in 3.4.2 [basic.lookup.argdep]:

In addition, if the argument is the name or address of a set of overloaded functions and/or function templates, its associated classes and namespaces are the union of those associated with each of the members of the set: the namespace in which the function or function template is defined and the classes and namespaces associated with its (non-dependent) parameter types and return type.

This wording is self-contradictory: although it claims that the treatment of overload sets is intended to be “the union of those associated with each of the members of the set,” it says that the namespace of which each function or function template is a member is to be considered an associated namespace. That is different from the case of a non-overloaded function argument; in that case, because only the type of the argument is considered, the namespace of which the function is a member is not an associated namespace. This should be rectified so that overloaded and unoverloaded functions really are treated the same.

Proposed resolution (June, 2008):

Change 3.4.2 [basic.lookup.argdep] paragraph 2 as follows:

...In addition, if the argument is the name or address of a set of overloaded functions and/or function templates, its associated classes and namespaces are the union of those associated with each of the members of the set: the namespace in which the function or function template is defined and the classes and namespaces associated with its (non-dependent) parameter types and return type.



373. Lookup on namespace qualified name in using-directive

Section: 3.4.6  [basic.lookup.udir]     Status: review     Submitter: Clark Nelson     Date: 15 August 2002

Is this case valid? G++ compiles it.

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

The relevant citation from the standard is 3.4.6 [basic.lookup.udir]: "When looking up a namespace-name in a using-directive or namespace-alias-definition, only namespace names are considered." This statement could reasonably be interpreted to apply only to the last element of a qualified name, and that's the way EDG and Microsoft seem to interpret it.

However, since a class can't contain a namespace, it seems to me that this interpretation is, shall we say, sub optimal. If the X qualifiers in the above example are interpreted as referring to the struct X, an error of some sort is inevitable, since there can be no namespace for the qualified name to refer to. G++ apparently interprets 3.4.6 [basic.lookup.udir] as applying to nested-name-specifiers in those contexts as well, which makes a valid interpretation of the test possible.

I'm thinking it might be worth tweaking the words in 3.4.6 [basic.lookup.udir] to basically mandate the more useful interpretation. Of course a person could argue that the difference would matter only to a perverse program. On the other hand, namespaces were invented specifically to enable the building of programs that would otherwise be considered perverse. Where name clashes are concerned, one man's perverse is another man's real world.

Proposed Resolution (November, 2006):

Change 3.4.6 [basic.lookup.udir] paragraph 1 as follows:

When looking up a namespace-name in a using-directive or namespace-alias-definition, In a using-directive or namespace-alias-definition, during the lookup for a namespace-name or for a name in a nested-name-specifier, only namespace names are considered.



571. References declared const

Section: 3.5  [basic.link]     Status: review     Submitter: Dave Abrahams     Date: 31 March 2006

According to 3.5 [basic.link] paragraph 3,

A name having namespace scope (3.3.5 [basic.scope.namespace]) has internal linkage if it is the name of

It is not possible to declare a reference to be const.

Proposed resolution (March, 2008):

Change 3.5 [basic.link] paragraph 3 as indicated (note addition of punctuation in the first bullet):

A name having namespace scope (3.3.5 [basic.scope.namespace]) has internal linkage if it is the name of




688. Constexpr constructors and static initialization

Section: 3.6.2  [basic.start.init]     Status: review     Submitter: Peter Dimov     Date: 26 March, 2008

Given this literal type,

    struct X {
        constexpr X() { }
    };

and this definition,

    static X x;

the current specification does not require that x be statically initialized because it is not “initialized with a constant expression” (3.6.1 [basic.start.main] paragraph 1).

Lawrence Crowl:

This guarantee is essential for atomics.

Jens Maurer:

Suggestion:

A reference with static storage duration or an object of literal type with static storage duration can be initialized with a constant expression (5.19 [expr.const]) or with a constexpr constructor; this is called constant initialization.

(Not spelling out “default constructor” makes it easier to handle multiple-parameter constexpr constructors, where there isn't “a” constant expression but several.)

Peter Dimov:

In addition, there is a need to enforce static initialization for non-literal types: std::shared_ptr, std::once_flag, and std::atomic_* all have nontrivial copy constructors, making them non-literal types. However, we need a way to ensure that a constexpr constructor called with constant expressions will guarantee static initialization, regardless of the nontriviality of the copy constructor.

Proposed resolution (April, 2008):

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

  2. ...A reference with static storage duration and an object of trivial or literal type with static storage duration can be initialized with a constant expression (5.19 [expr.const]); this If a reference with static storage duration is initialized with a constant expression (5.19 [expr.const]) or if the initialization of an object with static storage duration satisfies the requirements for the object being declared with constexpr (7.1.5 [dcl.constexpr]), that initialization is called constant initialization...
  3. Change 6.7 [stmt.dcl] paragraph 4 as follows:

  4. ...A local object of trivial or literal type (3.9 [basic.types]) with static storage duration initialized with constant-expressions is initialized Constant initialization (3.6.2 [basic.start.init]) of a local entity with static storage duration is performed before its block is first entered...
  5. Change 7.1.5 [dcl.constexpr] paragraph 7 as follows:

  6. A constexpr specifier used in an object declaration declares the object as const. Such an object shall be initialized, and every expression that appears in its initializer (8.5 [dcl.init]) shall be a constant expression. Every implicit conversion used in converting the initializer expressions and every constructor call used for the initialization shall be one of those allowed in a constant expression (5.19 [expr.const])...
  7. Replace 8.5.1 [dcl.init.aggr] paragraph 14 as follows:

  8. When an aggregate with static storage duration is initialized with a brace-enclosed initializer-list, if all the member initializer expressions are constant expressions, and the aggregate is a trivial type, the initialization shall be done during the static phase of initialization (3.6.2 [basic.start.init]); otherwise, it is unspecified whether the initialization of members with constant expressions takes place during the static phase or during the dynamic phase of initialization. [Note: The order of initialization for aggregates with static storage duration is specified in 3.6.2 [basic.start.init] and 6.7 [stmt.dcl]. —end note]



220. All deallocation functions should be required not to throw

Section: 3.7.3.2  [basic.stc.dynamic.deallocation]     Status: review     Submitter: Herb Sutter     Date: 31 Mar 2000

[Picked up by evolution group at October 2002 meeting.]

The default global operators delete are specified to not throw, but there is no requirement that replacement global, or class-specific, operators delete must not throw. That ought to be required.

In particular:

We already require that all versions of an allocator's deallocate() must not throw, so that part is okay.

Rationale (04/00):

  1. Replacement deallocation functions are already required not to throw an exception (cf 17.4.3.7 [res.on.functions] paragraph 2, as applied to 18.5.1.1 [new.delete.single] paragraph 12 and 18.5.1.2 [new.delete.array] paragraph 11).
  2. Section 17.4.3.5 [replacement.functions] is describing the signatures of the functions to be replaced; exception specfications are not part of the signature.
  3. There does not appear to be any pressing need to require that class member deallocation functions not throw.

Note (March, 2008):

The Evolution Working Group has accepted the intent of this issue and referred it to CWG for action for C++0x (see paper J16/07-0033 = WG21 N2173).

Proposed resolution (March, 2008):

Change 3.7.3.2 [basic.stc.dynamic.deallocation] paragraph 3 as follows:

A deallocation function shall not terminate by throwing an exception. The value of the first argument supplied to a deallocation function...



597. Conversions applied to out-of-lifetime non-POD lvalues

Section: 3.8  [basic.life]     Status: review     Submitter: Mike Miller     Date: 27 September 2006

An lvalue referring to an out-of-lifetime non-POD class objects can be used in limited ways, subject to the restrictions in 3.8 [basic.life] paragraph 6:

if the original object will be or was of a non-POD class type, the program has undefined behavior if:

There are at least a couple of questionable things in this list. First, there is no “implicit conversion to a reference to a base class,” as assumed by the second bullet. Presumably this is intended to say that the lvalue is bound to a reference to a base class, and the cross-reference should be to 8.5.3 [dcl.init.ref], not to 4.10 [conv.ptr] (which deals with pointer conversions). However, even given that adjustment, it is not clear why it is forbidden to bind a reference to a non-virtual base class of an out-of-lifetime object, as that is just an address offset calculation. (Binding to a virtual base, of course, would require access to the value of the object and thus cannot be done outside the object's lifetime.)

The third bullet also appears questionable. It's not clear why static_cast is discussed at all here, as the only permissible static_cast conversions involving reference types and non-POD classes are to references to base or derived classes and to the same type, modulo cv-qualification; if implicit “conversion” to a base class reference is forbidden in the second bullet, why would an explicit conversion be permitted in the third? Was this intended to refer to reinterpret_cast? Also, is there a reason to allow char types but disallow array-of-char types (which are more likely to be useful than a single char)?

Proposed resolution (March, 2008):

  1. Change 3.8 [basic.life] paragraph 5 as follows:

  2. ...If the object will be or was of a non-trivial class type, the program has undefined behavior if:

  3. Change 3.8 [basic.life] paragraph 6 as follows:

  4. ...if the original object will be or was of a non-trivial class type, the program has undefined behavior if:

[Drafting notes: Paragraph 5 was changed to track the changes to paragraph 6. See also the resolution for issue 658.]




619. Completeness of array types

Section: 3.9  [basic.types]     Status: review     Submitter: Steve Clamage     Date: 16 February 2007

Is the following example well-formed?

    struct S {
        static char a[5];
    };
    char S::a[];    // Unspecified bound in definition

3.5 [basic.link] paragraph 10 certainly makes allowance for declarations to differ in the presence or absence of a major array bound. However, 3.1 [basic.def] paragraph 5 says that

A program is ill-formed if the definition of any object gives the object an incomplete type (3.9 [basic.types]).

3.9 [basic.types] paragraph 7 says,

The declared type of an array object might be an array of unknown size and therefore be incomplete at one point in a translation unit and complete later on; the array types at those two points (“array of unknown bound of T” and “array of N T”) are different types.

This wording appears to make no allowance for the C concept of “composite type;” instead, each declaration is said to have its own type. By this interpretation, the example is ill-formed, because the type declared by the definition of S::a is incomplete.

If the example is intended to be well-formed, the Standard needs explicit wording stating that an omitted array bound in a declaration is implicitly taken from that of a visible declaration of that object, if any.

Notes from the April, 2007 meeting:

The CWG agreed that this usage should be permitted.

Proposed resolution (June, 2008):

  1. Change 8.3.4 [dcl.array] paragraph 1 as follows:

  2. ...If Except as noted below, if the constant expression is omitted, the type of the identifier of D is “derived-declarator-type-list array of unknown bound of T,” an incomplete object type...
  3. Change 8.3.4 [dcl.array] paragraph 3 as follows:

  4. When several “array of” specifications are adjacent, a multidimensional array is created; only the first of the constant expressions that specify the bounds of the arrays can may be omitted only for the first member of the sequence. [Note: this elision is useful for function parameters of array types, and when the array is external and the definition, which allocates storage, is given elsewhere. —end note] In addition to declarations in which an incomplete object type is allowed, an array bound may be omitted in the declaration of a function parameter (8.3.5 [dcl.fct]). The first constant-expression can An array bound may also be omitted when the declarator is followed by an initializer (8.5 [dcl.init]). In this case the bound is calculated from the number of initial elements (say, N) supplied (8.5.1 [dcl.init.aggr]), and the type of the identifier of D is “array of N T.” Furthermore, if there is a visible declaration of the name declared by the declarator-id (if any) in which the bound was specified, an omitted array bound is taken to be the same as in that earlier declaration.



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



240. Uninitialized values and undefined behavior

Section: 4.1  [conv.lval]     Status: review     Submitter: Mike Miller     Date: 8 Aug 2000

4.1 [conv.lval] paragraph 1 says,

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

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

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

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

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

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

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

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

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

John Max Skaller:

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

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

and you can see the bracketed expression is an lvalue.

A consequence is:

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

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

Proposed Resolution (November, 2006):

  1. Add the indicated words to 3.9 [basic.types] paragraph 4:

    ... For trivial types, the value representation is a set of bits in the object representation that determines a value, which is one discrete element of an implementation-defined set of values. Any use of an indeterminate value (5.3.4 [expr.new], 8.5 [dcl.init], 12.6.2 [class.base.init]) of a type other than unsigned char results in undefined behavior.
  2. Change 4.1 [conv.lval] paragraph 1 as follows:

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

Additional note (May, 2008):

The C committee is dealing with a similar issue in their DR336. According to this analysis, they plan to take almost the opposite approach to the one described above by augmenting the description of their version of the lvalue-to-rvalue conversion. The CWG did not consider that access to an unsigned char might still trap if it is allocated in a register and needs to reevaluate the proposed resolution in that light. See also issue 129.




693. New string types and deprecated conversion

Section: 4.2  [conv.array]     Status: review     Submitter: Alisdair Meredith     Date: 21 April, 2008

The deprecated conversion from string literal to pointer to (non-const) character in 4.2 [conv.array] paragraph 2 has been extended to apply to char16_t and char32_t types, but not to UTF8 and raw string literals. Is this disparity intentional? Should it be extended to all new string types, reverted to just the original character types, or revoked altogether?

Additional places in the Standard that may need to change include 15.1 [except.throw] paragraph 3 and 13.3.3.2 [over.ics.rank] paragraph 3.

Proposed resolution (June, 2008):

  1. Remove 4.2 [conv.array] paragraph 2:

  2. A string literal (2.13.4 [lex.string]) with no prefix, with a u prefix, with a U prefix, or with an L prefix can be converted to an rvalue of type “pointer to char”, “pointer to char16_t”, “pointer to char32_t”, or “pointer to wchar_t”, respectively. In any case, the result is a pointer to the first element of the array. This conversion is considered only when there is an explicit appropriate pointer target type, and not when there is a general need to convert from an lvalue to an rvalue. [Note: this conversion is deprecated. See Annex D [depr]. —end note] For the purpose of ranking in overload resolution (13.3.3.1.1 [over.ics.scs]), this conversion is considered an array-to-pointer conversion followed by a qualification conversion (4.4 [conv.qual]). [Example: "abc" is converted to “pointer to const char” as an array-to-pointer conversion, and then to “pointer to char” as a qualification conversion. —end example]
  3. Delete the indicated text from the third sub-bullet of the first bullet of paragraph 3 of 13.3.3.2 [over.ics.rank]:

  4. Delete the note from 15.1 [except.throw] paragraph 3 as follows:

  5. A throw-expression initializes a temporary object, called the exception object, the type of which is determined by removing any top-level cv-qualifiers from the static type of the operand of throw and adjusting the type from “array of T” or “function returning T” to “pointer to T” or “pointer to function returning T”, respectively. [Note: the temporary object created for a throw-expression that is a string literal is never of type char*, char16_t*, char32_t*, or wchar_t*; that is, the special conversions for string literals from the types “array of const char”, “array of const char16_t”, “array of const char32_t”, and “array of const wchar_t” to the types “pointer to char”, “pointer to char16_t”, “pointer to char32_t”, and “pointer to wchar_t”, respectively (4.2 [conv.array]), are never applied to a throw-expression. —end note] The temporary is an lvalue...
  6. Change the discussion of 2.13.4 [lex.string] in C.1.1 [diff.lex] as follows:

  7. ...

    Difficulty of converting: Simple syntactic transformation, because string literals can be converted to char*; (4.2 [conv.array]). The most common cases are handled by a new but deprecated standard conversion: Semantic transformation.

        char* p = "abc";               // valid in C, deprecated in C++
        char* q = expr ? "abc" : "de"; // valid in C, invalid in C++
    

    How widely used: Programs that have a legitimate reason to treat string literals as pointers to potentially modifiable memory are probably rare. Seldom.

  8. Delete D.4 [depr.string]:

  9. D.4 Implicit conversion from const strings

    The implicit conversion from const to non-const qualification for string literals (4.2 [conv.array]) is deprecated.




222. Sequence points and lvalue-returning operators

Section: 5  [expr]     Status: review     Submitter: Andrew Koenig     Date: 20 Dec 1999

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

    (a += b) += c;

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

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

    a += b; a += c;

but what about

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

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

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

    x[i++] = y;

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

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

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

5.17 [expr.ass] paragraph 1 says,

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

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

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

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

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

Francis Glassborow:

My understanding is that for a single variable:

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

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

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

Erwin Unruh:

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

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

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

In my view the semantics could be:

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

Jerry Schwarz:

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

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

Lawrence Crowl:

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

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

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

Erwin Unruh:

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

So we have one of several choices:

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

Andrew Koenig:

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

    volatile int v;
    int i;

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

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

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

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

I wonder if the following resolution is sufficient:

Append to 5.17 [expr.ass] paragraph 1:

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

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

Notes on 10/01 meeting:

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

Notes from the 4/02 meeting:

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

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

Notes from October 2004 meeting:

Discussion centered around whether a sequence point “between assigning the new value to the left operand and yielding the result of the expression” would require completion of all side effects of the operand expressions before the value of the assignment expression was used in another expression. The consensus opinion was that it would, that this is the definition of a sequence point. Jason Merrill pointed out that adding a sequence point after the assignment is essentially the same as rewriting

    b += a

as

    b += a, b

Clark Nelson expressed a desire for something like a “weak” sequence point that would force the assignment to occur but that would leave the side effects of the operands unconstrained. In support of this position, he cited the following expression:

    j = (i = j++)

With the proposed addition of a full sequence point after the assignment to i, the net effect is no change to j. However, both g++ and MSVC++ behave differently: if the previous value of j is 5, the value of the expression is 5 but j gets the value 6.

Clark Nelson will investigate alternative approaches and report back to the working group.

Proposed resolution (March, 2008):

See issue 637.




556. Conflicting requirements for acceptable aliasing

Section: 5.17  [expr.ass]     Status: review     Submitter: Mike Miller     Date: 30 January 2006

There appear to be two different specifications for when aliasing is permitted. One is in 3.10 [basic.lval] paragraph 15:

If a program attempts to access the stored value of an object through an lvalue of other than one of the following types the behavior is undefined

There is also a much more restrictive specification in 5.17 [expr.ass] paragraph 8:

If the value being stored in an object is accessed from another object that overlaps in any way the storage of the first object, then the overlap shall be exact and the two objects shall have the same type, otherwise the behavior is undefined.

This affects, for example, the definedness of operations on union members: when may a value be stored into one union member and accessed via another.

It should be noted that this conflict existed in C90 and is unchanged in C99 (see, for example, section 6.5 paragraph 7 and section 6.5.16.1 paragraph 3 of ISO/IEC 9899:1999, which directly parallel the sections cited above).

Notes from the October, 2006 meeting:

This issue is based on a misunderstanding of the intent of the wording in 5.17 [expr.ass] paragraph 8. Instead of being a general statement about aliasing, it's describing the situation in which the source of the value being assigned is storage that overlaps the storage of the target object. The proposed resolution should make that clearer rather than changing the specification.

Proposed resolution (June, 2008):

Add the following note at the end of 5.17 [expr.ass] paragraph 8:

If the value being stored in an object is accessed from another object that overlaps in any way the storage of the first object, then the overlap shall be exact and the two objects shall have the same type, otherwise the behavior is undefined. [Note: this restriction applies to the relationship between the left and right sides of the assignment operation; it is not a statement about how the target of the assignment may be aliased in general. See 3.10 [basic.lval]. —end note]



236. Explicit temporaries and integral constant expressions

Section: 5.19  [expr.const]     Status: review     Submitter: Mike Miller     Date: 19 Jul 2000

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

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

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

Note (March, 2008):

This issue should be closed as NAD as a result of the rewrite of 5.19 [expr.const] in conjunction with the constexpr proposal.




684. Constant expressions involving the address of an automatic variable

Section: 5.19  [expr.const]     Status: review     Submitter: Jens Maurer     Date: 13 March, 2008

The expressions that are excluded from being constant expressions in 5.19 [expr.const] paragraph 2 does not address an example like the following:

    void f() {
       int a;
       constexpr int* p = &a;    // should be ill-formed, currently isn't
    }

Suggested resolution:

Add the following bullet to the list in 5.19 [expr.const] paragraph 2:

Proposed resolution (June, 2008):

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

  2. Objects with static storage duration (3.7.1 [basic.stc.static]) or thread storage duration (3.7.2) shall be zero-initialized (8.5 [dcl.init]) before any other initialization takes place. A reference with static or thread storage duration and an object of trivial or literal type with static or thread storage duration can be initialized with a constant expression (5.19 [expr.const]); this is called constant initialization. Constant initialization is performed:
    • if an object of trivial or literal type with static or thread storage duration is initialized with a constant expression (5.19 [expr.const]), or

    • if a reference with static or thread storage duration is initialized with a constant expression that is not an lvalue designating an object with thread or automatic storage duration.

    Together, zero-initialization and constant initialization...
  3. Add the following in 5.19 [expr.const] paragraph 2:




573. Conversions between function pointers and void*

Section: 5.2.10  [expr.reinterpret.cast]     Status: review     Submitter: Steve Adamczyk     Date: 13 April 2006

The resolution to issue 195 makes “converting a pointer to a function into a pointer to an object type or vice versa” conditionally-supported behavior. In doing so, however, it overlooked the fact that void is not an “object type” (3.9 [basic.types] paragraph 9). The wording should be amended to allow conversion to and from void* types.

Proposed resolution (June, 2008):

  1. Change 3.9.2 [basic.compound] paragraph 4 as follows:

  2. Objects of cv-qualified (3.9.3 [basic.type.qualifier]) or cv-unqualified type void* (pointer to void) A pointer to cv-qualified or cv-unqualified void can be used to point to objects of unknown type. A void* shall be able to hold any object pointer and is thus considered to be an object pointer type, although it is not a pointer to object type (because void is not an object type). A cv-qualified or cv-unqualified (3.9.3 [basic.type.qualifier]) An object of type cv void* shall have the same representation and alignment requirements as a cv-qualified or cv-unqualified cv char*.
  3. Change 4.10 [conv.ptr] paragraph 1 as follows:

  4. ...A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of pointer to object or pointer to function object pointer or function pointer type...
  5. Change 5.2.10 [expr.reinterpret.cast] paragraph 7 as follows:

  6. A pointer to an object An object pointer can be explicitly converted to a pointer to an object an object pointer of different type. Except that converting an rvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types or void and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value, the result of such a pointer conversion is unspecified.
  7. Change 5.2.10 [expr.reinterpret.cast] paragraph 8 as follows:

  8. Converting a pointer to a function into a pointer to an object type a function pointer to an object pointer or vice versa is conditionally-supported...

[Drafting note: 14.1 [temp.param] paragraph 4 was not changed and thus continues to allow only pointers to objects, not object pointers, as non-type template parameters.]




658. Defining reinterpret_cast for pointer types

Section: 5.2.10  [expr.reinterpret.cast]     Status: review     Submitter: Dave Abrahams     Date: 4 November 2007

For years I've noticed that people will write code like this to get the address of an object's bytes:

  void foo(long* p) {
      char* q = reinterpret_cast<char*>(p);  // #1
      // do something with the bytes of *p by using q
  }

When in fact the only portable way to do it according to the standard is:

  void foo(long* p) {
      char* q = static_cast<char*>(static_cast<void*>(p));  // #2
      // do something with the bytes of *p by using q
  }

I thought reinterpret_cast existed so that vendors could provide some weird platform-specific things. However, recently Peter Dimov pointed out to me that if we substitute a class type for long above, reinterpret_cast is required to work as expected by 9.2 [class.mem] paragraph 18:

A pointer to a standard-layout struct object, suitably converted using a reinterpret_cast, points to its initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa.

So there isn't a whole lot of flexibility to do something different and useful on non-class types. Are there any implementations for which #1 actually fails? If not, I think it would be a good idea to nail reinterpret_cast down so that the standard says it does what people (correctly) think it does in practice.

Proposed resolution (March, 2008):

Change 5.2.10 [expr.reinterpret.cast] paragraph 7 as indicated:

A pointer to an object can be explicitly converted to a pointer to an object of different type. When an rvalue v of type “pointer to T1” is converted to the type “pointer to cv T2,” the result is static_cast<cv T2*>(static_cast<cv void*>(v)) if both T1 and T2 are standard-layout types (3.9 [basic.types]) and the alignment requirements of T2 are no stricter than those of T1. Except that cConverting an rvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value, t. The result of any other such a pointer conversion is unspecified.



665. Problems in the specification of dynamic_cast

Section: 5.2.7  [expr.dynamic.cast]     Status: review     Submitter: Daniel Krügler     Date: 1 December 2007

At least one implementation accepts the following example as well-formed (returning a null pointer at runtime), although others reject it at compile time:

    struct A { virtual ~A(); };
    struct B: private A { } b;
    A* pa = dynamic_cast<A*>(&b);

Presumably the intent of 5.2.7 [expr.dynamic.cast] paragraph 5 is that all up-casts (converting from derived to base) are to be handled at compile time, regardless of whether the class involved is polymorphic or not:

If T is “pointer to cv1 B” and v has type “pointer to cv2 D” such that B is a base class of D, the result is a pointer to the unique B subobject of the D object pointed to by v. Similarly, if T is “reference to cv1 B” and v has type cv2 D such that B is a base class of D, the result is the unique B subobject of the D object referred to by v... In both the pointer and reference cases, cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2, and B shall be an accessible unambiguous base class of D.

One explanation for the implementation that accepts the example at compile time is that the final sentence is interpreted as part of the condition for the applicability of this paragraph, so that this case falls through into the description of runtime checking that follows. This (mis-)interpretation is buttressed by the example in paragraph 9, which reads in significant part:

    class A { virtual void f(); };
    class B { virtual void g(); };
    class D : public virtual A, private B {};
    void g() {
        D d;
        B* bp;
        bp = dynamic_cast<B*>(&d); // fails
    }

The “fails” comment is identical to the commentary on the lines in the example where the run-time check fails. If the interpretation that paragraph 5 is supposed to apply to all up-casts, presumably this comment should change to “ill-formed,” or the line should be removed from the example altogether.

It should be noted that this interpretation (that the example is ill-formed and the runtime check applies only to down-casts and cross-casts) rejects some programs that could plausibly be accepted and actually work at runtime. For example,

    struct B { virtual ~B(); };
    struct D: private virtual B { };

    void test(D* pd) {
        B* pb = dynamic_cast<B*>(pd); // #1
    }

    struct D2: virtual B, virtual D {};

    void demo() {
        D2 d2;
        B* pb = dynamic_cast<B*>(&d2); // #2
        test(&d2); // #3
    }

According to the interpretation that paragraph 5 applies, line #1 is ill-formed. However, converting from D2 to B (line #2) is well-formed; if the alternate interpretation were applied, the conversion in line #1 could succeed when applied to d2 (line #3).

One final note: the wording in 5.2.7 [expr.dynamic.cast] paragraph 8 is incorrect:

The run-time check logically executes as follows:

All uses of T in this paragraph treat it as if it were a class type; in fact, T is the type to which the expression is being cast and thus is either a pointer type or a reference type, not a class type.

Proposed resolution (June, 2008):

  1. Change 5.2.7 [expr.dynamic.cast] paragraph 5 as follows:

  2. ...In both the pointer and reference cases, cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2, and B shall be an accessible unambiguous base class of D the program is ill-formed if cv2 is greater cv-qualification than cv1 or if B is an inaccessible or ambiguous base class of D.
  3. Change the comment in the example in 5.2.7 [expr.dynamic.cast] paragraph 9 as follows:

  4.     bp = dynamic_cast<B*>(&d);     // fails ill-formed (not a run-time check)
    
  5. Change 5.2.7 [expr.dynamic.cast] paragraph 8 as follows:

  6. The If C is the class type to which T points or refers, the run-time check logically executes as follows:




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 (December, 2006):

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

  2. The unary * operator performs indirection dereferences a pointer value: 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 applied values are added and the result is dereferenced 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 through dereferencing the (pointer) result to yield an integer. In the declarator (*pif)(const char*, const char*), the extra parentheses are necessary to indicate that indirection through dereferencing a pointer to a function yields a function, which is then called.
  7. Change the index for * and “dereferencing” no longer to refer to “indirection.”

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




292. Deallocation on exception in new before arguments evaluated

Section: 5.3.4  [expr.new]     Status: review     Submitter: Andrei Iltchenko     Date: 26 Jun 2001

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

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

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

Now suppose we have:

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

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

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

Suggested resolution:

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

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

Proposed resolution (March, 2008):

Change 5.3.4 [expr.new] paragraph 18 as follows:

If any part of the object initialization described above [Footnote: ...] terminates by throwing an exception, storage has been obtained for the object, and a suitable deallocation function can be found, the deallocation function is called...



624. Overflow in calculating size of allocation

Section: 5.3.4  [expr.new]     Status: review     Submitter: Jens Maurer     Date: 8 March 2007

Issue 256 was closed without action, principally on the the grounds that an implementation could provide a means (command-line option, #pragma, etc.) for requesting that the allocation size be checked for validity, but that “it would not be appropriate to require this overhead for every array allocation in every program.”

This rationale may be giving too much weight to the overhead such a check would add, especially when compared to the likely cost of actually doing the storage allocation. In particular, the test essentially amounts to something like

    if (max_allocation_size / sizeof(T) < num_elements)
        throw std::bad_alloc();

(noting that max_allocation_size/sizeof(T) is a compile-time constant). It might make more sense to turn the rationale around and require the check, assuming that implementations could provide a mechanism for suppressing it if needed.

Suggested resolution:

In 5.3.4 [expr.new] paragraph 7, add the following words before the example:

If the value of the expression is such that the size of the allocated object would exceed the implementation-defined limit, an exception of type std::bad_alloc is thrown and no storage is obtained.

Note (March, 2008):

The Evolution Working Group has accepted the intent of issue 256 and referred it to CWG for action for C++0x (see paper J16/07-0033 = WG21 N2173).

Proposed resolution (March, 2008):

As suggested.

Notes from the June, 2008 meeting:

The CWG felt that this situation should not be treated like an out-of-memory situation and thus an exception of type std::bad_alloc (or, alternatively, returning a null pointer for a throw() allocator) would not be appropriate.

Proposed resolution (June, 2008):

Change 5.3.4 [expr.new] paragraph 8 as follows:

If the value of the expression in a direct-new-declarator is such that the size of the allocated object would exceed the implementation-defined limit, no storage is obtained and the new-expression terminates by throwing an exception of a type that would match a handler (15.3 [except.handle]) of type std::length_error (19.1.4 [length.error]). Otherwise, if When the value of the that expression in a direct-new-declarator is zero, the allocation function is called to allocate an array with no elements.

[Drafting note: std::length_error is thrown by std::string and std::vector and thus appears to be the right choice for the exception to be thrown here.]




672. Sequencing of initialization in new-expressions

Section: 5.3.4  [expr.new]     Status: review     Submitter: Clark Nelson     Date: 11 January, 2008

Consider the following code, which uses double-checked locking (DCL):

    Widget* Widget::Instance() {
      if (pInstance == 0) {           // 1: first check
        lock<mutex> hold(mutW);       // 2: acquire lock
        if (pInstance == 0) {         // 3: second check
          pInstance = new Widget();   // 4: create and assign
        }
      }                               // 5: release lock
    }

We want this to be fully correct when pInstance is an atomic pointer to Widget. To get that result, we have to disallow any assignment to pInstance until after the new object is fully constructed. In other words, we want this to be an invalid transformation of line 4:

    pInstance = operator new(sizeof(Widget));
    new (pInstance) Widget;

I don't think it would be surprising if this were disallowed. For example, if the constructor were to throw an exception, I think many people would expect the variable not to be modified. I think the question is whether it's sufficiently clearly disallowed.

This could be clarified by stating (somewhere appropriate — probably either in 5.3.4 [expr.new] paragraph 16 or paragraph 22) that the initialization of the allocated object is sequenced before the value computation of the new-expression. Then by 5.17 [expr.ass] paragraph 1 (“In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.”), the initialization would have to be sequenced before the assignment.

This is probably not a problem for atomic<Widget*> because its operator= is a function, and function calls provide the necessary guarantees. But for the plain pointer assignment case, there's still a question about whether the sequencing of side effects is constrained as tightly as it should be. In fact, you don't even have to throw an exception from the constructor for there to be a question.

    struct X {
        static X* p;
        X();
    };

    X* X::p = new X;

When the constructor for X is invoked by this new-expression, would it be valid for X::p to be non-null? If the answer is supposed to be “no,” then I think the Standard should express that intent more clearly.

Proposed resolution (March, 2008):

Change 5.3.4 [expr.new] paragraph 22 as indicated:

Whether Initialization of the allocated object is sequenced before the value computation of the new-expression. It is unspecified whether the allocation function is called before evaluating the constructor arguments or after evaluating the constructor arguments but before entering the constructor is unspecified. It is also unspecified whether the arguments to a constructor are evaluated if the allocation function returns the null pointer or exits using an exception.

[Drafting note: The editor may wish to move paragraph 22 up to immediately follow paragraph 16 or 17. The paragraphs numbered 18-21 deal with the case where deallocation is done because initialization terminates with an exception, whereas paragraph 22 applies more to the initialization itself, described in paragraph 16.]




599. Deleting a null function pointer

Section: 5.3.5  [expr.delete]     Status: review     Submitter: Martin Sebor     Date: 3 October 2006

The requirements for the operand of the delete operators are given in 5.3.5 [expr.delete] paragraph 2:

In either alternative, the value of the operand of delete may be a null pointer value. If it is not a null pointer value, in the first alternative (delete object), the value of the operand of delete shall be a pointer to a non-array object or a pointer to a subobject (1.8 [intro.object]) representing a base class of such an object (clause 10 [class.derived]). If not, the behavior is undefined. In the second alternative (delete array), the value of the operand of delete shall be the pointer value which resulted from a previous array new-expression. If not, the behavior is undefined.

There are no restrictions on the type of a null pointer, only on a pointer that is not null. That seems wrong.

Proposed resolution (June, 2008):

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

...The operand shall have a pointer to object type, or a class type having a single non-explicit conversion function (12.3.2 [class.conv.fct]) to a pointer to object type...



378. Wording that says temporaries are declared

Section: 6.6  [stmt.jump]     Status: review     Submitter: Gennaro Prota     Date: 07 September 2002

Paragraph 6.6 [stmt.jump] paragraph 2 of the standard says:

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

It refers to objects "that are declared" but the text in parenthesis also mentions temporaries, which cannot be declared. I think that text should be removed.

This is related to issue 276.

Proposed Resolution (November, 2006):

This issue is resolved by the resolution of issue 276.




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.



576. Typedefs in function definitions

Section: 7.1.3  [dcl.typedef]     Status: review     Submitter: Jon Caves     Date: 21 April 2006

7.1.3 [dcl.typedef] paragraph 1 says,

The typedef specifier shall not be used in a function-definition (8.4 [dcl.fct.def])...

Does this mean that the following is ill-formed?

    void f() {
        typedef int INT;
    }

Proposed resolution (March, 2008):

Change 7.1.3 [dcl.typedef] paragraph 1 as follows:

...The typedef specifier shall not be used in a function-definition (8.4 [dcl.fct.def]), and it shall not be combined in a decl-specifier-seq with any other kind of specifier except a type-specifier, and it shall not be used in the declaration of a function parameter nor in the decl-specifier-seq of a function-definition (8.4 [dcl.fct.def])...



539. Constraints on type-specifier-seq

Section: 7.1.6  [dcl.type]     Status: review     Submitter: Mike Miller     Date: 5 October 2005

The constraints on type-specifiers given in 7.1.6 [dcl.type] paragraphs 2 and 3 (at most one type-specifier except as specified, at least one type-specifier, no redundant cv-qualifiers) are couched in terms of decl-specifier-seqs and declarations. However, they should also apply to constructs that are not syntactically declarations and that are defined to use type-specifier-seqs, including 5.3.4 [expr.new], 6.6 [stmt.jump], 8.1 [dcl.name], and 12.3.2 [class.conv.fct].

Proposed resolution (March, 2008):

Change 7.1.6 [dcl.type] paragraph 3 as follows:

At In a complete type-specifier-seq or in a complete decl-specifier-seq of a declaration, at least one type-specifier that is not a cv-qualifier is required in a declaration shall appear unless it the declaration declares a constructor, destructor or conversion function.

(Note: paper N2546, voted into the Working Draft in February, 2008, addresses part of this issue.)




628. The values of an enumeration with no enumerator

Section: 7.2  [dcl.enum]     Status: review     Submitter: Gennaro Prota     Date: 15 March 2007

According to 7.2 [dcl.enum] paragraph 6, the underlying type of an enumeration with an empty enumeration-list is determined as if the enumeration-list contained a single enumerator with value 0. Paragraph 7, which specifies the values of an enumeration and the minimum size of bit-field needed represent those values needs a similar provision for empty enumeration-lists.

Proposed resolution (March, 2008):

Add the indicated sentence to the end of 7.2 [dcl.enum] paragraph 5:

...It is possible to define an enumeration that has values not defined by any of its enumerators. If the enumerator-list is empty, the values of the enumeration are as if the enumeration had a single enumerator with value 0.



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

Section: 7.5  [dcl.link]     Status: review     Submitter: Steve Adamczyk     Date: 1 Mar 2002

Here's an interesting case:

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

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

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

Notes from the 4/02 meeting:

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

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

Additional note (September, 2004)

This problem need not involve a conflict between a function and a variable; it can also arise with two variable declarations:

    int x;
    namespace N {
        extern "C" int x;
    }

Proposed resolution (March, 2008):

Change 7.5 [dcl.link] paragraph 6 as follows:

At most one function with a particular name can have C language linkage. Two declarations for a function with C language linkage with the same function name (ignoring the namespace names that qualify it) that appear in different namespace scopes refer to the same function. Two declarations for an object with C language linkage with the same name (ignoring the namespace names that qualify it) that appear in different namespace scopes refer to the same object. A function or object with C linkage shall not be declared with the same name (clause 3 [basic]) as an object or reference declared in global scope, unless both declarations denote the same object; no diagnostic is required if the declarations appear in different translation units. [Note: because of the one definition rule (3.2 [basic.def.odr]), only Only one definition for a function or object with C linkage may appear in the program (see 3.2 [basic.def.odr]); that is, implies that such a function or object must not be defined in more than one namespace scope. For example,

    int x;
    namespace A {
      extern "C" int f();
      extern "C" int g() { return 1; }
      extern "C" int h();
      extern "C" int x();               // ill-formed: same name as global-scope object x
    }

    namespace B {
      extern "C" int f();               // A::f and B::f refer
                                        // to the same function
      extern "C" int g() { return 1; }  // ill-formed, the function g
                                        // with C language linkage
                                        // has two definitions
    }

    int A::f() { return 98; }           // definition for the function f
                                        // with C language linkage
    extern "C" int h() { return 97; }
                                        // definition for the function h
                                        // with C language linkage
                                        // A::h and ::h refer to the same function

end note]




564. Agreement of language linkage or linkage-specifications?

Section: 7.5  [dcl.link]     Status: review     Submitter: Daveed Vandevoorde     Date: 8 March 2006

The wording of 7.5 [dcl.link] paragraph 5 is suspect:

If two declarations of the same function or object specify different linkage-specifications (that is, the linkage-specifications of these declarations specify different string-literals), the program is ill-formed if the declarations appear in the same translation unit, and the one definition rule (3.2) applies if the declarations appear in different translation units.

But what if only one of the declarations has a linkage-specification, while the other is left with the default C++ linkage? Shouldn't this restriction be phrased in terms of the functions’ or objects’ language linkage rather than linkage-specifications?

(Additional note [wmm]: Is the ODR the proper vehicle for enforcing this requirement? This is dealing with declarations, not necessarily definitions. Shouldn't this say “ill-formed, no diagnostic required” instead of some vague reference to the ODR?)

Proposed resolution (June, 2008):

Change 7.5 [dcl.link] paragraph 5 as follows:

If two declarations of the same function or object declare functions with the same name and parameter-type-list (8.3.5 [dcl.fct]) to be members of the same namespace or declare objects with the same name to be members of the same namespace specify different linkage-specifications (that is, the linkage-specifications of these declarations specify different string-literals) and the declarations give the names different language linkages, the program is ill-formed if the declarations appear in the same translation unit, and the one definition rule (3.2 [basic.def.odr]) applies; no diagnostic is required if the declarations appear in different translation units.



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




547. Partial specialization on member function types

Section: 8.3.5  [dcl.fct]     Status: review     Submitter: Peter Dimov     Date: 04 November 2005

The following example appears to be well-formed, with the partial specialization matching the type of Y::f(), even though it is rejected by many compilers:

    template<class T> struct X;

    template<class R> struct X< R() > {
    };

    template<class F, class T> void test(F T::* pmf) {
        X<F> x;
    }

    struct Y {
        void f() {
        }
    };

    int main() {
        test( &Y::f );
    }

However, 8.3.5 [dcl.fct] paragraph 4 says,

A cv-qualifier-seq shall only be part of the function type for a non-static member function, the function type to which a pointer to member refers, or the top-level function type of a function typedef declaration. The effect of a cv-qualifier-seq in a function declarator is not the same as adding cv-qualification on top of the function type. In the latter case, the cv-qualifiers are ignored.

This specification makes it impossible to write a partial specialization for a const member function:

    template<class R> struct X<R() const> {
    };

A template argument is not one of the permitted contexts for cv-qualification of a function type. This restriction should be removed.

Notes from the April, 2006 meeting:

During the meeting the CWG was of the opinion that the “R() const” specialization would not match the const member function even if it were allowed and so classified the issue as NAD. Questions have been raised since the meeting, however, suggesting that the template argument in the partial specialization would, in fact, match the type of a const member function (see, for example, the very similar usage via typedefs in 9.3 [class.mfct] paragraph 9). The issue is thus being left open for renewed discussion at the next meeting.

Proposed resolution (June, 2008):

Change 8.3.5 [dcl.fct] paragraph 7 as follows:

A cv-qualifier-seq shall only be part of the function type for a non-static member function, the function type to which a pointer to member refers, or the top-level function type of a function typedef declaration, or the top-level function type of a type-id that is a template-argument for a type template-parameter. The effect... A ref-qualifier shall only be part of the function type for a non-static member function, the function type to which a pointer to member refers, or the top-level function type of a function typedef declaration, or the top-level function type of a type-id that is a template-argument for a type template-parameter. The return type...



681. Restrictions on declarators with late-specified return types

Section: 8.3.5  [dcl.fct]     Status: review     Submitter: Mike Miller     Date: 10 March, 2008

The wording added to 8.3.5 [dcl.fct] for declarators with late-specified return types says,

In a declaration T D where D has the form

and the type of the contained declarator-id in the declaration T D1 is “derived-declarator-type-list T,” T shall be the single type-specifier auto and the derived-declarator-type-list shall be empty.

These restrictions were intended to ensure that the return type of the function is exactly the specified type-id following the ->, not modified by declarator operators and cv-qualification.

Unfortunately, the requirement for an empty derived-declarator-type-list does not achieve this goal but instead forbids declarations like

    auto (*fp)() -> int;    // pointer to function returning int

while allowing declarations like

    auto *f() -> int;       // function returning pointer to int

The reason for this is that, according to the grammar in 8 [dcl.decl] paragraph 4, the declarator *f() -> int is parsed as a ptr-operator applied to the direct-declarator f() -> int; that is, the declarator D1 seen in 8.3.5 [dcl.fct] is just f, and the derived-declarator-type-list is thus empty.

By contrast, the declarator (*fp)() -> int is parsed as the direct-declarator (*fp) followed by the parameter-declaration-clause, etc. In this case, D1 in 8.3.5 [dcl.fct] is (*fp) and the derived-declarator-type-list is “pointer to,” i.e., not empty.

My personal view is that there is no reason to forbid the (*fp)() -> int form, and that doing so is problematic. For example, this restriction would require users desiring the late-specified return type syntax to write function parameters as function types and rely on parameter type transformations rather than writing them as pointer-to-function types, as they will actually turn out to be:

    void f(auto (*fp)() -> int);  // ill-formed
    void f(auto fp() -> int);     // OK (but icky)

It may be helpful in deciding whether to allow this form to consider the example of a function returning a pointer to a function. With the current restriction, only one of the three plausible forms is allowed:

    auto (*f())() -> int;           // Disallowed
    auto f() -> int (*)();          // Allowed
    auto f() -> auto (*)() -> int;  // Disallowed
Suggested resolution:
  1. Delete the words “and the derived-declarator-type-list shall be empty” from 8.3.5 [dcl.fct] paragraph 2.

  2. Add a new paragraph following 8.4 [dcl.fct.def] paragraph 4:

  3. A ptr-operator shall not be applied, directly or indirectly, to a function declarator with a late-specified return type (8.3.5 [dcl.fct]).

Proposed resolution (June, 2008):

  1. Change the grammar in 8 [dcl.decl] paragraph 4 as follows:

  2. Change the grammar in 8.1 [dcl.name] paragraph 1 as follows:

  3. Change 8.3.5 [dcl.fct] paragraph 2 as follows:

  4. ... T shall be the single type-specifier auto and the derived-declarator-type-list shall be empty. Then the type...
  5. Change all occurrences of direct-new-declarator in 5.3.4 [expr.new] to noptr-new-declarator. These changes appear in the grammar in paragraph 1 and in the text of paragraphs 6-8, as follows:

  6. When the allocated object is an array (that is, the direct-noptr-new-declarator syntax is used or the new-type-id or type-id denotes an array type), the new-expression yields a pointer to the initial element (if any) of the array. [Note: both new int and new int[10] have type int* and the type of new int[i][10] is int (*)[10]end note]

    Every constant-expression in a direct-noptr-new-declarator shall be an integral constant expression (5.19 [expr.const]) and evaluate to a strictly positive value. The expression in a direct-noptr-new-declarator shall be of integral type, enumeration type, or a class type for which a single non-explicit conversion function to integral or enumeration type exists (12.3 [class.conv]). If the expression is of class type, the expression is converted by calling that conversion function, and the result of the conversion is used in place of the original expression. If the value of the expression is negative, the behavior is undefined. [Example: given the definition int n = 42, new float[n][5] is well-formed (because n is the expression of a direct-noptr-new-declarator), but new float[5][n] is ill-formed (because n is not a constant expression). If n is negative, the effect of new float[n][5] is undefined. —end example]

    When the value of the expression in a direct-noptr-new-declarator is zero, the allocation function is called to allocate an array with no elements.




543. Value initialization and default constructors

Section: 8.5  [dcl.init]     Status: review     Submitter: Mike Miller     Date: 27 October 2005

The wording resulting from the resolution of issue 302 does not quite implement the intent of the issue. The revised wording of 3.2 [basic.def.odr] paragraph 2 is:

A default constructor for a class is used by default initialization or value initialization as specified in 8.5 [dcl.init].

This sounds as if 8.5 [dcl.init] specifies how and under what circumstances value initialization uses a default constructor (which was, in fact, the case for default initialization in the original wording). However, the normative text there makes it plain that value initialization does not call the default constructor (the permission granted to implementations to call the default constructor for value initialization is in a non-normative footnote).

The example that occasioned this observation raises an additional question. Consider:

    struct POD {
      const int x;
    };

    POD data = POD();

According to the (revised) resolution of issue 302, this code is ill-formed because the implicitly-declared default constructor will be implicitly defined as a result of being used by value initialization (12.1 [class.ctor] paragraph 7), and the implicitly-defined constructor fails to initialize a const-qualified member (12.6.2 [class.base.init] paragraph 4). This seems unfortunate, because the (trivial) default constructor of a POD class is otherwise not used — default initialization applies only to non-PODs — and it is not actually needed in value initialization. Perhaps value initialization should be defined to “use” the default constructor only for non-POD classes? If so, both of these problems would be resolved by rewording the above-referenced sentence of 3.2 [basic.def.odr] paragraph 2 as:

A default constructor for a non-POD class is used by default initialization or value initialization as specified in (8.5 [dcl.init]).

Notes from the April, 2006 meeting:

The approach favored by the CWG was to leave 3.2 [basic.def.odr] unchanged and to add normative wording to 8.5 [dcl.init] indicating that it is unspecified whether the default constructor is called.

Notes from the October, 2006 meeting:

The CWG now prefers that it should not be left unspecified whether programs of this sort are well- or ill-formed; instead, the Standard should require that the default constructor be defined in such cases. Three possibilities of implementing this decision were discussed:

  1. Change 3.2 [basic.def.odr] to state flatly that the default constructor is used by value initialization (removing the implication that 8.5 [dcl.init] determines the conditions under which it is used).

  2. Change 8.5 [dcl.init] to specify that non-union class objects with no user-declared constructor are value-initialized by first zero-initializing the object and then calling the (implicitly-defined) default constructor, replacing the current specification of value-initializing each of its sub-objects.

  3. Add a normative statement to 8.5 [dcl.init] that value-initialization causes the implicitly-declared default constructor to be implicitly defined, even if it is not called.

Proposed resolution (June, 2008):

Change the second bullet of the value-initialization definition in 8.5 [dcl.init] paragraph 5 as follows:




615. Incorrect description of variables that can be initialized

Section: 8.5  [dcl.init]     Status: review     Submitter: comp.std.c++     Date: 30 January 2007

8.5 [dcl.init] paragraph 2 reads,

Automatic, register, static, and external variables of namespace scope can be initialized by arbitrary expressions involving literals and previously declared variables and functions.

Both “automatic” and “static” are used to describe storage durations, “register” is a storage class specifier which indicates the object has automatic storage duration, “external” describes linkage, and “namespace scope” is a kind of scope. Automatic, register, static and external, together with namespace scope, are used to restrict the “variables.”

Register objects are only a sub-set of automatic objects and thus the word “register” is redundant and should be elided. If register objects are to be emphasized, they should be mentioned like “Automatic (including register)...”

Variables having namespace scope can never be automatic; they can only be static, with either external or internal linkage. Therefore, there are in fact no “automatic variables of namespace scope,” and the “static” in “static variables of namespace scope” is useless.

In fact, automatic and static variables already compose all variables with either external linkage or not, and thus the “external*#8221; becomes redundant, too, and the quoted sentence seems to mean that all variables of namespace scope can be initialized by arbitrary expressions. But this is not true because not all internal variables of namespace scope can. Therefore, the restrictive “external” is really necessary, not redundant.

As a result, the erroneous restrictive “automatic, register, static” should be removed and the quoted sentence may be changed to:

External variables of namespace scope can be initialized by arbitrary expressions involving literals and previously declared variables and functions.

Notes from the April, 2007 meeting:

This sentence is poorly worded, but the analysis given in the issue description is incorrect. The intent is simply that the storage class of a variable places no restrictions on the kind of expression that can be used to initialize it (in contrast to C, where variables of static storage duration can only be initialized by constant expressions).

Proposed resolution (June, 2008):

Change 8.5 [dcl.init] paragraph 2 as follows:

Automatic, register, static, and external variables of namespace scope Variables of automatic, thread, and static storage duration can be initialized by arbitrary expressions involving literals and previously declared variables and functions...



632. Brace-enclosed initializer for scalar member of aggregate

Section: 8.5.1  [dcl.init.aggr]     Status: review     Submitter: Greg Comeau     Date: 3 May 2007

C (both C90 and C99) appear to allow a declaration of the form

    struct S { int i; } s = { { 5 } };

in which the initializer of a scalar member of an aggregate can itself be brace-enclosed. The relevant wording from the C99 Standard is found in 6.7.8 paragraph 11:

The initializer for a scalar shall be a single expression, optionally enclosed in braces.

and paragraph 16:

Otherwise, the initializer for an object that has aggregate or union type shall be a brace-enclosed list of initializers for the elements or named members.

The “list of initializers” in paragraph 16 must be a recursive reference to paragraph 11 (that's the only place that describes how an initialized item gets its value from the initializer expression), which would thus make the “brace-enclosed” part of paragraph 11 apply to each of the initializers in the list in paragraph 16 as well.

This appears to be an incompatibility between C and C++: 8.5.1 [dcl.init.aggr] paragraph 11 says,

If the initializer-list begins with a left brace, then the succeeding comma-separated list of initializer-clauses initializes the members of a subaggregate....

which clearly leaves the impression that only a subaggregate may be initialized by a brace-enclosed initializer-clause.

Either the specification in 8.5.1 [dcl.init.aggr] should be changed to allow a brace-enclosed initializer of a scalar member of an aggregate, as in C, or this incompatibility should be listed in Appendix C [diff].

Notes from the July, 2007 meeting:

It was noted that implementations differ in their handling of this construct; however, the issue is long-standing and fairly obscure.

Notes from the October, 2007 meeting:

The initializer-list proposal will resolve this issue when it is adopted.




355. Global-scope :: in nested-name-specifier

Section: 9  [class]     Status: review     Submitter: Clark Nelson     Date: 16 May 2002

In looking at a large handful of core issues related to elaborated-type-specifiers and the naming of classes in general, I discovered an odd fact. It turns out that there is exactly one place in the grammar where nested-name-specifier is not immediately preceded by "::opt": in class-head, which is used only for class definitions. So technically, this example is ill-formed, and should evoke a syntax error:

  struct A;
  struct ::A { };

However, all of EDG, GCC and Microsoft's compiler accept it without a qualm. In fact, I couldn't get any of them to even warn about it.

Suggested resolution:

It would simplify the grammar, and apparently better reflect existing practice, to factor the global-scope operator into the rule for nested-name-specifier.

Proposed resolution (November, 2006):

  1. In 3.4.3 [basic.lookup.qual] paragraph 6, change the grammar snippet as follows:

  2. Delete 5.1 [expr.prim] paragraph 4 (“The operator :: followed by...”). [Drafting note: It's covered by paragraph 8 (type, lvalue-ness, member-ness, reference to 3.4.3.2 [namespace.qual]) and 3.4.3.2 [namespace.qual] (qualified lookup for namespace members).]

  3. Change the grammar in 5.1 [expr.prim] paragraph 7 as follows (deleting the :: forms from qualified-id and adding :: as a new production for nested-name-specifier):

  4. Change 5.1 [expr.prim] paragraph 8 as follows:

    A nested-name-specifier that names designates a namespace (7.3 [basic.namespace]), followed by the name of a member of that namespace...
  5. Change 5.1 [expr.prim] paragraph 10 as follows:

    In a qualified-id, if the id-expression unqualified-id is a conversion-function-id...
  6. In 5.2 [expr.post] paragraph 1, change the grammar as follows:

  7. In 5.2.4 [expr.pseudo] paragraph 2, change the grammar snippet as follows:

  8. In 7.1.6.2 [dcl.type.simple] paragraph 1, change the grammar as follows:

  9. In 7.1.6.3 [dcl.type.elab] before paragraph 1, change the grammar as follows:

  10. In 7.1.6.3 [dcl.type.elab] paragraph 1, change the grammar snippet as follows:

  11. In 7.3.2 [namespace.alias] paragraph 1, change the grammar as follows:

  12. In 7.3.3 [namespace.udecl] paragraph 1, change the grammar as follows:

  13. In 7.3.4 [namespace.udir] before paragraph 1, change the grammar as follows:

  14. In 8 [dcl.decl] paragraph 4, change the grammar as follows:

  15. In 8.3.3 [dcl.mptr] paragraph 1, change the grammar snippet as follows:

  16. In 9.2 [class.mem] before paragraph 1, change the grammar as follows:

  17. In 10 [class.derived] paragraph 1, change the grammar as follows:

  18. In 12.6.2 [class.base.init] paragraph 1, change the grammar as follows:

  19. In 14.6 [temp.res] paragraph 3, change the grammar as follows:

[Drafting notes: gcc 4.1.1 rejects the example in the issue description. I still think it's a good idea to make the grammar more uniform, and there ought to be nothing special about the global scope operator. However, there is a slight change in effective grammar with these modification: all places that require a non-optional nested-name-specifier used to required at least one named level of nesting. With these changes, "::" is a valid nested-name-specifier (that denotes the global scope). Any such use needed to protect against non-class (i.e. namespace) scopes in its semantic description anyway, which also covers the "::" case.]




645. Are bit-field and non-bit-field members layout compatible?

Section: 9.2  [class.mem]     Status: review     Submitter: Alan Stokes     Date: 9 Aug 2007

The current wording defining a “common initial sequence” in 9.2 [class.mem] paragraph 17 does not address the case in which one member is a bit-field and the corresponding member is not:

Two standard-layout structs share a common initial sequence if corresponding members have layout-compatible types (and, for bit-fields, the same widths) for a sequence of one or more initial members.

Presumably the intent was something like, “(and, if one of the pair is a bit-field, the other is also a bit-field of the same width).”

Proposed Resolution (October, 2007):

Change 9.2 [class.mem] paragraph 18 as follows:

... Two standard-layout structs share a common initial sequence if corresponding members have layout-compatible types (and, for bit-fields, the same widths) and either neither is a bit-field or both are bit-fields with the same widths for a sequence of one or more initial members.



512. Union members with user-declared non-default constructors

Section: 9.5  [class.union]     Status: review     Submitter: Alisdair Meredith     Date: 19 Mar 2005

Can a member of a union be of a class that has a user-declared non-default constructor? The restrictions on union membership in 9.5 [class.union] paragraph 1 only mention default and copy constructors:

An object of a class with a non-trivial default constructor (12.1 [class.ctor]), a non-trivial copy constructor (12.8 [class.copy]), a non-trivial destructor (12.4 [class.dtor]), or a non-trivial copy assignment operator (13.5.3 [over.ass], 12.8 [class.copy]) cannot be a member of a union...

(12.1 [class.ctor] paragraph 11 does say, “a non-trivial constructor,” but it's not clear whether that was intended to refer only to default and copy constructors or to any user-declared constructor. For example, 12.2 [class.temporary] paragraph 3 also speaks of a “non-trivial constructor,” but the cross-references there make it clear that only default and copy constructors are in view.)

Note (March, 2008):

This issue was resolved by the adoption of paper J16/08-0054 = WG21 N2544 (“Unrestricted Unions”) at the Bellevue meeting.




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

Section: 9.7  [class.nest]     Status: review     Submitter: Jason Shirk     Date: 21 March 2002

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

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

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

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

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

  struct base {
    struct nested;
  };

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

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

Proposed resolution (February, 2008):

The existing wording in 9 [class] paragraph 10 makes the example ill-formed:

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

The issue should be closed as NAD.






Issues with "Drafting" Status


690. The dynamic type of an rvalue reference

Section: 1.3  [intro.defs]     Status: drafting     Submitter: Eelis van der Weegen     Date: 7 April, 2008

According to 1.3 [intro.defs], “dynamic type,”

The dynamic type of an rvalue expression is its static type.

This is not true of an rvalue reference, which can be bound to an object of a class type derived from the reference's static type.

Proposed resolution (June, 2008):

Change 1.3 [intro.defs], “dynamic type,” as follows:

the type of the most derived object (1.8 [intro.object]) to which the lvalue denoted by an lvalue or an rvalue-reference (clause 5 [expr]) expression refers. [Example: if a pointer (8.3.1 [dcl.ptr]) p whose static type is “pointer to class B” is pointing to an object of class D, derived from B (clause 10 [class.derived]), the dynamic type of the expression *p is “D.” References (8.3.2 [dcl.ref]) are treated similarly. —end example] The dynamic type of an rvalue expression that is not an rvalue reference is its static type.

Notes from the June, 2008 meeting:

Because expressions have an rvalue reference type only fleetingly, immediately becoming either lvalues or rvalues and no longer references, the CWG expressed a desire for a different approach that would somehow describe an rvalue that resulted from an rvalue reference instead of using the concept of an expression that is an rvalue reference, as above. This approach could also be used in the resolution of issue 664.




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.




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




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.




680. What is a move constructor?

Section: 12.8  [class.copy]     Status: drafting     Submitter: Steve Adamczyk     Date: 3 March, 2008

Although the term “move constructor” appears multiple times in the library clauses and is referenced in the newly-added text for the lambda feature, it is not defined anywhere.

Notes from the June, 2008 meeting:

The only reference to “move constructor” in the core language clauses of the Standard is in 5.1.1 [expr.prim.lambda] paragraph 10; there are no semantic implications of the term. This issue will be addressed by using a function signature instead of the term, thus allowing the library section to provide a definition that is appropriate for its needs.




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?




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.




691. Template parameter packs in class template partial specializations

Section: 14.1  [temp.param]     Status: drafting     Submitter: Doug Gregor     Date: 9 April, 2008

14.1 [temp.param] paragraph 11 currently says,

If a template-parameter of a class template is a template parameter pack, it shall be the last template-parameter. [Note: These are not requirements for function templates because template arguments might be deduced (14.8.2 [temp.deduct])...

This restriction was only meant to apply to primary class templates, not partial specializations.

Suggested resolution:

If a template-parameter of a primary class template is a template parameter pack, it shall be the last template-parameter. [Note: These are not requirements for function templates or class template partial specializations because template arguments might be deduced (14.8.2 [temp.deduct])...



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.




674. “matching specialization” for a friend declaration

Section: 14.5.4  [temp.friend]     Status: drafting     Submitter: James Widman     Date: 7 February, 2008

14.5.4 [temp.friend] paragraph 1 bullet 3 says:

I'm not sure this says what it's supposed to say. For example:

    namespace N {
        template<class T> int f(T);
    }

    class A {
        friend int N::f(int);
        int m;
        A();
    };

    namespace N {
        template< class T > int f(T) {
            A a;            // ok for T=int?
            return a.m;     // ok for T=int?
        }
    }

    int m = N::f(42);       // ok?
    char c = N::f('a');     // Clearly ill-formed.

The key is that the wording talks about a “matching specialization,” which to me means that N::f<int> is befriended only if that specialization existed in N before the friend declaration. So it's ill-formed as written, but if we move the call to N::f<int> up to a point before the definition of A, it's well-formed.

That seems surprising, especially given that the first bullet does not require a pre-existing specialization. So I suggest replacing bullet 3 with something like:




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.

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

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




287. Order dependencies in template instantiation

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

Implementations differ in their treatment of the following code:

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

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

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

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

Erwin Unruh:

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

So each class needs the other to be complete.

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

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

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

John Spicer:

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

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

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

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

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

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

Bill Gibbons:

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

Mike Miller:

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

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

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

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

        struct B_int;

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

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

Notes from 10/01 meeting:

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

Proposed resolution:

In 14.6.4.1 [temp.point] paragraph 3 change:

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

To:

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

Add following paragraph 3:

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

and the following example

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

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

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

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

Notes from the October 2002 meeting:

This needs work. Moved back to drafting status.




561. Internal linkage functions in dependent name lookup

Section: 14.6.4.2  [temp.dep.candidate]     Status: drafting     Submitter: Joaquín López Muñoz     Date: 17 February 2006

According to 14.6.4.2 [temp.dep.candidate],

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

It is not at all clear why a call using a template-id would be treated differently from one not using a template-id. Furthermore, is it really necessary to exclude internal linkage functions from the lookup? Doesn't the ODR give implementations sufficient latitude to handle this case without another wrinkle on name lookup?

(See also issue 524.)

Notes from the April, 2006 meeting:

The consensus of the group was that template-ids should not be treated differently from unqualified-ids (although it's not clear how argument-dependent lookup works for template-ids), and that internal-linkage functions should be found by the lookup (although they may result in errors if selected by overload resolution).

Note (June, 2006):

Although the notes from the Berlin meeting indicate that argument-dependent lookup for template-ids is under-specified in the Standard, further examination indicates that that is not the case: the note in 14.8.1 [temp.arg.explicit] paragraph 8 clearly indicates that argument-dependent lookup is to be performed for template-ids, and 3.4.2 [basic.lookup.argdep] paragraph 4 describes the lookup performed:

When considering an associated namespace, the lookup is the same as the lookup performed when the associated namespace is used as a qualifier (3.4.3.2 [namespace.qual]) except that:



212. Implicit instantiation is not described clearly enough

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

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

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

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

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

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

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

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

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

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

  3. According to paragraph 6,
    If an implicit instantiation of a class template specialization is required and the template is declared but not defined, the program is ill-formed.

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

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

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

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

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

(See also issues 204 and 63.)

Notes on 10/01 meeting:

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




546. Explicit instantiation of class template members

Section: 14.7.2  [temp.explicit]     Status: drafting     Submitter: Martin Sebor     Date: 29 October 2005

Issue 470 specified the explicit instantiation of members of explicitly-instantiated class templates. In restricting the affected members to those “whose definition is visible at the point of instantiation,” however, this resolution introduced an incompatibility between explicitly instantiating a member function or static data member and explicitly instantiating the class template of which it is a member (14.7.2 [temp.explicit] paragraph 3 requires only that the class template definition, not that of the member function or static data member, be visible at the point of the explicit instantiation). It would be better to treat the member instantiations the same, regardless of whether they are directly or indirectly explicitly instantiated.

Notes from the April, 2006 meeting:

In forwarding document J16/06-0057 = WG21 N1987 to be approved by the full Committee, the CWG reaffirmed its position that explicitly instantiating a class template only explicitly instantiates those of its members that have been defined before the point of the explicit instantiation. The effect of the position advocated above would be to require all non-exported member functions to be defined in the translation unit in which the class template is explicitly instantiated (cf paragraph 4), and we did not want to require that. We did agree that the “visible” terminology should be replaced by wording along the lines of “has been defined.”




529. Use of template<> with “explicitly-specialized” class templates

Section: 14.7.3  [temp.expl.spec]     Status: drafting     Submitter: James Widman     Date: 16 August 2005

Paragraph 17 of 14.7.3 [temp.expl.spec] says,

A member or a member template may be nested within many enclosing class templates. In an explicit specialization for such a member, the member declaration shall be preceded by a template<> for each enclosing class template that is explicitly specialized.

This is curious, because paragraph 3 only allows explicit specialization of members of implicitly-instantiated class specializations, not explicit specializations. Furthermore, paragraph 4 says,

Definitions of members of an explicitly specialized class are defined in the same manner as members of normal classes, and not using the explicit specialization syntax.

Paragraph 18 provides a clue for resolving the apparent contradiction:

In an explicit specialization declaration for a member of a class template or a member template that appears in namespace scope, the member template and some of its enclosing class templates may remain unspecialized, except that the declaration shall not explicitly specialize a class member template if its enclosing class templates are not explicitly specialized as well. In such explicit specialization declaration, the keyword template followed by a template-parameter-list shall be provided instead of the template<> preceding the explicit specialization declaration of the member.

It appears from this and the following example that the phrase “explicitly specialized” in paragraphs 17 and 18, when referring to enclosing class templates, does not mean that explicit specializations have been declared for them but that their names in the qualified-id are followed by template argument lists. This terminology is confusing and should be changed.

Proposed resolution (October, 2005):

  1. Change 14.7.3 [temp.expl.spec] paragraph 17 as indicated:

  2. A member or a member template may be nested within many enclosing class templates. In an explicit specialization for such a member, the member declaration shall be preceded by a template<> for each enclosing class template that is explicitly specialized specialization. [Example:...
  3. Change 14.7.3 [temp.expl.spec] paragraph 18 as indicated:

  4. In an explicit specialization declaration for a member of a class template or a member template that appears in namespace scope, the member template and some of its enclosing class templates may remain unspecialized, except that the declaration shall not explicitly specialize a class member template if its enclosing class templates are not explicitly specialized as well that is, the template-id naming the template may be composed of template parameter names rather than template-arguments. In For each unspecialized template in such an explicit specialization declaration, the keyword template followed by a template-parameter-list shall be provided instead of the template<> preceding the explicit specialization declaration of the member. The types of the template-parameters in the template-parameter-list shall be the same as those specified in the primary template definition. In such declarations, an unspecialized template-id shall not precede the name of a template specialization in the qualified-id naming the member. [Example:...

Notes from the April, 2006 meeting:

The revised wording describing “unspecialized” templates needs more work to ensure that the parameter names in the template-id are in the correct order; the distinction between template argyments and parameters is also probably not clear enough. It might be better to replace this paragraph completely and avoid the “unspecialized” wording altogether.




531. Defining members of explicit specializations

Section: 14.7.3  [temp.expl.spec]     Status: drafting     Submitter: Mike Miller     Date: 1 October 2005

The Standard does not fully describe the syntax to be used when a member of an explicitly-specialized member class or member class template is defined in namespace scope. 14.7.3 [temp.expl.spec] paragraph 4 says that the “explicit specialization syntax” (presumably referring to “template<>”) is not used in defining a member of an explicit specialization when a class template is explicitly specialized as a class. However, nothing is said anywhere about how to define a member of a specialization when:

  1. the entity being specialized is a class (member of a template class) rather than a class template.

  2. the result of the specialization is a class template rather than a class (cf 14.7.3 [temp.expl.spec] paragraph 18, which describes this case as a “member template that... remain[s] unspecialized”).

(See paper J16/05-0148 = WG21 N1888 for further details, including a survey of existing implementation practice.)

Notes from the October, 2005 meeting:

The CWG felt that the best approach, balancing consistency with implementation issues and existing practice, would be to require that template<> be used when defining members of all explicit specializations, including those currently covered by 14.7.3 [temp.expl.spec] paragraph 4.




605. Linkage of explicit specializations

Section: 14.7.3  [temp.expl.spec]     Status: drafting     Submitter: Steve Clamage     Date: 30 November 2006

Given

    template <class T> static T f(T   t) { ... }
    template <>             int f(int t) { ... }

what is the linkage of f(int)?

Section 14 [temp] paragraph 4 says,

Entities generated from a template with internal linkage are distinct from all entities generated in other translation units.

But is the explicit specialization “generated from” the primary template? Does it inherit the local linkage? If so, where do I find a reference saying so explicitly?

James Widman: Data points: EDG 3.8 inherits, GCC 4.0 does not.

Mike Miller: There's a pretty strong presumption that the linkage of an explicit specialization cannot be different from that of its primary template, given that storage class specifiers cannot appear in an explicit specialization (7.1.1 [dcl.stc] paragraph 1).

Notes from the April, 2007 meeting:

The CWG agreed that the linkage of an explicit specialization must be that of the template. Gabriel dos Reis will investigate the reason for the different behavior of g++.




606. Template argument deduction for rvalue references

Section: 14.8.2.1  [temp.deduct.call]     Status: drafting     Submitter: Peter Dimov     Date: 1 December 2006

There are a couple of minor problems with the rvalue reference wording in the WP. The non-normative note in 14.8.2.1 [temp.deduct.call] paragraph 3 says,

[Note: The effect of this rule for lvalue arguments and rvalue reference parameters is that deduction in such cases will fail unless the function parameter is of the form cv T&& (14.8.2.5 [temp.deduct.type]). —end note]

It turns out that this isn't correct. For example:

    template <class T> void g(basic_string<T> && );
    ...
    basic_string<char> s;
    g(s);    // Note says that it should fail, we want it to call
             // g<char>(basic_string<char>&&)

Additionally, consider this case:

    template <class T> void f(const T&&);
    ...
    int i;
    f(i);

If we deduce T as int& in this case then f(i) calls f<int&>(int&), which seems counterintuitive. We prefer that f<int>(const int&&) be called. Therefore, we would like the wording clarified that the A& deduction rule in 14.8.2.1 [temp.deduct.call] paragraph 3 applies only to the form T&& and not to cv T&& as the note currently implies.

These are minor tweaks to the rvalue reference wording and a fallout from issue 540. In particular, the major applications of move semantics and perfect forwarding are not impacted with respect to the original intentions of the rvalue reference work by these suggestions.

Suggested resolution:

Change 14.8.2.1 [temp.deduct.call] paragraph 3 as follows:

If P is an rvalue reference type of the form T&&, where T is a template parameter, and the argument is an lvalue, the type A& is used in place of A for type deduction T is deduced as A&. [Example:

    template <typename T> int f(T&&);
    int i;
    int j = f(i); // calls f<int&>(i)
    template <typename T> int g(const T&&);
    int k;
    int n = g(k); // calls g<int>(k)

end example][Note: The effect of this rule for lvalue arguments and rvalue reference parameters is that deduction in such cases will fail unless the function parameter is of the form cv T&& (14.8.2.5 [temp.deduct.type]). —end note]




493. Type deduction from a bool context

Section: 14.8.2.3  [temp.deduct.conv]     Status: drafting     Submitter: John Spicer     Date: 17 Dec 2004

An expression used in an if statement is implicitly converted to type bool (6.4 [stmt.select]). According to the rules of template argument deduction for conversion functions given in 14.8.2.3 [temp.deduct.conv], the following example is ill-formed:

    struct X {
      template<class T> operator const T&() const;
    };
    int main()
    {
      if( X() ) {}
    }

Following the logic in 14.8.2.3 [temp.deduct.conv], A is bool and P is const T (because cv-qualification is dropped from P before the reference is removed), and deduction fails.

It's not clear whether this is the intended outcome or not.

Notes from the April, 2005 meeting:

The CWG observed that there is nothing special about either bool or the context in the example above; instead, it will be a problem wherever a copy occurs, because cv-qualification is always dropped in a copy operation. This appears to be a case where the conversion deduction rules are not properly symmetrical with the rules for arguments. The example should be accepted.




586. Default template-arguments and template argument deduction

Section: 14.8.2.5  [temp.deduct.type]     Status: drafting     Submitter: James Widman     Date: 20 June 2006

Issue 226 removed the original prohibition on default template-arguments for function templates. However, the note in 14.8.2.5 [temp.deduct.type] paragraph 19 still reflects that prohibition. It should be revised or removed.




475. When is std::uncaught_exception() true? (take 2)

Section: 15.5.3  [except.uncaught]     Status: drafting     Submitter: Martin Sebor     Date: 27 Sep 2004

See also issue 37.

Given this piece of code and S having a user-defined ctor, at precisely which point must std::uncaught_exception() return true and where false?

    try { S s0; throw s0; } catch (S s2) { }

My understanding of the semantics of the code is as follows:

  1. The throw expression creates a temporary for a copy of s0, say s1, using the copy ctor of S. In this invocation of the copy ctor uncaught_exception() must return true.
  2. s0 is destroyed during stack unwinding. In the invocation of S dtor uncaught_exception() must still return true.
  3. The variable s2 is initialized from s1 by invoking the copy ctor of S. In this invocation uncaught_exception() must also return true.
  4. s2 and s1 are destroyed. In the invocations of S dtor uncaught_exception() must return false.

Is my understanding correct?

15.1 [except.throw] paragraph 3 talks about “the exception object” when describing the semantics of the throw-expression:

a throw-expression initializes a temporary object, called the exception object...

However, 15.5.1 [except.terminate] paragraph 1 talks about “the expression to be thrown” when enumerating the conditions under which terminate() is called:

when the exception handling mechanism, after completing evaluation of the expression to be thrown but before the exception is caught (15.1 [except.throw]), calls a user function that exits via an uncaught exception...

And, 15.5.3 [except.uncaught] paragraph 1 refers to “the object to be thrown” in the description of uncaught_exception():

The function std::uncaught_exception() returns true after completing evaluation of the object to be thrown...

Are all these objects one and the same? I believe the answer is important in case the construction of the temporary exception object throws another exception.

Suppose they are the same. Then uncaught_exception() invoked from the copy ctor for s1 (from the example [above]) must return false and a new exception (e.g., bad_alloc) may be thrown and caught by a matching handler (i.e., without calling terminate()).

But if they are not the same, then uncaught_exception() invoked from the copy ctor for s1 must return true and throwing another exception would end up calling terminate(). This would, IMO, have pretty severe consequences on writing exception safe exception classes.

As in the first case, different compilers behave differently, with most compilers not calling terminate() when the ctor for the temporary exception object throws. Unfortunately, the two compilers that I trust the most do call terminate().

FWIW, my feeling is that it should be possible for the copy ctor invoked to initialize the temporary exception object to safely exit by throwing another exception, and that the new exception should be allowed to be caught without calling terminate.

Mike Miller: The way I see this, a throw-expression has an assignment-expression as an operand. This expression is “the expression to be thrown.” Evaluation of this expression yields an object; this object is “the object to be thrown.” This object is then copied to the exception object.

Martin Sebor: Here's a survey of the return value from uncaught_exception() in the various stages of exception handling, implemented by current compilers:

expr temp unwind handlr 2nd ex
HP aCC 6 0 0 1 0 OK
Compaq C++ 6.5 0 0 1 1 ABRT
EDG eccp 3.4 0 1 1 1 ABRT
g++ 3.4.2 0 0 1 0 OK
Intel C++ 7.0 0 0 1 0 OK
MIPSpro 7.4.1 0 0 1 1 ABRT
MSVC 7.0 0 0 1 0 OK
SunPro 5.5 1 1 1 0 OK
VisualAge 6.0 0 1 1 1 OK

In the table above:

Proposed resolution (October, 2004):

  1. Change 15.1 [except.throw] paragraph 3 as follows:

    A throw-expression initializes a temporary object, called the exception object, the by copying the thrown object (i.e., the result of evaluating its assignment-expression operand) to it. The type of which the exception object is determined by removing any top-level cv-qualifiers from the static type of the operand of throw and adjusting the type from “array of T” or “function returning T” to “pointer to T” or “pointer to function returning T,” respectively. [Note: the temporary object created for by a throw-expression that whose operand is a string literal is never of type char* or wchar_t*; that is, the special conversions for string literals from the types “array of const char” and “array of const wchar_t” to the types “pointer to char” and “pointer to wchar_t,” respectively (4.2 [conv.array]), are never applied to the operand of a throw-expression. —end note] The temporary is an lvalue and is used to initialize the variable named in the matching handler (15.3 [except.handle]). The type of the operand of a throw-expression shall not be an incomplete type, or a pointer to an incomplete type other than (possibly cv-qualified) void. [...]
  2. Change the note in 15.3 [except.handle] paragraph 3 as follows:

    [Note: a throw-expression operand that which is an integral constant expression of integer type that evaluates to zero does not match a handler of pointer type; that is, the null pointer constant conversions (4.10 [conv.ptr], 4.11 [conv.mem]) do not apply. —end note]
  3. Change 15.5.1 [except.terminate] paragraph 1 bullet 1 as follows:

    when the exception handling mechanism, after completing evaluation of the expression to be thrown operand of throw but before the exception is caught (15.1 [except.throw]), calls a user function that exits via an uncaught exception,
  4. Change 15.5.3 [except.uncaught] paragraph 1 as follows:

    The function std::uncaught_exception() returns true after completing evaluation of the object to be thrown operand of throw until completing the initialization of the exception-declaration in the matching handler (18.7.4 [uncaught]).
  5. Change 18.7.4 [uncaught] paragraph 1 by adding the indicated words:

    Returns: true after completing evaluation of the operand of a throw-expression until either completing initialization of the exception-declaration in the matching handler or entering unexpected() due to the throw; or after entering terminate() for any reason other than an explicit call to terminate(). [Note: This includes stack unwinding (15.2 [except.ctor]). —end note]

Notes from the April, 2005 meeting:

The CWG discussed this resolution both within the group and with other interested parties. Among the points that were made:

The CWG felt that more input from a wider audience was necessary before a decision could be made on the appropriate resolution.

Notes from the April, 2006 meeting:

The CWG agreed with the position that std::uncaught_exception() should return false during the copy to the exception object and that std::terminate() should not be called if that constructor exits with an exception. The issue was returned to “drafting” status for rewording to reflect this position.

Additional notes (September, 2007):

Although this issue deals primarily with when std::uncaught_exception() begins to return true, the specification of when it begins to return false is also problematic. There are two parallel sections that define the meaning of std::uncaught_exception() and each has a different problem. 15.5.3 [except.uncaught] reads,

The function std::uncaught_exception() returns true after completing evaluation of the object to be thrown until completing the initialization of the exception-declaration in the matching handler (18.7.4 [uncaught]).

The problem here is that whether an exception is considered caught (the underlying condition tested by the function) is here presented in terms of having initialized the exception-declaration, while in other places it is specified by having an active handler for the exception, e.g., 15.1 [except.throw] paragraph 6:

An exception is considered caught when a handler for that exception becomes active (15.3 [except.handle]).

This distinction is important because of 15.3 [except.handle] paragraph 3:

A handler is considered active when initialization is complete for the formal parameter (if any) of the catch clause. [Note: the stack will have been unwound at that point. —end note] Also, an implicit handler is considered active when std::terminate() or std::unexpected() is entered due to a throw.

Note that there is no exception-declaration to be initialized for the std::terminate() and std::unexpected() cases; nevertheless, according to 18.7.4 [uncaught], std::uncaught_exception() is supposed to return false when one of those two functions is entered.

The specification in 18.7.4 [uncaught] is not well phrased, however, and is open to misinterpretation. It reads,

Returns: true after completing evaluation of a throw-expression until either completing initialization of the exception-declaration in the matching handler or entering unexpected() due to the throw; or after entering terminate() for any reason other than an explicit call to terminate().

The problem here is lack of parallelism: does “after entering terminate” refer to the condition for returning true or false? This would be better phrased along the lines of

Returns: true after completing evaluation of a throw-expression until a handler for the exception becomes active (15.3 [except.handle]).



618. Casts in preprocessor conditional expressions

Section: 16.1  [cpp.cond]     Status: drafting     Submitter: Martin Sebor     Date: 12 February 2007

16.1 [cpp.cond] paragraph 1 states,

The expression that controls conditional inclusion shall be an integral constant expression except that: it shall not contain a cast...

The prohibition of casts is vacuous and misleading: as pointed out in the footnote in that paragraph,

Because the controlling constant expression is evaluated during translation phase 4, all identifiers either are or are not macro names — there simply are no keywords, enumeration constants, and so on.

As a result, there can be no casts, which require either keywords or identifiers that resolve to types in order to be recognized as casts. The wording on casts should be removed and replaced by a note recognizing this implication.

Notes from the April, 2007 meeting:

The CWG agreed with this suggested resolution; however, the reference is in the “Preprocessing Directives” clause, which WG21 intends to keep in as close synchronization as possible with the corresponding wording in the C Standard. Any change here must therefore be done in consultation with WG14. Clark Nelson will fulfill this liaison function.

It was also noted that the imminent introduction of constexpr also has the potential for a similar kind of confusion, so the proposed resolution should address both casts and constexpr.




189. Definition of operator and punctuator

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

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

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

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

Suggested resolution:


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

Additional note (April, 2005):

The resolution for this problem should also address the fact that sizeof and typeid (and potentially others like decltype that may be added in the future) are described in some places as “operators” but are not listed in 13.5 [over.oper] paragraph 3 among the operators that cannot be overloaded.

(See also issue 369.)




630. Equality of narrow and wide character values in the basic character set

Section: 2.2  [lex.charset]     Status: drafting     Submitter: Tom Plum     Date: 21 April 2007

WG14 accepted DR 279 regarding the rule known colloquially as the L'x'=='x' rule. This change was made to C99 in TC2. The Austin Group subsequently opened DR 321 against TC2, observing that the change made in TC2 would invalidate existing conforming C code that relied on the L'x'=='x' rule.

DR 321 is now closed and will be included in the TC3 to C99. This change defines a new standard macro, which WG14 drafted as follows:

__STDC_MB_MIGHT_NEQ_WC__: The integer constant 1, intended to indicate that there might be some character x in the basic character set, such that 'x' need not be equal to L'x'.

WG14 requests that WG21 adopt this revision and this macro in C++0x.




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

Section: 2.4  [lex.pptoken]     Status: drafting     Submitter: Martin v. Loewis     Date: 30 July 2002

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

#define delete foo

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

(See also issue 189.)




676. static_assert-declarations and general requirements for declarations

Section: 3.1  [basic.def]     Status: drafting     Submitter: Alisdair Meredith     Date: 12 February, 2008

3.1 [basic.def] makes statements about declarations that do not appear to apply to static_assert-declarations. For example, paragraph 1 says,

A declaration (clause 7 [dcl.dcl]) introduces names into a translation unit or redeclares names introduced by previous declarations. A declaration specifies the interpretation and attributes of these names.

What name is being declared or described by a static_assert-declaration?

Also, paragraph 2 lists the kinds of declarations that are not definitions, and a static_assert-declaration is not among them. Is it intentional that static_assert-declarations are definitions?




636. Dynamic type of objects and aliasing

Section: 3.10  [basic.lval]     Status: drafting     Submitter: Gabriel Dos Reis     Date: 23 May 2007

The aliasing rules given in 3.10 [basic.lval] paragraph 10 rely on the concept of “dynamic type.” The problem is that the dynamic type of an object often cannot be determined (or even sufficiently constrained) at the point at which an optimizer needs to be able to determine whether aliasing might occur or not. For example, consider the function

    void foo(int* p, double* q) {
        *p = 42;
        *q = 3.14;
    }

An optimizer, on the basis of the existing aliasing rules, might decide that an int* and a double* cannot refer to the same object and reorder the assignments. This reordering, however, could result in undefined behavior if the function foo is called as follows:

   void goo() {
      union {
         int i; 
         double d;
      } t;

      t.i = 12;

      foo(&t.i, &t.d);

      cout << t.d << endl;
   };

Here, the reference to t.d after the call to foo will be valid only if the assignments in foo are executed in the order in which they were written; otherwise, the union will contain an int object rather than a double.

One possibility would be to require that if such aliasing occurs, it be done only via member names and not via pointers.

Notes from the July, 2007 meeting:

This is the same issue as C's DR236. The CWG expressed a desire to address the issue the same way C99 does. The issue also occurs in C++ when placement new is used to end the lifetime of one object and start the lifetime of a different object occupying the same storage.




678. Language linkage of member function parameter types and the ODR

Section: 3.2  [basic.def.odr]     Status: drafting     Submitter: James Widman     Date: 15 February, 2008

I thought this case would result in undefined behavior according to 3.2 [basic.def.odr]:

    // t.h:
    struct A { void (*p)(); };

    // t1.cpp:
    #include "t.h" // A::p is a pointer to C++ func

    // t2.cpp:
    extern "C" {
    #include "t.h" // A::p is a pointer to C func
    }

...but I don't see how any of the bullets in the list in paragraph 5 apply.




490. Name lookup in friend declarations

Section: 3.4.1  [basic.lookup.unqual]     Status: drafting     Submitter: Ben Hutchings     Date: 7 Dec 2004

When 3.4.1 [basic.lookup.unqual] paragraph 10 says,

In a friend declaration naming a member function, a name used in the function declarator and not part of a template-argument in a template-id is first looked up in the scope of the member function's class. If it is not found, or if the name is part of a template-argument in a template-id, the look up is as described for unqualified names in the definition of the class granting friendship.

what does “in the scope of the member function's class” mean? Does it mean that only members of the class and its base classes are considered? Or does it mean that the same lookup is to be performed as if the name appeared in the member function's class? Implementations vary in this regard. For example:

     struct s1;

     namespace ns {
         struct s1;
     }

     struct s2 {
         void f(s1 &);
     };

     namespace ns {
         struct s3 {
             friend void s2::f(s1 &);
         };
     }

Microsoft Visual C++ and Comeau C++ resolve s1 in the friend declaration to ns::s1 and issue an error, while g++ resolves it to ::s1 and accepts the code.

Notes from the April, 2005 meeting:

The phrase “looked up in the scope of [a] class” occurs frequently throughout the Standard and always refers to the member name lookup described in 10.2 [class.member.lookup]. This is the first interpretation mentioned above (“only members of the class and its base classes”), resolving s1 to ns::s1. A cross-reference to 10.2 [class.member.lookup] will be added to 3.4.1 [basic.lookup.unqual] paragraph 10 to make this clearer.

In discussing this question, the CWG noticed another problem: the text quoted above applies to all template-arguments appearing in the function declarator. The intention of this rule, however, is that only template-arguments in the declarator-id should ignore the member function's class scope; template-arguments used elsewhere in the function declarator should be treated like other names. For example:

     template<typename T> struct S;
     struct A {
       typedef int T;
       void foo(S<T>);
     };
     template <typename T> struct B {
       friend void A::foo(S<T>);  // i.e., S<A::T>
     };



225. Koenig lookup and fundamental types

Section: 3.4.2  [basic.lookup.argdep]     Status: drafting     Submitter: Derek Inglis     Date: 26 Jan 2000

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




156. Name lookup for conversion functions

Section: 3.4.5  [basic.lookup.classref]     Status: drafting     Submitter: Derek Inglis     Date: 18 Aug 1999

Paragraph 7 of 3.4.5 [basic.lookup.classref] says,

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

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

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

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

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

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

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

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

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




682. Missing description of lookup of template aliases

Section: 3.4.5  [basic.lookup.classref]     Status: drafting     Submitter: Daveed Vandevoorde     Date: 1 March, 2008

3.4.5 [basic.lookup.classref] does not mention template aliases as the possible result of the lookup but should do so.




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

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

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

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

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

Notes from October 2003 meeting:

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

Proposed resolution (October, 2005):

Change 3.5 [basic.link] paragraph 6 as indicated:

...Otherwise, if no matching entity is found, the block scope entity receives external linkage. If, within a translation unit, the same entity is declared with both internal and external linkage, the behavior is undefined.

[Example:

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

There are three objects named i in this program. The object with internal linkage introduced by the declaration in global scope (line //1 ), the object with automatic storage duration and no linkage introduced by the declaration on line //2, and the object with static storage duration and external linkage introduced by the declaration on line //3. Without the declaration at line //2, the declaration at line //3 would link with the declaration at line //1. But because the declaration with internal linkage is hidden, //3 is given external linkage, resulting in a linkage conflict.end example]

Notes frum the April 2006 meeting:

According to 3.5 [basic.link] paragraph 9, the two variables with linkage in the proposed example are not “the same entity” because they do not have the same linkage. Some other formulation will be needed to describe the relationship between those two variables.

Notes from the October 2006 meeting:

The CWG decided that it would be better to make a program with this kind of linkage mismatch ill-formed instead of having undefined behavior.




527. Problems with linkage of types

Section: 3.5  [basic.link]     Status: drafting     Submitter: Daveed Vandevoorde     Date: 28 July 2005

The resolution of issue 389 makes code like

    static struct {
        int i;
        int j;
    } X;

ill-formed. This breaks a lot of code for no apparent reason, since the name X is not known outside the translation unit in which it appears; there is therefore no danger of collision and no need to mangle its name.

There has also been recent discussion on the email reflectors as to whether the restrictions preventing use of types without linkage as template arguments is needed or not, with the suggestion that a mechanism like that used to give members of the unnamed namespace unique names could be used for unnamed and local types. See also issue 488, which would become moot if types without linkage could be used as template parameters.

Notes from the October, 2005 meeting:

The Evolution Working Group is discussing changes that would address this issue. CWG will defer consideration until the outcome of the EWG discussions is clear.

Notes from the April, 2006 meeting:

The CWG agreed that the restriction in 3.5 [basic.link] paragraph 8 on use of a type without linkage should apply only to variables and functions with external linkage, not to variables and functions with internal linkage (i.e., the example should be accepted). This is a separate issue from the question before the EWG and should be resolved independently.

Additional note (April, 2006):

Even the restriction of the rule to functions and objects with external linkage may not be exactly what we want. Consider an example like:

    namespace {
        struct { int i; } s;
    }

The variable s has external linkage but can't be named outside its translation unit, so there's again no reason to prohibit use of a type without linkage in its declaration.

Notes from the June, 2008 meeting:

Paper N2657, adopted at the June, 2008 meeting, allows local and unnamed types to be used as template parameters. That resolution is narrowly focused, however, and does not address this issue.




350. signed char underlying representation for objects

Section: 3.9  [basic.types]     Status: drafting     Submitter: Noah Stein     Date: 16 April 2002

Sent in by David Abrahams:

Yes, and to add to this tangent, 3.9.1 [basic.fundamental] paragraph 1 states "Plain char, signed char, and unsigned char are three distinct types." Strangely, 3.9 [basic.types] paragraph 2 talks about how "... the underlying bytes making up the object can be copied into an array of char or unsigned char. If the content of the array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value." I guess there's no requirement that this copying work properly with signed chars!

Notes from October 2002 meeting:

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

Proposed resolution (April 2003):

Change 3.8 [basic.life] paragraph 5 bullet 3 from

to

Change 3.8 [basic.life] paragraph 6 bullet 3 from

to

Change the beginning of 3.9 [basic.types] paragraph 2 from

For any object (other than a base-class subobject) of POD type T, whether or not the object holds a valid value of type T, the underlying bytes (1.7 [intro.memory]) making up the object can be copied into an array of char or unsigned char.

to

For any object (other than a base-class subobject) of POD type T, whether or not the object holds a valid value of type T, the underlying bytes (1.7 [intro.memory]) making up the object can be copied into an array of byte-character type.

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

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

Change 3.10 [basic.lval] paragraph 15 last bullet from

to

Notes from October 2003 meeting:

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

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

Notes from October 2004 meeting:

The C99 Standard appears to be inconsistent in its requirements. For example, 6.2.6.1 paragraph 4 says:

The value may be copied into an object of type unsigned char [n] (e.g., by memcpy); the resulting set of bytes is called the object representation of the value.

On the other hand, 6.2 paragraph 6 says,

If a value is copied into an object having no declared type using memcpy or memmove, or is copied as an array of character type, then the effective type of the modified object for that access and for subsequent accesses that do not modify the value is the effective type of the object from which the value is copied, if it has one.

Mike Miller will investigate further.




617. Lvalue-to-rvalue conversions of uninitialized char objects

Section: 4.1  [conv.lval]     Status: drafting     Submitter: Alan Stokes     Date: 6 February 2007

According to 4.1 [conv.lval] paragraph 1, applying the lvalue-to-rvalue conversion to any uninitialized object results in undefined behavior. However, character types are intended to allow any data, including uninitialized objects and padding, to be copied (hence the statements in 3.9.1 [basic.fundamental] paragraph 1 that “For character types, all bits of the object representation participate in the value representation” and in 3.10 [basic.lval] paragraph 15 that char and unsigned char types can alias any object). The lvalue-to-rvalue conversion should be permitted on uninitialized objects of character type without evoking undefined behavior.




170. Pointer-to-member conversions

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

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

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

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

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


685. Integral promotion of enumeration ignores fixed underlying type

Section: 4.5  [conv.prom]     Status: drafting     Submitter: Alberto Ganesh Barbati     Date: 6 January, 2008

According to 4.5 [conv.prom] paragraph 2,

An rvalue of an unscoped enumeration type (7.2 [dcl.enum]) can be converted to an rvalue of the first of the following types that can represent all the values of the enumeration (i.e. the values in the range bmin to bmax as described in 7.2 [dcl.enum]): int, unsigned int, long int, unsigned long int, long long int, or unsigned long long int.

This wording may have surprising behavior in this case:

    enum E: long { e };

    void f(int);
    void f(long);

    void g() {
        f(e);    // Which f is called?
    }

Intuitively, as the programmer has explicitly expressed preference for long as the underlying type, he/she might expect f(long) to be called. However, if long and int happen to have the same size, then e is promoted to int (as it is the first type in the list that can represent all values of E) and f(int) is called instead.

According to 7.2 [dcl.enum] the underlying type of an enumeration is always well-defined for both the fixed and the non-fixed cases, so it makes sense simply to promote to the underlying type unless such a type would itself require promotion.

Suggested resolution:

In 4.5 [conv.prom] paragraph 2, replace all the text from “An rvalue of an unscoped enumeration type” through the end of the paragraph with the following:

An rvalue of an unscoped enumeration type (7.2 [dcl.enum]) is converted to an rvalue of its underlying type if it is different from char16_t, char32_t, wchar_t, or has integer conversion rank greater than or equal to int. Otherwise, it is converted to an rvalue of the first of the following types that can represent all the values of its underlying type: int, unsigned int, long int, unsigned long int, long long int, or unsigned long long int.

(Note that this wording no longer needs to mention extended integer types as special cases.)




536. Problems in the description of id-expressions

Section: 5.1  [expr.prim]     Status: drafting     Submitter: Mike Miller     Date: 13 October 2005

There are at least a couple of problems in the description of the various id-expressions in 5.1 [expr.prim]:

  1. Paragraph 4 embodies an incorrect assumption about the syntax of qualified-ids:

    The operator :: followed by an identifier, a qualified-id, or an operator-function-id is a primary-expression.

    The problem here is that the :: is actually part of the syntax of qualified-id; consequently, “:: followed by... a qualified-id” could be something like “:: ::i,” which is ill-formed. Presumably this should say something like, “A qualified-id with no nested-name-specifier is a primary-expression.”

  2. More importantly, some kinds of id-expressions are not described by 5.1 [expr.prim]. The structure of this section is that the result, type, and lvalue-ness are specified for each of the cases it covers:

    This treatment leaves unspecified all the non-identifier unqualified-ids (operator-function-id, conversion-function-id, and template-id), as well as (perhaps) “:: template-id” (it's not clear whether the “:: followed by a qualified-id” case is supposed to apply to template-ids or not). Note also that the proposed resolution of issue 301 slightly exacerbates this problem by removing the form of operator-function-id that contains a tmeplate-argument-list; as a result, references like “::operator+<X>” are no longer covered in 5.1 [expr.prim].




587. Lvalue operands of a conditional expression differing only in cv-qualification

Section: 5.16  [expr.cond]     Status: drafting     Submitter: Howard Hinnant     Date: 20 June 2006

Consider the following example:

    template <typename T>
    const T* f(bool b) {
        static T t1 = T();
        static const T t2 = T();
        return &(b ? t1 : t2);  // error?
    }

According to 5.16 [expr.cond], this function is well-formed if T is a class type and ill-formed otherwise. If the second and third operands of a conditional expression are lvalues of the same class type except for cv-qualification, the result of the conditional expression is an lvalue; if they are lvalues of the same non-class type except for cv-qualification, the result is an rvalue.

This difference seems gratuitous and should be removed.




652. Compile-time evaluation of floating-point expressions

Section: 5.19  [expr.const]     Status: drafting     Submitter: Jens Maurer     Date: 3 October 2007

It was the intention of the constexpr proposal that implementations be required to evaluate floating-point expressions at compile time. This intention is not reflected in the actual wording of 5.19 [expr.const] paragraph 2, bullet 5:

This restriction has the effect of forbidding the use of floating-point expressions in integral constant expressions.

Proposed resolution (June, 2008):

Delete bullet 6 of 5.19 [expr.const] paragraph 2:

Notes from the June, 2008 meeting:

The CWG agreed with the intent of this issue, that floating-point calculations should be permitted at compile time, but acknowledged that this opens the possibility of differing results between compile time and run time. Such issues should be addressed non-normatively, e.g., via a “recommended practice” note like that of C99's 6.4.4.2 or in a technical report.




232. Is indirection through a null pointer undefined behavior?

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

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

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

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

This is inconsistent and should be cleaned up.

Bill Gibbons:

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

For example:

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

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

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

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

Mike Miller:

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

Tom Plum:

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

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

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

Mike Miller:

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

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

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

Notes from the October 2003 meeting:

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

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

Proposed resolution (October, 2004):

(Note: the resolution of issue 453 also resolves part of this issue.)

  1. Add the indicated words to 3.10 [basic.lval] paragraph 2:

    An lvalue refers to an object or function or is an empty lvalue (5.3.1 [expr.unary.op]).
  2. Add the indicated words to 5.3.1 [expr.unary.op] paragraph 1:

    The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points, if any. If the pointer is a null pointer value (4.10 [conv.ptr]) or points one past the last element of an array object (5.7 [expr.add]), the result is an empty lvalue and does not refer to any object or function. An empty lvalue is not modifiable. If the type of the expression is “pointer to T,” the type of the result is “T.” [Note: a pointer to an incomplete type (other than cv void) can be dereferenced. The lvalue thus obtained can be used in limited ways (to initialize a reference, for example); this lvalue must not be converted to an rvalue, see 4.1 [conv.lval].—end note]
  3. Add the indicated words to 4.1 [conv.lval] paragraph 1:

    If the object to which the lvalue refers is not an object of type T and is not an object of a type derived from T, or if the object is uninitialized, or if the lvalue is an empty lvalue (5.3.1 [expr.unary.op]), a program that necessitates this conversion has undefined behavior.
  4. Change 1.9 [intro.execution] as indicated:

    Certain other operations are described in this International Standard as undefined (for example, the effect of dereferencing the null pointer division by zero).

Note (March, 2005):

The 10/2004 resolution interacts with the resolution of issue 73. We added wording to 3.9.2 [basic.compound] paragraph 3 to the effect that a pointer containing the address one past the end of an array is considered to “point to” another object of the same type that might be located there. The 10/2004 resolution now says that it would be undefined behavior to use such a pointer to fetch the value of that object. There is at least the appearance of conflict here; it may be all right, but it at needs to be discussed further.

Notes from the April, 2005 meeting:

The CWG agreed that there is no contradiction between this direction and the resolution of issue 73. However, “not modifiable” is a compile-time concept, while in fact this deals with runtime values and thus should produce undefined behavior instead. Also, there are other contexts in which lvalues can occur, such as the left operand of . or .*, which should also be restricted. Additional drafting is required.




407. Named class with associated typedef: two names or one?

Section: 7.1.3  [dcl.typedef]     Status: drafting     Submitter: Clark Nelson     Date: 31 March 2003

Here's an example:

  typedef struct S { ... } S;
  void fs(S *x) { ... }

The big question is, to what declaration does the reference to identifier S actually refer? Is it the S that's declared as a typedef name, or the S that's declared as a class name (or in C terms, as a struct tag)? (In either case, there's clearly only one type to which it could refer, since a typedef declaration does not introduce a new type. But the debugger apparently cares about more than just the identity of the type.)

Here's a classical, closely related example:

  struct stat { ... };
  int stat();
  ... stat( ... ) ...

Does the identifier stat refer to the class or the function? Obviously, in C, you can't refer to the struct tag without using the struct keyword, because it is in a different name space, so the reference must be to the function. In C++, the reference is also to the function, but for a completely different reason.

Now in C, typedef names and function names are in the same name space, so the natural extrapolation would be that, in the first example, S refers to the typedef declaration, as it would in C. But C++ is not C. For the purposes of this discussion, there are two important differences between C and C++

The first difference is that, in C++, typedef names and class names are not in separate name spaces. On the other hand, according to section 3.3.8 [basic.scope.hiding] (Name hiding), paragraph 2:

A class name (9.1) or enumeration name (7.2) can be hidden by the name of an object, function, or enumerator declared in the same scope. If a class or enumeration name and an object, function, or enumerator are declared in the same scope (in any order) with the same name, the class or enumeration name is hidden wherever the object, function, or enumerator name is visible.

Please consider carefully the phrase I have highlighted, and the fact that a typedef name is not the name of an object, function or enumerator. As a result, this example:

  struct stat { ... };
  typedef int stat;

Which would be perfectly legal in C, is disallowed in C++, both implicitly (see the above quote) and explicitly (see section 7.1.3 [dcl.typedef] (The typedef specifier), paragraph 3):

In a given scope, a typedef specifier shall not be used to redefine the name of any type declared in that scope to refer to a different type. Similarly, in a given scope, a class or enumeration shall not be declared with the same name as a typedef-name that is declared in that scope and refers to a type other than the class or enumeration itself.

From which we can conclude that in C++ typedef names do not hide class names declared in the same scope. If they did, the above example would be legal.

The second difference is that, in C++, a typedef name that refers to a class is a class-name; see 7.1.3 [dcl.typedef] paragraph 4:

A typedef-name that names a class is a class-name(9.1). If a typedef-name is used following the class-key in an elaborated-type-specifier (7.1.5.3) or in the class-head of a class declaration (9), or is used as the identifier in the declarator for a constructor or destructor declaration (12.1, 12.4), the program is ill-formed.

This implies, for instance, that a typedef-name referring to a class can be used in a nested-name-specifier (i.e. before :: in a qualified name) or following ~ to refer to a destructor. Note that using a typedef-name as a class-name in an elaborated-type-specifier is not allowed. For example:

  struct X { };
  typedef struct X X2;
  X x; // legal
  X2 x2; // legal
  struct X sx; // legal
  struct X2 sx2; // illegal

The final relevant piece of the standard is 7.1.3 [dcl.typedef] paragraph 2:

In a given scope, a typedef specifier can be used to redefine the name of any type declared in that scope to refer to the type to which it already refers.

This of course is what allows the original example, to which let us now return:

  typedef struct S { ... } S;
  void fs(S *x) { ... }

The question, again is, to which declaration of S does the reference actually refer? In C, it would clearly be to the second, since the first would be accessible only by using the struct keyword. In C++, if typedef names hid class names declared in the same scope, the answer would be the same. But we've already seen that typedef names do not hide class names declared in the same scope.

So to which declaration does the reference to S refer? The answer is that it doesn't matter. The second declaration of S, which appears to be a declaration of a typedef name, is actually a declaration of a class name (7.1.3 [dcl.typedef] paragraph 4), and as such is simply a redeclaration. Consider the following example:

  typedef int I, I;
  extern int x, x;
  void f(), f();

To which declaration would a reference to I, x or f refer? It doesn't matter, because the second declaration of each is really just a redeclaration of the thing declared in the first declaration. So to save time, effort and complexity, the second declaration of each doesn't add any entry to the compiler's symbol table.

Note (March, 2005):

Matt Austern: Is this legal?

    struct A { };
    typedef struct A A;
    struct A* p;

Am I right in reading the standard [to say that this is ill-formed]? On the one hand it's a nice uniform rule. On the other hand, it seems likely to confuse users. Most people are probably used to thinking that 'typedef struct A A' is a null operation, and, if this code really is illegal, it would seem to be a gratuitous C/C++ incompatibility.

Mike Miller: I think you're right. 7.1.3 [dcl.typedef] paragraph 1:

A name declared with the typedef specifier becomes a typedef-name.

7.1.3 [dcl.typedef] paragraph 2:

In a given non-class scope, a typedef specifier can be used to redefine the name of any type declared in that scope to refer to the type to which it already refers.

After the typedef declaration in the example, the name X has been “redefined” — it is no longer just a class-name, it has been “redefined” to be a typedef-name (that, by virtue of the fact that it refers to a class type, is also a class-name).

John Spicer: In C, and originally in C++, an elaborated-type-specifier did not consider typedef names, so “struct X* x” would find the class and not the typedef.

When C++ was changed to make typedefs visible to elaborated-type-specifier lookups, I believe this issue was overlooked and inadvertantly made ill-formed.

I suspect we need add text saying that if a given scope contains both a class/enum and a typedef, that an elaborated type specifier lookup finds the class/enum.

Mike Miller: I'm a little uncomfortable with this approach. The model we have for declaring a typedef in the same scope as a class/enum is redefinition, not hiding (like the “struct stat” hack). This approach seems to assume that the typedef hides the class/enum, which can then be found by an elaborated-type-specifier, just as if it were hidden by a variable, function, or enumerator.

Also, this approach reduces but doesn't eliminate the incompatibility with C. For example:

    struct S { };
    {
        typedef struct S S;
        struct S* p;        // still ill-formed
    }

My preference would be for something following the basic principle that declaring a typedef-name T in a scope where T already names the type designated by the typedef should have no effect on whether an elaborated-type-specifier in that or a nested scope is well-formed or not. Another way of saying that is that a typedef-name that designates a same-named class or enumeration in the same or a containing scope is transparent with respect to elaborated-type-specifiers.

John Spicer: This strikes me as being a rather complicated solution. When we made the change to make typedefs visible to elaborated-type-specifiers we did so knowing it would make some C cases ill-formed, so this does not bother me. We've lived with the C incompatibility for many years now, so I don't personally feel a need to undo it. I also don't like the fact that you have to essentially do the old-style elaborated-type-specifier lookup to check the result of the lookup that found the typedef.

I continue to prefer the direction I described earlier where if a given scope contains both a class/enum and a typedef, that an elaborated-type-specifier lookup finds the class/enum.

Notes from the April, 2005 meeting:

The CWG agreed with John Spicer's approach, i.e., permitting a typedef-name to be used in an elaborated-type-specifier only if it is declared in the same scope as the class or enumeration it names.




625. Use of auto as a template-argument

Section: 7.1.6.4  [dcl.spec.auto]     Status: drafting     Submitter: John Spicer     Date: 9 March 2007

The auto specifier can be used only in certain contexts, as specified in 7.1.6.4 [dcl.spec.auto] paragraphs 2-3:

Otherwise (auto appearing with no type specifiers other than cv-qualifiers), the auto type-specifier signifies that the type of an object being declared shall be deduced from its initializer. The name of the object being declared shall not appear in the initializer expression.

This use of auto is allowed when declaring objects in a block (6.3 [stmt.block]), in namespace scope (3.3.5 [basic.scope.namespace]), and in a for-init-statement (6.5.3 [stmt.for]). The decl-specifier-seq shall be followed by one or more init-declarators, each of which shall have a non-empty initializer of either of the following forms:

It was intended that auto could be used only at the top level of a declaration, but it is not clear whether this wording is sufficient to forbid usage like the following:

    template <class T> struct A {};
    template <class T> void f(A<T> x) {}

    void g()
    {
        f(A<short>());

        A<auto> x = A<short>();
    }

Notes from the February, 2008 meeting:

It was agreed that the example should be ill-formed.




138. Friend declaration name lookup

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

7.3.1.2 [namespace.memdef] paragraph 3 says,

If a friend declaration in a non-local class first declares a class or function the friend class or function is a member of the innermost enclosing namespace... When looking for a prior declaration of a class or a function declared as a friend, scopes outside the innermost enclosing namespace scope are not considered.
It is not clear from this passage how to determine whether an entity is "first declared" in a friend declaration. One question is whether a using-declaration influences this determination. For instance:
    void foo();
    namespace A{
      using ::foo;
      class X{
	friend void foo();
      };
    }
Is the friend declaration a reference to ::foo or a different foo?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

According to 9.1 [class.name] paragraph 2,

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

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

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




374. Can explicit specialization outside namespace use qualified name?

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

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

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

Notes from October 2002 meeting:

There was agreement that we wanted to allow this.




453. References may only bind to “valid” objects

Section: 8.3.2  [dcl.ref]     Status: drafting     Submitter: Gennaro Prota     Date: 18 Jan 2004

8.3.2 [dcl.ref] paragraph 4 says:

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

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

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

Suggested resolution: Changing the final part of 8.3.2 [dcl.ref] paragraph 4 to:

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

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

Proposed Resolution (October, 2004):

(Note: the following wording depends on the proposed resolution for issue 232.)

Change 8.3.2 [dcl.ref] paragraph 4 as follows:

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

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

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

  extern int& ir3;
  int* ip = 0;

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

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

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

Notes from the April, 2005 meeting:

The CWG decided that whether to require an implementation to diagnose initialization of a reference to itself should be handled as a separate issue (504) and also suggested referring to “storage” instead of “memory” (because 1.8 [intro.object] defines an object as a “region of storage”).

Proposed Resolution (April, 2005):

(Note: the following wording depends on the proposed resolution for issue 232.)

Change 8.3.2 [dcl.ref] paragraph 4 as follows:

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

Any use of a reference before it is initialized results in undefined behavior. [Example:

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

  extern int& ir3;
  int* ip = 0;

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

Note (February, 2006):

The word “use” in the last paragraph of the proposed resolution was intended to refer to the description in 3.2 [basic.def.odr] paragraph 2. However, that section does not define what it means for a reference to be “used,” dealing only with objects and functions. Additional drafting is required to extend 3.2 [basic.def.odr] paragraph 2 to apply to references.

Additional note (May, 2008):

The proposed resolution for issue 570 adds wording to define “use” for references.




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

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

EDG rejects this code:

  template <typename T>
  struct S {};

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

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

The language from 8.3.5 [dcl.fct] is:

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

Notes from April 2003 meeting:

We agreed that the example should be allowed.




508. Non-constructed value-initialized objects

Section: 8.5  [dcl.init]     Status: drafting     Submitter: Alisdair Meredith     Date: 18 Mar 2005

According to the definition of value initialization (8.5 [dcl.init] paragraph 5), non-union class types without user-declared constructors are value-initialized by value-initializing each of their members rather than by executing the (generated) default constructor. However, a number of other items in the Standard are described in relationship to the execution of the constructor:

Proposed resolution (October, 2005):

Add the indicated words to 8.5 [dcl.init] paragraph 6:

A program that calls for default-initialization or value-initialization of an entity of reference type is ill-formed. If T is a cv-qualified type, the cv-unqualified version of T is used for these definitions of zero-initialization, default-initialization, and value-initialization. Even when value-initialization of an object does not call that object's constructor, the object is deemed to have been fully constructed once its initialization is complete and thus subject to provisions of this International Standard applying to “constructed” objects, objects “for which the constructor has completed execution,” etc.

Notes from April, 2006 meeting:

There was some concern about whether this wording covered (or needed to cover) cases where an object is “partially constructed.” Another approach might be simply to define value initialization to be “construction.” Returned to “drafting” status for further investigation.




233. References vs pointers in UDC overload resolution

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

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

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

    struct Z { Z(){} };

    struct A {
       Z x;

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

    struct B {
       Z x;

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

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

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

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

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

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

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

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

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

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

So why should this be a defect?

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

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

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




589. Direct binding of class and array rvalues in reference initialization

Section: 8.5.3  [dcl.init.ref]     Status: drafting     Submitter: Steve Adamczyk     Date: 26 July 2006

The resolutions of issues 391 and 450 say that the reference is “bound to” the class or array rvalue, but it does not say that the reference “binds directly” to the initializer, as it does for the cases that fall under the first bullet in 8.5.3 [dcl.init.ref] paragraph 5. However, this phrasing is important in determining the implicit conversion sequence for an argument passed to a parameter with reference type (13.3.3.1.4 [over.ics.ref]), where paragraph 2 says,

When a parameter of reference type is not bound directly to an argument expression, the conversion sequence is the one required to convert the argument expression to the underlying type of the reference according to 13.3.3.1 [over.best.ics]. Conceptually, this conversion sequence corresponds to copy-initializing a temporary of the underlying type with the argument expression.

The above-mentioned issue resolutions stated that no copy is to be made in such reference initializations, so the determination of the conversion sequence does not reflect the initialization semantics.

Simply using the “binds directly” terminology in the new wording may not be the right approach, however, as there are other places in the Standard that also give special treatment to directly-bound references. For example, the first bullet of 5.16 [expr.cond] paragraph 3 says,

If E2 is an lvalue: E1 can be converted to match E2 if E1 can be implicitly converted (clause 4 [conv]) to the type “reference to T2,” subject to the constraint that in the conversion the reference must bind directly (8.5.3 [dcl.init.ref]) to E1.

The effect of simply saying that a reference “binds directly” to a class rvalue can be seen in this example:

    struct B { };
    struct D: B { };
    D f();
    void g(bool x, const B& br) {
        x ? f() : br;   // result would be lvalue
    }

It is not clear that treating this conditional expression as an lvalue is a desirable outcome, even if the result of f() were to “bind directly” to the const B& reference.




664. Direct binding of references to non-class rvalue references

Section: 8.5.3  [dcl.init.ref]     Status: drafting     Submitter: Eric Niebler     Date: 1 December 2007

According to 8.5.3 [dcl.init.ref] paragraph 5, a reference initialized with a reference-compatible rvalue of class type binds directly to the object. A reference-compatible non-class rvalue reference, however, is first copied to a temporary and the reference binds to that temporary, not to the target of the rvalue reference. This can cause problems when the result of a forwarding function is used in such a way that the address of the result is captured. For example:

    struct ref {
        explicit ref(int&& i): p(&i) { }
        int* p;
    };

    int&& forward(int&& i) {
        return i;
    }

    void f(int&& i) {
        ref r(forward(i));
        // Here r.p is a dangling pointer, pointing to a defunct int temporary
    }

A formulation is needed so that rvalue references are treated like class and array rvalues.

Notes from the February, 2008 meeting:

You can't just treat scalar rvalues like class and array rvalues, because they might not have an associated object. However, if you have an rvalue reference, you know that there is an object, so probably the best way to address this issue is to specify somehow that binding a reference to an rvalue reference does not introduce a new temporary.

(See also issue 690.)






Issues with "Open" Status


616. Definition of “indeterminate value”

Section: 1.3  [intro.defs]     Status: open     Submitter: Bjarne Stroustrup     Date: 2 February 2007

The C++ Standard uses the phrase “indeterminate value” without defining it. C99 defines it as “either an unspecified value or a trap representation.” Should C++ follow suit?

In addition, 4.1 [conv.lval] paragraph 1 says that applying the lvalue-to-rvalue conversion to an “object [that] is uninitialized” results in undefined behavior; this should be rephrased in terms of an object with an indeterminate value.




129. Stability of uninitialized auto variables

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

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

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

Mike Miller: 1.9 [intro.execution] paragraph 10 says,

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

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

Tom Plum: 7.1.6.1 [dcl.type.cv] paragraph 8 says,

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

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

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




380. Definition of "ambiguous base class" missing

Section: 10.2  [class.member.lookup]     Status: open     Submitter: Jason Merrill     Date: 22 Oct 2002

The term "ambiguous base class" doesn't seem to be actually defined anywhere. 10.2 [class.member.lookup] paragraph 7 seems like the place to do it.




230. Calls to pure virtual functions

Section: 10.4  [class.abstract]     Status: open     Submitter: Jim Hill     Date: 4 May 2000

According to 10.4 [class.abstract] paragraph 6,

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

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

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

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

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

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

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




600. Does access control apply to members or to names?

Section: 11  [class.access]     Status: open     Submitter: Alisdair Meredith     Date: 3 October 2006

Referring to a private member of a class, 11 [class.access] paragraph 1 says,

its name can be used only by members and friends of the class in which it is declared.

That wording does not appear to reflect the intent of access control, however. Consider the following:

    struct S {
        void f(int);
    private:
        void f(double);
    };

    void g(S* sp) {
        sp->f(2);        // Ill-formed?
    }

The statement from 11 [class.access] paragraph 1 says that the name f can be used only by members and friends of S. Function g is neither, and it clearly contains a use of the name f. That appears to make it ill-formed, in spite of the fact that overload resolution will select the public member.

A related question is whether the use of the term “name” in the description of the effect of access control means that it does not apply to constructors and destructors, which do not have names.

Mike Miller: The phrase “its name can be used” should be understood as “it can be referred to by name.” Paragraph 4, among other places, makes it clear that access control is applied after overload resolution. The “name” phrasing is there to indicate that access control does not apply where the name is not used (in a call via a pointer, for example).




360. Using-declaration that reduces access

Section: 11.2  [class.access.base]     Status: open     Submitter: Steve Clamage     Date: 4 June 2002

I have heard a claim that the following code is valid, but I don't see why.

  struct A {
    int foo ();
  };

  struct B: A {
  private:
    using A::foo;
  };

  int main ()
  {
    return B ().foo ();
  }

It seems to me that the using declaration in B should hide the public foo in A. Then the call to B::foo should fail because B::foo is not accessible in main.

Am I missing something?

Steve Adamczyk: This is similar to the last example in 11.2 [class.access.base]. In prose, the rule is that if you have access to cast to a base class and you have access to the member in the base class, you are given access in the derived class. In this case, A is a public base class of B and foo is public in A, so you can access foo through a B object. The actual permission for this is in the fourth bullet in 11.2 [class.access.base] paragraph 4.

The wording changes for issue 9 make this clearer, but I believe even without them this example could be discerned to be valid.

See my paper J16/96-0034, WG21/N0852 on this topic.

Steve Clamage: But a using-declaration is a declaration (7.3.3 [namespace.udecl]). Compare with

  struct B : A {
  private:
    int foo();
  };

In this case, the call would certainly be invalid, even though your argument about casting B to an A would make it OK. Your argument basically says that an access adjustment to make something less accessible has no effect. That also doesn't sound right.

Steve Adamczyk: I agree that is strange. I do think that's what 11.2 [class.access.base] says, but perhaps that's not what we want it to say.




581. Can a templated constructor be explicitly instantiated or specialized?

Section: 12.1  [class.ctor]     Status: open     Submitter: Mark Mitchell     Date: 19 May 2006

Although it is not possible to specify a constructor's template arguments in a constructor invocation (because the constructor has no name but is invoked by use of the constructor's class's name), it is possible to “name” the constructor in declarative contexts: per 3.4.3.1 [class.qual] paragraph 2,

In a lookup in which the constructor is an acceptable lookup result, if the nested-name-specifier nominates a class C, and the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C (clause 9 [class]), the name is instead considered to name the constructor of class C... Such a constructor name shall be used only in the declarator-id of a declaration that names a constructor.

Should it therefore be possible to specify template-arguments for a templated constructor in an explicit instantiation or specialization? For example,

    template <int dim> struct T {};
    struct X {
      template <int dim> X (T<dim> &) {};
    };

    template X::X<> (T<2> &);

If so, that should be clarified in the text. In particular, 12.1 [class.ctor] paragraph 1 says,

Constructors do not have names. A special declarator syntax using an optional sequence of function-specifiers (7.1.2 [dcl.fct.spec]) followed by the constructor’s class name followed by a parameter list is used to declare or define the constructor.

This certainly sounds as if the parameter list must immediately follow the class name, with no allowance for a template argument list.

It would be worthwhile in any event to revise this wording to utilize the “considered to name” approach of 3.4.3.1 [class.qual]; as it stands, this wording sounds as if the following would be acceptable:

    struct S {
        S();
    };
    S() { }    // qualified-id not required?

Notes from the October, 2006 meeting:

It was observed that explicitly specifying the template arguments in a constructor declaration is never actually necessary because the arguments are, by definition, all deducible and can thus be omitted.




395. Conversion operator template syntax

Section: 12.3.2  [class.conv.fct]     Status: open     Submitter: Daveed Vandevoorde     Date: 18 Dec 2002

A posting in comp.lang.c++.moderated prompted me to try the following code:

  struct S {
    template<typename T, int N> (&operator T())[N];
  };

The goal is to have a (deducible) conversion operator template to a reference-to-array type.

This is accepted by several front ends (g++, EDG), but I now believe that 12.3.2 [class.conv.fct] paragraph 1 actually prohibits this. The issue here is that we do in fact specify (part of) a return type.

OTOH, I think it is legitimate to expect that this is expressible in the language (preferably not using the syntax above ;-). Maybe we should extend the syntax to allow the following alternative?

  struct S {
    template<typename T, int N> operator (T(&)[N])();
  };

Eric Niebler: If the syntax is extended to support this, similar constructs should also be considered. For instance, I can't for the life of me figure out how to write a conversion member function template to return a member function pointer. It could be useful if you were defining a null_t type. This is probably due to my own ignorance, but getting the syntax right is tricky.

Eg.

  struct null_t {
    // null object pointer. works.
    template<typename T> operator T*() const { return 0; }
    // null member pointer. works.
    template<typename T,typename U> operator T U::*() const { return 0; }
    // null member fn ptr.  doesn't work (with Comeau online).  my error?
    template<typename T,typename U> operator T (U::*)()() const { return 0; }
  };

Martin Sebor: Intriguing question. I have no idea how to do it in a single declaration but splitting it up into two steps seems to work:

  struct null_t {
    template <class T, class U>
    struct ptr_mem_fun_t {
      typedef T (U::*type)();
    };

    template <class T, class U>
    operator typename ptr_mem_fun_t<T, U>::type () const {
      return 0;
    }
  };

Note: In the April 2003 meeting, the core working group noticed that the above doesn't actually work.




344. Naming destructors

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

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

Change 12.4 [class.dtor], paragraph 1 from

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

to

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



255. Placement deallocation functions and lookup ambiguity

Section: 12.5  [class.free]     Status: open     Submitter: Mike Miller     Date: 26 Oct 2000

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

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

Suggested resolution (Mike Miller, March 2002):

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

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



607. Lookup of mem-initializer-ids

Section: 12.6.2  [class.base.init]     Status: open     Submitter: Richard Corden     Date: 5 December 2006

In an example like,

    struct Y {};

    template <typename T>
    struct X : public virtual Y { };

    template <typename T>
    class A : public X<T> {
      template <typename S>
      A (S)
        : S ()
      {
      }
    };

    template A<int>::A (Y);

Should S be found? (S is a dependent name, so if it resolves to a base class type in the instantiated template, it should satisfy the requirements.) All the compilers I tried allowed this example, but 12.6.2 [class.base.init] paragraph 2 says,

Names in a mem-initializer-id are looked up in the scope of the constructor’s class and, if not found in that scope, are looked up in the scope containing the constructor’s definition.

The name S is not declared in those scopes.

Mike Miller: Here's another example that is accepted by most/all compilers but not by the current wording:

    namespace N {
      struct B { B(int); };
      typedef B typedef_B;
      struct D: B {
        D();
      };
    }

    N::D::D(): typedef_B(0) { }

Except for the fact that the constructor function parameter names are ignored (see paragraph 7), what the compilers seem to be doing is essentially ordinary unqualified name lookup.




6. Should the optimization that allows a class object to alias another object also allow the case of a parameter in an inline function to alias its argument?

Section: 12.8  [class.copy]     Status: open     Submitter: unknown     Date: unknown

[Picked up by evolution group at October 2002 meeting.]

(See also paper J16/99-0005 = WG21 N1182.)

At the London meeting, 12.8 [class.copy] paragraph 15 was changed to limit the optimization described to only the following cases:

One other case was deemed desirable as well: However, there are cases when this aliasing was deemed undesirable and, at the London meeting, the committee was not able to clearly delimit which cases should be allowed and which ones should be prohibited.

Can we find an appropriate description for the desired cases?

Rationale (04/99): The absence of this optimization does not constitute a defect in the Standard, although the proposed resolution in the paper should be considered when the Standard is revised.

Note (March, 2008):

The Evolution Working Group has accepted the intent of this issue and referred it to CWG for action (not for C++0x). See paper J16/07-0033 = WG21 N2173.

Notes from the June, 2008 meeting:

The CWG decided to take no action on this issue until an interested party produces a paper with analysis and a proposal.




683. Requirements for trivial subobject special functions

Section: 12.8  [class.copy]     Status: open     Submitter: Jens Maurer     Date: 13 March, 2008

Part of the decision regarding whether a class has a trivial special function (copy constructor, copy assignment operator, default constructor) is whether its base and member subobjects have corresponding trivial member functions. However, with the advent of defaulted functions, it is now possible for a single class to have both trivial and nontrivial overloads for those functions. For example,

    struct B {
       B(B&) = default;    // trivial
       B(const B&);        // non-trivial, because user-provided
    };

    struct D : B { };

Although B has a trivial copy constructor and thus satisfies the requirements in 12.8 [class.copy] paragraph 6, the copy constructor in B that would be called by the implicitly-declared copy constructor in D is not trivial. This could be fixed either by requiring that all the subobject's copy constructors (or copy assignment operators, or default constructors) be trivial or that the one that would be selected by overload resolution be trivial.




545. User-defined conversions and built-in operator overload resolution

Section: 13.3.1.2  [over.match.oper]     Status: open     Submitter: Steve Clamage     Date: 31 October 2005

Consider the following example:

    class B1 {};
    typedef void (B1::*PB1) (); // memptr to B1

    class B2 {};
    typedef void (B2::*PB2) (); // memptr to B2

    class D1 : public B1, public B2 {};
    typedef void (D1::*PD) (); // memptr to D1

    struct S {
         operator PB1(); // can be converted to PD
    } s;
    struct T {
         operator PB2(); // can be converted to PD
    } t;

    void foo() {
         s == t; // Is this an error?
    }

According to 13.6 [over.built] paragraph 16, there is an operator== for PD (“For every pointer to member type...”), so why wouldn't it be used for this comparison?

Mike Miller: The problem, as I understand it, is that 13.3.1.2 [over.match.oper] paragraph 3, bullet 3, sub-bullet 3 is broader than it was intended to be. It says that candidate built-in operators must “accept operand types to which the given operand or operands can be converted according to 13.3.3.1 [over.best.ics].” 13.3.3.1.2 [over.ics.user] describes user-defined conversions as having a second standard conversion sequence, and there is nothing to restrict that second standard conversion sequence.

My initial thought on addressing this would be to say that user-defined conversion sequences whose second standard conversion sequence contains a pointer conversion or a pointer-to-member conversion are not considered when selecting built-in candidate operator functions. They would still be applicable after the hand-off to Clause 5 (e.g., in bringing the operands to their common type, 5.10 [expr.eq], or composite pointer type, 5.9 [expr.rel]), just not in constructing the list of built-in candidate operator functions.

I started to suggest restricting the second standard conversion sequence to conversions having Promotion or Exact Match rank, but that would exclude the Boolean conversions, which are needed for !, &&, and ||. (It would have also restricted the floating-integral conversions, though, which might be a good idea. They can't be used implicitly, I think, because there would be an ambiguity among all the promoted integral types; however, none of the compilers I tested even tried those conversions because the errors I got were not ambiguities but things like “floating point operands not allowed for %”.)

Bill Gibbons: I recall seeing this problem before, though possibly not in committee discussions. As written this rule makes the set of candidate functions dependent on what classes have been defined, including classes not otherwise required to have been defined in order for "==" to be meaningful. For templates this implies that the set is dependent on what templates have been instantiated, e.g.

  template<class T> class U : public T { };
  U<B1> u;  // changes the set of candidate functions to include
            // operator==(U<B1>,U<B1>)?

There may be other places where the existence of a class definition, or worse, a template instantiation, changes the semantics of an otherwise valid program (e.g. pointer conversions?) but it seems like something to be avoided.




418. Imperfect wording on error on multiple default arguments on a called function

Section: 13.3.3  [over.match.best]     Status: open     Submitter: Chris Bowler     Date: 27 May 2003

According to 13.3.3 [over.match.best] paragraph 4, the following program appears to be ill-formed:

  void f(int, int=0);
  void f(int=0, int);

  void g() {
    f();
  }

Though I do not expect this is the intent of this paragraph in the standard.

13.3.3 [over.match.best] paragraph 4:

If the best viable function resolves to a function for which multiple declarations were found, and if at least two of these declarations or the declarations they refer to in the case of using-declarations specify a default argument that made the function viable, the program is ill-formed. [Example:
namespace A {
  extern "C" void f(int = 5);
}
namespace B {
  extern "C" void f(int = 5);
}
using A::f;
using B::f;
void use() {
f(3); //OK, default argument was not used for viability
f(); //Error: found default argument twice
}
end example]



455. Partial ordering and non-deduced arguments

Section: 13.3.3  [over.match.best]     Status: open     Submitter: Rani Sharoni     Date: 19 Jan 2004

It's not clear how overloading and partial ordering handle non-deduced pairs of corresponding arguments. For example:

template<typename T>
struct A { typedef char* type; };

template<typename T> char* f1(T, typename A<T>::type);  // #1
template<typename T> long* f1(T*, typename A<T>::type*); // #2

long* p1 = f1(p1, 0); // #3

I thought that #3 is ambiguous but different compilers disagree on that. Comeau C/C++ 4.3.3 (EDG 3.0.3) accepted the code, GCC 3.2 and BCC 5.5 selected #1 while VC7.1+ yields ambiguity.

I intuitively thought that the second pair should prevent overloading from triggering partial ordering since both arguments are non-deduced and has different types - (char*, char**). Just like in the following:

template<typename T> char* f2(T, char*);   // #3
template<typename T> long* f2(T*, char**); // #4

long* p2 = f2(p2, 0); // #5

In this case all the compilers I checked found #5 to be ambiguous. The standard and DR 214 is not clear about how partial ordering handle such cases.

I think that overloading should not trigger partial ordering (in step 13.3.3 [over.match.best]/1/5) if some candidates have non-deduced pairs with different (specialized) types. In this stage the arguments are already adjusted so no need to mention it (i.e. array to pointer). In case that one of the arguments is non-deuced then partial ordering should only consider the type from the specialization:

template<typename T> struct B { typedef T type; };

template<typename T> char* f3(T, T);                   // #7
template<typename T> long* f3(T, typename B<T>::type); // #8

char* p3 = f3(p3, p3); // #9

According to my reasoning #9 should yield ambiguity since second pair is (T, long*). The second type (i.e. long*) was taken from the specialization candidate of #8. EDG and GCC accepted the code. VC and BCC found an ambiguity.

John Spicer: There may (or may not) be an issue concerning whether nondeduced contexts are handled properly in the partial ordering rules. In general, I think nondeduced contexts work, but we should walk through some examples to make sure we think they work properly.

Rani's description of the problem suggests that he believes that partial ordering is done on the specialized types. This is not correct. Partial ordering is done on the templates themselves, independent of type information from the specialization.

Notes from October 2004 meeting:

John Spicer will investigate further to see if any action is required.




507. Ambiguity assigning class object to built-in type

Section: 13.6  [over.built]     Status: open     Submitter: Steve Adamczyk     Date: 9 Mar 2005

The following example is ambiguous according to the Standard:

    struct Y {
      operator int();
      operator double();
    };
    void f(Y y) {
      double d;
      d = y;    // Ambiguous: Y::operator int() or Y::operator double()?
    }

The reason for the ambiguity is that 13.6 [over.built] paragraph 18 says that there are candidate functions double& operator=(double&, int) and double& operator=(double&, double) (among others). In each case, the second argument is converted by a user-defined conversion sequence (13.3.3.1.2 [over.ics.user]) where the initial and final standard conversion sequences are the identity conversion — i.e., the conversion sequences for the second argument are indistinguishable for each of these candidate functions, and they are thus ambiguous.

Intuitively one might expect that, because it converts directly to the target type in the assignment, Y::operator double() would be selected, and in fact, most compilers do select it, but there is currently no rule to distinghish between these user-defined conversions. Should there be?

Additional note (May, 2008):

Here is another example that is somewhat similar:

    enum En { ec };

    struct S {
       operator int();
       operator En();
    };

    void foo () {
       S() == 0;   // ambiguous?
    }

According to 13.6 [over.built] paragraph 12, the candidate functions are

where R is int and L is every promoted arithmetic type. Overload resolution proceeds in two steps: first, for each candidate function, determine which implicit conversion sequence is used to convert from the argument type to the parameter type; then compare the candidate functions on the basis of the relative costs of those conversion sequences.

In the case of operator==(int, int) there is a clear winner: S::operator int() is chosen because the identity conversion int -> int is better than the promotion En -> int. For all the other candidates, the conversion for the first parameter is ambiguous: both S::operator int() and S::operator En() require either an integral conversion (for integral L) or a floating-integral conversion (for floating point L) and are thus indistinguishable.

These additional candidates are not removed from the set of viable functions, however; because of 13.3.3.1 [over.best.ics] paragraph 10, they are assigned the “ambiguous conversion sequence,” which “is treated as a user-defined sequence that is indistinguishable from any other user-defined conversion sequence.” As a result, all the viable functions are indistinguishable and the call is ambiguous. Like the earlier example, one might naively think that the exact match with S::operator int() and bool operator==(int, int) would be selected, but that is not the case.




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

Section: 14  [temp]     Status: open     Submitter: John Spicer     Date: 28 Apr 1999

According to 14 [temp] paragraph 5,

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

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

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

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




343. Make template optional in contexts that require a type

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

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

This was suggested by issue 314.




579. What is a “nested” > or >>?

Section: 14.2  [temp.names]     Status: open     Submitter: Daveed Vandevoorde     Date: 11 May 2006

The Standard does not normatively define which > and >> tokens are to be taken as closing a template-argument-list; instead, 14.2 [temp.names] paragraph 3 uses the undefined and imprecise term “non-nested:”

When parsing a template-id, the first non-nested > is taken as the end of the template-argument-list rather than a greater-than operator. Similarly, the first non-nested >> is treated as two consecutive but distinct > tokens, the first of which is taken as the end of the template-argument-list and completes the template-id.

The (non-normative) footnote clarifies that

A > that encloses the type-id of a dynamic_cast, static_cast, reinterpret_cast or const_cast, or which encloses the template-arguments of a subsequent template-id, is considered nested for the purpose of this description.

Aside from the questionable wording of this footnote (e.g., in what sense does a single terminating character “enclose” anything, and is a nested template-id “subsequent?”) and the fact that it is non-normative, it does not provide a complete definition of what “nesting” is intended to mean. For example, is the first > in this putative template-id “nested” or not?

    X<a ? b > c : d>



440. Allow implicit pointer-to-member conversion on nontype template argument

Section: 14.3  [temp.arg]     Status: open     Submitter: David Abrahams     Date: 13 Nov 2003

None of my compilers accept this, which surprised me a little. Is the base-to-derived member function conversion considered to be a runtime-only thing?

  template <class D>
  struct B
  {
      template <class X> void f(X) {}
      template <class X, void (D::*)(X) = &B<D>::f<X> >
      struct row {};
  };
  struct D : B<D>
  {
      void g(int);
      row<int,&D::g> r1;
      row<char*> r2;
  };

John Spicer: This is not among the permitted conversions listed in 14.3.

I'm not sure there is a terribly good reason for that. Some of the template argument rules for external entities were made conservatively because of concerns about issues of mangling template argument names.

David Abrahams: I'd really like to see that restriction loosened. It is a serious inconvenience because there appears to be no way to supply a usable default in this case. Zero would be an OK default if I could use the function pointer's equality to zero as a compile-time switch to choose an empty function implementation:

  template <bool x> struct tag {};

  template <class D>
  struct B
  {
      template <class X> void f(X) {}

      template <class X, void (D::*pmf)(X) = 0 >
      struct row {
          void h() { h(tag<(pmf == 0)>(), pmf); }
          void h(tag<1>, ...) {}
          void h(tag<0>, void (D::*q)(X)) { /*something*/}
      };
  };

  struct D : B<D>
  {
      void g(int);
      row<int,&D::g> r1;
      row<char*> r2;
  };

But there appears to be no way to get that effect either. The result is that you end up doing something like:

      template <class X, void (D::*pmf)(X) = 0 >
      struct row {
          void h() { if (pmf) /*something*/ }
      };

which invariably makes compilers warn that you're switching on a constant expression.




150. Template template parameters and default arguments

Section: 14.3.3  [temp.arg.template]     Status: open     Submitter: Mike Miller     Date: 3 Aug 1999

[Picked up by evolution group at October 2002 meeting.]

How are default template arguments handled with respect to template template parameters? Two separate questions have been raised:

  1. Do default template arguments allow a template argument to match a template parameter with fewer template parameters, and can the template template parameter be specialized using the smaller number of template arguments? For example,
        template <class T, class U = int>
        class ARG { };
    
        template <class X, template <class Y> class PARM>
        void f(PARM<X>) { }    // specialization permitted?
    
        void g() {
            ARG<int> x;        // actually ARG<int, int>
            f(x);              // does ARG (2 parms, 1 with default)
                               // match PARM (1 parm)?
    
    Template template parameters are deducible (14.8.2.5 [temp.deduct.type] paragraph 9), but 14.3.3 [temp.arg.template] does not specify how matching is done.

    Jack Rouse: I implemented template template parameters assuming template signature matching is analogous to function type matching. This seems like the minimum reasonable implementation. The code in the example would not be accepted by this compiler. However, template default arguments are compile time entities so it seems reasonable to relax the matching rules to allow cases like the one in the example. But I would consider this to be an extension to the language.

    Herb Sutter: An open issue in the LWG is that the standard doesn't explicitly permit or forbid implementations' adding additional template-parameters to those specified by the standard, and the LWG may be leaning toward explicitly permitting this. [Under this interpretation,] if the standard is ever modified to allow additional template-parameters, then writing "a template that takes a standard library template as a template template parameter" won't be just ugly because you have to mention the defaulted parameters; it would not be (portably) possible at all except possibly by defining entire families of overloaded templates to account for all the possible numbers of parameters vector<> (or anything else) might actually have. That seems unfortunate.

  2. Are default arguments permitted in the template parameter list of a template template parameter? For example,
        template <template <class T, class U = int> class PARM>
        class C {
            PARM<int> pi;
        };
    

    Jack Rouse: I decided they could not in the compiler I support. This continues the analogy with function type matching. Also, I did not see a strong need to allow default arguments in this context.

    A class template used as a template template argument can have default template arguments from its declarations. How are the two sources of default arguments to be reconciled? The default arguments from the template template formal could override. But it could be cofusing if a template-id using the argument template, ARG<int>, behaves differently from a template-id using the template formal name, FORMAL<int>.

Rationale (10/99): Template template parameters are intended to be handled analogously to function function parameters. Thus the number of parameters in a template template argument must match the number of parameters in a template template parameter, regardless of whether any of those paramaters have default arguments or not. Default arguments are allowed for the parameters of a template template parameter, and those default arguments alone will be considered in a specialization of the template template parameter within a template definition; any default arguments for the parameters of a template template argument are ignored.

Note (Mark Mitchell, February, 2006):

Perhaps it is already obvious to all, but it seems worth noting that this extension would change the meaning of conforming programs:

    struct Dense { static const unsigned int dim = 1; };

    template <template <typename> class View,
              typename Block>
    void operator+(float, View<Block> const&);

    template <typename Block,
              unsigned int Dim = Block::dim>
    struct Lvalue_proxy { operator float() const; };

    void test_1d (void) {
        Lvalue_proxy<Dense> p;
        float b;
        b + p;
    }

If Lvalue_proxy is allowed to bind to View, then the template operator+ will be used to perform addition; otherwise, Lvalue_proxy's implicit conversion to float, followed by the built-in addition on floats will be used.

Note (March, 2008):

The Evolution Working Group has accepted the intent of this issue and referred it to CWG for action (not for C++0x). See paper J16/07-0033 = WG21 N2173.

Notes from the June, 2008 meeting:

The CWG decided to take no action on this issue until an interested party produces a paper with analysis and a proposal.




638. Explicit specialization and friendship

Section: 14.5.4  [temp.friend]     Status: open     Submitter: Daveed Vandevoorde     Date: 6 July 2007

Is this code well-formed?

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

    class C {
        template <typename T> friend struct A<T>::B;
        static int bar;
    };

    template <> struct A<char> {
        struct B {
            int f() {
                return C::bar;   // Is A<char>::B a friend of C?
            }
        };
    };

According to 14.5.4 [temp.friend] paragraph 5,

A member of a class template may be declared to be a friend of a non-template class. In this case, the corresponding member of every specialization of the class template is a friend of the class granting friendship.

This would tend to indicate that the example is well-formed. However, technically A<char>::B does not “correspond to” the same-named member of the class template: 14.7.3 [temp.expl.spec] paragraph 4 says,

The definition of an explicitly specialized class is unrelated to the definition of a generated specialization. That is, its members need not have the same names, types, etc. as the members of a generated specialization.

In other words, there are no “corresponding members” in an explicit specialization.

Is this the outcome we want for examples like the preceding? There is diversity among implementations on this question, with some accepting the example and others rejecting it as an access violation.




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

Section: 14.5.6.1  [temp.over.link]     Status: open     Submitter: Andrei Iltchenko     Date: 29 Aug 2001

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

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

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


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

The implementation complains that

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

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

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

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




23. Some questions regarding partial ordering of function templates

Section: 14.5.6.2  [temp.func.order]     Status: open     Submitter: unknown     Date: unknown

Issue 1:

14.5.6.2 [temp.func.order] paragraph 2 says:

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

Rationale:

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

Issue 2:

14.5.6.2 [temp.func.order] paragraph 4 says:

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

Issue 3:

14.5.6.2 [temp.func.order] paragraph 5 says:

A template is more specialized than another if, and only if, it is at least as specialized as the other template and that template is not at least as specialized as the first.
What happens in this case:
    template<class T> void f(T,int);
    template<class T> void f(T, T);
    void f(1,1);
For the first function template, there is no type deduction for the second parameter. So the rules in this clause seem to imply that the second function template will be chosen.

Rationale:

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




402. More on partial ordering of function templates

Section: 14.5.6.2  [temp.func.order]     Status: open     Submitter: Nathan Sidwell     Date: 7 Apr 2003

This was split off from issue 214 at the April 2003 meeting.

Nathan Sidwell: John Spicer's proposed resolution does not make the following well-formed.

  template <typename T> int Foo (T const *) {return 1;} //#1
  template <unsigned I> int Foo (char const (&)[I]) {return 2;} //#2

  int main ()
  {
    return Foo ("a") != 2;
  }

Both #1 and #2 can deduce the "a" argument, #1 deduces T as char and #2 deduces I as 2. However, neither is more specialized because the proposed rules do not have any array to pointer decay.

#1 is only deduceable because of the rules in 14.8.2.1 [temp.deduct.call] paragraph 2 that decay array and function type arguments when the template parameter is not a reference. Given that such behaviour happens in deduction, I believe there should be equivalent behaviour during partial ordering. #2 should be resolved as more specialized as #1. The following alteration to the proposed resolution of DR214 will do that.

Insert before,

the following

For the example above, this change results in deducing 'T const *' against 'char const *' in one direction (which succeeds), and 'char [I]' against 'T const *' in the other (which fails).

John Spicer: I don't consider this a shortcoming of my proposed wording, as I don't think this is part of the current rules. In other words, the resolution of 214 might make it clearer how this case is handled (i.e., clearer that it is not allowed), but I don't believe it represents a change in the language.

I'm not necessarily opposed to such a change, but I think it should be reviewed by the core group as a related change and not a defect in the proposed resolution to 214.

Notes from the October 2003 meeting:

There was some sentiment that it would be desirable to have this case ordered, but we don't think it's worth spending the time to work on it now. If we look at some larger partial ordering changes at some point, we will consider this again.




186. Name hiding and template template-parameters

Section: 14.6.1  [temp.local]     Status: open     Submitter: John Spicer     Date: 11 Nov 1999

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

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

Presumably, we should also prohibit

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


459. Hiding of template parameters by base class members

Section: 14.6.1  [temp.local]     Status: open     Submitter: Daveed Vandevoorde     Date: 2 Feb 2004

Currently, member of nondependent base classes hide references to template parameters in the definition of a derived class template.

Consider the following example:

   class B {
      typedef void *It;    // (1)
      // ...
    };

    class M: B {};

    template<typename> X {};

    template<typename It> struct S   // (2)
        : M, X<It> {   // (3)
      S(It, It);   // (4)
      // ...
    };

As the C++ language currently stands, the name "It" in line (3) refers to the template parameter declared in line (2), but the name "It" in line (4) refers to the typedef in the private base class (declared in line (1)).

This situation is both unintuitive and a hindrance to sound software engineering. (See also the Usenet discussion at http://tinyurl.com/32q8d .) Among other things, it implies that the private section of a base class may change the meaning of the derived class, and (unlike other cases where such things happen) there is no way for the writer of the derived class to defend the code against such intrusion (e.g., by using a qualified name).

Changing this can break code that is valid today. However, such code would have to:

  1. name a template parameter and not use it after the opening brace, and
  2. use that same name to access a base-class name within the braces.
I personally have no qualms breaking such a program.

It has been suggested to make situations like these ill-formed. That solution is unattractive however because it still leaves the writer of a derived class template without defense against accidental name conflicts with base members. (Although at least the problem would be guaranteed to be caught at compile time.) Instead, since just about everyone's intuition agrees, I would like to see the rules changed to make class template parameters hide members of the same name in a base class.

See also issue 458.

Notes from the March 2004 meeting:

We have some sympathy for a change, but the current rules fall straightforwardly out of the lookup rules, so they're not “wrong.” Making private members invisible also would solve this problem. We'd be willing to look at a paper proposing that.

Additional discussion (April, 2005):

John Spicer: Base class members are more-or-less treated as members of the class, [so] it is only natural that the base [member] would hide the template parameter.

Daveed Vandevoorde: Are base class members really “more or less” members of the class from a lookup perspective? After all, derived class members can hide base class members of the same name. So there is some pretty definite boundary between those two sets of names. IMO, the template parameters should either sit between those two sets, or they should (for lookup purposes) be treated as members of the class they parameterize (I cannot think of a practical difference between those two formulations).

John Spicer: How is [hiding template parameters] different from the fact that namespace members can be hidden by private parts of a base class? The addition of int C to N::A breaks the code in namespace M in this example:

    namespace N {
       class A {
    private:
         int C;
       };
    }

    namespace M {
       typedef int C;
       class B : public N::A {
         void f() {
             C c;
         }
       };
    }

Daveed Vandevoorde: C++ has a mechanism in place to handle such situations: qualified names. There is no such mechanism in place for template parameters.

Nathan Myers: What I see as obviously incorrect ... is simply that a name defined right where I can see it, and directly attached to the textual scope of B's class body, is ignored in favor of something found in some other file. I don't care that C1 is defined in A, I have a C1 right here that I have chosen to use. If I want A::C1, I can say so.

I doubt you'll find any regular C++ coder who doesn't find the standard behavior bizarre. If the meaning of any code is changed by fixing this behavior, the overwhelming majority of cases will be mysterious bugs magically fixed.

John Spicer: I have not heard complaints that this is actually a cause of problems in real user code. Where is the evidence that the status quo is actually causing problems?

In this example, the T2 that is found is the one from the base class. I would argue that this is natural because base class members are found as part of the lookup in class B:

    struct A {
             typedef int T2;
    };
    template <class T2> struct B : public A {
             typedef int T1;
             T1 t1;
             T2 t2;
    };

This rule that base class members hide template parameters was formalized about a dozen years ago because it fell out of the principle that base class members should be found at the same stage of lookup as derived class members, and that to do otherwise would be surprising.

Gabriel Dos Reis: The bottom line is that:

  1. the proposed change is a silent change of meaning;
  2. the proposed change does not make the language any more regular; the current behavior is consistent with everything else, however “surprising” that might be;
  3. the proposed change does have its own downsides.

Unless presented with real major programming problems the current rules exhibit, I do not think the simple rule “scopes nest” needs a change that silently mutates program meaning.

Mike Miller: The rationale for the current specification is really very simple:

  1. “Unless redeclared in the derived class, members of a base class are also considered to be members of the derived class.” (10 [class.derived] paragraph 2)
  2. In class scope, members hide nonmembers.

That's it. Because template parameters are not members, they are hidden by member names (whether inherited or not). I don't find that “bizarre,” or even particularly surprising.

I believe these rules are straightforward and consistent, so I would be opposed to changing them. However, I am not unsympathetic toward Daveed's concern about name hijacking from base classes. How about a rule that would make a program ill-formed if a direct or inherited member hides a template parameter?

Unless this problem is a lot more prevalent than I've heard so far, I would not want to change the lookup rules; making this kind of collision a diagnosable error, however, would prevent hijacking without changing the lookup rules.

Erwin Unruh: I have a different approach that is consistent and changes the interpretation of the questionable code. At present lookup is done in this sequence:

If we change this order to

it is still consistent in that no lookup is placed between the base class and the derived class. However, it introduces another inconsistency: now scopes do not nest the same way as curly braces nest — but base classes are already inconsistent this way.

Nathan Myers: This looks entirely satisfactory. If even this seems like too big a change, it would suffice to say that finding a different name by this search order makes the program ill-formed. Of course, a compiler might issue only a portability warning in that case and use the name found Erwin's way, anyhow.

Gabriel Dos Reis: It is a simple fact, even without templates, that a writer of a derived class cannot protect himself against declaration changes in the base class.

Richard Corden: If a change is to be made, then making it ill-formed is better than just changing the lookup rules.

    struct B
    {
      typedef int T;
      virtual void bar (T const & );
    };

    template <typename T>
    struct D : public B
    {
      virtual void bar (T const & );
    };

    template class D<float>;

I think changing the semantics of the above code silently would result in very difficult-to-find problems.

Mike Miller: Another case that may need to be considered in deciding on Erwin's suggestion or the “ill-formed” alternative is the treatment of friend declarations described in 3.4.1 [basic.lookup.unqual] paragraph 10:

    struct A {
        typedef int T;
        void f(T);
    };
    template<typename T> struct B {
        friend void A::f(T);  // Currently T is A::T
    };

Notes from the October, 2005 meeting:

The CWG decided not to consider a change to the existing rules at this time without a paper exploring the issue in more detail.




602. When is the injected-class-name of a class template a template?

Section: 14.6.1  [temp.local]     Status: open     Submitter: Daveed Vandevoorde     Date: 23 October 2006

Consider the following example:

    template<class T>
    struct A {
         template<class U>
             friend struct A; // Which A?
    };

Presumably the lookup for A in the friend declaration finds the injected-class-name of the template. However, according to 14.6.1 [temp.local] paragraph 1,

The injected-class-name can be used with or without a template-argument-list. When it is used without a template-argument-list, it is equivalent to the injected-class-name followed by the template-parameters of the class template enclosed in <>. When it is used with a template-argument-list, it refers to the specified class template specialization, which could be the current specialization or another specialization.

If that rule applies, then this example is ill-formed (because you can't have a template-argument-list in a class template declaration that is not a partial specialization).

Mike Miller: The injected-class-name has a dual nature, as described in 14.6.1 [temp.local], acting as either a template name or a class name, depending on the context; a template argument list forces the name to be interpreted as a template. It seems reasonable that in this example the injected-class-name has to be understood as referring to the class template; a template header is at least as strong a contextual indicator as a template argument list. However, the current wording doesn't say that.




591. When a dependent base class is the current instantiation

Section: 14.6.2  [temp.dep]     Status: open     Submitter: James Widman     Date: 24 August 2006

Is the following example well-formed?

    template<class T> struct A {
         typedef int M;
         struct B {
             typedef void M;
             struct C;
         };
    };

    template<class T> struct A<T>::B::C : A<T> {
         M  // A<T>::M or A<T>::B::M?
             p[2];
    };

14.6.2 [temp.dep] paragraph 3 says the use of M should refer to A<T>::B::M because the base class A<T> is not searched because it's dependent. But in this case A<T> is also the current instantiation (14.6.2.1 [temp.dep.type]) so it seems like it should be searched.




502. Dependency of nested enumerations and enumerators

Section: 14.6.2.1  [temp.dep.type]     Status: open     Submitter: Mark Mitchell     Date: 05 Feb 2005

The Standard is currently silent on the dependency status of enumerations and enumerators that are members of class templates. There are three questions that must be answered in this regard:

  1. Are enumeration members of class templates dependent types?

    It seems clear that nested enumerations must be dependent. For example:

        void f(int);
    
        template<typename T> struct S {
            enum E { e0 };
            void g() {
                f(e0);
            }
        };
    
        void f(S<int>::E);
    
        void x() {
            S<int> si;
            si->g();       // Should call f(S<int>::E)
        }
    
  2. Is sizeof applied to a nested enumeration a value-dependent expression (14.6.2.3 [temp.dep.constexpr])?

    There are three distinct cases that might have different answers to this question:

    14.6.2.3 [temp.dep.constexpr] paragraph 2 says that a sizeof expression is value-dependent if the type of the operand is type-dependent. Unless enumerations are given special treatment, all three of these examples will have value-dependent sizes. This could be surprising for the first case, at least, if not the second as well.

  3. Are nested enumerators value-dependent expressions?

    Again the question of dependent initializers comes into play. As an example, consider:

        template<short I> struct S {
            enum E { e0, e1 = I, e2 };
        };
    

    There seem to be three possible approaches as to whether the enumerators of E are value-dependent:

    1. The enumerators of a nested enumeration are all value-dependent, regardless of whether they have a value-dependent initializer or not. This is the current position of 14.6.2.3 [temp.dep.constexpr] paragraph 2, which says that an identifier is value-dependent if it is a name declared with a dependent type.

    2. The enumerators of a nested enumeration are all value-dependent if any of the enumeration's enumerators has a value-dependent initializer. In this approach, e0 would be value-dependent, even though it is clear that it has the value 0.

    3. An enumerator of a nested enumeration is value-dependent only if it has a value-dependent initializer (explict or implicit). This approach would make e1 and e2 value-dependent, but not e0.

    An example that bears on the third approach is the following:

        template<typename T> struct S {
            enum E { N = UINT_MAX, O = T::O };
            int a[N + 2];
        };
    

    With the normal treatment of enumerations, the type of a might be either int[UINT_MAX+2] or int[1], depending on whether the value of T::O was such that the underlying type of E is unsigned int or long.

    One possibility for addressing this problem under the third approach would be to treat a given enumerator as having the type of its initializer in such cases, rather than the enumeration type. This would be similar to the way enumerators are treated within the enumerator list, before the enumeration declaration is complete (7.2 [dcl.enum] paragraph 5). The argument against this is that it makes arithmetic using enumerators behave differently when the enumeration is a member of a class template and when it is not.

Notes from the April, 2005 meeting:

The CWG agreed on the following positions:

  1. Nested enumerations are dependent types.

  2. The result of the sizeof operator applied to a nested enumeration is value-dependent unless there are no dependent initializers in its definition; the first case above is not dependent, while the second and third are dependent.

  3. The approach described in 3.C above is correct. This is similar to the treatment of static const integral data members, which are dependent only if their initializer is dependent.

Notes from the October, 2005 meeting:

There was no consensus among the CWG regarding question #3 (which enumerators should be considered value-dependent). The argument in favor of 3.C is principally that the values of enumerators with non-dependent initializers are known at definition time, so there is no need to treat them as dependent.

One objection to 3.C is that, according to the consensus of the CWG, the enumeration type is dependent and thus even the known values of the enumeration would have a dependent type, which could affect the results when such enumerations are used in expressions. A possible response to this concern would be to treat non-dependent initializers as having the type of the initializer rather than the enumeration type, similar to the treatment of enumerators within the enumerator-list (7.2 [dcl.enum] paragraph 5). However, this approach would be inconsistent with the treatment of other enumeration types. It would also interfere with overload resolution (e.g., the call in the example under question #1 above would resolve to f(int) with this approach rather than f(S<int>::E)).

Those in favor of option 3.A also suggested that it would be simpler and require less drafting: if all the enumerators have the (dependent) type of the enumeration, 14.6.2.3 [temp.dep.constexpr] paragraph 2 already says that a name with a dependent type is value-dependent, so nothing further would need to be said. Option 3.C would require additional caveats to exempt some enumerators.

The proponents of 3.A also pointed out that there are many other cases where a known value with a dependent type is treated as dependent:

    static const T t = 0;
    ... A<t> ...

or

    template <int I> void f() {
        g(I-I);
    }

With regard to current practice, g++ and MSVC++ implement 3.A, while EDG implements 3.C.




590. Nested classes and the “current instantiation”

Section: 14.6.2.1  [temp.dep.type]     Status: open     Submitter: James Widman     Date: 23 August 2006

In 14.6.2.1 [temp.dep.type] paragraph 5 we have:

A name is a member of an unknown specialization if the name is a qualified-id in which the nested-name-specifier names a dependent type that is not the current instantiation.

So given:

    template<class T> struct A {
        struct B {
            struct C {
                A<T>::B::C f();
            };
        };
    };

it appears that the name A<T>::B::C should be taken as a member of an unknown specialization, because the WP refers to “the” current instantiation, implying that there can be at most one at any given time. At the declaration of f(), the current instantiation is C, so A<T>::B is not the current instantiation.

Would it be better to refer to “a known instantiation” instead of “the current instantiation?”

Mike Miller:

I agree that there is a problem here, but I don't think the “current instantiation” terminology needs to be replaced. By way of background, paragraph 1 makes it clear that A<T>::B “refers to” the current instantiation:

In the definition of a class template, a nested class of a class template, a member of a class template, or a member of a nested class of a class template, a name refers to the current instantiation if it is

A<T>::B satisfies bullet 3. Paragraph 4 says,

A name is a member of the current instantiation if it is

So clearly by paragraphs 1 and 4, A<T>::B::C is a member of the current instantiation. The problem is in the phrasing of paragraph 5, which incorrectly requires that the nested-name-specifier “be” the current instantiation rather than simply “referring to” the current instantiation, which would be the correct complement to paragraph 4. Perhaps paragraph 5 could simply be rephrased as, “...a dependent type and it is not a member of the current instantiation.”

(Paragraph 1 may require a bit more wordsmithing to make it truly recursive across multiple levels of nested classes; as it stands, it's not clear whether the name of a nested class of a nested class of a class template is covered or not.)




293. Syntax of explicit instantiation/specialization too permissive

Section: 14.7.2  [temp.explicit]     Status: open     Submitter: Mark Mitchell     Date: 27 Jun 2001

14.7.2 [temp.explicit] defines an explicit instantiation as

Syntactically, that allows things like:

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

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

    template namespace N { void f(); }

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

Suggested resolution:

I think we should say:

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

and then say that:

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

Here, I think we want:

with similar restrictions as above.

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




264. Unusable template constructors and conversion functions

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

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




271. Explicit instantiation and template argument deduction

Section: 14.8.2  [temp.deduct]     Status: open     Submitter: John Spicer     Date: 20 Feb 2001

Nicolai Josuttis sent me an example like the following:

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

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

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

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

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

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

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

Suggested resolution:

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



297. Which template does an explicit specialization specialize?

Section: 14.8.2  [temp.deduct]     Status: open     Submitter: Andrei Iltchenko     Date: 7 Jul 2001

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

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

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

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

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

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

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




503. Cv-qualified function types in template argument deduction

Section: 14.8.2.1  [temp.deduct.call]     Status: open     Submitter: Gabriel Dos Reis     Date: 22 Feb 2005

Consider the following program:

    template <typename T> int ref (T&)                { return 0; }
    template <typename T> int ref (const T&)          { return 1; }
    template <typename T> int ref (const volatile T&) { return 2; }
    template <typename T> int ref (volatile T&)       { return 4; }

    template <typename T> int ptr (T*)                { return 0; }
    template <typename T> int ptr (const T*)          { return 8; }
    template <typename T> int ptr (const volatile T*) { return 16; }
    template <typename T> int ptr (volatile T*)       { return 32; }

    void foo() {}

    int main()
    {
        return ref(foo) + ptr(&foo);
    }

The Standard appears to specify that the value returned from main is 2. The reason for this result is that references and pointers are handled differently in template argument deduction.

For the reference case, 14.8.2.1 [temp.deduct.call] paragraph 3 says that “If P is a reference type, the type referred to by P is used for type deduction.” Because of issue 295, all four of the types for the ref function parameters are the same, with no cv-qualification; overload resolution does not find a best match among the parameters and thus the most-specialized function is selected.

For the pointer type, argument deduction does not get as far as forming a cv-qualified function type; instead, argument deduction fails in the cv-qualified cases because of the cv-qualification mismatch, and only the cv-unqualified version of ptr survives as a viable function.

I think the choice of ignoring cv-qualifiers in the reference case but not the pointer case is very troublesome. The reason is that when one considers function objects as function parameters, it introduces a semantic difference whether the function parameter is declared a reference or a pointer. In all other contexts, it does not matter: a function name decays to a pointer and the resulting semantics are the same.




469. Const template specializations and reference arguments

Section: 14.8.2.5  [temp.deduct.type]     Status: open     Submitter: Matt Austern     Date: 19 Mar 2004

Consider the following:

	template <typename T> struct X {};  // #1
	template <typename T> struct X<const T>; //#2
	template struct X<int&>; //#3

Which specialization are we instantiating in #3? The "obvious" answer is #1, because "int&" doesn't have a top level cv-qualification. However, there's also an argument saying that we should actually be instantiating #2. The argument is: int& can be taken as a match for either one (top-level cv-qualifiers are ignored on references, so they're equally good), and given two equally good matches we must choose the more specialized one.

Is this a valid argument? If so, is this behavior intentional?

John Spicer: I don't see the rationale for any choice other than #1. While it is true that if you attempt to apply const to a reference type it just gets dropped, that is very different from saying that a reference type is acceptable where a const-qualified type is required.

If the type matched both templates, the const one would be more specialized, but "int&" does not match "const T".

Nathan Sidwell: thanks for bringing this one to the committee. However this is resolved, I'd like clarification on the followup questions in the gcc bug report regarding deduced and non-deduced contexts and function templates. Here're those questions for y'all,

template <typename T> void Foo (T *); // #1
template <typename T> void Foo (T const *); // #2
void Baz ();
Foo (Baz); // which?

template <typename T> T const *Foo (T *); // #1
void Baz ();
Foo (Baz); // well formed?

template <typename T> void Foo (T *, T const * = 0);
void Baz ();
Foo (Baz); // well formed?

BTW, I didn't go trying to break things, I implemented the cv-qualifier ignoring requirements and fell over this. I could find nothing in the standard saying 'don't do this ignoring during deduction'.




388. Catching base*& from a throw of derived*

Section: 15.3  [except.handle]     Status: open     Submitter: John Spicer     Date: 28 Oct 2002

I have a question about exception handling with respect to derived to base conversions of pointers caught by reference.

What should the result of this program be?

  struct S             {};
  struct SS : public S {};

  int main()
  {
  	SS ss;
  	int result = 0;
  	try
  	{
  		throw &ss; // throw object has type SS*
  		           // (pointer to derived class)
  	}
  	catch (S*& rs) // (reference to pointer to base class)
  	{
  		result = 1;
  	}
  	catch (...)
  	{
  		result = 2;
  	}
  	return result;
  }

The wording of 15.3 [except.handle] paragraph 3 would seem to say that the catch of S*& does not match and so the catch ... would be taken.

All of the compilers I tried (EDG, g++, Sun, and Microsoft) used the catch of S*& though.

What do we think is the desired behavior for such cases?

My initial reaction is that this is a bug in all of these compilers, but the fact that they all do the same thing gives me pause.

On a related front, if the handler changes the parameter using the reference, what is caught by a subsequent handler?

  extern "C" int printf(const char *, ...);
  struct S             {};
  struct SS : public S {};
  SS ss;

  int f()
  {
  	try
  	{
  		throw &ss;
  	}
  	catch (S*& rs) // (reference to pointer to base class)
  	{
  		rs = 0;
  		throw;
  	}
  	catch (...)
  	{
  	}
  	return 0;
  }

  int main()
  {
  	try { f(); }
  	catch (S*& rs) {
  		printf("rs=%p, &ss=%p\n", rs, &ss);
  	}
  }

EDG, g++, and Sun all catch the original (unmodified) value. Microsoft catches the modified value. In some sense the EDG/g++/Sun behavior makes sense because the later catch could catch the derived class instead of the base class, which would be difficult to do if you let the catch clause update the value to be used by a subsequent catch.

But on this non-pointer case, all of the compilers later catch the modified value:

  extern "C" int printf(const char *, ...);
  int f()
  {
  	try
  	{
  		throw 1;
  	}
  	catch (int& i)
  	{
  		i = 0;
  		throw;
  	}
  	catch (...)
  	{
  	}
  	return 0;
  }

  int main()
  {
  	try { f(); }
  	catch (int& i) {
  		printf("i=%p\n", i);
  	}
  }

To summarize:

  1. Should "base*const&" be able to catch a "derived*"? The current standard seems to say "no" but parallels to how calls work, and existing practice, suggest that the answer should be "yes".
  2. Should "base*&" be able to catch a "derived*". Again, the standard seems seems to say "no". Parallels to how calls work still suggest "no", but existing practice suggests "yes".
  3. If either of the above is "yes", what happens if you modify the pointer referred to by the reference. This requires a cast to remove const for case #2.
  4. On a related front, if you catch "derived*&" when a "derived*" is thrown, what happens if you modify the pointer referred to by the reference? EDG/g++/Sun still don't modify the underlying value that would be caught by a rethrow in this case. This case seems like it should be the same as the "int&" example above, but is not on the three compilers mentioned.



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

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

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

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

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

See also issues 25, 87, and 133.




595. Exception specifications in templates instantiated from class bodies

Section: 15.4  [except.spec]     Status: open     Submitter: Daveed Vandevoorde     Date: 7 September 2006

A type used in an exception specification must be complete (15.4 [except.spec] paragraph 2). The resolution of issue 437 stated that a class type appearing in an exception specification inside its own member-specification is considered to be complete. Should this also apply to exception specifications in class templates instantiated because of a reference inside the member-specification of a class? For example,

    template<class T> struct X {
        void f() throw(T) {}
    };

    struct S {
        X<S> xs;
    };



219. Cannot defend against destructors that throw exceptions

Section: 15.5.1  [except.terminate]     Status: open     Submitter: Herb Sutter     Date: 31 Mar 2000

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

    struct XY { X x; Y y; };

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

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

Suggested Resolution:

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

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

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

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

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

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

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

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

Herb Sutter (June, 2007): IMO, it's fine to suppress it. The user had the option of flushing the buffer and thus being notified of the problem and chose not to use it. If the caller didn't flush, then likely the caller isn't ready for an exception from the destructor, either. You could also put an assert into the destructor that would trigger if flush() had not been called, to force callers to use the interface that would report the error.

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

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

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

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

(See also issue 265.)




596. Replacing an exception object

Section: 15.5.2  [except.unexpected]     Status: open     Submitter: Alisdair Meredith     Date: 12 September 2006

When a function throws an exception that is not in its exception-specification, std::unexpected() is called. According to 15.5.2 [except.unexpected] paragraph 2,

If [std::unexpected()] throws or rethrows an exception that the exception-specification does not allow then the following happens: If the exception-specification does not include the class std::bad_exception (18.7.2.1 [bad.exception]) then the function std::terminate() is called, otherwise the thrown exception is replaced by an implementation-defined object of the type std::bad_exception, and the search for another handler will continue at the call of the function whose exception-specification was violated.

The “replaced by” wording is imprecise and undefined. For example, does this mean that the destructor is called for the existing exception object, or is it simply abandoned? Is the replacement in situ, so that a pointer to the existing exception object will now point to the std::bad_exception object?

Mike Miller: The call to std::unexpected() is not described as analogous to invoking a handler, but if it were, that would resolve this question; it is clearly specified what happens to the previous exception object when a new exception is thrown from a handler (15.1 [except.throw] paragraph 4).

This approach would also clarify other questions that have been raised regarding the requirements for stack unwinding. For example, 15.5.1 [except.terminate] paragraph 2 says that

In the situation where no matching handler is found, it is implementation-defined whether or not the stack is unwound before std::terminate() is called.

This requirement could be viewed as in conflict with the statement in 15.5.2 [except.unexpected] paragraph 1 that

If a function with an exception-specification throws an exception that is not listed in the exception-specification, the function std::unexpected() is called (18.7.2 [exception.unexpected]) immediately after completing the stack unwinding for the former function.

If it is implementation-defined whether stack unwinding occurs before calling std::terminate() and std::unexpected() is called only after doing stack unwinding, does that mean that it is implementation-defined whether std::unexpected() is called if there is ultimately no handler found?

Again, if invoking std::unexpected() were viewed as essentially invoking a handler, the answer to this would be clear, because unwinding occurs before invoking a handler.




268. Macro name suppression in rescanned replacement text

Section: 16.3.4  [cpp.rescan]     Status: open     Submitter: Bjarne Stroustrup     Date: 18 Jan 2001

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

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

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

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

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

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

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

The original intent of the J11 committee in this text was that the result should be 42, as demonstrated by the original pseudo-code description of the replacement algorithm provided by Dave Prosser, its author. The English description, however, omits some of the subtleties of the pseudo-code and thus arguably gives an incorrect answer for this case.

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

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

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

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

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

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

Notes (via Tom Plum) from April, 2004 WG14 Meeting:

Back in the 1980's it was understood by several WG14 people that there were tiny differences between the "non-replacement" verbiage and the attempts to produce pseudo-code. The committee's decision was that no realistic programs "in the wild" would venture into this area, and trying to reduce the uncertainties is not worth the risk of changing conformance status of implementations or programs.




578. Phase 1 replacement of characters with universal-character-names

Section: 2.1  [lex.phases]     Status: open     Submitter: Martin Vejnár     Date: 7 May 2006

According to 2.1 [lex.phases] paragraph 1, in translation phase 1,

Any source file character not in the basic source character set (2.2 [lex.charset]) is replaced by the universal-character-name that designates that character.

If a character that is not in the basic character set is preceded by a backslash character, for example

    "\á"

the result is equivalent to

    "\\u00e1"

that is, a backslash character followed by the spelling of the universal-character-name. This is different from the result in C99, which accepts characters from the extended source character set without replacing them with universal-character-names.




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

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

2.13.4 [lex.string] paragraph 5 reads

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

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

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

Notes from October 2003 meeting:

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

Reply from John Benito:

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

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

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

I hope this helps.




481. Scope of template parameters

Section: 3.3  [basic.scope]     Status: open     Submitter: Gabriel Dos Reis     Date: 01 Nov 2004

Sections 3.3.2 [basic.scope.local] to 3.3.6 [basic.scope.class] define and summarize different kinds of scopes in a C++ program. However it is missing a description for the scope of template parameters. I believe a section is needed there — even though some information may be found in clause 14.




191. Name lookup does not handle complex nesting

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

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

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

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

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



192. Name lookup in parameters

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

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

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

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

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



405. Unqualified function name lookup

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

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

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

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

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

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

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

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

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

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

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

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

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

Suggested resolution:

Change 3.4.1 [basic.lookup.unqual] paragraph 1 from

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

to

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

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

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

to

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



321. Associated classes and namespaces for argument-dependent lookup

Section: 3.4.2  [basic.lookup.argdep]     Status: open     Submitter: Andrei Iltchenko     Date: 12 Nov 2001

The last bullet of the second paragraph of section 3.4.2 [basic.lookup.argdep] says that:

If T is a template-id, its associated namespaces and classes are the namespace in which the template is defined; for member templates, the member template's class; the namespaces and classes associated with the types of the template arguments provided for template type parameters (excluding template template parameters); the namespaces in which any template template arguments are defined; and the classes in which any member templates used as template template arguments are defined.

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

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

namespace  A  {

   template<class T>
   struct  test  {

      template<class U>
      struct  mem_templ  {   };

   };

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

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

}

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

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

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

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

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

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

Suggested resolution:

Replace the last bullet of the second paragraph of section 3.4.2 [basic.lookup.argdep]

with

Replace the second bullet of the second paragraph of section 3.4.2 [basic.lookup.argdep]

with




562. qualified-ids in non-expression contexts

Section: 3.4.3.1  [class.qual]     Status: open     Submitter: Mike Miller     Date: 6 April 2006

Both 3.4.3.1 [class.qual] and 3.4.3.2 [namespace.qual] specify that some lookups are to be performed “in the context of the entire postfix-expression,” ignoring the fact that qualified-ids can appear outside of expressions.

It was suggested in document J16/05-0156 = WG21 N1896 that these uses be changed to “the context in which the qualified-id occurs,” but it isn't clear that this formulation adequately covers all the places a qualified-id can occur.




278. External linkage and nameless entities

Section: 3.5  [basic.link]     Status: open     Submitter: Daveed Vandevoorde     Date: 12 Apr 2000

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

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

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

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

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

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

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

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

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

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

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

had been written.

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

    class X { enum { d } y; };

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

(See also issues 132 and 216.)




279. Correspondence of "names for linkage purposes"

Section: 3.5  [basic.link]     Status: open     Submitter: Daveed Vandevoorde     Date: 4 Apr 2001

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

    typedef enum {} E;
    extern E *p;

can appear in multiple translation units.

How about the following combination?

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

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

Is this valid C++?

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

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

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

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




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

Section: 3.5  [basic.link]     Status: open     Submitter: Daveed Vandevoorde     Date: 26 Feb 2002

The following declarations are allowed within a translation unit:

  struct S;
  enum { S };

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

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

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

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

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

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

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

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

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

For example:

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

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




371. Interleaving of constructor calls

Section: 3.6.2  [basic.start.init]     Status: open     Submitter: Matt Austern     Date: 7 August 2002

Is a compiler allowed to interleave constructor calls when performing dynamic initialization of nonlocal objects? What I mean by interleaving is: beginning to execute a particular constructor, then going off and doing something else, then going back to the original constructor. I can't find anything explicit about this in clause 3.6.2 [basic.start.init].

I'll present a few different examples, some of which get a bit wild. But a lot of what this comes down to is exactly what the standard means when it talks about the order of initialization. If it says that some object x must be initialized before a particular event takes place, does that mean that x's constructor must be entered before that event, or does it mean that it must be exited before that event? If object x must be initialized before object y, does that mean that x's constructor must exit before y's constructor is entered?

(The answer to that question might just be common sense, but I couldn't find an answer in clause 3.6.2 [basic.start.init]. Actually, when I read 3.6.2 [basic.start.init] carefully, I find there are a lot of things I took for granted that aren't there.)

OK, so a few specific scenerios.

  1. We have a translation unit with nonlocal objects A and B, both of which require dynamic initialization. A comes before B. A must be initialized before B. May the compiler start to construct A, get partway through the constructor, then construct B, and then go back to finishing A?
  2. We have a translation unit with nonlocal object A and function f. Construction of A is deferred until after the first statement of main. A must be constructed before the first use of f. Is the compiler permitted to start constructing A, then execute f, then go back to constructing A?
  3. We have nonlocal objects A and B, in two different translation units. The order in which A and B are constructed is unspecified by the Standard. Is the compiler permitted to begin constructing A, then construct B, then finish A's constructor? Note the implications of a 'yes' answer. If A's and B's constructor both call some function f, then the call stack might look like this:
       <runtime gunk>
         <Enter A's constructor>
            <Enter f>
               <runtime gunk>
                  <Enter B's constructor>
                     <Enter f>
                     <Leave f>
                  <Leave B's constructor>
            <Leave f>
         <Leave A's constructor>
    
    The implication of a 'yes' answer for users is that any function called by a constructor, directly or indirectly, must be reentrant.
  4. This last example is to show why a 'no' answer to #3 might be a problem too. New scenerio: we've got one translation unit containing a nonlocal object A and a function f1, and another translation unit containing a nonlocal object B and a function f2. A's constructor calls f2. Initialization of A and B is deferred until after the first statement of main(). Someone in main calls f1. Question: is the compiler permitted to start constructing A, then go off and construct B at some point before f2 gets called, then go back and finish constructing A? In fact, is the compiler required to do that? We've got an unpleasant tension here between the bad implications of a 'yes' answer to #3, and the explicit requirement in 3.6.2 [basic.start.init] paragraph 3.

At this point, you might be thinking we could avoid all of this nonsense by removing compilers' freedom to defer initialization until after the beginning of main(). I'd resist that, for two reasons. First, it would be a huge change to make after the standard has been out. Second, that freedom is necessary if we want to have support for dynamic libraries. I realize we don't yet say anything about dynamic libraries, but I'd hate to make decisions that would make such support even harder.




640. Accessing destroyed local objects of static storage duration

Section: 3.6.3  [basic.start.term]     Status: open     Submitter: Howard Hinnant     Date: 30 July 2007

3.6.3 [basic.start.term] paragraph 2 says,

If a function contains a local object of static storage duration that has been destroyed and the function is called during the destruction of an object with static storage duration, the program has undefined behavior if the flow of control passes through the definition of the previously destroyed local object.

I would like to turn this behavior from undefined to well-defined behavior for the purpose of achieving a graceful shutdown, especially in a multi-threaded world.

Background: Alexandrescu describes the “phoenix singleton” in Modern C++ Design. This is a class used as a function local static, that will reconstruct itself, and reapply itself to the atexit chain, if the program attempts to use it after it is destructed in the atexit chain. It achieves this by setting a “destructed flag” in its own state in its destructor. If the object is later accessed (and a member function is called on it), the member function notes the state of the “destructed flag” and does the reconstruction dance. The phoenix singleton pattern was designed to address issues only in single-threaded code where accesses among static objects can have a non-scoped pattern. When we throw in multi-threading, and the possibility that threads can be running after main returns, the chances of accessing a destroyed static significantly increase.

The very least that I would like to see happen is to standardize what I believe is existing practice: When an object is destroyed in the atexit chain, the memory the object occupied is left in whatever state the destructor put it in. If this can only be reliably done for objects with standard layout, that would be an acceptable compromise. This would allow objects to set “I'm destructed” flags in their state and then do something well-defined if accessed, such as throw an exception.

A possible refinement of this idea is to have the compiler set up a 3-state flag around function-local statics instead of the current 2-state flag:

We have the first two states today. We might choose to add the third state, and if execution passes over a function-local static with “destroyed” state, an exception could be thrown. This would mean that we would not have to guarantee memory stability in destroyed objects of static duration.

This refinement would break phoenix singletons, and is not required for the ~mutex()/~condition() I've described and prototyped. But it might make it easier for Joe Coder to apply this kind of guarantee to his own types.




365. Storage duration and temporaries

Section: 3.7  [basic.stc]     Status: open     Submitter: James Kanze     Date: 24 July 2002

There are several problems with 3.7 [basic.stc]:

Steve Adamczyk: There may well be an issue here, but one should bear in mind the difference between storage duration and object lifetime. As far as I can see, there is no particular problem with temporaries having automatic or static storage duration, as appropriate. The point of 12.2 [class.temporary] is that they have an unusual object lifetime.

Notes from Ocrober 2002 meeting:

It might be desirable to shorten the storage duration of temporaries to allow reuse of them. The as-if rule allows some reuse, but such reuse requires analysis, including noting whether the addresses of such temporaries have been taken.




312. “use” of invalid pointer value not defined

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

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

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

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

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

(See also issue 623.)




523. Can a one-past-the-end pointer be invalidated by deleting an adjacent object?

Section: 3.7.3.2  [basic.stc.dynamic.deallocation]     Status: open     Submitter: comp.std.c++     Date: 8 July 2005

When an object is deleted, 3.7.3.2 [basic.stc.dynamic.deallocation] says that the deallocation “[renders] invalid all pointers referring to any part of the deallocated storage.” According to 3.9.2 [basic.compound] paragraph 3, a pointer whose address is one past the end of an array is considered to point to an unrelated object that happens to reside at that address. Does this need to be clarified to specify that the one-past-the-end pointer of an array is not invalidated by deleting the following object? (See also 5.3.5 [expr.delete] paragraph 4, which also mentions that the system deallocation function renders a pointer invalid.)




419. Can cast to virtual base class be done on partially-constructed object?

Section: 3.8  [basic.life]     Status: open     Submitter: Judy Ward     Date: 2 June 2003

Consider

  extern "C" int printf (const char *,...);

  struct Base { Base();};
  struct Derived: virtual public Base {
     Derived() {;}
  };

  Derived d;
  extern Derived& obj = d;

  int i;

  Base::Base() {
    if ((Base *) &obj) i = 4;
    printf ("i=%d\n", i);
  }

  int main() { return 0; }

12.7 [class.cdtor] paragraph 2 makes this valid, but 3.8 [basic.life] paragraph 5 implies that it isn't valid.

Steve Adamczyk: A second issue:

  extern "C" int printf(const char *,...);
  struct A                      { virtual ~A(); int x; };
  struct B : public virtual A   { };
  struct C : public B           { C(int); };
  struct D : public C           { D(); };
 
  int main()                    { D t; printf("passed\n");return 0; }
 
  A::~A()                       {} 
  C::C(int)                     {} 
  D::D() : C(this->x)           {}

Core issue 52 almost, but not quite, says that in evaluating "this->x" you do a cast to the virtual base class A, which would be an error according to 12.7 [class.cdtor] paragraph 2 because the base class B constructor hasn't started yet. 5.2.5 [expr.ref] should be clarified to say that the cast does need to get done.

James Kanze submitted the same issue via comp.std.c++ on 11 July 2003:

Richard Smith: Nonsense. You can use "this" perfectly happily in a constructor, just be careful that (a) you're not using any members that are not fully initialised, and (b) if you're calling virtual functions you know exactly what you're doing.

In practice, and I think in intent, you are right. However, the standard makes some pretty stringent restrictions in 3.8 [basic.life]. To start with, it says (in paragraph 1):

The lifetime of an object is a runtime property of the object. The lifetime of an object of type T begins when: The lifetime of an object of type T ends when:
(Emphasis added.) Then when we get down to paragraph 5, it says:

Before the lifetime of an object has started but after the storage which the object will occupy has been allocated [which sounds to me like it would include in the constructor, given the text above] or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that refers to the storage location where the object will be or was located may be used but only in limited ways. [...] If the object will be or was of a non-POD class type, the program has undefined behavior if:

[...]

I can't find any exceptions for the this pointer.

Note that calling a non-static function in the base class, or even constructing the base class in initializer list, involves an implicit conversion of this to a pointer to the base class. Thus undefined behavior. I'm sure that this wasn't the intent, but it would seem to be what this paragraph is saying.




496. Is a volatile-qualified type really a POD?

Section: 3.9  [basic.types]     Status: open     Submitter: John Maddock     Date: 30 Dec 2004

In 3.9 [basic.types] paragraph 10, the standard makes it quite clear that volatile qualified types are PODs:

Arithmetic types (3.9.1 [basic.fundamental]), enumeration types, pointer types, and pointer to member types (3.9.2 [basic.compound]), and cv-qualified versions of these types (3.9.3 [basic.type.qualifier]) are collectively called scalar types. Scalar types, POD-struct types, POD-union types (clause 9 [class]), arrays of such types and cv-qualified versions of these types (3.9.3 [basic.type.qualifier]) are collectively called POD types.

However in 3.9 [basic.types] paragraph 3, the standard makes it clear that PODs can be copied “as if” they were a collection of bytes by memcpy:

For any POD type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base-class subobject, if the value of obj1 is copied into obj2, using the std::memcpy library function, obj2 shall subsequently hold the same value as obj1.

The problem with this is that a volatile qualified type may need to be copied in a specific way (by copying using only atomic operations on multithreaded platforms, for example) in order to avoid the “memory tearing” that may occur with a byte-by-byte copy.

I realise that the standard says very little about volatile qualified types, and nothing at all (yet) about multithreaded platforms, but nonetheless this is a real issue, for the following reason:

The forthcoming TR1 will define a series of traits that provide information about the properties of a type, including whether a type is a POD and/or has trivial construct/copy/assign operations. Libraries can use this information to optimise their code as appropriate, for example an array of type T might be copied with a memcpy rather than an element-by-element copy if T is a POD. This was one of the main motivations behind the type traits chapter of the TR1. However it's not clear how volatile types (or POD's which have a volatile type as a member) should be handled in these cases.

Notes from the April, 2005 meeting:

It is not clear whether the volatile qualifier actually guarantees atomicity in this way. Also, the work on the memory model for multithreading being done by the Evolution Working Group seems at this point likely to specify additional semantics for volatile data, and that work would need to be considered before resolving this issue.




146. Floating-point zero

Section: 3.9.1  [basic.fundamental]     Status: open     Submitter: Andy Sawyer     Date: 23 Jul 1999

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

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




251. How many signed integer types are there?

Section: 3.9.1  [basic.fundamental]     Status: open     Submitter: Beman Dawes     Date: 18 Oct 2000

3.9.1 [basic.fundamental] paragraph 2 says that

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

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




483. Normative requirements on integral ranges

Section: 3.9.1  [basic.fundamental]     Status: open     Submitter: Steve Adamczyk     Date: 21 Oct 2004

There is no normative requirement on the ranges of the integral types, although the footnote in 3.9.1 [basic.fundamental] paragraph 2 indicates the intent (for int, at least) that they match the values given in the <climits> header. Should there be an explicit requirement of some sort?

(See also paper N1693.)




689. Maximum values of signed and unsigned integers

Section: 3.9.1  [basic.fundamental]     Status: open     Submitter: James Kanze     Date: 30 March, 2008

The relationship between the values representable by corresponding signed and unsigned integer types is not completely described, but 3.9 [basic.types] paragraph 4 says,

The value representation of an object is the set of bits that hold the value of type T.

and 3.9.1 [basic.fundamental] paragraph 3 says,

The range of nonnegative values of a signed integer type is a subrange of the corresponding unsigned integer type, and the value representation of each corresponding signed/unsigned type shall be the same.

I.e., the maximum value of each unsigned type must be larger than the maximum value of the corresponding signed type.

C90 doesn't have this restriction, and C99 explicitly says (6.2.6.2, paragraph 2),

For signed integer types, the bits of the object representation shall be divided into three groups: value bits, padding bits, and the sign bit. There need not be any padding bits; there shall be exactly one sign bit. Each bit that is a value bit shall have the same value as the same bit in the object representation of the corresponding unsigned type (if there are M value bits in the signed type and N in the unsigned type, then M <= N).

Unlike C++, the sign bit is not part of the value, and on an architecture that does not have native support of unsigned types, an implementation can emulate unsigned integers by simply ignoring what would be the sign bit in the signed type and be conforming.

The question is whether we intend to make a conforming implementation on such an architecture impossible. More generally, what range of architectures do we intend to support? And to what degree do we want to follow C99 in its evolution since C89?

(See paper J16/08-0141 = WG21 N2631.)




330. Qualification conversions and pointers to arrays of pointers

Section: 4.4  [conv.qual]     Status: open     Submitter: Roger Orr     Date: 2 Jan 2002

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

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

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

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

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

Suggested resolution:

Artem Livshits proposed a resolution :-

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

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

and

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

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

it would address the problem.

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




238. Precision and accuracy constraints on floating point

Section: 5  [expr]     Status: open     Submitter: Christophe de Dinechin     Date: 31 Jul 2000

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




438. Possible flaw in wording for multiple accesses to object between sequence points

Section: 5  [expr]     Status: open     Submitter: Jason Merrill     Date: 29 Oct 2003

Lisa Lippincott mentioned this case to me:

  A[0] = 0;
  A[A[0]] = 1;

This seems to use the old value of A[0] other than to calculate the new value, which is said to be undefined, but it also seems reasonable, since the old value is used in order to select the object to modify, so there's no ordering ambiguity.

Steve Adamczyk: the ordering rule referred to is in 5 [expr] paragraph 4.

Notes from the March 2004 meeting:

Clark Nelson mentions that the C committee may have done something on this.




695. Compile-time calculation errors in constexpr functions

Section: 5  [expr]     Status: open     Submitter: Mike Miller     Date: 9 June, 2008

Evaluating an expression like 1/0 is intended to produce undefined behavior during the execution of a program but be ill-formed at compile time. The wording for this is in 5 [expr] paragraph 4:

If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined, unless such an expression appears where an integral constant expression is required (5.19 [expr.const]), in which case the program is ill-formed.

The formulation “appears where an integral constant expression is required” is intended as an acceptable Standardese circumlocution for “evaluated at compile time,” a concept that is not directly defined by the Standard. It is not clear that this formulation adequately covers constexpr functions.




687. template keyword with unqualified-ids

Section: 5.1  [expr.prim]     Status: open     Submitter: Mihai Rusu     Date: 27 February, 2008

Consider the following:

    namespace N {
        struct A { };
        template<typename T>
        T func(const A&) { return T(); }
    }

    void f() {
        N::A a;
        func<int>(a);    // error
    }

Although argument-dependent lookup would allow N::func to be found in this call, the < is taken as a less-than operator rather than as the beginning of a template argument list. If the use of the template keyword for syntactic disambiguation were permitted for unqualified-ids, this problem could be solved by prefixing the function name with template, allowing the template-id to be parsed and argument-dependent lookup to be performed.




282. Namespace for extended_type_info

Section: 5.2.8  [expr.typeid]     Status: open     Submitter: Jens Maurer     Date: 01 May 2001

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




528. Why are incomplete class types not allowed with typeid?

Section: 5.2.8  [expr.typeid]     Status: open     Submitter: Dave Abrahams     Date: 18 May 2005

5.2.8 [expr.typeid] paragraph 4 says,

When typeid is applied to a type-id, the result refers to a std::type_info object representing the type of the type-id. If the type of the type-id is a reference type, the result of the typeid expression refers to a std::type_info object representing the referenced type. If the type of the type-id is a class type or a reference to a class type, the class shall be completely-defined.

I'm wondering whether this is not overly restrictive. I can't think of a reason to require that T be completely-defined in typeid(T) when T is a class type. In fact, several popular compilers enforce that restriction for typeid(T), but not for typeid(T&). Can anyone explain this?

Nathan Sidwell: I think this restriction is so that whenever the compiler has to emit a typeid object of a class type, it knows what the base classes are, and can therefore emit an array of pointers-to-base-class typeids. Such a tree is necessary to implement dynamic_cast and exception catching (in a commonly implemented and obvious manner). If the class could be incomplete, the compiler might have to emit a typeid for incomplete Foo in one object file and a typeid for complete Foo in another object file. The compilation system will then have to make sure that (a) those compare equal and (b) the complete Foo gets priority, if that is applicable.

Unfortunately, there is a problem with exceptions that means there still can be a need to emit typeids for incomplete class. Namely one can throw a pointer-to-pointer-to-incomplete. To implement the matching of pointer-to-derived being caught by pointer-to-base, it is necessary for the typeid of a pointer type to contain a pointer to the typeid of the pointed-to type. In order to do the qualification matching on a multi-level pointer type, one has a chain of pointer typeids that can terminate in the typeid of an incomplete type. You cannot simply NULL-terminate the chain, because one must distinguish between different incomplete types.

Dave Abrahams: So if implementations are still required to be able to do it, for all practical purposes, why aren't we letting the user have the benefits?

Notes from the April, 2006 meeting:

There was some concern expressed that this might be difficult under the IA64 ABI. It was also observed that while it is necessary to handle exceptions involving incomplete types, there is no requirement that the RTTI data structures be used for exception handling.




203. Type of address-of-member expression

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

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

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

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

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

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

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

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

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

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

Notes from 04/00 meeting:

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

Additional notes:

Another problematic example has been mentioned:

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

    class Derived : public Base {
    };

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

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

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

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

(See also issue 247.)




256. Overflow in size calculations

Section: 5.3.4  [expr.new]     Status: open     Submitter: James Kanze     Date: 15 Oct 2000

[Picked up by evolution group at October 2002 meeting.]

(See also issue 476.)

The size requested by an array allocation is computed by multiplying the number of elements requested by the size of each element and adding an implementation-specific amount for overhead. It is possible for this calculation to overflow. Is an implementation required to detect this situation and, for instance, throw std::bad_alloc?

On one hand, the maximum allocation size is one of the implementation limits specifically mentioned in Annex B [implimits], and, according to 1.4 [intro.compliance] paragraph 2, an implementation is only required to "accept and correctly execute" programs that do not violate its resource limits.

On the other hand, it is difficult or impossible for user code to detect such overflows in a portable fashion, especially given that the array allocation overhead is not fixed, and it would be a service to the user to handle this situation gracefully.

Rationale (04/01):

Each implementation is required to document the maximum size of an object (Annex B [implimits]). It is not difficult for a program to check array allocations to ensure that they are smaller than this quantity. Implementations can provide a mechanism in which users concerned with this problem can request extra checking before array allocations, just as some implementations provide checking for array index and pointer validity. However, it would not be appropriate to require this overhead for every array allocation in every program.

(See issue 624 for a request to reconsider this resolution.)

Note (March, 2008):

The Evolution Working Group has accepted the intent of this issue and referred it to CWG for action for C++0x (see paper J16/07-0033 = WG21 N2173).




267. Alignment requirement for new-expressions

Section: 5.3.4  [expr.new]     Status: open     Submitter: James Kuyper     Date: 4 Dec 2000

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

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

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

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

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

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

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

Suggested resolution: Change the wording to

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

The same change would also be needed in paragraph 9.




473. Block-scope declarations of allocator functions

Section: 5.3.4  [expr.new]     Status: open     Submitter: John Spicer     Date: 12 Jul 2004

Looking up operator new in a new-expression uses a different mechanism from ordinary lookup. According to 5.3.4 [expr.new] paragraph 9,

If the new-expression begins with a unary :: operator, the allocation function's name is looked up in the global scope. Otherwise, if the allocated type is a class type T or array thereof, the allocation function's name is looked up in the scope of T. If this lookup fails to find the name, or if the allocated type is not a class type, the allocation function's name is looked up in the global scope.

Note in particular that the scope in which the new-expression occurs is not considered. For example,

    void f() {
        void* operator new(std::size_t, void*);
        int* i = new int;    // okay?
    }

In this example, the implicit reference to operator new(std::size_t) finds the global declaration, even though the block-scope declaration of operator new with a different signature would hide it from an ordinary reference.

This seems strange; either the block-scope declaration should be ill-formed or it should be found by the lookup.

Notes from October 2004 meeting:

The CWG agreed that the block-scope declaration should not be found by the lookup in a new-expression. It would, however, be found by ordinary lookup if the allocation function were invoked explicitly.




476. Determining the buffer size for placement new

Section: 5.3.4  [expr.new]     Status: open     Submitter: Ben Hutchings     Date: 14 Sep 2004

(See also issue 256.)

An implementation may have an unspecified amount of array allocation overhead (5.3.4 [expr.new] paragraph 10), so that evaluation of a new-expression in which the new-type-id is T[n] involves a request for more than n * sizeof(T) bytes of storage through the relevant operator new[] function.

The placement operator new[] function does not and cannot check whether the requested size is less than or equal to the size of the provided region of memory (18.5.1.3 [new.delete.placement] paragraphs 5-6). A program using placement array new must calculate what the requested size will be in advance.

Therefore any program using placement array new must take into account the implementation's array allocation overhead, which cannot be obtained or calculated by any portable means.

Notes from the April, 2005 meeting:

While the CWG agreed that there is no portable means to accomplish this task in the current language, they felt that a paper is needed to analyze the numerous mechanisms that might address the problem and advance a specific proposal. There is no volunteer to write such a paper at this time.




196. Arguments to deallocation functions

Section: 5.3.5  [expr.delete]     Status: open     Submitter: Matt Austern     Date: 20 Jan 2000

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

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

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

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

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




265. Destructors, exceptions, and deallocation

Section: 5.3.5  [expr.delete]     Status: open     Submitter: Mike Miller     Date: 21 Nov 2000

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

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

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

Notes from 04/01 meeting:

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

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




242. Interpretation of old-style casts

Section: 5.4  [expr.cast]     Status: open     Submitter: Mike Miller     Date: 30 Aug 2000

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

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

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

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

An expression e can be explicitly converted to a type T using a static_cast of the form static_cast<T>(e) if the declaration "T t(e);" is well-formed, for some invented temporary variable t.

Given the declarations above, the hypothetical declaration

    A* t(p);

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

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




614. Results of integer / and %

Section: 5.6  [expr.mul]     Status: open     Submitter: Gabriel Dos Reis     Date: 15 January 2007

The current Standard leaves it implementation-defined whether integer division rounds the result toward 0 or toward negative infinity and thus whether the result of % may be negative. C99, apparently reflecting (nearly?) unanimous hardware practice, has adopted the rule that integer division rounds toward 0, thus requiring that the result of -1 % 5 be -1. Should the C++ Standard follow suit?

On a related note, does INT_MIN % -1 invoke undefined behavior? The % operator is defined in terms of the / operator, and INT_MIN / -1 overflows, which by 5 [expr] paragraph 5 causes undefined behavior; however, that is not the “result” of the % operation, so it's not clear. The wording of 5.6 [expr.mul] paragraph 4 appears to allow % to cause undefined behavior only when the second operand is 0.




583. Relational pointer comparisons against the null pointer constant

Section: 5.9  [expr.rel]     Status: open     Submitter: James Widman     Date: 24 May 2006

In C, this is ill-formed (cf C99 6.5.8):

    void f(char* s) {
        if (s < 0) { }
    }

...but in C++, it's not. Why? Who would ever need to write (s > 0) when they could just as well write (s != 0)?

This has been in the language since the ARM (and possibly earlier); apparently it's because the pointer conversions (4.10 [conv.ptr]) need to be performed on both operands whenever one of the operands is of pointer type. So it looks like the "null-ptr-to-real-pointer-type" conversion is hitching a ride with the other pointer conversions.




631. Jumping into a “then” clause

Section: 6.4.1  [stmt.if]     Status: open     Submitter: James Kanze     Date: 24 April 2007

6.4.1 [stmt.if] is silent about whether the else clause of an if statement is executed if the condition is not evaluated. (This could occur via a goto or a longjmp.) C99 covers the goto case with the following provision:

If the first substatement is reached via a label, the second substatement is not executed.

It should probably also be stated that the condition is not evaluated when the “then” clause is entered directly.




157. Omitted typedef declarator

Section: 7  [dcl.dcl]     Status: open     Submitter: Daveed Vandevoorde     Date: 19 Aug 1999

7 [dcl.dcl] paragraph 3 reads,

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

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




498. Storage class specifiers in definitions of class members

Section: 7.1.1  [dcl.stc]     Status: open     Submitter: Matt Austern     Date: 13 Jan 2005

Suppose we've got this class definition:

    struct X {
       void f();
       static int n;
    };

I think I can deduce from the existing standard that the following member definitions are ill-formed:

    static void X::f() { }
    static int X::n;

To come to that conclusion, however, I have to put together several things in different parts of the standard. I would have expected to find an explicit statement of this somewhere; in particular, I would have expected to find it in 7.1.1 [dcl.stc]. I don't see it there, or anywhere.

Gabriel Dos Reis: Or in 3.5 [basic.link] which is about linkage. I would have expected that paragraph to say that that members of class types have external linkage when the enclosing class has an external linkage. Otherwise 3.5 [basic.link] paragraph 8:

Names not covered by these rules have no linkage.

might imply that such members do not have linkage.

Notes from the April, 2005 meeting:

The question about the linkage of class members is already covered by 3.5 [basic.link] paragraph 5.




609. What is a “top-level” cv-qualifier?

Section: 7.1.6.1  [dcl.type.cv]     Status: open     Submitter: Dawn Perchik     Date: 5 November 2006

The phrase “top-level cv-qualifier” is used numerous times in the Standard, but it is not defined. The phrase could be misunderstood to indicate that the const in something like const T& is at the “top level,” because where it appears is the highest level at which it is permitted: T& const is ill-formed.




144. Position of friend specifier

Section: 7.1.6.3  [dcl.type.elab]     Status: open     Submitter: Daveed Vandevoorde     Date: 22 Jul 1999

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

    class C friend;
be well-formed?


673. Injection of names from elaborated-type-specifiers in friend declarations

Section: 7.3.1.2  [namespace.memdef]     Status: open     Submitter: Mike Miller     Date: 6 February, 2008

7.3.1.2 [namespace.memdef] paragraph 3 is intended to prevent injection of names from friend declarations into the containing namespace scope:

If a friend declaration in a non-local class first declares a class or function the friend class or function is a member of the innermost enclosing namespace. The name of the friend is not found by unqualified lookup (3.4.1 [basic.lookup.unqual]) or by qualified lookup (3.4.3 [basic.lookup.qual]) until a matching declaration is provided in that namespace scope (either before or after the class definition granting friendship).

However, this does not address names declared by elaborated-type-specifiers that are part of the friend declaration. Are these names intended to be visibly injected? For example, is the following well-formed?

    class A {
        friend class B* f();
    };
    B* bp;    // Is B visible here?

Implementations differ in their treatment of this example: EDG and MSVC++ 8.0 accept it, while g++ 4.1.1 rejects it.




36. using-declarations in multiple-declaration contexts

Section: 7.3.3  [namespace.udecl]     Status: open     Submitter: Andrew Koenig     Date: 20 Aug 1998

Section 7.3.3 [namespace.udecl] paragraph 8 says:

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

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

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

Notes from 04/00 meeting:

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

As a result of the lack of agreement, the issue was returned to "open" status.

See also issues 56, 85, and 138..

Additional notes (January, 2005):

Some related issues have been raised concerning the following example (modified from a C++ validation suite test):

    struct A
    {
        int i;
        static int j;
    };

    struct B : A { };
    struct C : A { };

    struct D : virtual B, virtual C
    {
        using B::i;
        using C::i;
        using B::j;
        using C::j;
    };

Currently, it appears that the using-declarations of i are ill-formed, on the basis of 7.3.3 [namespace.udecl] paragraph 10:

Since a using-declaration is a declaration, the restrictions on declarations of the same name in the same declarative region (3.3 [basic.scope]) also apply to using-declarations.

Because the using-declarations of i refer to different objects, declaring them in the same scope is not permitted under 3.3 [basic.scope]. It might, however, be preferable to treat this case as many other ambiguities are: allow the declaration but make the program ill-formed if a name reference resolves to the ambiguous declarations.

The status of the using-declarations of j, however, is less clear. They both declare the same entity and thus do not violate the rules of 3.3 [basic.scope]. This might (or might not) violate the restrictions of 9.2 [class.mem] paragraph 1:

Except when used to declare friends (11.4 [class.friend]) or to introduce the name of a member of a base class into a derived class (7.3.3 [namespace.udecl], 11.3 [class.access.dcl]), member-declarations declare members of the class, and each such member-declaration shall declare at least one member name of the class. A member shall not be declared twice in the member-specification, except that a nested class or member class template can be declared and then later defined.

Do the using-declarations of j repeatedly declare the same member? Or is the preceding sentence an indication that a using-declaration is not a declaration of a member?




386. Friend declaration of name brought in by using-declaration

Section: 7.3.3  [namespace.udecl]     Status: open     Submitter: Herb Sutter     Date: 8 Oct 2002

The following came up recently on comp.lang.c++.moderated (edited for brevity):

  namespace N1 {
    template<typename T> void f( T* x ) {
      // ... other stuff ...
      delete x;
    }
  }

  namespace N2 {
    using N1::f;

    template<> void f<int>( int* ); // A: ill-formed

    class Test {
      ~Test() { }
      friend void f<>( Test* x );   // B: ill-formed?
    };
  }

I strongly suspect, but don't have standardese to prove, that the friend declaration in line B is ill-formed. Can someone show me the text that allows or disallows line B?

Here's my reasoning: Writing "using" to pull the name into namespace N2 merely allows code in N2 to use the name in a call without qualification (per 7.3.3 [namespace.udecl]). But just as declaring a specialization must be done in the namespace where the template really lives (hence line A is ill-formed), I suspect that declaring a specialization as a friend must likewise be done using the original namespace name, not obliquely through a "using". I see nothing in 7.3.3 [namespace.udecl] that would permit this use. Is there?

Andrey Tarasevich: 14.5.4 [temp.friend] paragraph 2 seems to get pretty close: "A friend declaration that is not a template declaration and in which the name of the friend is an unqualified 'template-id' shall refer to a specialization of a function template declared in the nearest enclosing namespace scope".

Herb Sutter: OK, thanks. Then the question in this is the word "declared" -- in particular, we already know we cannot declare a specialization of a template in any other namespace but the original one.

John Spicer: This seems like a simple question, but it isn't.

First of all, I don't think the standard comments on this usage one way or the other.

A similar example using a namespace qualified name is ill-formed based on 8.3 [dcl.meaning] paragraph 1:

  namespace N1 {
        void f();
  }

  namespace N2 {
        using N1::f;
        class A {
                friend void N2::f();
        };
  }

Core issue 138 deals with this example:

  void foo();
  namespace A{
    using ::foo;
    class X{
      friend void foo();
    };
  }

The proposed resolution (not yet approved) for issue 138 is that the friend declares a new foo that conflicts with the using-declaration and results in an error.

Your example is different than this though because the presence of the explicit argument list means that this is not declaring a new f but is instead using a previously declared f.

One reservation I have about allowing the example is the desire to have consistent rules for all of the "declaration like" uses of template functions. Issue 275 (in DR status) addresses the issue of unqualified names in explicit instantiation and explicit specialization declarations. It requires that such declarations refer to templates from the namespace containing the explicit instantiation or explicit specialization. I believe this rule is necessary for those directives but is not really required for friend declarations -- but there is the consistency issue.

Notes from April 2003 meeting:

This is related to issue 138. John Spicer is supposed to update his paper on this topic. This is a new case not covered in that paper. We agreed that the B line should be allowed.




565. Conflict rules for using-declarations naming function templates

Section: 7.3.3  [namespace.udecl]     Status: open     Submitter: Paolo Carlini     Date: 9 March 2006

The Standard does not appear to specify what happens for code like the following:

    namespace one {
      template<typename T> void fun(T);
    }

    using one::fun;

    template<typename T> void fun(T);

7.3.3 [namespace.udecl] paragraph 13 does not appear to apply because it deals only with functions, not function templates:

If a function declaration in namespace scope or block scope has the same name and the same parameter types as a function introduced by a using-declaration, and the declarations do not declare the same function, the program is ill-formed.

John Spicer: For function templates I believe the rule should be that if they have the same function type (parameter types and return type) and have identical template parameter lists, the program is ill-formed.




563. Linkage specification for objects

Section: 7.5  [dcl.link]     Status: open     Submitter: Daveed Vandevoorde     Date: 8 March 2006

It is not clear whether some of the wording in 7.5 [dcl.link] that applies only to function types and names ought also to apply to object names. In particular, paragraph 3 says,

Every implementation shall provide for linkage to functions written in the C programming language, "C", and linkage to C++ functions, "C++".

Nothing is said about variable names, apparently meaning that implementations need not provide C (or even C++!) linkage for variable names. Also, paragraph 5 says,

Except for functions with C++ linkage, a function declaration without a linkage specification shall not precede the first linkage specification for that function. A function can be declared without a linkage specification after an explicit linkage specification has been seen; the linkage explicitly specified in the earlier declaration is not affected by such a function declaration.

There doesn't seem to be a good reason for these provisions not to apply to variable names, as well.




504. Should use of a reference in its own initializer require a diagnostic?

Section: 8.3.2  [dcl.ref]     Status: open     Submitter: Bjarne Stroustrup     Date: 14 Apr 2005

Split off from issue 453.

It is in general not possible to determine at compile time whether a reference is used before it is initialized. Nevertheless, there is some sentiment to require a diagnostic in the obvious cases that can be detected at compile time, such as the name of a reference appearing in its own initializer. The resolution of issue 453 originally made such uses ill-formed, but the CWG decided that this question should be a separate issue.

Rationale (October, 2005):

The CWG felt that this error was not likely to arise very often in practice. Implementations can warn about such constructs, and the resolution for issue 453 makes executing such code undefined behavior; that seemed to address the situation adequately.

Note (February, 2006):

Recent discussions have suggested that undefined behavior be reduced. One possibility (broadening the scope of this issue to include object declarations as well as references) was to require a diagnostic if the initializer uses the value, but not just the address, of the object or reference being declared:

    int i = i;        // Ill-formed, diagnostic required
    void* p = &p;     // Okay



332. cv-qualified void parameter types

Section: 8.3.5  [dcl.fct]     Status: open     Submitter: Michiel Salters     Date: 9 Jan 2002

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

Suggested resolution:

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

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



550. Pointer to array of unknown bound in parameter declarations

Section: 8.3.5  [dcl.fct]     Status: open     Submitter: Daveed Vandevoorde     Date: 22 November 2005

The current wording of 8.3.5 [dcl.fct] paragraph 6 encompasses more than it should:

If the type of a parameter includes a type of the form “pointer to array of unknown bound of T” or “reference to array of unknown bound of T,” the program is ill-formed. [Footnote: This excludes parameters of type “ptr-arr-seq T2” where T2 is “pointer to array of unknown bound of T” and where ptr-arr-seq means any sequence of “pointer to” and “array of” derived declarator types. This exclusion applies to the parameters of the function, and if a parameter is a pointer to function or pointer to member function then to its parameters also, etc. —end footnote]

The normative wording (contrary to the intention expressed in the footnote) excludes declarations like

    template<class T> struct S {};
    void f(S<int (*)[]>);

and

    struct S {};
    void f(int(*S::*)[]);

but not

    struct S {};
    void f(int(S::*)[]);



577. void in an empty parameter list

Section: 8.3.5  [dcl.fct]     Status: open     Submitter: Ben Hutchings     Date: 22 April 2006

8.3.5 [dcl.fct] paragraph 2 says,

The parameter list (void) is equivalent to the empty parameter list.

This special case is intended for C compatibility, but C99 describes it differently (6.7.5.3 paragraph 10):

The special case of an unnamed parameter of type void as the only item in the list specifies that the function has no parameters.

The C99 formulation allows typedefs for void, while C++ (and C90) accept only the keyword itself in this role. Should the C99 approach be adopted?

Notes from the October, 2006 meeting:

The CWG did not take a formal position on this issue; however, there was some concern expressed over the treatment of function templates and member functions of class templates if the C++ rule were changed: for a template parameter T, would a function taking a single parameter of type T become a no-parameter function if it were instantiated with T = void?




325. When are default arguments parsed?

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

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

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

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

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

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

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

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

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

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

Suggested resolution:

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

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

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

Notes from the October, 2005 meeting:

The CWG agreed that the first example (A) is currently well-formed and that it is not unreasonable to expect implementations to handle it by processing default arguments recursively.




361. Forward reference to default argument

Section: 8.3.6  [dcl.fct.default]     Status: open     Submitter: Steve Clamage     Date: 17 June 2002

Is this program well-formed?

  struct S {
    static int f2(int = f1()); // OK?
    static int f1(int = 2);
  };
  int main()
  {
    return S::f2();
  }

A class member function can in general refer to class members that are declared lexically later. But what about referring to default arguments of member functions that haven't yet been declared?

It seems to me that if f2 can refer to f1, it can also refer to the default argument of f1, but at least one compiler disagrees.




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

Section: 8.5  [dcl.init]     Status: open     Submitter: Mike Miller     Date: 11 Jul 2000

Paragraph 9 of 8.5 [dcl.init] says:

If no initializer is specified for an object, and the object is of (possibly cv-qualified) non-POD class type (or array thereof), the object shall be default-initialized; if the object is of const-qualified type, the underlying class type shall have a user-declared default constructor. Otherwise, if no initializer is specified for an object, the object and its subobjects, if any, have an indeterminate initial value; if the object or any of its subobjects are of const-qualified type, the program is ill-formed.

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

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

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

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

(See also issue 78.)




509. Dead code in the specification of default initialization

Section: 8.5  [dcl.init]     Status: open     Submitter: Mike Miller     Date: 18 Mar 2005

The definition of default initialization (8.5 [dcl.init] paragraph 5) is:

However, default initialization is invoked only for non-POD class types and arrays thereof (5.3.4 [expr.new] paragraph 15 for new-expressions, 8.5 [dcl.init] paragraph 10 for top-level objects, and 12.6.2 [class.base.init] paragraph 4 for member and base class subobjects — but see issue 510). Consequently, all cases that invoke default initialization are handled by the first two bullets; the third bullet can never be reached. Its presence is misleading, so it should be removed.




611. Zero-initializing references

Section: 8.5  [dcl.init]     Status: open     Submitter: Alisdair Meredith     Date: 29 December 2006

According to 8.5 [dcl.init] paragraph 5,

To zero-initialize an object of type T means:

However, a reference is not an object, so this makes no sense.




670. Copy initialization via derived-to-base conversion in the second step

Section: 8.5  [dcl.init]     Status: open     Submitter: Jason Merrill     Date: 20 December 2007

In this example:

    struct A {};

    struct B: A {
       B(int);
       B(B&);
       B(A);
    };

    void foo(B);

    void bar() {
       foo(0);
    }

we are copy-initializing a B from 0. So by 13.3.1.4 [over.match.copy] we consider all the converting constructors of B, and choose B(int) to create a B. Then, by 8.5 [dcl.init] paragraph 15, we direct-initialize the parameter from that temporary B. By 13.3.1.3 [over.match.ctor] we consider all constructors. The copy constructor cannot be called with a temporary, but B(A) is callable.

As far as I can tell, the Standard says that this example is well-formed, and calls B(A). EDG and G++ have rejected this example with a message about the copy constructor not being callable, but I have been unsuccessful in finding anything in the Standard that says that we only consider the copy constructor in the second step of copy-initialization. I wouldn't mind such a rule, but it doesn't seem to be there. And implementing issue 391 causes G++ to start accepting the example.

This question came up before in a GCC bug report; in the discussion of that bug Nathan Sidwell said that some EDG folks explained to him why the testcase is ill-formed, but unfortunately didn't provide that explanation in the bug report.

I think the resolution of issue 391 makes this example well-formed; it was previously ill-formed because in order to bind the temporary B(0) to the argument of A(const A&) we needed to make another temporary B, and that's what made the example ill-formed. If we want this example to stay ill-formed, we need to change something else.

Steve Adamczyk:

I tracked down my response to Nathan at the time, and it related to my paper N1232 (on the auto_ptr problem). The change that came out of that paper is in 13.3.3.1 [over.best.ics] paragraph 4:

However, when considering the argument of a user-defined conversion function that is a candidate by 13.3.1.3 [over.match.ctor] when invoked for the copying of the temporary in the second step of a class copy-initialization, or by 13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv], or 13.3.1.6 [over.match.ref] in all cases, only standard conversion sequences and ellipsis conversion sequences are allowed.

This is intended to prevent use of more than one implicit user- defined conversion in an initialization.

I told Nathan B(A) can't be called because its argument would require yet another user-defined conversion, but I was wrong. I saw the conversion from B to A and immediately thought “user-defined,” but in fact because B is a derived class of A the conversion according to 13.3.3.1 [over.best.ics] paragraph 6 is a derived-to-base Conversion (even though it will be implemented by calling a copy constructor).

So I agree with you: with the analysis above and the change for issue 391 this example is well-formed. We should discuss whether we want to make a change to keep it ill-formed.




694. Zero- and value-initialization of union objects

Section: 8.5  [dcl.init]     Status: open     Submitter: Clark Nelson     Date: 14 May, 2008

The C committee is considering changing the definition of zero-initialization of unions to guarantee that the bytes of the entire union are set to zero before assigning 0, converted to the appropriate type, to the first member. The argument (summarized here) is for backward compatibility. The C++ Committee may want to consider the same change.




656. Direct binding to the result of a conversion operator

Section: 8.5.3  [dcl.init.ref]     Status: open     Submitter: Jason Merrill     Date: 23 October 2007

Consider the following example:

    struct A { };
    struct B : public A { };
    struct X {
       operator B();
    };
    X x;

    int main() {
       const A& r = x;
       return 0;
    }

It seems like the resolution of issue 391 doesn't actually cover this; X is not reference-compatible with A, so we go past the modified bullet (8.5.3 [dcl.init.ref] paragraph 5, bullet 2, sub-bullet 1), which reads:

If the initializer expression is an rvalue, with T2 a class type, and “cv1 T1” is reference-compatible with “cv2 T2,” the reference is bound to the object represented by the rvalue (see 3.10 [basic.lval]) or to a sub-object within that object.

and hit

Otherwise, a temporary of type “cv1 T1” is created and initialized from the initializer expression using the rules for a non-reference copy initialization (8.5 [dcl.init]). The reference is then bound to the temporary.

which seems to require that we create an A temporary copied from the return value of X::operator B() rather than bind directly to the A subobject. I think that the resolution of issue 391 should cover this situation as well, and the EDG compiler seems to agree with me.




511. POD-structs with template assignment operators

Section: 9  [class]     Status: open     Submitter: Alisdair Meredith     Date: 19 Mar 2005

A POD-struct is not permitted to have a user-declared copy assignment operator (9 [class] paragraph 4). However, a template assignment operator is not considered a copy assignment operator, even though its specializations can be selected by overload resolution for performing copy operations (12.8 [class.copy] paragraph 9 and especially footnote 114). Consequently, X in the following code is a POD, notwithstanding the fact that copy assignment (for a non-const operand) is a member function call rather than a bitwise copy:

    struct X {
      template<typename T> const X& operator=(T&);
    };
    void f() {
      X x1, x2;
      x1 = x2;  // calls X::operator=<X>(X&)
    }

Is this intentional?




57. Empty unions

Section: 9.5  [class.union]     Status: open     Submitter: Steve Adamczyk     Date: 13 Oct 1998

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

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

What about:

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

(See also the resolution for issue 151.)

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




675. Signedness of bit-field with typedef or template parameter type

Section: 9.6  [class.bit]     Status: open     Submitter: Richard Corden     Date: 11 February, 2008

Is the signedness of x in the following example implementation-defined?

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

    template struct A<long>;

A similar example could be created with a typedef.

Lawrence Crowl: According to 9.6 [class.bit] paragraph 3,

It is implementation-defined whether a plain (neither explicitly signed nor unsigned) char, short, int or long bit-field is signed or unsigned.

This clause is conspicuously silent on typedefs and template parameters.

Clark Nelson: At least in C, the intention is that the presence or absence of this redundant keyword is supposed to be remembered through typedef declarations. I don't remember discussing it in C++, but I would certainly hope that we don't want to do something different. And presumably, we would want template type parameters to work the same way.

So going back to the original example, in an instantiation of A<long>, the signedness of the bit-field is implementation-defined, but in an instantiation of A<signed long>, the bit-field is definitely signed.

Peter Dimov: How can this work? Aren't A<long> and A<signed long> the same type?




696. Use of block-scope constants in local classes

Section: 9.8  [class.local]     Status: open     Submitter: Steve Adamczyk     Date: 29 May, 2008

According to 9.8 [class.local] paragraph 1,

Declarations in a local class can use only type names, static variables, extern variables and functions, and enumerators from the enclosing scope.

This would presumably make both of the members of S2 below ill-formed:

    void test () {
      const int local_const = 7;
      struct S2 {
        int member:local_const;
        void f() { int j = local_const; }
      };
    }

Should there be an exception to this rule for constant values? Current implementations seem to accept the reference to local_const in the bit-field declaration but not in the member function definition. Should they be the same or different?




223. The meaning of deprecation

Section: D  [depr]     Status: open     Submitter: Mike Miller     Date: 19 Apr 2000

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

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

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

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




248. Identifier characters

Section: E  [extendid]     Status: open     Submitter: John Spicer     Date: 6 Oct 2000

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

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

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

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

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

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

Notes from 10/00 meeting:

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

Notes from October, 2005 meeting:

The working group expressed interest in the kind of approach taken by XML 1.1, in which the definition of an identifier character is done by excluding large ranges of the Unicode character set and accepting any character outside those ranges, rather than by affirmatively designating each identifier character in each language. As noted above, consideration of this issue was previously deferred pending other related standardization efforts. Clark Nelson will investigate whether these have reached a point at which progress on this issue in C++ is now possible.

Additional note (May, 2008):

Issue 663 also deals with this appendix, and the proposed resolution there is to update the table to reflect the newest available technical report, ISO/IEC TR 10176:2003. That resolution might be seen as sufficient for this issue, as well. However, that approach does not address several of the concerns mentioned in the discussion above: coordination with WG14, the extensibility of the list of identifiers, the alternative approach used in the XML specification, etc.