Document number:   J16/01-0035 = WG21 N1321
Date:  12 September, 2001
Project:  Programming Language C++
Reference:  ISO/IEC IS 14882:1998(E)
Reply to:  J. Stephen Adamczyk
 jsa@edg.com


C++ Standard Core Language Active Issues, Revision 19

Committee Version


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

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

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

Issues represent potential defects in the ISO/IEC IS 14882:1998(E) document; they are not necessarily formal ISO Defect Reports (DRs). While some issues will eventually be elevated to DR status, others will be disposed of in other ways. (See Issue Status below.)

This set of documents exists in two slightly different versions. The Committee Version (this version) is the master version and should be made available only to committee members. The Public Version is extracted from the Committee Version by removal of sensitive and/or unnecessary information, such as drafting assignments, reflector message numbers, and the like.

Committee members with the HTML version of the IS may wish to place the issues list documents in the same directory; references to the IS in the issues can then be explored by hyperlink.

The most current public version of this document can be found at http://www.dkuug.dk/jtc1/sc22/wg21. Requests for further information about these documents should include the document number, reference ISO/IEC 14882:1998(E), and be submitted to the Information Technology Information Council (ITI), 1250 Eye Street NW, Washington, DC 20005, USA.

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


Revision History

Issue status

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

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

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

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

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

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

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

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

Extension: The working group has reached consensus that the issue is not a defect in the Standard but is a request for an extension to the language. Under ISO rules, extensions cannot be considered for at least five years from the approval of the Standard, at which time the Standard will be open for review. The working group expresses no opinion on the merits of an issue with this status; however, the issue will be maintained on the list for possible future consideration when extension proposals will be in order.


Issues with "Ready" Status


139. Error in friend lookup example

Section: 3.4.1  basic.lookup.unqual     Status: ready     Submitter: Mike Miller     Date: 14 Jul 1999     Priority: 2     Drafting: Miller

The example in 3.4.1  basic.lookup.unqual paragraph 3 is incorrect:

    typedef int f;
    struct A {
        friend void f(A &);
        operator int();
        void g(A a) {
            f(a);
        }
    };
Regardless of the resolution of other issues concerning the lookup of names in friend declarations, this example is ill-formed (the function and the typedef cannot exist in the same scope).

One possible repair of the example would be to make f a class with a constructor taking either A or int as its parameter.

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

Proposed resolution (04/01):

  1. Change the example in 3.4.1  basic.lookup.unqual paragraph 3 to read:

        typedef int f;
        namespace N {
            struct A {
                friend int f(A &);
                operator int();
                void g(A a) {
                    int i = f(a);
                          // f is the typedef, not the friend function:
                          // equivalent to int(a)
                }
            };
        }
    
  2. Delete the sentence immediately following the example:

    The expression f(a) is a cast-expression equivalent to int(a).



216. Linkage of nameless class-scope enumeration types

Section: 3.5  basic.link     Status: ready     Submitter: Daveed Vandevoorde     Date: 13 Mar 2000     Drafting: Vandevoorde

From reflector message 8600.

3.5  basic.link paragraph 4 says (among other things):
A name having namespace scope has external linkage if it is the name of
That prohibits for example:
    typedef enum { e1 } *PE;
    void f(PE) {}  // Cannot declare a function (with linkage) using a 
		   // type with no linkage.

However, the same prohibition was not made for class scope types. Indeed, 3.5  basic.link paragraph 5 says:

In addition, a member function, static data member, class or enumeration of class scope has external linkage if the name of the class has external linkage.

That allows for:

    struct S {
       typedef enum { e1 } *MPE;
       void mf(MPE) {}
    };

My guess is that this is an unintentional consequence of 3.5  basic.link paragraph 5, but I would like confirmation on that.

Proposed resolution:

Change text in 3.5  basic.link paragraph 5 from:

In addition, a member function, static data member, class or enumeration of class scope has external linkage if the name of the class has external linkage.
to:
In addition, a member function, a static data member, a named class or enumeration of class scope, or an unnamed class or enumeration defined in a class-scope typedef declaration such that the class or enumeration has the typedef name for linkage purposes (7.1.3  dcl.typedef), has external linkage if the name of the class has external linkage.



122. template-ids as unqualified-ids

Section: 5.1  expr.prim     Status: ready     Submitter: Mike Miller     Date: 3 June 1999     Priority: 2     Drafting: Miller

From reflector message core-8091.

5.1  expr.prim paragraph 11 reads,

A template-id shall be used as an unqualified-id only as specified in 14.7.2  temp.explicit , 14.7  temp.spec , and 14.5.4  temp.class.spec .

What uses of template-ids as unqualified-ids is this supposed to prevent? And is the list of referenced sections correct/complete? For instance, what about 14.8.1  temp.arg.explicit, "Explicit template argument specification?" Does its absence from the list in 5.1  expr.prim paragraph 11 mean that "f<int>()" is ill-formed?

This is even more confusing when you recall that unqualified-ids are contained in qualified-ids:

qualified-id: ::opt nested-name-specifier templateopt unqualified-id

Is the wording intending to say "used as an unqualified-id that is not part of a qualified-id?" Or something else?

Proposed resolution (10/00):

Remove the referenced sentence altogether.




113. Visibility of called function

Section: 5.2.2  expr.call     Status: ready     Submitter: Christophe de Dinechin     Date: 5 May 1999     Drafting: Vandevoorde

From reflector messages 8046-8047.

Christophe de Dinechin: In 5.2.2  expr.call , paragraph 2 reads:

If no declaration of the called function is visible from the scope of the call the program is ill-formed.
I think nothing there or in the previous paragraph indicates that this does not apply to calls through pointer or virtual calls.

Mike Miller: "The called function" is unfortunate phraseology; it makes it sound as if it's referring to the function actually called, as opposed to the identifier in the postfix expression. It's wrong with respect to Koenig lookup, too (the declaration need not be visible if it can be found in a class or namespace associated with one or more of the arguments).

In fact, this paragraph should be a note. There's a general rule that says you have to find an unambiguous declaration of any name that is used (3.4  basic.lookup paragraph 1); the only reason this paragraph is here is to contrast with C's implicit declaration of called functions.

Proposed resolution:

Change section 5.2.2  expr.call paragraph 2 from:
If no declaration of the called function is visible from the scope of the call the program is ill-formed.
to:
[Note: if a function or member function name is used, and name lookup (3.4  basic.lookup) does not find a declaration of that name, the program is ill-formed. No function is implicitly declared by such a call. ]

(See also issue 218.)




160. Missing std:: qualification

Section: 8.2  dcl.ambig.res     Status: ready     Submitter: Al Stevens     Date: 23 Aug 1999     Priority: 2     Drafting: Maurer

8.2  dcl.ambig.res paragraph 3 shows an example that includes <cstddef> with no using declarations or directives and refers to size_t without the std:: qualification.

Many references to size_t throughout the document omit the std:: namespace qualification.

This is a typical case. The use of std:: is inconsistent throughout the document.

In addition, the use of exception specifications should be examined for consistency.

(See also issue 282.)

Proposed resolution:

In 1.9  intro.execution paragraph 9, replace all two instances of "sig_atomic_t" by "std::sig_atomic_t".

In 3.1  basic.def paragraph 4, replace all three instances of "string" by "std::string" in the example and insert "#include <string>" at the beginning of the example code.

In 3.6.1  basic.start.main paragraph 4, replace

Calling the function
void exit(int);
declared in <cstdlib>...

by

Calling the function std::exit(int) declared in <cstdlib>...

and also replace "exit" by "std::exit" in the last sentence of that paragraph.

In 3.6.1  basic.start.main first sentence of paragraph 5, replace "exit" by "std::exit".

In 3.6.2  basic.start.init paragraph 4, replace "terminate" by "std::terminate".

In 3.6.3  basic.start.term paragraph 1, replace "exit" by "std::exit" (see also issue 28).

In 3.6.3  basic.start.term paragraph 3, replace all three instances of "atexit" by "std::atexit" and both instances of "exit" by "std::exit" (see also issue 28).

In 3.6.3  basic.start.term paragraph 4, replace

Calling the function
void abort();
declared in <cstdlib>...

by

Calling the function std::abort() declared in <cstdlib>...
and "atexit" by "std::atexit" (see also issue 28).

In 3.7.3.1  basic.stc.dynamic.allocation paragraph 1 third sentence, replace "size_t" by "std::size_t".

In 3.7.3.1  basic.stc.dynamic.allocation paragraph 3, replace "new_handler" by "std::new_handler". Furthermore, replace "set_new_handler" by "std::set_new_handler" in the note.

In 3.7.3.1  basic.stc.dynamic.allocation paragraph 4, replace "type_info" by "std::type_info" in the note.

In 3.7.3.2  basic.stc.dynamic.deallocation paragraph 3, replace all four instances of "size_t" by "std::size_t".

In 3.8  basic.life paragraph 5, replace "malloc" by "std::malloc" in the example code and insert "#include <cstdlib>" at the beginning of the example code.

In 3.9  basic.types paragraph 2, replace "memcpy" by "std::memcpy" (the only instance in the footnote and both instances in the example) and replace "memmove" by "std::memmove" in the footnote (see also issue 43).

In 3.9  basic.types paragraph 3, replace "memcpy" by "std::memcpy", once in the normative text and once in the example (see also issue 43).

In 3.9.1  basic.fundamental paragraph 8 last sentence, replace "numeric_limits" by "std::numeric_limits".

In 5.2.7  expr.dynamic.cast paragraph 9 second sentence, replace "bad_cast" by "std::bad_cast".

In 5.2.8  expr.typeid paragraph 2, replace "type_info" by "std::type_info" and "bad_typeid" by "std::bad_typeid".

In 5.2.8  expr.typeid paragraph 3, replace "type_info" by "std::type_info".

In 5.2.8  expr.typeid paragraph 4, replace both instances of "type_info" by "std::type_info".

In 5.3.3  expr.sizeof paragraph 6, replace both instances of "size_t" by "std::size_t".

In 5.3.4  expr.new paragraph 11 last sentence, replace "size_t" by "std::size_t".

In 5.7  expr.add paragraph 6, replace both instances of "ptrdiff_t" by "std::ptrdiff_t".

In 5.7  expr.add paragraph 8, replace "ptrdiff_t" by "std::ptrdiff_t".

In 6.6  stmt.jump paragraph 2, replace "exit" by "std::exit" and "abort" by "std::abort" in the note.

In 8.2  dcl.ambig.res paragraph 3, replace "size_t" by "std::size_t" in the example.

In 8.4  dcl.fct.def paragraph 5, replace "printf" by "std::printf" in the note.

In 12.4  class.dtor paragraph 13, replace "size_t" by "std::size_t" in the example.

In 12.5  class.free paragraph 2, replace all four instances of "size_t" by "std::size_t" in the example.

In 12.5  class.free paragraph 6, replace both instances of "size_t" by "std::size_t" in the example.

In 12.5  class.free paragraph 7, replace all four instances of "size_t" by "std::size_t" in the two examples.

In 12.7  class.cdtor paragraph 4, replace "type_info" by "std::type_info".

In 13.6  over.built paragraph 13, replace all five instances of "ptrdiff_t" by "std::ptrdiff_t".

In 13.6  over.built paragraph 14, replace "ptrdiff_t" by "std::ptrdiff_t".

In 13.6  over.built paragraph 21, replace both instances of "ptrdiff_t" by "std::ptrdiff_t".

In 14.2  temp.names paragraph 4, replace both instances of "size_t" by "std::size_t" in the example. (The example is quoted in issue 96.)

In 14.3  temp.arg paragraph 1, replace "complex" by "std::complex", once in the example code and once in the comment.

In 14.7.3  temp.expl.spec paragraph 8, issue 24 has already corrected the example.

In 15.1  except.throw paragraph 6, replace "uncaught_exception" by "std::uncaught_exception".

In 15.1  except.throw paragraph 7, replace "terminate" by "std::terminate" and both instances of "unexpected" by "std::unexpected".

In 15.1  except.throw paragraph 8, replace "terminate" by "std::terminate".

In 15.2  except.ctor paragraph 3, replace "terminate" by "std::terminate".

In 15.3  except.handle paragraph 9, replace "terminate" by "std::terminate".

In 15.4  except.spec paragraph 8, replace "unexpected" by "std::unexpected".

In 15.4  except.spec paragraph 9, replace "unexpected" by "std::unexpected" and "terminate" by "std::terminate".

In 15.5  except.special paragraph 1, replace "terminate" by "std::terminate" and "unexpected" by "std::unexpected".

In the heading of 15.5.1  except.terminate, replace "terminate" by "std::terminate".

In 15.5.1  except.terminate paragraph 1, footnote in the first bullet, replace "terminate" by "std::terminate". In the same paragraph, fifth bullet, replace "atexit" by "std::atexit". In the same paragraph, last bullet, replace "unexpected_handler" by "std::unexpected_handler".

In 15.5.1  except.terminate paragraph 2, replace

In such cases,
void terminate();
is called...

by

In such cases, std::terminate() is called...

and replace all three instances of "terminate" by "std::terminate".

In the heading of 15.5.2  except.unexpected, replace "unexpected" by "std::unexpected".

In 15.5.2  except.unexpected paragraph 1, replace

...the function
void unexpected();
is called...

by

...the function std::unexpected() is called...
.

In 15.5.2  except.unexpected paragraph 2, replace "unexpected" by "std::unexpected" and "terminate" by "std::terminate".

In 15.5.2  except.unexpected paragraph 3, replace "unexpected" by "std::unexpected".

In the heading of 15.5.3  except.uncaught, replace "uncaught_exception" by "std::uncaught_exception".

In 15.5.3  except.uncaught paragraph 1, replace

The function
bool uncaught_exception()
returns true...

by

The function std::uncaught_exception() returns true...
.

In the last sentence of the same paragraph, replace "uncaught_exception" by "std::uncaught_exception".




112. Array types and cv-qualifiers

Section: 8.3.4  dcl.array     Status: ready     Submitter: Steve Clamage     Date: 4 May 1999     Priority: 2     Drafting: Merrill

From reflector messages 8041-8044.

Steve Clamage: Section 8.3.4  dcl.array paragraph 1 reads in part as follows:

Any type of the form "cv-qualifier-seq array of N T" is adjusted to "array of N cv-qualifier-seq T," and similarly for "array of unknown bound of T." [Example:
    typedef int A[5], AA[2][3];
    typedef const A CA;     // type is "array of 5 const int"
    typedef const AA CAA;   // type is "array of 2 array of 3 const int"
end example] [Note: an "array of N cv-qualifier-seq T" has cv-qualified type; such an array has internal linkage unless explicitly declared extern (7.1.5.1  dcl.type.cv ) and must be initialized as specified in 8.5  dcl.init . ]
The Note appears to contradict the sentence that precedes it.

Mike Miller: I disagree; all it says is that whether the qualification on the element type is direct ("const int x[5]") or indirect ("const A CA"), the array itself is qualified in the same way the elements are.

Steve Clamage: In addition, section 3.9.3  basic.type.qualifier paragraph 2 says:

A compound type (3.9.2  basic.compound ) is not cv-qualified by the cv-qualifiers (if any) of the types from which it is compounded. Any cv-qualifiers applied to an array type affect the array element type, not the array type (8.3.4  dcl.array )."
The Note appears to contradict that section as well.

Mike Miller: Yes, but consider the last two sentences of 3.9.3  basic.type.qualifier paragraph 5:

Cv-qualifiers applied to an array type attach to the underlying element type, so the notation "cv T," where T is an array type, refers to an array whose elements are so-qualified. Such array types can be said to be more (or less) cv-qualified than other types based on the cv-qualification of the underlying element types.
I think this says essentially the same thing as 8.3.4  dcl.array paragraph 1 and its note: the qualification of an array is (bidirectionally) equivalent to the qualification of its members.

Mike Ball: I find this a very far reach. The text in 8.3.4  dcl.array is essentially that which is in the C standard (and is a change from early versions of C++). I don't see any justification at all for the bidirectional equivalence. It seems to me that the note is left over from the earlier version of the language.

Steve Clamage: Finally, the Note seems to say that the declaration

    volatile char greet[6] = "Hello";
gives "greet" internal linkage, which makes no sense.

Have I missed something, or should that Note be entirely removed?

Mike Miller: At least the wording in the note should be repaired not to indicate that volatile-qualification gives an array internal linkage. Also, depending on how the discussion goes, either the wording in 3.9.3  basic.type.qualifier paragraph 2 or in paragraph 5 needs to be amended to be consistent regarding whether an array type is considered qualified by the qualification of its element type.

Steve Adamczyk pointed out that the current state of affairs resulted from the need to handle reference binding consistently. The wording is intended to define the question, "Is an array type cv-qualified?" as being equivalent to the question, "Is the element type of the array cv-qualified?"

Proposed resolution (10/00):

Replace the portion of the note in 8.3.4  dcl.array paragraph 1 reading

such an array has internal linkage unless explicitly declared extern (7.1.5.1  dcl.type.cv) and must be initialized as specified in 8.5  dcl.init.

with

see 3.9.3  basic.type.qualifier.



140. Agreement of parameter declarations

Section: 8.3.5  dcl.fct     Status: ready     Submitter: Steve Clamage     Date: 15 Jul 1999     Drafting: O'Riordan

From reflector messages 8214, 8216, and 8220.

8.3.5  dcl.fct paragraph 3 says,

All declarations for a function with a given parameter list shall agree exactly both in the type of the value returned and in the number and type of parameters.
It is not clear what this requirement means with respect to a pair of declarations like the following:
    int f(const int);
    int f(int x) { ... }
Do they violate this requirement? Is x const in the body of the function declaration?

Tom Plum: I think the FDIS quotation means that the pair of decls are valid. But it doesn't clearly answer whether x is const inside the function definition. As to intent, I know the intent was that if the function definition wants to specify that x is const, the const must appear specifically in the defining decl, not just on some decl elsewhere. But I can't prove that intent from the drafted words.

Mike Miller: I think the intent was something along the following lines:

Two function declarations denote the same entity if the names are the same and the function signatures are the same. (Two function declarations with C language linkage denote the same entity if the names are the same.) All declarations of a given function shall agree exactly both in the type of the value returned and in the number and type of parameters; the presence or absence of the ellipsis is considered part of the signature.
(See 3.5  basic.link paragraph 9. That paragraph talks about names in different scopes and says that function references are the same if the "types are identical for purposes of overloading," i.e., the signatures are the same. See also 7.5  dcl.link paragraph 6 regarding C language linkage, where only the name is required to be the same for declarations in different namespaces to denote the same function.)

According to this paragraph, the type of a parameter is determined by considering its decl-specifier-seq and declarator and then applying the array-to-pointer and function-to-pointer adjustments. The cv-qualifier and storage class adjustments are performed for the function type but not for the parameter types.

If my interpretation of the intent of the second sentence of the paragraph is correct, the two declarations in the example violate that restriction — the parameter types are not the same, even though the function types are. Since there's no dispensation mentioned for "no diagnostic required," an implementation presumably must issue a diagnostic in this case. (I think "no diagnostic required" should be stated if the declarations occur in different translation units — unless there's a blanket statement to that effect that I have forgotten?)

(I'd also note in passing that, if my interpretation is correct,

    void f(int);
    void f(register int) { }
is also an invalid pair of declarations.)

Note also reflector message c++std-edit-838, in which Sean Corfield observed that some people have read this paragraph as indicating that the parameter adjustments apply only to overload resolution and suggested that the paragraph be split in two to clarify the intent.

Proposed resolution (10/00):

  1. In 1.3.10  defns.signature, change "the types of its parameters" to "its parameter-type-list (8.3.5  dcl.fct)".

  2. In the third bullet of 3.5  basic.link paragraph 9 change "the function types are identical for the purposes of overloading" to "the parameter-type-lists of the functions (8.3.5  dcl.fct) are identical."

  3. In the sub-bullets of the third bullet of 5.2.5  expr.ref paragraph 4, change all four occurrences of "function of (parameter type list)" to "function of parameter-type-list."

  4. In 8.3.5  dcl.fct paragraph 3, change

    All declarations for a function with a given parameter list shall agree exactly both in the type of the value returned and in the number and type of parameters; the presence or absence of the ellipsis is considered part of the function type.
    to
    All declarations for a function shall agree exactly in both the return type and the parameter-type-list.

  5. In 8.3.5  dcl.fct paragraph 3, change

    The resulting list of transformed parameter types is the function's parameter type list.
    to
    The resulting list of transformed parameter types and the presence or absence of the ellipsis is the function's parameter-type-list.

  6. In 8.3.5  dcl.fct paragraph 4, change "the parameter type list" to "the parameter-type-list."

  7. In the second bullet of 13.1  over.load paragraph 2, change all three occurrences of "parameter types" to "parameter-type-list."

  8. In 13.3  over.match paragraph 1, change "the types of the parameters" to "the parameter-type-list."

  9. In the last sub-bullet of the third bullet of 13.3.1.2  over.match.oper paragraph 3, change "parameter type list" to "parameter-type-list."

Note, 7 Sep 2001:

Editorial changes while putting in issue 147 brought up the fact that injected-class-name is not a syntax term and therefore perhaps shouldn't be written with hyphens. The same can be said of parameter-type-list. See message c++std-edit-880.




136. Default arguments and friend declarations

Section: 8.3.6  dcl.fct.default     Status: ready     Submitter: Daveed Vandevoorde     Date: 9 July 1999     Priority: 2

From reflector messages 8180-90, 8192-8205, and 8215.

8.3.6  dcl.fct.default paragraph 4 says,

For non-template functions, default arguments can be added in later declarations of a function in the same scope. Declarations in different scopes have completely distinct sets of default arguments. That is, declarations in inner scopes do not acquire default arguments from declarations in outer scopes, and vice versa.
It is unclear how this wording applies to friend function declarations. For example,
    void f(int, int, int=0);             // #1
    class C {
        friend void f(int, int=0, int);  // #2
    };
    void f(int=0, int, int);             // #3
Does the declaration at #2 acquire the default argument from #1, and does the one at #3 acquire the default arguments from #2?

There are several related questions involved with this issue:

  1. Is the friend declaration in the scope of class C or in the surrounding namespace scope?

    Mike Miller: 8.3.6  dcl.fct.default paragraph 4 is speaking about the lexical location of the declaration... The friend declaration occurs in a different declarative region from the declaration at #1, so I would read [this paragraph] as saying that it starts out with a clean slate of default arguments.

    Bill Gibbons: Yes. It occurs in a different region, although it declares a name in the same region (i.e. a redeclaration). This is the same as with local externs and is intended to work the same way. We decided that local extern declarations cannot add (beyond the enclosing block) new default arguments, and the same should apply to friend declarations.

    John Spicer: The question is whether [this paragraph] does (or should) mean declarations that appear in the same lexical scope or declarations that declare names in the same scope. In my opinion, it really needs to be the latter. It seems somewhat paradoxical to say that a friend declaration declares a function in namespace scope yet the declaration in the class still has its own attributes. To make that work I think you'd have to make friends more like block externs that really do introduce a name into the scope in which the declaration is contained.

  2. Should default arguments be permitted in friend function declarations, and what effect should they have?

    Bill Gibbons: In the absence of a declaration visible in class scope to which they could be attached, default arguments on friend declarations do not make sense. [They should be] ill-formed, to prevent surprises.

    John Spicer: It is important that the following case work correctly:

            class X {
                    friend void f(X x, int i = 1){}
            };
    
            int main()
            {
                    X x;
                    f(x);
            }
    

    In other words, a function first declared in a friend declaration must be permitted to have default arguments and those default arguments must be usable when the function is found by argument dependent lookup. The reason that this is important is that it is common practice to define functions in friend declarations in templates, and that definition is the only place where the default arguments can be specified.

  3. What restrictions should be placed on default argument usage with friend declarations?

    John Spicer: We want to avoid instantiation side effects. IMO, the way to do this would be to prohibit a friend declaration from providing default arguments if a declaration of that function is already visible. Once a function has had a default specified in a friend declaration it should not be possible to add defaults in another declaration be it a friend or normal declaration.

    Mike Miller: The position that seems most reasonable to me is to allow default arguments in friend declarations to be used in Koenig lookup, but to say that they are completely unrelated to default arguments in declarations in the surrounding scope; and to forbid use of a default argument in a call if more than one declaration in the overload set has such a default, as in the proposed resolution for issue 1.

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

Notes from 10/99 meeting:

Four possible outcomes were identified:

  1. If a friend declaration declares a default parameter, allow no other declarations of that function in the translation unit.
  2. Same as preceding, but only allow the friend declaration if it is also a definition.
  3. Disallow default arguments in friend declarations.
  4. Treat the default arguments in each friend declaration as a distinct set, causing an error if the call would be ambiguous.

The core group eliminated the first and fourth options from consideration, but split fairly evenly between the remaining two.

A straw poll of the full committee yielded the following results (given as number favoring/could live with/"over my dead body"):

  1. 0/14/5
  2. 8/13/5
  3. 11/7/14
  4. 7/10/9

Additional discussion is recorded in the "Record of Discussion" for the meeting, J16/99-0036 = WG21 N1212. See also paper J16/00-0040 = WG21 N1263.

Proposed resolution (10/00):

In 8.3.6  dcl.fct.default, add following paragraph 4:

If a friend declaration specifies a default argument expression, that declaration must be a definition and shall be the only declaration of the function or function template in the translation unit.



277. Zero-initialization of pointers

Section: 8.5  dcl.init     Status: ready     Submitter: Andrew Sawyer     Date: 5 Apr 2001     Priority: 0     Drafting: Miller

(From messages 9110-15, 9130, and 9135.)

The intent of 8.5  dcl.init paragraph 5 is that pointers that are zero-initialized will contain a null pointer value. Unfortunately, the wording used,

...set to the value of 0 (zero) converted to T

does not match the requirements for creating a null pointer value given in 4.10  conv.ptr paragraph 1:

A null pointer constant is an integral constant expression (5.19  expr.const) rvalue of integer type that evaluates to zero. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type...

The problem is that the "value of 0" in the description of zero-initialization is not specified to be an integral constant expression. Nonconstant expressions can also have the value 0, and converting a nonconst 0 to pointer type need not result in a null pointer value.

Proposed resolution (04/01):

In 8.5  dcl.init paragraph 5, change

...set to the value 0 (zero) converted to T;

to

...set to the value 0 (zero), taken as an integral constant expression, converted to T; [footnote: as specified in 4.10  conv.ptr, converting an integral constant expression whose value is 0 to a pointer type results in a null pointer value.]



175. Class name injection and base name access

Section: class     Status: ready     Submitter: John Spicer     Date: 21 February 1999     Priority: 2     Drafting: Spicer

With class name injection, when a base class name is used in a derived class, the name found is the injected name in the base class, not the name of the class in the scope containing the base class. Consequently, if the base class name is not accessible (e.g., because is is in a private base class), the base class name cannot be used unless a qualified name is used to name the class in the class or namespace of which it is a member.

Without class name injection the following example is valid. With class name injection, A is inaccessible in class C.

    class A { };
    class B: private A { };
    class C: public B {
        A* p;    // error: A inaccessible
    };

At the least, the standard should be more explicit that this is, in fact, ill-formed.

(See paper J16/99-0010 = WG21 N1187.)

Proposed resolution (04/01):

Add to the end of 11.1  class.access.spec paragraph 3:

[Note: In a derived class, the lookup of a base class name will find the injected-class-name instead of the name of the base class in the scope in which it was declared. The injected-class-name might be less accessible than the name of the base class in the scope in which it was declared.] [Example:

    class A { };
    class B : private A { };
    class C : public B {
        A* p;    // error: injected-class-name A is inaccessible
        ::A* q;  // OK
    };

end example]




207. using-declarations and protected access

Section: 11.2  class.access.base     Status: ready     Submitter: Jason Merrill     Date: 28 Feb 2000     Drafting: Merrill

From reflector messages 8562-3.

Consider the following example:

  class A {
  protected:
    static void f() {};
  };

  class B : A {
  public:
    using A::f;
    void g() {
      A::f();
    }
  };

The standard says in 11.2  class.access.base paragraph 4 that the call to A::f is ill-formed:

A member m is accessible when named in class N if

Here, m is A::f and N is A.

It seems clear to me that the third bullet should say "public, private or protected".

Steve Adamczyk:The words were written before using-declarations existed, and therefore didn't anticipate this case.

Proposed resolution (04/01):

Modify the third bullet of the third change ("A member m is accessible...") in the resolution of issue 9 to read "public, private, or protected" instead of "private or protected."




252. Looking up deallocation functions in virtual destructors

Section: 12.4  class.dtor     Status: ready     Submitter: Steve Clamage     Date: 19 Oct 2000     Priority: 1     Drafting: Miller
From messages 8934-5.

There is a mismatch between 12.4  class.dtor paragraph 11 and 12.5  class.free paragraph 4 regarding the lookup of deallocation functions in virtual destructors. 12.4  class.dtor says,

At the point of definition of a virtual destructor (including an implicit definition (12.8  class.copy)), non-placement operator delete shall be looked up in the scope of the destructor's class (3.4.1  basic.lookup.unqual) and if found shall be accessible and unambiguous. [Note: this assures that an operator delete corresponding to the dynamic type of an object is available for the delete-expression (12.5  class.free). ]

The salient features to note from this description are:

  1. The lookup is "in the scope of the destructor's class," which implies that only members are found (cf 12.2  class.temporary). (The cross-reference would indicate otherwise, however, since it refers to the description of looking up unqualified names; this kind of lookup "spills over" into the surrounding scope.)
  2. Only non-placement operator delete is looked up. Presumably this means that a placement operator delete is ignored in the lookup.

On the other hand, 12.5  class.free says,

If a delete-expression begins with a unary :: operator, the deallocation function's name is looked up in global scope. Otherwise, if the delete-expression is used to deallocate a class object whose static type has a virtual destructor, the deallocation function is the one found by the lookup in the definition of the dynamic type's virtual destructor (12.4  class.dtor). Otherwise, if the delete-expression is used to deallocate an object of class T or array thereof, the static and dynamic types of the object shall be identical and the deallocation function's name is looked up in the scope of T. If this lookup fails to find the name, the name is looked up in the global scope. If the result of the lookup is ambiguous or inaccessible, or if the lookup selects a placement deallocation function, the program is ill-formed.

Points of interest in this description include:

  1. For a class type with a virtual destructor, the lookup is described as being "in the definition of the dynamic type's virtual destructor," rather than "in the scope of the dynamic type." That is, the lookup is assumed to be an unqualified lookup, presumably terminating in the global scope.
  2. The assumption is made that the lookup in the virtual destructor was successful ("...the one found...", not "...the one found..., if any"). This will not be the case if the deallocation function was not declared as a member somewhere in the inheritance hierarchy.
  3. The lookup in the non-virtual-destructor case does find placement deallocation functions and can fail as a result.

Suggested resolution: Change the description of the lookup in 12.4  class.dtor paragraph 11 to match the one in 12.5  class.free paragraph 4.

Proposed resolution (10/00):

  1. Replace 12.4  class.dtor paragraph 11 with the following:

    At the point of definition of a virtual destructor (including an implicit definition), the non-array deallocation function is looked up in the scope of the destructor's class (10.2  class.member.lookup), and, if no declaration is found, the function is looked up in the global scope. If the result of this lookup is ambiguous or inaccessible, or if the lookup selects a placement deallocation function, the program is ill-formed. [Note: this assures that a deallocation function corresponding to the dynamic type of an object is available for the delete-expression (12.5  class.free).]
  2. In 12.5  class.free paragraph 4, change

    ...the deallocation function is the one found by the lookup in the definition of the dynamic type's virtual destructor (12.4  class.dtor).

    to

    ...the deallocation function is the one selected at the point of definition of the dynamic type's virtual destructor (12.4  class.dtor).



272. Explicit destructor invocation and qualified-ids

Section: 12.4  class.dtor     Status: ready     Submitter: Mike Miller     Date: 22 Feb 2001     Priority: 0     Drafting: Adamczyk
12.4  class.dtor paragraph 12 contains the following note:
an explicit destructor call must always be written using a member access operator (5.2.5  expr.ref); in particular, the unary-expression ~X() in a member function is not an explicit destructor call (5.3.1  expr.unary.op).

This note is incorrect, as an explicit destructor call can be written as a qualified-id, e.g., X::~X(), which does not use a member access operator.

Proposed resolution (04/01):

Change 12.4  class.dtor paragraph 12 as follows:

[Note: an explicit destructor call must always be written using a member access operator (5.2.5  expr.ref) or a qualified-id (5.1  expr.prim); in particular, the unary-expression ~X() in a member function is not an explicit destructor call (5.3.1  expr.unary.op).]



224. Definition of dependent names

Section: 14.6.2.1  temp.dep.type     Status: ready     Submitter: Derek Inglis     Date: 30 Nov 1999     Priority: 1     Drafting: Spicer

From reflector messages 8384-89, 8394.

The definition of when a type is dependent, given in 14.6.2.1  temp.dep.type, is essentially syntactic: if the reference is a qualified-id and one of the class-names in the nested-name-specifier is dependent, the type is dependent. This approach leads to surprising results:

    template <class T> class X {
        typedef int I;
	I a;                 // non-dependent
        typename X<T>::I b;  // dependent
        typename X::I c;     // dependent (X is equivalent to X<T>)
    };

Suggested resolution:

The decision on whether a name is dependent or non-dependent should be based on lookup, not on the form of the name: if the name can be looked up in the definition context and cannot be anything else as the result of specialization, the name should be non-dependent.

See papers J16/00-0028 = WG21 N1251 and J16/00-0056 = WG21 N1279.

Proposed resolution (10/00):

  1. Replace section 14.6.2.1  temp.dep.type with the following:

    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

    • the injected-class-name (clause 9  class) of the class template or nested class,
    • in the definition of a primary class template, the name of the class template followed by the template argument list of the primary template (as described below) enclosed in <>,
    • in the definition of a nested class of a class template, the name of the nested class referenced as a member of the current instantiation, or
    • in the definition of a partial specialization, the name of the class template followed by the template argument list of the partial specialization enclosed in <>.

    The template argument list of a primary template is a template argument list in which the nth template argument has the value of the nth template parameter of the class template.

    A template argument that is equivalent to a template parameter (i.e., has the same constant value or the same type as the template parameter) can be used in place of that template parameter in a reference to the current instantiation. In the case of a nontype template argument, the argument must have been given the value of the template parameter and not an expression involving the template parameter.

    [Example:

    template <class T> class A {
        A* p1;      // A is the current instantiation
        A<T>* p2;   // A<T> is the current instantiation
        A<T*> p3;   // A<T*> is not the current instantiation
        ::A<T>* p4; // ::A<T> is the current instantiation
        class B {
    	B* p1;        // B is the current instantiation
    	A<T>::B* p2;  // A<T>::B is the current instantiation
    	typename A<T*>::B* p3; // A<T*>::B is not the
    			     // current instantiation
        };
    };
    
    template <class T> class A<T*> {
        A<T*>* p1;  // A<T*> is the current instantiation
        A<T>* p2;   // A<T> is not the current instantiation
    };
    
    template <class T1, class T2, int I> struct B {
        B<T1, T2, I>*	b1;        // refers to the current instantiation
        B<T2, T1, I>*	b2;        // not the current instantiation
        typedef T1 my_T1;
        static const int my_I = I;
        static const int my_I2 = I+0;
        static const int my_I3 = my_I;
        B<my_T1, T2, my_I>* b3;  // refers to the current instantiation
        B<my_T1, T2, my_I2>* b4; // not the current instantiation
        B<my_T1, T2, my_I3>* b5; // refers to the current instantiation
    };
    

    end example]

    A name is a member of the current instantiation if it is

    • An unqualified name that, when looked up, refers to a member of a class template. [Note: This can only occur when looking up a name in a scope enclosed by the definition of a class template.]
    • A qualified-id in which the nested-name-specifier refers to the current instantiation.

    [Example:

    template <class T> class A {
        static const int i = 5;
        int n1[i];        // i refers to a member of the current instantiation 
        int n2[A::i];     // A::i refers to a member of the current instantiation 
        int n3[A<T>::i];  // A<T>::i refers to a member of the current instantiation 
        int f();
    };
    
    template <class T> int A<T>::f()
    {
        return i;  // i refers to a member of the current instantiation
    }
    

    end example]

    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.

    A type is dependent if it is

    • a template parameter,
    • a member of an unknown specialization,
    • a nested class that is a member of the current instantiation,
    • a cv-qualified type where the cv-unqualified type is dependent,
    • a compound type constructed from any dependent type,
    • an array type constructed from any dependent type or whose size is specified by a constant expression that is value-dependent, or
    • a template-id in which either the template name is a template parameter or any of the template arguments is a dependent type or an expression that is type-dependent or value-dependent.

    [Note: Because typedefs to not introduce new types, but instead simply refer to other types, a name that refers to a typedef that is a member of the current instantiation is dependent only if the type referred to is dependent.]

  2. In 14.6.2.2  temp.dep.expr paragraph 3, replace

    • a nested-name-specifier that contains a class-name that names a dependent type.

    with

    • a nested-name-specifier or qualified-id that names a member of an unknown specialization.
  3. In 14.6.2.2  temp.dep.expr, add the following paragraph:

    A class member access expression (5.2.5  expr.ref) is type-dependent if the type of the referenced member is dependent. [Note: In an expression of the form x.y or xp->y the type of the expression is usually the type of the member y of the class of x (or the class pointed to by xp). However, if x or xp refers to a dependent type that is not the current instantiation, the type of y is always dependent. If x or xp refers to a non-dependent type or refers to the current instantiation, the type of y is the type of the class member access expression.]
  4. In 14.6  temp.res paragraph 3, replace

    A qualified-name that refers to a type and that depends on a template-parameter (14.6.2  temp.dep) shall be prefixed by the keyword typename.

    with

    A qualified-id that refers to a type and that depends on a template-parameter (14.6.2  temp.dep) but does not refer to a member of the current instantiation shall be prefixed by the keyword typename.
  5. In 14.2  temp.names paragraph 4, replace

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

    with

    When the name of a member template specialization appears after . or -> in a postfix-expression, or after a nested-name-specifier in a qualified-id, and the postfix-expression or qualified-id explicitly 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), the member template name must be prefixed by the keyword template. Otherwise the name is assumed to name a non-template.
  6. In 14.6.1  temp.local paragraph 2, remove the following text, which was added for issue 108. The updated definition of dependent name now addresses this case.

    Within the scope of a class template, when the unqualified name of a nested class of the class template is referred to, it is equivalent to the name of the nested class qualified by the name of the enclosing class template. [Example:

    template <class T> struct A {
    	class B {};
    	// B is equivalent to A::B, which is equivalent to A<T>::B,
    	// which is dependent.
    	class C : B { };
    };
    

    end example]




106. Creating references to references during template deduction/instantiation

Section: unknown  unknown     Status: ready     Submitter: Bjarne Stroustrup     Date: unknown     Drafting: Adamczyk/Unruh

The main defect is in the library, where the binder template can easily lead to reference-to-reference situations.

Proposed resolution (04/01):

  1. Add the following as paragraph 6 of 7.1.3  dcl.typedef:

    If a typedef TD names a type "reference to cv1 S," an attempt to create the type "reference to cv2 TD" creates the type "reference to cv12" S," where cv12 is the union of the cv-qualifiers cv1 and cv2. Redundant qualifiers are ignored. [Example:

        int i;
        typedef int& RI;
        RI& r = i;          // r has the type int&
        const RI& r = i;    // r has the type const int&
    
    end example]
  2. Add the following as paragraph 4 of 14.3.1  temp.arg.type:

    If a template-argument for a template-parameter T names a type "reference to cv1 S," an attempt to create the type "reference to cv2 T" creates the type "reference to cv12 S," where cv12 is the union of the cv-qualifiers cv1 and cv2. Redundant cv-qualifiers are ignored. [Example:

        template <class T> class X {
            f(const T&);
            /* ... */
        };
        X<int&> x;    // X<int&>::f has the parameter type const int&
    
    end example]
  3. In 14.8.2  temp.deduct paragraph 2 bullet 3 sub-bullet 5, remove the indicated text:
    Attempting to create a reference to a reference type or a reference to void.

(See also paper J16/00-0022 = WG21 N1245.)






Issues with "Review" Status


143. Friends and Koenig lookup

Section: 3.4.2  basic.lookup.koenig     Status: review     Submitter: Mike Miller     Date: 21 Jul 1999     Drafting: Miller

From reflector message 8228.

Paragraphs 1 and 2 of 3.4.2  basic.lookup.koenig say, in part,

When an unqualified name is used as the postfix-expression in a function call (5.2.2  expr.call )... namespace-scope friend function declarations (11.4  class.friend ) not otherwise visible may be found... the set of declarations found by the lookup of the function name [includes] the set of declarations found in the... classes associated with the argument types.
The most straightforward reading of this wording is that if a function of namespace scope (as opposed to a class member function) is declared as a friend in a class, and that class is an associated class in a function call, the friend function will be part of the overload set, even if it is not visible to normal lookup.

Consider the following example:

    namespace A {
	class S;
    };
    namespace B {
	void f(A::S);
    };
    namespace A {
	class S {
	    int i;
	    friend void B::f(S);
	};
    }
    void g() {
	A::S s;
	f(s); // should find B::f(A::S)
    }
This example would seem to satisfy the criteria from 3.4.2  basic.lookup.koenig : A::S is an associated class of the argument, and A::S has a friend declaration of the namespace-scope function B::f(A::S), so Koenig lookup should include B::f(A::S) as part of the overload set in the call.

Another interpretation is that, instead of finding the friend declarations in associated classes, one only looks for namespace-scope functions, visible or invisible, in the namespaces of which the the associated classes are members; the only use of the friend declarations in the associated classes is to validate whether an invisible function declaration came from an associated class or not and thus whether it should be included in the overload set or not. By this interpretation, the call f(s) in the example will fail, because B::f(A::S) is not a member of namespace A and thus is not found by the lookup.

Notes from 10/99 meeting: The second interpretation is correct. The wording should be revised to make clear that Koenig lookup works by finding "invisible" declarations in namespace scope and not by finding friend declarations in associated classes.

Proposed resolution (04/01): The "associated classes" are handled adequately under this interpretation by 3.4.2  basic.lookup.koenig paragraph 3, which describes the lookup in the associated namespaces as including the friend declarations from the associated classes. Other mentions of the associated classes should be removed or qualified to avoid the impression that there is a lookup in those classes:

  1. In 3.4.2  basic.lookup.koenig, change

    When an unqualified name is used as the postfix-expression in a function call (5.2.2  expr.call), other namespaces not considered during the usual unqualified lookup (3.4.1  basic.lookup.unqual) may be searched, and namespace-scope friend function declarations (11.4  class.friend) not otherwise visible may be found.

    to

    When an unqualified name is used as the postfix-expression in a function call (5.2.2  expr.call), other namespaces not considered during the usual unqualified lookup (3.4.1  basic.lookup.unqual) may be searched, and in those namespaces, namespace-scope friend function declarations (11.4  class.friend) not otherwise visible may be found.
  2. In 3.4.2  basic.lookup.koenig paragraph 2, delete the words and classes in the following two sentences:

    If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated namespaces and classes are not considered. Otherwise 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.

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




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

Section: 3.6.2  basic.start.init     Status: review     Submitter: Andrei Iltchenko     Date: 8 Feb 2001     Priority: 3     Drafting: Crowl

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

Now consider the following example:

src1.cpp:

    #include <iostream>

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

    int   g_data1 = initializer();

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

    int   g_data2 = initializer();
    extern int   g_data3;

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

src2.cpp:

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

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

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

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

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

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

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

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

Proposed resolution (04/01): See issue 270.




270. Order of initialization of static data members of class templates

Section: 3.6.2  basic.start.init     Status: review     Submitter: Jonathan H. Lundquist     Date: 9 Feb 2001     Priority: 0     Drafting: Crowl

The Standard does not appear to address how the rules for order of initialization apply to static data members of class templates.

Suggested resolution: Add the following verbiage to either 3.6.2  basic.start.init or 9.4.2  class.static.data:

Initialization of static data members of class templates shall be performed during the initialization of static data members for the first translation unit to have static initialization performed for which the template member has been instantiated. This requirement shall apply to both the static and dynamic phases of initialization.

Notes from 04/01 meeting:

Enforcing an order of initialization on static data members of class templates will result in substantial overhead on access to such variables. The problem is that the initialization be required as the result of instantiation in a function used in the initialization of a variable in another translation unit. In current systems, the order of initialization of static data data members of class templates is not predictable. The proposed resolution is to state that the order of initialization is undefined.

Proposed resolution (04/01):

Replace the following sentence in 3.6.2  basic.start.init paragraph 1:

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

with

Dynamic initialization is either ordered or unordered. Explicit specializions of class template static data members have ordered initialization. Other class template static data member instances have unordered initialization. Other objects defined in namespace scope have ordered initialization. Objects 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 with unordered initialization and for objects defined in different translation units.

(This resolution also resolves issue 269.)

Note (07/01):

Brian McNamara argues against the proposed resolution. The following excerpt captures the central point of a long message on comp.std.c++:

(The full text is in core message 9263.)

I have a class for representing linked lists which looks something like
    template <class T>
    class List {
       ...  static List<T>* sentinel; ...
    };
 
    template <class T>
    List<T>* List<T>::sentinel( new List<T> ); // static member definition

The sentinel list node is used to represent "nil" (the null pointer cannot be used with my implementation, for reasons which are immaterial to this discussion). All of the List's non-static member functions and constructors depend upon the value of the sentinel. Under the proposed resolution for issue #270, Lists cannot be safely instantiated before main() begins, as the sentinel's initialization is "unordered".

(Some readers may propose that I should use the "singleton pattern" in the List class. This is undesirable, for reasons I shall describe at the end of this post at the location marked "[*]". For the moment, indulge me by assuming that "singleton" is not an adequate solution.)

Though this is a particular example from my own experience, I believe it is representative of a general class of examples. It is common to use static data members of a class to represent the "distinguished values" which are important to instances of that class. It is imperative that these values be initialized before any instances of the class are created, as the instances depend on the values.

In a comp.std.c++ posting on 28 Jul 2001, Brian McNamara proposes the following alternative resolution:

Replace the following sentence in 3.6.2  basic.start.init paragraph 1:

Objects with static storage duration defined in namespace scope in the same translation unit and dynamically initialized shall be initialized in the order in which their definition appears in the translation unit.
with
Objects with static storage duration defined in namespace scope shall be initialized in the order described below.
and then after paragraph 1, add this text:
Dynamic initialization is either ordered or quasi-ordered. Explicit specializations of class template static data members have ordered initialization. Other class template static data member instances have quasi-ordered initialization. All other objects defined in namespace scope have ordered initialization. The order of initialization is specified as follows:
along with a non-normative note along the lines of
[ Note: The intention is that translation units can each be compiled separately with no knowledge of what objects may be re-defined in other translation units. Each translation unit can contain a method which initializes all objects (both quasi-ordered and ordered) as though they were ordered. When these translation units are linked together to create an executable program, all of these objects can be initialized by simply calling the initialization methods (one from each translation unit) in any order. Quasi-ordered objects require some kind of guard to ensure that they are not initialized more than once (the first attempt to initialize such an object should succeed; any subsequent attempts should simply be ignored). ]

Erwin Unruh replies: There is a point which is not mentioned with this posting. It is the cost for implementing the scheme. It requires that each static template variable is instantiated in ALL translation units where it is used. There has to be a flag for each of these variables and this flag has to be checked in each TU where the instantiation took place.

I would reject this idea and stand with the proposed resolution of issue 270.

There just is no portable way to ensure the "right" ordering of construction.




119. Object lifetime and aggregate initialization

Section: 3.8  basic.life     Status: review     Submitter: Jack Rouse     Date: 20 May 1999     Priority: 2     Drafting: Miller

From reflector messages 8072-8073.

Jack Rouse: 3.8  basic.life paragraph 1 includes:

The lifetime of an object is a runtime property of the object. The lifetime of an object of type T begins when:
Consider the code:
    struct B {
        B( int = 0 );
        ~B();
    };

    struct S {
        B b1;
    };

    int main()
    {
        S s = { 1 };
        return 0;
    }
In the code above, class S does have a non-trivial constructor, the default constructor generated by the compiler. According the text above, the lifetime of the auto s would never begin because a constructor for S is never called. I think the second case in the text needs to include aggregate initialization.

Mike Miller: I see a couple of ways of fixing the problem. One way would be to change "the constructor call has completed" to "the object's initialization is complete."

Another would be to add following "a class type with a non-trivial constructor" the phrase "that is not initialized with the brace notation (8.5.1  dcl.init.aggr )."

The first formulation treats aggregate initialization like a constructor call; even POD-type members of an aggregate could not be accessed before the aggregate initialization completed. The second is less restrictive; the POD-type members of the aggregate would be usable before the initialization, and the members with non-trivial constructors (the only way an aggregate can acquire a non-trivial constructor) would be protected by recursive application of the lifetime rule.

Proposed resolution (04/01):

In 3.8  basic.life paragraph 1, change

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

to

If T is a class type with a non-trivial constructor (12.1  class.ctor), the initialization is complete. [Note: the initialization can be performed by a constructor call or, in the case of an aggregate with an implicitly-declared non-trivial default constructor, an aggregate initialization (8.5.1  dcl.init.aggr).]

Notes from 04/01 meeting: It was pointed out that this resolution is a small substantive change. Originally, the lifetime of the object began before the destruction of temporaries used in constructor arguments. With this change, it begins afterwards.




158. Aliasing and qualification conversions

Section: 3.10  basic.lval     Status: review     Submitter: Mike Stump     Date: 20 Aug 1999     Priority: 2     Drafting: Nelson

3.10  basic.lval paragraph 15 lists the types via which an lvalue can be used to access the stored value of an object; using an lvalue type that is not listed results in undefined behavior. It is permitted to add cv-qualification to the actual type of the object in this access, but only at the top level of the type ("a cv-qualified version of the dynamic type of the object").

However, 4.4  conv.qual paragraph 4 permits a "conversion [to] add cv-qualifiers at levels other than the first in multi-level pointers." The combination of these two rules allows creation of pointers that cannot be dereferenced without causing undefined behavior. For instance:

    int* jp;
    const int * const * p1 = &jp;
    *p1;    // undefined behavior!

The reason that *p1 results in undefined behavior is that the type of the lvalue is const int * const", which is not "a cv-qualified version of" int*.

Since the conversion is permitted, we must give it defined semantics, hence we need to fix the wording in 3.10  basic.lval to include all possible conversions of the type via 4.4  conv.qual.

Proposed resolution (04/01):

Add a new bullet to 3.10  basic.lval paragraph 15, following "a cv-qualified version of the dynamic type of the object:"




125. Ambiguity in friend declaration syntax

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

The example below is ambiguous.

    struct A{
      struct B{};
    };

    A::B C();

    namespace B{
      A C();
    }

    struct Test {
      friend A::B ::C();
    };
Here, it is not clear whether the friend declaration denotes A B::C() or A::B C(), yet the standard does not resolve this ambiguity.

The ambiguity arises since both the simple-type-specifier (7.1.5.2  dcl.type.simple paragra 1) and an init-declararator (8  dcl.decl paragraph 1) contain an optional :: and an optional nested-name-specifier (5.1  expr.prim paragraph 1). Therefore, two different ways to analyse this code are possible:

simple-type-specifier = A::B
init-declarator = ::C()
simple-declaration = friend A::B ::C();
or
simple-type-specifier = A
init-declarator = ::B::C()
simple-declaration = friend A ::B::C();
Since it is a friend declaration, the init-declarator may be qualified, and start with a global scope.

Suggested Resolution: In the definition of nested-name-specifier, add a sentence saying that a :: token immediately following a nested-name-specifier is always considered as part of the nested-name-specifier. Under this interpretation, the example is ill-formed, and should be corrected as either

    friend A (::B::C)();   //or
    friend A::B (::C)();

An alternate suggestion — changing 7.1  dcl.spec to say that

The longest sequence of tokens that could possibly be a type name is taken as the decl-specifier-seq of a declaration.

— is undesirable because it would make the example well-formed rather than requiring the user to disambiguate the declaration explicitly.

Proposed resolution (04/01):

In 5.1  expr.prim paragraph 7,

  1. Before the grammar for qualified-id, start a new paragraph 7a with the text

    A qualified-id is an id-expression that contains the scope resolution operator ::.
  2. Following the grammar fragment, insert the following:

    The longest sequence of tokens that could form a qualified-id constitutes a single qualified-id. [Example:

        // classes C, D; functions F, G, namespace N; non-class type T
        friend C ::D::F();   // ill-formed, means friend (C::D::F)();
        friend C (::D::F)(); // well-formed
        friend N::T ::G();   // ill-formed, means friend (N::T::G)();
        friend N::T (::G)(); // well-formed
    

    end example]

  3. Start a new paragraph 7b following the example.

(This resolution depends on that of issue 215.)




177. Lvalues vs rvalues in copy-initialization

Section: 8.5  dcl.init     Status: review     Submitter: Steve Adamczyk     Date: 25 October 1999     Priority: 2     Drafting: Schmeiser

Is the temporary created during copy-initialization of a class object treated as an lvalue or an rvalue? That is, is the following example well-formed or not?

    struct B { };
    struct A {
        A(A&);    // not const
        A(const B&);
    };
    B b;
    A a = b;

According to 8.5  dcl.init paragraph 14, the initialization of a is performed in two steps. First, a temporary of type A is created using A::A(const B&). Second, the resulting temporary is used to direct-initialize a using A::A(A&).

The second step requires binding a reference to non-const to the temporary resulting from the first step. However, 8.5.3  dcl.init.ref paragraph 5 requires that such a reference be bound only to lvalues.

It is not clear from 3.10  basic.lval whether the temporary created in the process of copy-initialization should be treated as an lvalue or an rvalue. If it is an lvalue, the example is well-formed, otherwise it is ill-formed.

Proposed resolution (04/01):

  1. In 8.5  dcl.init paragraph 14, insert the following after "the call initializes a temporary of the destination type:"

    The temporary is an rvalue.
  2. In 15.1  except.throw paragraph 3, replace

    The temporary is used to initialize the variable...

    with

    The temporary is an lvalue and is used to initialize the variable...

(See also issue 84.)




39. Conflicting ambiguity rules

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

From reflector message core-7816.

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

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

Proposed Resolution (10/00):

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

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

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

Notes from 04/01 meeting:

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

    struct A { static void f(); };

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

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

    struct C: B1, B2 { };

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



77. The definition of friend does not allow nested classes to be friends

Section: 11.4  class.friend     Status: review     Submitter: Judy Ward     Date: 15 Dec 1998     Drafting: Adamczyk

The definition of "friend" in 11.4  class.friend says:

A friend of a class is a function or class that is not a member of the class but is permitted to use the private and protected member names from the class. ...
A nested class, i.e. INNER in the example below, is a member of class OUTER. The sentence above states that it cannot be a friend. I think this is a mistake.
    class OUTER {
        class INNER;
        friend class INNER;
        class INNER {};
    };

Proposed resolution (04/01):

Change the first sentence of 11.4  class.friend as follows:

A friend of a class is a function or class that is not a member of the class but is allowed given permission to use the private and protected member names from the class. The name of a friend is not in the scope of the class, and the friend is not called with the member access operators (5.2.5  expr.ref) unless it is a member of another class. A class specifies its friends, if any, by way of friend declarations. Such declarations give special access rights to the friends, but they do not make the nominated friends members of the befriending class.



162. (&C::f)() with nonstatic members

Section: 13.3.1.1  over.match.call     Status: review     Submitter: Steve Adamczyk     Date: 26 Aug 1999     Priority: 3

From reflector messages 8289 and 8305.

13.3.1.1  over.match.call paragraph 3 says that when a call of the form

   (&C::f)()
is written, the set of overloaded functions named by C::f must not contain any nonstatic member functions. A footnote gives the rationale: if a member of C::f is a nonstatic member function, &C::f is a pointer to member constant, and therefore the call is invalid.

This is clear, it's implementable, and it doesn't directly contradict anything else in the standard. However, I'm not sure it's consistent with some similar cases.

In 13.4  over.over paragraph 5, second example, it is made amply clear that when &C::f is used as the address of a function, e.g.,

   int (*pf)(int) = &C::f;
the overload set can contain both static and nonstatic member functions. The function with the matching signature is selected, and if it is nonstatic &C::f is a pointer to member function, and otherwise &C::f is a normal pointer to function.

Similarly, 13.3.1.1.1  over.call.func paragraph 3 makes it clear that

   C::f();
is a valid call even if the overload set contains both static and nonstatic member functions. Overload resolution is done, and if a nonstatic member function is selected, an implicit this-> is added, if that is possible.

Those paragraphs seem to suggest the general rule that you do overload resolution first and then you interpret the construct you have according to the function selected. The fact that there are static and nonstatic functions in the overload set is irrelevant; it's only necessary that the chosen function be static or nonstatic to match the context.

Given that, I think it would be more consistent if the (&C::f)() case would also do overload resolution first. If a nonstatic member is chosen, the program would be ill-formed.

Proposed resolution (04/01):

  1. Remove the following highlighted text in 13.3.1.1  over.match.call paragraph 3:

    The fourth case arises from a postfix-expression of the form &F, where F names a set of overloaded functions. In the context of a function call, the set of functions named by F shall contain only non-member functions and static member functions. And in this context using &F behaves the same as using the name F by itself.
  2. Add the following before the parenthesized sentence at the end of the paragraph:

    If the function selected by overload resolution according to 13.3.1.1.1  over.call.func is a nonstatic member function, the program is ill-formed.



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

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

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

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

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

Notes from 04/00 meeting:

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

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

Proposed resolution (04/01):

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

to

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




226. Default template arguments for function templates

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

The prohibition of default template arguments for