Document number:   J16/00-0016R1 = WG21 N1239
Date:  21 May, 2000
Project:  Programming Language C++
Reference:  ISO/IEC IS 14882:1998(E)
Reply to:  William M. Miller
 wmm@fastdial.net


C++ Standard Core Language Active Issues, Revision 12


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

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://reality.sgi.com/austern_mti/std-c++/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


173. Constraints on execution character set

Section: 2.2  lex.charset     Status: ready     Submitter: Markus Mauhart     Date: 27 Sep 1999

22.2.1.1.2  lib.locale.ctype.virtuals paragraph 13 states a constraint on the values of the characters representing the decimal digits in the execution character set:

for any digit character c, the expression (do_narrow( c, dfault)-'0') evaluates to the digit value of the character.
This requirement is not reflected in the description of the execution character set (2.2  lex.charset paragraph 3).

Proposed resolution (10/99): In 2.2  lex.charset paragraph 3, after the sentence

For each basic execution character set, the values of the members shall be non-negative and distinct from one another.
insert the following:
In both the source and execution basic character sets, the value of each character after 0 in the above list of decimal digits shall be one greater than the value of the previous.



164. Overlap between Koenig and normal lookup

Section: 3.4.2  basic.lookup.koenig     Status: ready     Submitter: Derek Inglis     Date: 3 Sep 1999

The description of Koenig lookup in 3.4.2  basic.lookup.koenig paragraph 1 says,

...other namespaces not considered during the usual unqualified lookup (3.4.1  basic.lookup.unqual ) may be searched.
Does this mean that Koenig lookup does not search namespaces that were already searched during the usual unqualified lookup? The answer is academic except for the two-stage lookup during template instantiation. If a given namespace is searched in the context of the template definition, are declarations in that namespace in the instantiation context ignored during the Koenig lookup? For instance,
    void f(int);

    template <class T> void g(T t) {
        f(t);
    }

    enum E { e };

    void f(E);

    void h() {
        g(e);
    }
In this example, the call f(t) in the template function will resolve to f(E) if Koenig lookup reexamines already-searched namespaces and to f(int) if not.

Proposed Resolution (10/99): Immediately preceding the example at the end of 3.4.2  basic.lookup.koenig paragraph 2, add the following:

[Note: the namespaces and classes associated with the argument types can include namespaces and classes already considered by the ordinary unqualified lookup.]



85. Redeclaration of member class

Section: 3.4.4  basic.lookup.elab     Status: ready     Submitter: Steve Adamczyk     Date: 25 Jan 1999

In 3.4.4  basic.lookup.elab paragraph 3, there is the example

    struct Base {
        // ...
        struct Data { /* ... */ };  // Defines nested Data
        struct Data;                // OK: Redeclares nested Data
    };
The final redeclaration is invalid according to 9.2  class.mem paragraph 1 last sentence.

Proposed resolution: Remove the line

        struct Data;                // OK: Redeclares nested Data

See also Core issue 36 and Core issue 56.




132. Local types and linkage

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

3.5  basic.link paragraph 8 says,

A name with no linkage (notably, the name of a class or enumeration declared in a local scope (3.3.2  basic.scope.local )) shall not be used to declare an entity with linkage.
This wording does not, but should, prohibit use of an unnamed local type in the declaration of an entity with linkage. For example,
    void f() {
        extern struct { } x;  // currently allowed
    }

Proposed resolution:Change the text in 3.3  basic.scope paragraph 8 from:

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



89. Object lifetime does not account for reference rebinding

Section: 3.8  basic.life     Status: ready     Submitter: AFNOR     Date: 27 Oct 1998

From J16/98-0026 = WG21 N1169, "Proposed Defect Reports on ISO/IEC 14882, Programming Languages - C++":
A reference is rebindable. This is surprising and unnatural. This can also cause subtle optimizer bugs.

Example:

    struct T {
        int& ri;
        T (int& r) : ri (r) { }
    };
    
    void bar (T*);
    
    void foo () {
        int i;
        T x (i);
        x.ri = 3;   // the optimizer understands that this is really i = 3
        bar (&x);
        x.ri = 4;   // optimizer assumes that this writes to i, but this is incorrect
    }
    
    int gi;
    
    void bar (T* p) {
        p->~T ();
        new (p) T (gi);
    }
If we replace T& with T* const in the example then undefined behavior result and the optimizer is correct.

Proposal: make T& equivalent to T* const by extending the scope of 3.8  basic.life paragraph 9 to references.

(See also J16/99-0005 = WG21 N1182, "Proposed Resolutions for Core Language Issues 6, 14, 20, 40, and 89")

In addition, Lisa Lippincott pointed out the following example:

    void f( const bool * );
    void g();

    int main() {
       const bool *b = new const bool( false );
       f(b);
       if (*b)
          g();
    }

    void f( const bool *b ) {
       new ( const_cast<bool *>(b) ) const bool( true );
    }

The proposed wording in the paper would still permit this usage and thus prevent an optimizer from eliminating the call to g().

Proposed Resolution:

Add a new bullet to the list of restrictions in 3.8  basic.life paragraph 7, following the second bullet ("the new object is of the same type..."):




149. Accessibility and ambiguity

Section: 4.10  conv.ptr     Status: ready     Submitter: Nathan Sidwell     Date: 31 Jul 1999

The Standard uses confusing terminology when referring to accessibility in connection with ambiguity. For instance:

4.10  conv.ptr paragraph 3:

If B is an inaccessible or ambiguous base ...
5.2.7  expr.dynamic.cast paragraph 8:
... has an unambiguous public base ...
10.3  class.virtual paragraph 5:
... is an unambiguous direct or indirect base ... and is accessible ...
15.3  except.handle paragraph 3:
not involving conversions to pointers to private or protected or ambiguous classes

The phrase "unambiguous public base" is unfortunate as it could mean either "an unambiguous base not considering accessibility, which is public" or "an unambiguous base considering only the publicly accessible bases." I believe the former interpretation correct, as accessibility is applied after visibility (11  class.access paragraph 4) and ambiguity is described in terms of visibility (10.2  class.member.lookup paragraph 2).

Suggested Resolution: Use the phrases "public and unambiguous," "accessible and unambiguous," "non-public or ambiguous," or "inaccessible or ambiguous" as appropriate.

Proposed resolution (10/99):




147. Naming the constructor

Section: 5.1  expr.prim     Status: ready     Submitter: John Spicer     Date: 21 Feb 1999

From paper J16/99-0010 = WG21 N1187.

5.1  expr.prim paragraph 7 says that class-name::class-name names the constructor when both class-name refer to the same class. (Note the different perspective, at least, in 12.1  class.ctor paragraph 1, in which constructors have no names and are recognized by syntactic context rather than by name.)

This formulation does not address the case of classes in which a function template is declared as a constructor, for example:

    template <class T> struct A {
        template <class T2> A(T2);
    };
    template<> template<> A<int>::A<int>(int);

Here there is an ambiguity as to whether the second template argument list is for the injected class name or for the constructor.

Suggested resolution: restate the rule as a component of name lookup. Specifically, if when doing a qualified lookup in a given class you look up a name that is the same as the name of the class, the entity found is the constructor and not the injected class name. In all other cases, the name found is the injected class name. For example:

    class B { };
    class A: public B {
        A::B ab;       // B is the inherited injected B
        A::A aa;       // Error: A::A is the constructor
    };

Without this rule some very nasty backtracking is needed. For example, if the injected class name could be qualified by its own class name, the following code would be well-formed:

    template <class T> struct A {
        template <class T2> A(T2);
        static A x;
    };
    template<> A<int>::A<int>(A<int>::x);

Here the declarator for the definition of the static data member has redundant parentheses, and it's only after seeing the declarator that the parser can know that the second A<int> is the injected class name rather than the constructor.

Proposed resolution (10/99): In 9  class paragraph 2, change

The class-name is also inserted into the scope of the class itself. For purposes of access checking the inserted class name...

to

The class-name is also inserted into the scope of the class itself; this is known as the injected-class-name. For purposes of access checking, the injected-class-name...

Also, in 3.4.3.1  class.qual, add the following before paragraph 2:

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 only be used in the declarator-id of a constructor definition that appears outside of the class definition. [Example:
    struct A { A(); };
    struct B: public A { B(); };

    A::A() { }
    B::B() { }

    B::A ba;    // object of type A
    A::A a;     // error, A::A is not a type name
end example]

Also, change 3.4  basic.lookup paragraph 3 from

Because the name of a class is inserted in its class scope (clause 9  class), the name of a class is also considered a member of that class for the purposes of name hiding and lookup.

to

The injected-class-name of a class (clause 9  class) is also considered to be a member of that class for the purposes of name hiding and lookup.

(See also issue 194.)




52. Non-static members, member selection and access checking

Section: 5.2.5  expr.ref     Status: ready     Submitter: Steve Adamczyk     Date: 13 Oct 1998

5.2.5  expr.ref paragraph 4 should make it clear that when a nonstatic member is referenced in a member selection operation, the type of the left operand is implicitly cast to the naming class of the member. This allows for the detection of access and ambiguity errors on that implicit cast.

Proposed Resolution (04/00):

  1. In 11.2  class.access.base paragraph 4, remove the following from the second note:

    If the member m is accessible when named in the naming class according to the rules below, the access to m is nonetheless ill-formed if the type of p cannot be implicitly converted to type T (for example, if T is an inaccessible base class of p's class).
  2. Add the following as a new paragraph 5 of 11.2  class.access.base:

    If a class member access operator, including an implicit "this->," is used to access a nonstatic data member or nonstatic member function, the reference is ill-formed if the left operand (considered as a pointer in the "." operator case) cannot be implicitly converted to a pointer to the naming class of the right operand. [Note: this requirement is in addition to the requirement that the member be accessible as named.]
  3. In 11.2  class.access.base paragraph 4, fix a typographical error by adding the missing right parenthesis following the text

    (including cases where an implicit "this->" is added
  4. Add following the first sentence of 5.2.2  expr.call paragraph 4:

    If the function is a nonstatic member function, the "this" parameter of the function (9.3.2  class.this) shall be initialized with a pointer to the object of the call, converted as if by an explicit type conversion (5.4  expr.cast). [Note: there is no access checking on this conversion; the access checking is done as part of the (possibly implicit) class member access operator. See 11.2  class.access.base.]



53. Lvalue-to-rvalue conversion before certain static_casts

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

Section 5.2.9  expr.static.cast paragraph 6 should make it clear that when any of the "inverse of any standard conversion sequence" static_casts are done, the operand undergoes the lvalue-to-rvalue conversions first.

Proposed Resolution (04/00):

In 5.2.9  expr.static.cast paragraph 6, change

can be performed explicitly using static_cast subject to the restriction that the explicit conversion does not cast away constness (5.2.11  expr.const.cast), ...

to

can be performed explicitly using static_cast. The lvalue-to-rvalue (4.1  conv.lval), array-to-pointer (4.2  conv.array), and function-to-pointer (4.3  conv.func) conversions are applied to the operand. Such a static_cast is subject to the restriction that it does not cast away constness (5.2.11  expr.const.cast), ...



128. Casting between enum types

Section: 5.2.9  expr.static.cast     Status: ready     Submitter: Clark Nelson     Date: 10 June 1999

According to 7.2  dcl.enum paragraph 9, it is permitted to convert from one enumeration type to another. However, neither 5.2.9  expr.static.cast nor 5.4  expr.cast allows this conversion.

Proposed resolution (10/99): Change the first two sentences of 5.2.9  expr.static.cast paragraph 7 to read

A value of integral or enumeration type can be explicitly converted to an enumeration type. The value is unchanged if the original value is within the range of the enumeration values (7.2  dcl.enum ).



137. static_cast of cv void*

Section: 5.2.9  expr.static.cast     Status: ready     Submitter: Mike Miller     Date: 13 July 1999

According to 5.2.9  expr.static.cast paragraph 10,

An rvalue of type "pointer to cv void" can be explicitly converted to a pointer to object type.
No requirements are stated regarding the cv-qualification of the pointer to object type. Contrast this with the formula used in paragraphs 5, 8, and 9, where the treatment of cv-qualification is explicit, requiring that the target type be at least as cv-qualified as the source. There is an apparently general requirement on all forms of static_cast in 5.2.9  expr.static.cast paragraph 1 that it "shall not cast away constness." Assuming that this restriction applies to paragraph 10, since there is no explicit exception to the general rule, that still leaves open the question of whether one can "cast away volatility" in a conversion from volatile void* to a pointer to object type. Should 5.2.9  expr.static.cast paragraph 10 be rewritten to handle cv-qualification in the same way as paragraphs 5, 8, and 9?

Proposed resolution (10/99): Change the first sentence of 5.2.9  expr.static.cast paragraph 10 to

An rvalue of type "pointer to cv1 void" can be converted to an rvalue of type "pointer to cv2 T", where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1.



127. Ambiguity in description of matching deallocation function

Section: 5.3.4  expr.new     Status: ready     Submitter: Alexander Schiemann     Date: 8 June 1999

If a placement allocation function has default arguments for all its parameters except the first, it can be called using non-placement syntax. In such a case, it is not clear whether the deallocation function to be called if the constructor terminates by throwing an expression is determined on the basis of the syntax of the new-expression (i.e., a non-placement deallocation function) or the declaration of the selected (placement) allocation function. 5.3.4  expr.new paragraph 19 indicates that the deallocation function must match the declaration of the allocation function. However, 15.2  except.ctor says that the distinction is based on whether the new-expression contains a new-placement or not.

Proposed resolution (04/00):

In 15.2  except.ctor paragraph 2, replace

If the object or array was allocated in a new-expression and the new-expression does not contain a new-placement, the deallocation function (3.7.3.2  basic.stc.dynamic.deallocation, 12.5  class.free) is called to free the storage occupied by the object; the deallocation function is chosen as specified in 5.3.4  expr.new. If the object or array was allocated in a new-expression and the new-expression contains a new-placement, the storage occupied by the object is deallocated only if an appropriate placement operator delete is found, as specified in 5.3.4  expr.new.

with

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.



179. Function pointers and subtraction

Section: 5.7  expr.add     Status: ready     Submitter: Mike Miller     Date: Nov 1999

5.7  expr.add paragraph 8 explicitly allows subtraction of two pointers to functions:

If two pointers point to the same object or function... and the two pointers are subtracted...
However, 5.7  expr.add paragraph 2 requires that two pointers that are subtracted be pointers to an object type; function pointers are not allowed.

Being able to subtract two pointers to functions doesn't seem terribly useful, especially considering that subtracting two pointers to different functions appears to produce undefined behavior rather than simply a non-zero result, according to paragraph 6:

Unless both pointers point to elements of the same array object, or one past the last element of the array object, the behavior is undefined.

Suggested resolution: Remove the words or function from paragraph 8.




73. Pointer equality

Section: 5.10  expr.eq     Status: ready     Submitter: Nathan Myers     Date: 13 Nov 1998

Nathan Myers: In 5.10  expr.eq , we have:

Pointers to objects or functions of the same type (after pointer conversions) can be compared for equality. Two pointers of the same type compare equal if and only if they are both null, both point to the same object or function, or both point one past the end of the same array.
What does this say, when we have
    int i[1];
    int j[1];
about the expression (i+1 == j) ? It seems to require padding between i[0] and j[0] so that the comparison will come out false.

I think this may be a defect, in that the quoted paragraph extends operator=='s domain too far beyond operator<'s. It should permit (but not require) an off-the-end pointer to compare equal to another object, but not to any element of the same array.

Mike Miller: I think this is reading more into the statement in 5.10  expr.eq paragraph 1 than is actually there. What does it mean for a pointer to "point to" an object? I can't find anything that definitively says that i+1 cannot "point to" j[0] (although it's obviously not required to do so). If i+1 is allowed to "point to" j[0], then i+1==j is allowed to be true, and there's no defect. There are places where aliasing is forbidden, but the N+1th element of an array doesn't appear to be one of them.

To put it another way, "points to" is undefined in the Standard. The only definition I can think of that encompasses the possible ways in which a pointer can get its value (e.g., the implementation-defined conversion of an arbitrary integer value to a pointer) is that it means "having the same value representation as would be produced by applying the (builtin) & operator to an lvalue expression designating that object". In other words, if the bits are right, it doesn't matter how you produced the value, as long as you didn't perform any operations that have undefined results. The expression i+1 is not undefined, so if the bits of i+1 are the same as those of &j[0], then i+1 "points to" j[0] and i+i==j is allowed to be true.

Tom MacDonald: C9X contains the following words for the "==" operator:

Two pointers compare equal if both are null pointers, both are pointers to the same object (including a pointer to an object and a subobject at its beginning) or function, both are pointers to one past the last element of the same array object, or one is a pointer to one past the end of one array object and the other is a pointer to the start of a different array object that happens to immediately follow the first array object in the address space.
Matt Austern: I don't think there's anything wrong with saying that the result of
    int x[1];
    int y[1]; 
    std::cout << (y == x + 1) << std::endl;
is implementation defined, or even that it's undefined.

Mike Miller: A similar question could be raised about different objects that (sequentially) share the same storage. Consider the following:

    struct B {
        virtual void f();
    };
    struct D1: B { };
    struct D2: B { };
    void g() {
        B* bp1 = new D1;
        B* bp2 = new (bp1) D2;
        bp1 == bp2; // ???
    }
Section 3.8  basic.life paragraph 5 does not list this kind of comparison among the pointer operations that cause undefined behavior, so presumably the comparison is allowed. However, 5.10  expr.eq paragraph 1 describes pointer comparison in terms of "[pointing] to the same object," which bp1 and bp2 clearly do not do. How should we describe the result of this comparison?

Jason Merrill: When you consider comparing pointers to void, this seems to suggest that no two objects can have the same address, depending on your interpretation of "point to the same object." This would cripple the empty base optimization.

3.9.2  basic.compound refers to 'pointers to void or objects or functions'. In that case, 5.10  expr.eq does not allow you to compare them; it only allows comparing pointers to objects and functions.

Proposed Resolution:

(See also paper J16/00-0011 = WG21 N1234.)




188. Comma operator and rvalue conversion

Section: 5.18  expr.comma     Status: ready     Submitter: Mike Miller     Date: 20 Dec 1999

Given

    char arr[100];
    sizeof(0,arr);

What does the sizeof expression return? According to 5.18  expr.comma paragraph 1, the comma operator yields an lvalue if the second argument is an lvalue. Since 4.2  conv.array paragraph 1 says that the array-to-pointer conversion yields an rvalue, it seems that sizeof should see an array type and give the answer 100. If so, the value of the sizeof expression would be different from that of the corresponding expression in C, but there is nothing in Annex C  diff to indicate that an incompatible change was intended.

Proposed resolution (04/00):

Add the following as paragraph 3 of C.1.3  diff.expr:

5.16, 5.17, 5.18

Change: The result of a conditional expression, an assignment expression, or a comma expression may be an lvalue.
Rationale: C++ is an object-oriented language, placing relatively more emphasis on lvalues. For example, functions may return lvalues.
Effect on original feature: Change to semantics of well-defined feature. Some C expressions that implicitly rely on lvalue-to-rvalue conversions will yield different results. For example,

    char arr[100];
    sizeof(0, arr)
yields 100 in C++ and sizeof(char*) in C.
Difficulty of converting: Programs must add explicit casts to the appropriate rvalue.
How widely used: Rare.




94. Inconsistencies in the descriptions of constant expressions

Section: 5.19  expr.const     Status: ready     Submitter: Mike Miller     Date: 8 Feb 1999
  1. According to 9.4.2  class.static.data paragraph 4, a static const integral or const enumeration data member initialized with an integral constant expression "can appear in integral constant expressions within its scope" [emphasis mine]. This means that the following is not permitted:
        struct S {
            static const int c = 5;
        };
        int a[S::c];    // error: S::c not in scope
    
    Is this restriction intentional? If so, what was the rationale for the restriction?

    Bjarne Stroustrup: I think that once you have said S::, c is in scope so that

        int a[S::c];
    
    is ok.

    Mike Miller: I'd like to think that's what it meant, but I don't believe that's what it said. According to 3.3  basic.scope paragraph 1, the scope of a name is the region "in which that name may be used as an unqualified name." You can, indeed, use a qualified name to refer to a name that is not in scope, but that only goes to reinforce my point that "S::c" is not in scope at the point where the expression containing it is used. I think the phrase "within its scope" is at best misleading and should be removed. (Unless there's a reason I'm missing for restricting the use of static member constants to their scope.)

  2. According to 5.19  expr.const paragraph 1, integral constant expressions can "involve...const variables or static data members of integral or enumeration types initialized with constant expressions." However, in 5.19  expr.const paragraph 3, arithmetic constant expressions cannot include them. This seems a rather gratuitous distinction and one likely to bite programmers trained always to use const variables instead of preprocessor definitions. Again, is there a rationale for the difference?

    As far as I can tell from 5.19  expr.const paragraph 2, "arithmetic constant expressions" (as distinct from "integral constant expressions") are used only in static initializers to distinguish between static and dynamic initialization. They include floating point types and exclude non-type template parameters, as well as the const variables and static data members.

  3. There is a minor error in 5.19  expr.const paragraph 2. The first sentence says, "Other expressions are considered constant expressions only for the purpose of non-local static object initialization." However, 6.7  stmt.dcl paragraph 4 appears to rely on the same definition dealing with the initialization of local static objects. I think that the words "non-local" should be dropped and a cross reference to 6.7  stmt.dcl added.

  4. 5.19  expr.const paragraph 4 says, "An expression that designates the address of a member or base class of a non-POD class object (clause 9) is not an address constant expression (12.7  class.cdtor )."

    I'm guessing that should be "non-static member," like the similar prohibition in 12.7  class.cdtor regarding out-of-lifetime access to members of non-POD class objects.

Proposed Resolutions:

  1. Remove the phrase "within its scope" in 9.4.2  class.static.data paragraph 4.

  2. Replace 5.19  expr.const paragraph 3 with the following:
    An arithmetic constant expression shall satisfy the requirements for an integral constant expression, except that
    • floating literals need not be cast to integral or enumeration type, and
    • conversions to floating point types are permitted.
  3. This is not a defect; no change is required. The suggested wording would be more accurate, but since the effect on local initialization is unobservable the current wording is adequate.

  4. Change the sentence in 5.19  expr.const paragraph 4 to "An expression that designates the address of a subobject of a non-POD class object is not an address constant expression."




69. Storage class specifiers on template declarations

Section: 7.1.1  dcl.stc     Status: ready     Submitter: Mike Ball     Date: 17 Oct 1998

Mike Ball: I cannot find anything in the standard that tells me the meaning of a storage-class-specifier on a function template declaration. In particular, there is no indication what effect, if any, it has on the storage class of the instantiations.

There is an explicit prohibition of storage-class-specifiers on explicit specializations.

For example, if we have

    template<class T> static int foo(T) { return sizeof(T); }
does this generate static functions for all instantiations? By 7.1.1  dcl.stc the storage class applies to the name declared in the declarator, which is the template foo, not an instantiation of foo, which is named with a template-id. There is a statement in clause 14 that template names have linkage, which supports the contention that "static" applies to the template, not to instantiations.

So what does the specifier mean? Lacking a direct statement in the standard, I see the following posibilities, in my preference order.

  1. storage-class-specifiers have no meaning on template declarations, their use being subsumed by "export" (for the template name) and the unnamed namespace (for instantiations)
  2. storage-class-specifiers have no effect on the template name, but do affect the linkage of the instantiations, though this now applies linkage to template-ids, which I can find no support for. I suspect this is what was intended, though I don't remember
Of course, if anybody can find some concrete statement, that would settle it.

From John Spicer

The standard does say that a namespace scope template has external linkage unless it is a function template declared "static". It doesn't explicitly say that the linkage of the template is also the linkage of the instantiations, but I believe that is the intent. For example, a storage class is prohibited on an explicit specialization to ensure that a specialization cannot be given a different storage class than the template on which it is based.

Mike: This makes sense, but I couldn't find much support in the document. Sounds like yet another interpretation to add to the list.

John: Agreed.

The standard does not talk about the linkage of instantiations, because only "names" are considered to have linkage, and instances are not really names. So, from an implementation point of view, instances have linkage, but from a language point of view, only the template from which the instances are generated has linkage.
Mike: Which is why I think it would be cleaner to eliminate storage class specifiers entirely and rely on the unnamed namespace. There is a statement that specializations go into the namespace of the template. No big deal, it's not something it says, so we live with what's there.

John: That would mean prohibiting static function templates. I doubt those are common, but I don't really see much motivation for getting rid of them at this point.

"export" is an additional attribute that is separate from linkage, but that can only be applied to templates with external linkage.
Mike: I can't find that restriction in the standard, though there is one that templates in an unnamed namespace can't be exported. I'm pretty sure that we intended it, though.

John: I can't find it either. The "inline" case seems to be addressed, but not static. Surely this is an error as, by definition, a static template can't be used from elsewhere.

Proposed resolution:

Change text in 14  temp paragraph 4 from:
A template name may have linkage (3.5  basic.link).
to:
A template name has linkage (3.5  basic.link). A non-member function template can have internal linkage; any other template name shall have external linkage. Entities generated from a template with internal linkage are distinct from all entities generated in other translation units.



171. Global namespace scope

Section: 7.3  basic.namespace     Status: ready     Submitter: Greg Lutz     Date: 19 Sep 1999

7.3  basic.namespace paragraph 2 says:

A name declared outside all named namespaces, blocks (6.3  stmt.block ) and classes (clause 9  class ) has global namespace scope (3.3.5  basic.scope.namespace ).
But 3.3.5  basic.scope.namespace paragraph 3 says:
A name declared outside all named or unnamed namespaces (7.3  basic.namespace ), blocks (6.3  stmt.block ), function declarations (8.3.5  dcl.fct ), function definitions (8.4  dcl.fct.def ) and classes (clause 9  class ) has global namespace scope (also called global scope).
7.3  basic.namespace should evidently be changed to match the wording in 3.3.5  basic.scope.namespace — the unnamed namespace is not global scope.

Proposed resolution (10/99):

  1. Replace the first sentence of 3.3.5  basic.scope.namespace paragraph 3 with

    The outermost declarative region of a translation unit is also a namespace, called the global namespace. A name declared in the global namespace has global namespace scope (also called global scope).
  2. In the last sentence of the same paragraph, change "Names declared in the global namespace scope" to "Names with global namespace scope."

  3. Replace 7.3  basic.namespace paragraph 2 with

    The outermost declarative region of a translation unit is a namespace; see 3.3.5  basic.scope.namespace.




166. Friend declarations of template-ids

Section: 7.3.1.2  namespace.memdef     Status: ready     Submitter: John Spicer     Date: 8 Sep 1999

John Spicer: I believe the standard is not clear with respect to this example:

    namespace N {
      template <class T> void f(T);
      namespace M {
        struct A {
          friend void f<int>(int);  // okay - refers to N::f
        };
      }
    }
At issue is whether the friend declaration refers to N::f, or whether it is invalid.

A note in 3.3.1  basic.scope.pdecl paragraph 6 says

friend declarations refer to functions or classes that are members of the nearest enclosing namespace ...
I believe it is intended to mean unqualified friend declarations. Certainly friend void A::B() need not refer to a member of the nearest enclosing namespace. Only when the declarator is unqualified (i.e., it is a declaration and not a reference) does this rule need to apply. The presence of an explicit template argument list requires that a previous declaration be visible and renders this a reference and not a declaration that is subject to this rule.

Mike Miller: 7.3.1.2  namespace.memdef paragraph 3 says,

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.
On the other hand, the friend declaration would be a syntax error if f weren't declared as a template name; it would seem very strange not to find the declaration that made the friend declaration syntactically correct. However, it also seems strange to treat this case differently from ordinary functions and from templates:
    namespace N {
      template <class T> void f(T);
      void g();
      namespace M {
        struct A {
          friend void f<int>(int);               // N::f
          template <class T> friend void f(T);   // M::f
          friend void g();                       // M::g
        };
      }
    }

John Spicer: This section refers to "looking for a prior declaration". This gets back to an earlier discussion we've had about the difference between matching two declarations of the same name and doing name lookup. I would maintain that in f<int> the f is looked up using a normal lookup. In practice, this is really how it has to be done because the declaration could actually be f<int>::x.

Proposed resolution (10/99): In 7.3.1.2  namespace.memdef paragraph 3, change

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.
to
When looking for a prior declaration of a class or a function declared as a friend, and when the name of the friend class or function is neither a qualified name nor a template-id, scopes outside the innermost enclosing namespace scope are not considered.
Also, change the example in that paragraph as follows:
    void h(int);
    template <class T> void f2(T);
    namespace A {
        class X {
            friend void f(X);       // A::f(x) is a friend
            friend void f2<>(int);  // ::f2<>(int) is a friend
    ...

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




135. Class type in in-class member function definitions

Section: 8.3.5  dcl.fct     Status: ready     Submitter: Gabriel Netterdag     Date: 1 July 1999

3.2  basic.def.odr paragraph 4 and 8.3.5  dcl.fct paragraph 6 indicate that the return type and parameter types must be complete in a function definition. However, when 9.2  class.mem paragraph 2 lists the contexts in a class member-specification in which the class is considered complete, the return type and parameter types of a member function defined in the class definition are not included. It thus appears that the following example is ill-formed:

    struct S {
        S f() { return S(); }    // error: incomplete return type
        void g(S) { }            // error: incomplete parameter type
    };
Jack Rouse: I suggest supplementing the text in 8.3.5p6 with something like:
The type of a parameter or the return type for a function definition shall not be an incomplete class type unless the function definition is nested in the member-specification for that class (including definitions in nested classes defined within the class).

Proposed resolution (10/99): Replace the last sentence of 8.3.5  dcl.fct paragraph 6 with

The type of a parameter or the return type for a function definition shall not be an incomplete class type (possibly cv-qualified) unless the function definition is nested within the member-specification for that class (including definitions in nested classes defined within the class).



1. What if two using-declarations refer to the same function but the declarations introduce different default-arguments?

Section: 8.3.6  dcl.fct.default     Status: ready     Submitter: Bill Gibbons     Date: unknown

3.3  basic.scope paragraph 4 says:

Given a set of declarations in a single declarative region, each of which specifies the same unqualified name,
8.3.6  dcl.fct.default paragraph 9 says:
When a declaration of a function is introduced by way of a using-declaration (7.3.3  namespace.udecl), any default argument information associated with the declaration is imported as well.
This is not really clear regarding what happens in the following case:
    namespace A {
            extern "C" void f(int = 5);
    }
    namespace B {
            extern "C" void f(int = 7);
    }
     
    using A::f;
    using B::f;
     
    f(); // ???
Proposed Resolution:

Add the following at the end of 13.3.3  over.match.best:

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]




217. Default arguments for non-template member functions of class templates

Section: 8.3.6  dcl.fct.default     Status: ready     Submitter: Martin Sebor     Date: 22 Mar 2000

According to 8.3.6  dcl.fct.default paragraphs 4 and 6,

For non-template functions, default arguments can be added in later declarations of a function in the same scope.

The default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition.

This would appear to allow the following example, in which a default argument is added to a non-template member function of a class template:

    template <class T>
    struct S
    {
	void foo (int);
    };

    template <class T>
    void S<T>::foo (int = 0) { }

John Spicer: The wording "non-template functions" is somewhat unclear with respect to member functions of class templates, but I know that this was intended to include them because it originates from issue 3.13 of the template issues list that I maintained for several years.

Having said that, the rationale for this restriction has since been made obsolete, so this could (in theory) be changed in the standard if it is problematic for users.

(See also issue 205.)

Proposed resolution (04/00):

In 8.3.6  dcl.fct.default paragraph 6, replace

The default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition.

with

Except for member functions of class templates, the default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition. Default arguments for a member function of a class template must be specified on the initial declaration of the member function within the class template.



78. Section 8.5 paragraph 9 should state it only applies to non-static objects

Section: 8.5  dcl.init     Status: ready     Submitter: Judy Ward     Date: 15 Dec 1998

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.
It should be made clear that this paragraph does not apply to static objects.

Proposed resolution (10/99): In 8.5  dcl.init paragraph 9, replace

Otherwise, if no initializer is specified for an object..."
with
Otherwise, if no initializer is specified for a non-static object...



148. POD classes and pointers to members

Section: class     Status: ready     Submitter: Nathan Sidwell     Date: 31 Jul 1999

3.9  basic.types paragraph 10 defines pointer to member types to be scalar types. It also defines scalar types to be one of the POD types.

class paragraph 4 defines a POD struct as an aggregate class with no non-static data members of type pointer to member.

It seems contradictory that a type can be POD, yet a class containing that type is non-POD.

Suggested resolution: Alter 9  class paragraph 4 to allow pointer to member objects as non-static data members of POD class.

Proposed resolution (04/00):

In 9  class paragraph 4, remove all occurrences of "pointer to member."




176. Name injection and templates

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

There is some controversy about whether class name injection applies to class templates. If it does apply, what is injected? Is a class name injected or is the thing that is injected actually a template?

Clause 9  class paragraph 2 says,

The class-name is also inserted into the scope of the class itself.
In general, clause 9 applies to both classes and class templates, so I would take this to mean that class name imjection does indeed apply to class templates. One problem with this is that clause 9 uses the syntactic term class-name, which I would take to imply that the inserted name is always a class. This is clearly unacceptable for class templates as it makes the template itself unusable from with the template. For example:
    template <class T> struct A {
        A<T*> ptr;    // Invalid: A refers to a class
    };

Clearly the injected name must be usable as both a class and a class template. This kind of magic already exists in the standard. In 14.6.1  temp.local it says,

Within the scope of a class template, when the name of the template is neither qualified nor followed by <, it is equivalent to the name of the template followed by the template-parameters enclosed in <>.

The proposal here is that we clarify that name injection does indeed apply to class templates, and that it is the injected name that has the special property of being usable as both a class and a template name (as described in 14.6.1  temp.local ). This would eliminate the need for special wording regarding the qualification of the name, but would achieve the same result. This would also make this "special" name available to a derived class of a class template — something which is necessary if the benefits of class name injection are to be made uniformly available for class templates, too.

    template <class T> struct Base {
        Base* p;
        Base<T*>* p2;
        ::Base* p3;    // Error: only injected name usable as class
    };

    template <class T> struct Derived: public Base<T> {
        Base* p;    // Now okay
        Base<T*>* p2;    // Still okay
        Derived::Base* p3;    // Now okay
Note that by giving the special attribute of being usable as both a class and a template to the injected name it is now clear where this attribute can and cannot be used.

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

Proposed resolution (04/00):

[Note: these changes depend on the resolution for issue 147.]

Replace 14.6.1  temp.local paragraphs 1 and 2 with the following:

Like normal (non-template) classes, class templates have an injected-class-name (clause 9  class). 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.

Within the scope of a class template specialization or partial specialization, when the injected-class-name is not followed by a <, it is equivalent to the injected-class-name followed by the template-arguments of the class template specialization or partial specialization enclosed in <>. [Example:

    template<class T> class Y;
    template<> class Y<int> {
        Y* p;          // meaning Y<int>
        Y<char>* q;    // meaning Y<char>
    };

end example]

The injected-class-name of a class template or class template specialization can be used either with or without a template-argument-list wherever it is in scope. [Example:

    template <class T> struct Base {
        Base* p;
    };

    template <class T> struct Derived: public Base<T> {
        typename Derived::Base* p;  // meaning Derived::Base<T>
    };

end example]

A lookup that finds an injected-class-name (10.2  class.member.lookup) can result in an ambiguity in certain cases (for example, if it is found in more than one base class). If all of the injected-class-names that are found refer to specializations of the same class template, and if the name is followed by a template-argument-list, the reference refers to the class template itself and not a specialization thereof, and is not ambiguous. [Example:

    template <class T> struct Base { };
    template <class T> struct Derived: Base<int>, Base<char> {
        typename Derived::Base b;            // error: ambiguous
        typename Derived::Base<double> d;    // OK
    };

end example]

When the normal name of the template (i.e., the name from the enclosing scope, not the injected-class-name) is used without a template-argument-list, it refers to the class template itself and not a specialization of the template. [Example:

    template <class T> class X {
        X* p;         // meaning X<T>
        X<T>* p2;
        X<int>* p3;
        ::X* p4;      // error: missing template argument list
                      // ::X does not refer to the injected-class-name
    };

end example]




80. Class members with same name as class

Section: 9.2  class.mem     Status: ready     Submitter: Jason Merrill     Date: 5 Dec 1998

Between the May '96 and September '96 working papers, the text in 9.2  class.mem paragraph 13:

If T is the name of a class, then each of the following shall have a name different from T:
was changed by removing the word 'static'. Looking over the meeting minutes from Stockholm, none of the proposals seem to include this change, which breaks C compatibility and is not mentioned in the compatibility annex. Was this change actually voted in by the committee?

Specifically, this breaks /usr/include/netinet/in.h under Linux, in which "struct ip_opts" shares its name with one of its members.

Proposed resolution (10/99):

  1. Change the first bullet of 9.2  class.mem paragraph 13 to say
  2. Add another paragraph before 9.2  class.mem paragraph 14, reading
    In addition, if class T has a user-declared constructor (12.1  class.ctor ), every nonstatic data member of class T shall have a name different from T.



190. Layout-compatible POD-struct types

Section: 9.2  class.mem     Status: ready     Submitter: Steve Adamczyk     Date: 20 Dec 1999

The definition of layout-compatible POD-struct types in 9.2  class.mem paragraph 14 requires that the two types

have the same number of members, and corresponding members (in order) have layout-compatible types (3.9).
There does not appear to be any reason for including member functions and static data members in this requirement. It would be more logical to require only that the non-static data members of the two types must match.

The characteristics of layout-compatible types are not well described in the current wording, either. Apart from their use in 9.2  class.mem paragraph 16 to define the term "common initial sequence," there appears to be nothing said about which operations are possible between objects of layout-compatible types. For example, 3.9  basic.types paragraphs 2-3 give certain guarantees regarding use of memcpy on objects of the same type; it might be reasonable to assume that the same kinds of guarantees might apply to objects of layout-compatible types, but that is not said. Similarly, 3.10  basic.lval paragraph 15 describes permissible "type punning" but does not mention layout-compatible types.

Proposed resolution (04/00):

In 9.2  class.mem paragraphs 14 and 15, change all occurrences of "members" to "nonstatic data members."




142. Injection-related errors in access example

Section: 11.2  class.access.base     Status: ready     Submitter: Steve Adamczyk     Date: 16 Jul 1999

In the example in paragraph 3 of 11.2  class.access.base , all the references to B in DD::f() should be replaced by ::B. The reason is that the class name B is private in D and thus inaccessible in DD. (The example was probably not updated when class name injection was added.)

Proposed resolution (10/99): Replace the example in 11.2  class.access.base paragraph 3 with:

    class B {
    public:
        int mi;                 // nonstatic member
        static int si;          // static member
    };
    class D: private B {
    };
    class DD: public D {
        void f();
    };
    void DD::f() {
        mi = 3;                 // error: mi is private in D
        si = 3;                 // error: si is private in D
        ::B b;
        b.mi = 3;               // OK (b.mi is different from this->mi)
        b.si = 3;               // OK (b.si is different from this->si)
        ::B::si = 3;            // OK
        ::B* bp1 = this;        // error: B is a private base class
        ::B* bp2 = (::B*)this;  // OK with cast
        bp2->mi = 3;            // OK: access through a pointer to B
    }



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

Section: 11.4  class.friend     Status: ready     Submitter: Judy Ward     Date: 15 Dec 1998

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/00):

  1. In 11.4  class.friend paragraph 1, change

    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. 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.
    to
    A class may give access to its private and protected members to functions and classes that might not otherwise have access by making those functions and classes friends. Friendship is indicated by a friend declaration within the class, but such a declaration does not introduce a member.

    Also, following the example, add the sentence:

    A class that is a member of another class does not gain any special access to the enclosing class. However, such member classes may be declared as friends of the enclosing class.

  2. In 11.4  class.friend paragraph 2, add the following lines to the second example:

    class W {
        class X;         // declare a member class, distinct from ::X
        friend class X;  // make it a friend
        class X {        // define the member class
            int var;
        };
    };
    
    Note that without the initial declaration of W::X, the friend declaration would declare ::X as a friend of ::W.




209. Must friend declaration names be accessible?

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

11.4  class.friend, paragraph 7, says

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

Does that mean the following should be illegal?

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

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

Suggested resolution:

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




194. Identifying constructors

Section: 12.1  class.ctor     Status: ready     Submitter: Jamie Schmeiser     Date: 11 Jan 2000

According to 12.1  class.ctor paragraph 1, the syntax used in declaring a constructor allows at most one function-specifier. It is thus not permitted to declare a constructor both inline and explicit. This seems overly restrictive.

On a related note, there doesn't seem to be any explicit prohibition against member functions with the same name as the class. (Such a prohibition might reasonably be expected to occur in 9.2  class.mem paragraph 13, but member functions are not listed there.)

One possible interpretation would be that such member functions would violate the restrictions in 3.3.6  basic.scope.class paragraph 1, because the class name would refer to the class at some points in the class scope and to the member function at others. However, this seems a bit tenuous. Is an explicit prohibition needed?

(See also issue 147.)

Proposed resolution (04/00):

  1. Add to 9.2  class.mem paragraph 13

    • every member function of class T [Note: this restriction does not apply to constructors, which do not have names (12.1  class.ctor). ];

    immediately following the line

    • every data member of class T;
  2. Change 12.1  class.ctor paragraph 1 from

    A special declarator syntax using an optional function-specifier (7.1.2  dcl.fct.spec)...

    to

    A special declarator syntax using an optional sequence of function-specifiers (7.1.2  dcl.fct.spec)...



152. explicit copy constructors

Section: 12.3.1  class.conv.ctor     Status: ready     Submitter: Steve Adamczyk     Date: 4 August 1999

Can a copy-constructor declared as explicit be used to copy class values implicitly? For example,

   struct X {
      X();
      explicit X(const X&);
   };
   void f(X);
   int main() { X x; f(x); }
According to 12.3.1  class.conv.ctor paragraphs 2-3,
An explicit constructor constructs objects just like non-explicit constructors, but does so only where the direct-initialization syntax (8.5  dcl.init ) or where casts (5.2.9  expr.static.cast , 5.4  expr.cast ) are explicitly used... A copy-constructor (12.8  class.copy ) is a converting constructor. An implicitly-declared copy constructor is not an explicit constructor; it may be called for implicit type conversions.
This passage would appear to indicate that the call in the example is ill-formed, since it uses neither the direct-initialization syntax nor an explicit cast. The last sentences are especially interesting in this regard, indicating that explicit and non-explicit copy constructors are handled differently.

On the other hand, 8.5  dcl.init paragraph 14, bullet 4, sub-bullet 2 says,

If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination... [the] applicable constructors are enumerated (13.3.1.3  over.match.ctor )...
The cited passage says that
The candidate functions are all the constructors of the class of the object being initialized.

Proposed resolution (10/99): Change the first two sentences of 13.3.1.3  over.match.ctor paragraph 1 to

When objects of class type are direct-initialized, or copy-initialized from an expression of the same or a derived class type, overload resolution selects the constructor. For direct-initialization, the candidate functions are all the constructors of the class of the object being initialized. For copy-initialization, the candidate functions are all the converting constructors (12.3.1  class.conv.ctor ) of that class.



193. Order of destruction of local automatics of destructor

Section: 12.4  class.dtor     Status: ready     Submitter: Gerhard Menzl     Date: 7 Jan 2000

The Standard is not clear whether automatic objects in a destructor are destroyed before or after the destruction of the class's base and member subobjects. That is, given

    struct S { ~S(); };

    struct T {
        S x;
        ~T() {
            S y;
        };
    };

which will be destroyed first, x or y?

Proposed resolution (04/00):

In 12.4  class.dtor paragraph 6, change

A destructor for class X calls the destructors for X's direct members, ...
to
After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X's direct members, ...




185. "Named" temporaries and copy elision

Section: 12.8  class.copy     Status: ready     Submitter: Bill Wade     Date: 11 Nov 1999

12.8  class.copy paragraph 15 refers only to "temporary class objects." It needs to be made clear that these provisions do not apply to temporaries that have been bound to references. For instance,

    struct A {
        mutable int value;
        explicit A(int i) : value(i) {}
        void mutate(int i) const { value = i; }
    };

    int foo() {
        A const& t = A(1);
        A n(t);          // can this copy be elided?
        t.mutate(2);
        return n.value;  // can this return 2?
    }
The current wording seems to allow an implementation not to perform the copy in A N(t) because the source object is a temporary (created explicitly by A(1)).

Proposed resolution (04/00):

Change the wording proposed in the resolution of issue 20 from

to




84. Overloading and conversion loophole used by auto_ptr

Section: 13.3.3.1  over.best.ics     Status: ready     Submitter: Steve Adamczyk     Date: 10 Dec 1998

By the letter of the standard, the conversions required to make auto_ptr work should be accepted.

However, there's good reason to wonder if there isn't a bug in the standard here. Here's the issue: line 16 in the example below comes down to

copy-initialize an auto_ptr<Base> from an auto_ptr<Derived> rvalue
To do that, we first look to see whether we can convert an auto_ptr<Derived> to an auto_ptr<Base>, by enumerating the constructors of auto_ptr<Base> and the conversion functions of auto_ptr<Derived>. There's a single possible way to do the conversion, namely the conversion function

    auto_ptr<Derived>::operator auto_ptr<Base>()
(generated from the template). (The constructor auto_ptr<Base>(auto_ptr_ref<Base>) doesn't work because it requires a user-defined conversion on the argument.)

So far, so good. Now, we do the copy step:

direct-initialize an auto_ptr<Base> from an auto_ptr<Base> rvalue
This, as we've gone to great lengths to set up, is done by calling the conversion function
    auto_ptr<Base>::operator auto_ptr_ref<Base>()
(generated from the template), and then the constructor
    auto_ptr<Base>(auto_ptr_ref<Base>)
(generated from the template).

The problem with this interpretation is that it violates the long-standing common-law rule that only a single user-defined conversion will be called to do an implicit conversion. I find that pretty disturbing. (In fact, the full operation involves two conversion functions and two constructors, but "copy" constructors are generally considered not to be conversions.)

The direct-initialization second step of a copy-initialization was intended to be a simple copy — you've made a temporary, and now you use a copy constructor to copy it. Because it is defined in terms of direct initialization, however, it can exploit the loophole that auto_ptr is based on.

To switch to personal opinion for a second, I think it's bad enough that auto_ptr has to exploit a really arcane loophole of overload resolution, but in this case it seems like it's exploiting a loophole on a loophole.

    struct Base {                             //  2
       static void sink(auto_ptr<Base>);      //  3
    };                                        //  4

    struct Derived : Base {                   //  5
       static void sink(auto_ptr<Derived>);   //  6
    };                                        //  7

    auto_ptr<Derived> source() {              //  8
       auto_ptr<Derived> p(source());         //  9
       auto_ptr<Derived> pp(p);               // 10
       Derived::sink(source());               // 11
       p = pp;                                // 12
       p = source();                          // 13
       auto_ptr<Base> q(source());            // 14
       auto_ptr<Base> qp(p);                  // 15
       Base::sink(source());                  // 16
       q = pp;                                // 17
       q = source();                          // 18
       return p;                              // 19
       return source();
    }
Derek Inglis:

It seems clear to me that the result of this direct initilization must be the second standard conversion sequence in a user defined conversion sequence. Otherwise the resulting conversion sequence is not an implicit conversion sequence. By the letter of the standard, the sequence of conversions making up a copy-initialization must be an implicit conversion sequence.

Paragraph 3 of clause 4  conv:

An expression e can be implicitly converted to a type T if and only if the declaration "T t=e;" is well-formed, for some invented temporary variable t (8.5  dcl.init).

Paragraph 1 of 13.3.3.1  over.best.ics:

An implicit conversion sequence is a sequence of conversions used to convert an argument in a function call to the type of the corresponding parameter of the function being called. The sequence of conversions is an implicit conversion as defined in clause 4  conv, which means it is governed by the rules for initialization of an object or reference by a single expression (8.5  dcl.init, 8.5.3  dcl.init.ref).
Sentence 1 of paragraph 12 of 8.5  dcl.init:
The initialization that occurs in argument passing ... is called copy-initialization and is equivalent to the form
     T x = a;

For me, these sentences imply that all sequences of conversions permitted on a function argument must be valid implicit conversion sequences.

The 'loophole' can be closed by adding a sentence (or note) to the section describing the 'direct initialization second step of a copy initialization' stating that the copy initialization is ill-formed if the conversion sequence resulting from the direct initialization is not a standard conversion sequence.

(See also issue 177 and paper J16/00-0009 = WG21 N1232.)

Proposed resolution (04/00):

Change 13.3.3.1  over.best.ics paragraph 4 from

In the context of an initialization by user-defined conversion (i.e., when considering the argument of a user-defined conversion function; see 13.3.1.4, 13.3.1.5), only standard conversion sequences and ellipsis conversion sequences are allowed.

to

When considering the argument of a user-defined conversion function that is a candidate by 13.3.1.3 when invoked for the copying of the temporary in the second step of a class copy-initialization, or by 13.3.1.4, 13.3.1.5, or 13.3.1.6 in all cases, only standard conversion sequences and ellipsis conversion sequences are allowed.



83. Overloading and deprecated conversion of string literal

Section: 13.3.3.2  over.ics.rank     Status: ready     Submitter: Steve Adamczyk     Date: 24 Jan 1999

In 13.3.3.2  over.ics.rank , we have

This does not work right with respect to the deprecated conversion from string literal to "char *". Consider
    void f(char *);
    void f(const char *);
    
    f("abc");
The two conversion sequences differ only in their qualification conversions, and the destination types are similar. The cv-qualification signature of "char *", is a proper subset of the cv-qualification signature of "const char *", so f(char *) is chosen, which is wrong. The rule should be like the one for conversion to bool — the deprecated conversion should be worse than another exact match that is not the deprecated conversion.

Proposed resolution (10/99): Change 13.3.3.2  over.ics.rank paragraph 3 bullet 1 sub-bullet 3 from

S1 and S2 differ only in their qualification conversion and yield similar types T1 and T2 (4.4  conv.qual ), respectively, and the cv-qualification signature of type T1 is a proper subset of the cv-qualification signature of type T2.
to
S1 and S2 differ only in their qualification conversion and yield similar types T1 and T2 (4.4  conv.qual ), respectively, and the cv-qualification signature of type T1 is a proper subset of the cv-qualification signature of type T2, and S1 is not the deprecated string literal array-to-pointer conversion (4.2  conv.array ).



153. Misleading wording (rank of conversion)

Section: 13.3.3.2  over.ics.rank     Status: ready     Submitter: Valentin Bonnard     Date: 6 Aug 1999

13.3.3.2  over.ics.rank paragraph 3 bullet 1 sub-bullet 2 says,

the rank of S1 is better than the rank of S2 (by the rules defined below)...
This wording is confusing. The word "below" refers to paragraph 4 (which may not be clear), and the bulk of paragraph 4 deals with comparing conversion sequences whose "rank" is the same.

Proposed resolution (10/99): In 13.3.3.2  over.ics.rank paragraph 3, change

the rank of S1 is better than the rank of S2 (by the rules defined below)
to
the rank of S1 is better than the rank of S2, or S1 and S2 have the same rank and are distinguishable by the rules in the paragraph below



202. Use of overloaded function name

Section: 13.4  over.over     Status: ready     Submitter: Steve Clamage     Date: 2 Feb 2000

13.4  over.over paragraph 1 contains a supposedly exhaustive list of contexts in which the name of an overloaded function can be used without an argument list ("...shall not be used without arguments in contexts other than those listed"). However, 14.3.2  temp.arg.nontype paragraph 5, bullet 4 gives another context: as a template nontype argument.

Suggested resolution: Add the missing case to 13.4  over.over.

Proposed resolution (04/00):

Add as the final bullet in 13.4  over.over paragraph 1:

and adjust the "or" and final period on the preceding two bullets.




221. Must compound assignment operators be member functions?

Section: 13.5.3  over.ass     Status: ready     Submitter: Jim Hyslop     Date: 3 Apr 2000

Is the intent of 13.5.3  over.ass paragraph 1 that all assignment operators be non-static member functions (including operator+=, operator*=, etc.) or only simple assignment operators (operator=)?

Notes from 04/00 meeting:

Nearly all references to "assignment operator" in the IS mean operator= and not the compound assignment operators. The ARM was specific that this restriction applied only to operator=. If it did apply to compound assignment operators, it would be impossible to overload these operators for bool operands.

Proposed resolution (04/00):

  1. Change the title of 5.17  expr.ass from "Assignment operators" to "Assignment and compound assignment operators."

  2. Change the first sentence of 5.17  expr.ass paragraph 1 from

    There are several assignment operators, all of which group right-to-left. All require a modifiable lvalue as their left operand, and the type of an assignment expression is that of its left operand. 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.

    to

    The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue with the type and valoue of the left operand after the assignment has taken place.
  3. In 5.17  expr.ass paragraph 6, replace "assignment operator" with "assignment or compound assignment operator."




105. Meaning of "template function"

Section: 14  temp     Status: ready     Submitter: Daveed Vandevoorde     Date: 16 Apr 1999

The phrase "template function" is sometimes used to refer to a template (e.g., in 14  temp paragraph 8) and sometimes to refer to a function generated from a template (e.g., 13.4  over.over paragraph 4).

Suggested Resolution:

The phrase should mean "a function generated from a template" (or might perhaps include explicit specializations).

Proposed resolution (04/00):

In 1.3.10  defns.signature paragraph 10, replace "template function specialization" by "function template specialization".

In 13.3.1  over.match.funcs paragraph 7 and footnote, replace all instances of "template functions" by "function template specializations."

In 13.3.3  over.match.best paragraph 1, fourth bullet (counting all bullets in that paragraph), replace "template function specialization" by "function template specialization". In the fifth bullet, replace "template functions" by "function template specializations."

In 13.3.3  over.match.best paragraph 2, replace "template function" by "function template specialization."

In 13.3.3  over.match.best paragraph 4, replace "template functions" by "function template specializations" and all instances of "template function" by "function template specialization."

Change 13.4  over.over paragraph 4 from:

If more than one function is selected, any template functions in the set are eliminated if the set also contains a non-template function, and any given template function is eliminated if the set contains a second template function that is more specialized than the first according to the partial ordering rules of 14.5.5.2  temp.func.order. After such eliminations, if any, there shall remain exactly one selected function.
to:
If more than one function is selected, any function template specializations in the set are eliminated if the set also contains a non-template function, and any given function template specialization F1 is eliminated if the set contains a second function template specialization whose function template is more specialized than the function template of F1 according to the partial ordering rules of 14.5.5.2  temp.func.order. After such eliminations, if any, there shall remain exactly one selected function.

Change text in section 14  temp paragraph 8 from:

A template function declared both exported and inline is just inline and not exported.
to:
A function template declared both exported and inline is just inline and not exported.

In 14.5.3  temp.friend paragraph 1, third bullet, replace "template function" by "function template" and "function specialization" by "function template specialization."

In footnote 130 (14.5.5  temp.fct paragraph 2), replace "template functions" by "function template specializations."

In 14.5.5.2  temp.func.order paragraph 1, third bullet change "template function specialization" to "function template specialization".

In 14.8.2  temp.deduct paragraph 1, change "template function specialization" to "function template specialization".

In 17.1.5  defns.component change "non-member template functions that operate" to "non-member function templates that operate".

In 17.1.18  defns.traits change "template classes and template functions" to "class templates and function templates".

In 20.2  lib.utility paragraph 1 change:

This subclause contains some basic template functions and classes that are used throughout the rest of the library.
to:
This subclause contains some basic function and class templates that are used throughout the rest of the library.

In 20.2.2  lib.pairs paragrah 1 change "template function" to "function template".

In footnote 215 (20.3.7  lib.function.pointer.adaptors paragraph 6) change "template functions" to "function templates".

In 22.1.1  lib.locale paragraph 4 change "template function" to "function template".

In 24.1  lib.iterator.requirements paragraph 2 change "template function" to "function template".

In 24.3.3  lib.std.iterator.tags paragraph 1, change "template function" to "function template specialization."

In 24.3.4  lib.iterator.operations paragraph 1 change "template function" to "function template", and "These functions use" to "These function templates use".

In the section heading of 27.6.2.5.4  lib.ostream.inserters.character change "template functions" to "function templates".

In 17.3.1.2  lib.structure.requirements paragraph 2 change "template class name char_traits" to "class template char_traits".

In the section heading of 18.2.1.1  lib.numeric.limits change "Template class" to "Class template".

In 20.1.5  lib.allocator.requirements paragraph 3 change "template class member rebind" to "member class template rebind" and change "template typedef" to "typedef template".

In the section heading of 20.3.6.1  lib.binder.1st change "Template class" to "Class template".

In the section heading of 20.3.6.3  lib.binder.2nd change "Template class" to "Class template".

In the section heading of 20.4.5  lib.auto.ptr change "Template class" to "Class template".

In the section heading of 21.3  lib.basic.string change "Template class" to "Class template".

In 21.3  lib.basic.string paragraphs 1 and 2 change "template class basic_string" to "class template basic_string".

In the section heading of 22.2.1.1  lib.locale.ctype change "Template class" to "Class template".

In the section heading of 22.2.1.2  lib.locale.ctype.byname change "Template class" to "Class template".

In the section heading of 22.2.1.5  lib.locale.codecvt change "Template class" to "Class template".

In the section heading of 22.2.1.6  lib.locale.codecvt.byname change "Template class" to "Class template".

In the section heading of 22.2.2.1  lib.locale.num.get change "Template class" to "Class template".

In the section heading of 22.2.2.2  lib.locale.nm.put change "Template class" to "Class template".

In the section heading of 22.2.3.1  lib.locale.numpunct change "Template class" to "Class template".

In the section heading of 22.2.3.2  lib.locale.numpunct.byname change "Template class" to "Class template".

In the section heading of 22.2.4.1  lib.locale.collate change "Template class" to "Class template".

In the section heading of 22.2.4.2  lib.locale.collate.byname change "Template class" to "Class template".

In the section heading of 22.2.5.1  lib.locale.time.get change "Template class" to "Class template".

In the section heading of 22.2.5.2  lib.locale.time.get.byname change "Template class" to "Class template".

In the section heading of 22.2.5.3  lib.locale.time.put change "Template class" to "Class template".

In the section heading of 22.2.5.4  lib.locale.time.put.byname change "Template class" to "Class template".

In the section heading of 22.2.6.1  lib.locale.money.get change "Template class" to "Class template".

In the section heading of 22.2.6.2  lib.locale.money.put change "Template class" to "Class template".

In the section heading of 22.2.6.3  lib.locale.moneypunct change "Template class" to "Class template".

In the section heading of 22.2.6.4  lib.locale.moneypunct.byname change "Template class" to "Class template".

In the section heading of 22.2.7.1  lib.locale.messages change "Template class" to "Class template".

In the section heading of 22.2.7.2  lib.locale.messages.byname change "Template class" to "Class template".

In the section heading of 23.2.1  lib.deque change "Template class" to "Class template".

In the section heading of 23.2.2  lib.list change "Template class" to "Class template".

In the section heading of 23.2.3.1  lib.queue change "Template class" to "Class template".

In the section heading of 23.2.3.2  lib.priority.queue change "Template class" to "Class template".

In the section heading of 23.2.3.3  lib.stack change "Template class" to "Class template".

In the section heading of 23.2.4  lib.vector change "Template class" to "Class template".

In the section heading of 23.3.1  lib.map change "Template class" to "Class template".

In the section heading of 23.3.2  lib.multimap change "Template class" to "Class template".

In the section heading of 23.3.3  lib.set change "Template class" to "Class template".

In the section heading of 23.3.4  lib.multiset change "Template class" to "Class template".

In the section heading of 23.3.5  lib.template.bitset change "Template class" to "Class template".

In 23.3.5  lib.template.bitset paragraph 1, change "template class" to "class template".

In the section heading of 24.4.1.1  lib.reverse.iterator change "Template class" to "Class template".

In the section heading of 24.4.2.1  lib.back.insert.iterator change "Template class" to "Class template".

In the section heading of 24.4.2.3  lib.front.insert.iterator change "Template class" to "Class template".

In the section heading of 24.4.2.5  lib.insert.iterator change "Template class" to "Class template".

In 24.5  lib.stream.iterators paragraph 1, change "template classes" to "class templates".

In the section heading of 24.5.1  lib.istream.iterator change "Template class" to "Class template".

In the section heading of 24.5.2  lib.ostream.iterator [lib.ostream.iterator] change "Template class" to "Class template".

In the section heading of 24.5.3  lib.istreambuf.iterator change "Template class" to "Class template".

In 24.5.3  lib.istreambuf.iterator paragraph 1, change "template class" to "class template".

In the section heading of 24.5.3.1  lib.istreambuf.iterator::proxy change "Template class" to "Class template".

In the section heading of 24.5.4  lib.ostreambuf.iterator change "Template class" to "Class template".

In 24.5.4  lib.ostreambuf.iterator paragraph 1, change "template class" to "class template".

In 26.2  lib.complex.numbers paragraph 1, change "template class" to "class template".

In the section heading of 26.2.2  lib.complex change "Template class" to "Class template".

In 26.3.1  lib.valarray.synopsis paragraph 1, change "template classes" to "class templates" and change "function signatures" to "function templates".

In the section heading of 26.2.3  lib.complex.special change "Template class" to "Class template".

In the section heading of 26.3.5  lib.template.slice.array change "Template class" to "Class template".

In the section heading of 26.3.7  lib.template.gslice.array change "Template class" to "Class template".

In the section heading of 26.3.8  lib.template.mask.array change "Template class" to "Class template".

In the section heading of 26.3.9  lib.template.indirect.array change "Template class" to "Class template".

In 27.2  lib.iostream.forward [lib.iostream.forward] paragraphs 3 to 7, change "template classes" to "class templates".

In the section heading of 27.4.3  lib.fpos change "Template class" to "Class template".

In the section heading of 27.4.4  lib.ios change "Template class" to "Class template".

In the section heading of 27.5.2  lib.streambuf change "Template class" to "Class template".

In 27.5.2  lib.streambuf paragraphs 2 and 3, change "template class" to "class template".

In the section heading of 27.6.1.1  lib.istream change "Template class" to "Class template".

In the section heading of 27.6.1.5  lib.iostreamclass change "Template class" to "Class template".

In the section heading of 27.6.2.1  lib.ostream change "Template class" to "Class template".

In 27.7  lib.string.streams paragraph 1 change "template classes" to "class templates".

In the section heading of 27.7.1  lib.stringbuf change "Template class" to "Class template".

In the section heading of 27.7.2  lib.istringstream change "Template class" to "Class template".

In the section heading of 27.7.3.2  lib.ostringstream.members change "Template class" to "Class template".

In the section heading of 27.8.1.1  lib.filebuf change "Template class" to "Class template".

In the section heading of 27.8.1.5  lib.ifstream change "Template class" to "Class template".

In the section heading of 27.8.1.8  lib.ofstream change "Template class" to "Class template".

In the section heading of 27.8.1.11  lib.fstream change "Template class" to "Class template".




134. Template classes and declarator-ids

Section: 14  temp     Status: ready     Submitter: Mike Miller     Date: 17 June 1999

14  temp paragraph 2 says,

[Note: in a class template declaration, if the declarator-id is a template-id, the declaration declares a class template partial specialization (14.5.4  temp.class.spec ). ]
There is no declarator-id in a class template declaration (cf paragraph 3).

Proposed resolution: Replace the phrase "if the declarator-id is a template-id" with "if the class name is a template-id."




21. Can a default argument for a template parameter appear in a friend declaration?

Section: 14.1  temp.param     Status: ready     Submitter: unknown     Date: unknown

14.1  temp.param paragraph 10 says:

The set of default template-arguments available for use with a template declaration or definition is obtained by merging the default arguments from the definition (if in scope) and all declarations in scope in the same way as default function arguments are (8.3.6  dcl.fct.default )."
Can a default argument for a template argument appear in a friend declaration? If so, when is this default argument considered for template instantiations?

For example,

    template<class T1, class T2 = int> class A;
 
    class B {
        template<class T1 = int, class T2> friend class A;
    };
Is this well-formed? If it is, should the IS say when the default argument for T1 is considered for instantiations of class A?

Proposed resolution (10/99): Add to the end of 14.1  temp.param paragraph 9,

A default template-argument shall not be specified in a friend template declaration.

(See also issue 136.)




187. Scope of template parameter names

Section: 14.1  temp.param     Status: ready     Submitter: John Spicer     Date: 15 Nov 1999

At the Dublin meeting (04/99), the Committee proposed to resolve issue 22 by simply changing the wording to make clear that a template parameter cannot be used in its own default argument. This creates a third treatment of this kind of situation, in addition to 3.3.1  basic.scope.pdecl paragraph 1, where declarators are in scope and can be used in their initializers, and paragraph 3, where an enumerator is not in scope until after its complete enumerator-definition. The Dublin resolution is for the template parameter to be in scope in its default argument but not usable. It would be more consistent to treat template parameters like enumerators: simply not in scope until the entire template-parameter declaration is seen.

On a related note, 14.1  temp.param paragraph 14 should be rewritten to connect the prohibition with visibility rules; otherwise, it sounds as if the following example is not permitted:

    const int Z = 1;
    template <int X = Z, int Z> class A {};

Notes from 04/00 meeting:

The core working group did not reach consensus on the suggested approach to issue 22. However, it was agreed that the intent expressed in the earlier resolution would be better served by different wording.

Proposed resolution (04/00):

[Note: This resolution supersedes the resolution to issue 22.]

Replace 14.1  temp.param paragraph 14 as follows:

A template parameter shall not be used in its own default argument.



121. Dependent type names with non-dependent nested-name-specifiers

Section: 14.6  temp.res     Status: ready     Submitter: Bill Gibbons     Date: 28 May 1999

The wording in 14.6  temp.res paragraph 3:

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 to indicate that the qualified-name denotes a type, forming an elaborated-type-specifier (7.1.5.3  dcl.type.elab ).
was intended to say:
A qualified-id that refers to a type and in which the nested-name-specifier depends on a template-parameter (14.6.2  temp.dep ) shall ...
in much the same vein as 14.6.2.1  temp.dep.type , second bullet, first half.

Proposed resolution: As suggested.




183. typename in explicit specializations

Section: 14.6  temp.res     Status: ready     Submitter: John Spicer     Date: 9 Nov 1999

John Spicer: In 14.6  temp.res paragraph 5, the standard says

The keyword typename shall only be used in template declarations and definitions...
My understanding of the intent of this restriction is to say that typename is only allowed in contexts in which template dependent names can be found, but the wording leaves open to interpretation whether typename is allowed in an explicit specialization, such as:
    template <class T> struct A {};
    template <class T> struct B { typedef int X; };
    template <> struct A<int> {
        typename B<int>::X x;
    };
My understanding is that such usage is not permitted. This should be clarified one way or the other.

Mike Miller: I agree with your understanding that you are not allowed to use typename in an explicit specialization. However, I think the standard already says that — an explicit specialization is not a template declaration. According to the grammar in 14  temp paragraph 1, a template-declaration must have a non-empty template-parameter-list.

Nathan Myers: Is there any actual reason for this restriction? Its only apparent effect is to make it harder to specialize templates, with no corresponding benefit.

Proposed resolution (04/00):

In 14.6  temp.res paragraph 5, replace

The keyword typename shall only be applied to qualified names, but those names need not be dependent.

with

The keyword typename shall be applied only to qualified names, but those names need not be dependent. The keyword typename shall be used only in contexts in which dependent names can be used. This includes template declarations and definitions but excludes explicit specialization declarations and explicit instantiation declarations.



213. Lookup in dependent base classes

Section: 14.6.2  temp.dep     Status: ready     Submitter: John Spicer     Date: 10 Mar 2000

Paragraphs 3-4 of 14.6.2  temp.dep say, in part,

if a base class of [a class] template depends on a template-parameter, the base class scope is not examined during name lookup until the class template is instantiated... If a base class is a dependent type, a member of that class cannot hide a name declared within a template, or a name from the template's enclosing scope.

John Spicer: The wording in paragraph 4 seems particularly odd to me. It essentially changes the order in which scopes are considered. If a scope outside of the template declares a given name, that declaration hides entities of the same name from template dependent base classes (but not from nondependent base classes).

In the following example, the calls of f and g are handled differently because B::f cannot hide ::f, but B::g doesn't try to hide anything, so it can be called.

    extern "C" int printf(char *, ...);
    template <class T> struct A : T {
        void h(T t) {
            f(t);  // calls ::f(B)
            g(t);  // calls B::g
        }
    };

    struct B {
        void f(B){printf("%s", "in B::f\n");}
        void g(B){printf("%s", "in B::g\n");}
    };

    void f(B){printf("%s", "in ::f\n");}

    int main()
    {
        A<B> ab;
        B b;
        ab.h(b);
    }

I don't think the current wording in the standard provides a useful facility. The author of class A can't be sure that a given call is going to call a base class function unless the base class is explicitly specified. Adding a new global function could cause the program to suddenly change meaning.

What I thought the rule was is, "If a base class is a dependent type a member of that class is not found by unqualified lookup".

Derek Inglis: My understanding is the same except that I'd remove the word "qualified" from your sentence.

Erwin Unruh: My interpretation is based on 14.6.4  temp.dep.res and especially 14.6.4.2  temp.dep.candidate (and largely on my memory of the discussions). For all unqualified names you do something like the following algorithm:

  1. check whether it is a dependent function call
  2. Do a lookup in the definition context and remember what you found there
  3. Do a Koenig-Lookup at instantiation time
  4. perform overloading if necessary

Regarding names from base classes you cannot find them in 2) because you don't know what base class you have. You cannot find them in 3) because members of classes are not found by Koenig lookup (only namespaces are considered). So you don't find them at all (for unqualified names).

For a qualified name, you start lookup for each 'part' of the qualification. Once you reach a dependent part, you stop and continue lookup at the instantiation point. For example:

    namespace A {
      namepace B {
	template <class T> class C {
	  template <class U> class D {
	    typedef int E;
	    // ...
	  };
	};
      };
    };

    template <class T> class F : public T {
      typename A::B::C<int>::D<T>::E var1;
      typename A::B::C<T>::D<int>::E var2;
      typename F::T::X var3;
    }

For var1 you do lookup for A::B::C<int>::D at definition time, for var2 you only do lookup for A::B::C. The rest of the lookup is done at instantiation time since specialisations could change part of the lookup. Similarly the lookup for var3 stops after F::T at definition time.

My impression was that an unqualified name never refers to a name in a dependent base class.

(See also issue 197.)

Proposed resolution (04/00):

  1. In 14.6.2  temp.dep paragraph 3, replace

    In the definition of a class template or in the definition of a member of such a template that appears outside of the template definition, if a base class of this template depends on a template-parameter, the base class scope is not examined during name lookup until the class template is instantiated.

    with

    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.
  2. Remove from 14.6.2  temp.dep paragraph 4:

    If a base class is a dependent type, a member of that class cannot hide a name declared within a template, or a name from the template's enclosing scopes.



108. Are classes nested in templates dependent?

Section: 14.6.2.1  temp.dep.type     Status: ready     Submitter: Mark Mitchell     Date: 14 Apr 1999

Mark Mitchell (via John Spicer): Given:

  template <class T> struct S {
     struct I1 {
       typedef int X;
     };
     struct I2 : public I1 {
        X x;
     };
  };

Is this legal? The question really boils down to asking whether or not I1 is a dependent type. On the one hand, it doesn't seem to fit any of the qualifications in 14.6.2.1  temp.dep.type . On the other, 14.7.3  temp.expl.spec allows explicit specialization of a member class of a class template, so something like:

  template <> 
  struct S<double>::I1 {
     int X;
  };

is apparently legal. But, then, `X' no longer refers to a type name. So, it seems like `I1' should be classified as dependent. What am I missing?

Erwin Unruh: I wrote that particular piece of text and I just missed the problem above. It is intended to be a dependent type. The reasoning is that I1 is just a shorthand for S<T>::I1 which clearly is dependent.

Suggested Resolution: (Erwin Unruh)

I think the list of what is a dependent type should be extended to cover "a type declared and used within the same template" modulo of phrasing.

(See also paper J16/00-0009 = WG21 N1231. This issue is also somewhat related to issue 205: classes nested inside template classes are, in some sense, "templates," just as non-template member functions of class templates and static data members of class templates are "templates.")

Proposed resolution (04/00):

Add after 14.6.1  temp.local paragraph 2:

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]



206. Semantic constraints on non-dependent names

Section: 14.6.3  temp.nondep     Status: ready     Submitter: Mike Miller     Date: 23 Feb 2000

At what point are semantic constraints applied to uses of non-dependent names in template definitions? According to 14.6.3  temp.nondep , such names are looked up and bound at the point at which they are used, i.e., the point of definition and not the point of instantiation. However, the text does not mention the checking of semantic constraints.

Contrast this omission with the treatment of names in default argument expressions given in 8.3.6  dcl.fct.default paragraph 5, where the treatment of semantic constraints is explicit:

The names in the expression are bound, and the semantic constraints are checked, at the point where the default argument expression appears.
The following code is an example of where this distinction matters:
    struct S;

    template <class T> struct Q {
        S s;    // incomplete type if semantic constraints
                // are applied in the definition context
    };

    struct S { };

    // Point of instantiation of Q<int>; S is complete here

    Q<int> si;        
There is real-world code that depends on late checking of semantic constraints. The Standard should be explicit about whether this code is broken or not.

Proposed resolution (04/00):

In 14.6  temp.res paragraph 7, add the following immediately preceding the note:

If a type used in a non-dependent name is incomplete at the point at which a template is defined but is complete at the point at which an instantiation is done, and if the completeness of that type affects whether or not the program is well-formed or affects the semantics of the program, the program is ill-formed; no diagnostic is required.



64. Partial ordering to disambiguate explicit specialization

Section: 14.7.3  temp.expl.spec     Status: ready     Submitter: Steve Adamczyk     Date: 13 Oct 1998

Paragraph 12 should address partial ordering. It wasn't updated when that change was made and conflicts with 14.5.5.2  temp.func.order paragraph 1.

Proposed resolution (10/99): Remove 14.7.3  temp.expl.spec paragraph 12 and the example that follows.




181. Errors in template template-parameter example

Section: 14.8.2.4  temp.deduct.type     Status: ready     Submitter: John Spicer     Date: 4 Nov 1999

14.8.2.4  temp.deduct.type paragraph 18 uses incorrect syntax. Instead of

    template <template X<class T> > struct A { };
    template <template X<class T> > void f(A<X>) { }
it should be
    template <template <class T> class X> struct A { };
    template <template <class T> class X> void f(A<X>) { }



98. Branching into try block

Section: 15  except     Status: ready     Submitter: Jack Rouse     Date: 23 Feb 1999

At the top of clause 15, in paragraph 2, it says:

A goto, break, return, or continue statement can be used to transfer control out of a try block or handler, but not into one.
What about switch statements?
    switch ( f() )
    {
    case 1:
         try {
             g();
    case 2:
             h();
         }
         catch (...)
         {
             // handler
         }
    break;
    }
Daveed Vandevoorde:

Consider:

    void f() {
        try {
        label:
            ;
        } catch(...) {
            goto label;
        }
    }
Now the phrase "try block" (without a hyphen) is used in paragraph 1 in a way that causes me to think that it is not intended to include the corresponding handlers. On the other hand, the grammar entity "try-block" (with hyphen) does include the handlers. So is the intent to prohibit the above or not?

Proposed resolution: Change text in 15[except] paragraph 2 from:

A goto, break, return, or continue statement can be used to transfer control out of a try block or handler, but not into one.
to:
A goto or switch statement shall not be used to transfer control into a try block or into a handler.
[ Example:
void f() {
  goto l1;  // Ill-formed
  goto l2;  // Ill-formed
  try {
    goto l1;  // OK
    goto l2;  // Ill-formed
    l1: ;
  } catch (...) {
    l2: ;
    goto l1;  // Ill-formed
    goto l2;  // OK
  }
}
end example ]
A goto, break, return, or continue statement can be used to transfer control out of a try block or handler.



210. What is the type matched by an exception handler?

Section: 15.3  except.handle     Status: ready     Submitter: Scott Douglass     Date: 6 Mar 2000

15.3  except.handle paragraph 3 says,

A handler is a match for a throw-expression with an object of type E...

This wording leaves it unclear whether it is the dynamic type of the object being thrown or the static type of the expression that determines whether a handler is a match for a given exception. For instance,

    struct B { B(); virtual ~B(); };
    struct D : B { D(); };
    void toss(const B* b) { throw *b; }
    void f() { const D d; toss(&d); }

In this code, presumably the type to be matched is B and not const D (15.1  except.throw).

Suggested resolution: Replace the cited wording as follows:

A handler is a match for a throw-expression which initialized a temporary (15.1  except.throw) of type E...

Proposed resolution (04/00):

  1. Change 15.1  except.throw paragraph 3 from

    A throw-expression initializes a temporary object, the type of which is determined...

    to

    A throw-expression initializes a temporary object, called the exception object, the type of which is determined...
  2. Change 15.3  except.handle paragraph 3 from

    A handler is a match for a throw-expression with an object of type E if...

    to

    A handler is a match for an exception object of type E if...



126. Exception specifications and const

Section: 15.4  except.spec     Status: ready     Submitter: Martin von Loewis     Date: 8 June 1999

The standard is inconsistent about constness inside exception specifications.

    struct X {};
    struct Y:X {};

    const Y bar() {return Y();}

    void foo()throw(const X)
    {
      throw bar();
    }
It is unclear whether calling foo will result in a call to unexpected. According to 15.4  except.spec paragraph 7, only two cases are treated specially with regard to inheritance: If "class X" appears in the type-id-list, or if "class X*" appears in the type-id-list. Neither is the case here, so foo only allows exceptions of the same type (const X). As a result, unexpected should be called.

On the other hand, the intent of exception specification appears to allow an implementation of this example as

    void foo()
    try{
      throw bar();
    }catch(const X){
      throw;
    }catch(...){
      std::unexpected();
    }
According to 15.3  except.handle , this replacement code would catch the exception, so unexpected would not be called.

Suggested resolution: Change 15.4  except.spec paragraph 7 to read

A function is said to allow all exception objects of all types E for which one of the types T in the type-id-list would be a handler, according to 15.3  except.handle .

Proposed resolution (04/00):

Replace 15.4  except.spec paragraph 7 with the following:

A function is said to allow an exception of type E if its exception-specification contains a type T for which a handler of type T would be a match (15.3  except.handle) for an exception of type E.



145. Deprecation of prefix ++

Section: D.1  depr.post.incr     Status: ready     Submitter: Mike Miller     Date: 23 Jul 1999

D.1  depr.post.incr indicates that use of the postfix ++ with a bool operand is deprecated. Annex D  depr says nothing about prefix ++. However, this use of prefix ++ is also deprecated, according to 5.3.2  expr.pre.incr paragraph 1. Presumably D.1  depr.post.incr should be expanded to cover prefix ++, or another section should be added to Annex D  depr.

Proposed resolution: Change the entire section D.1  depr.post.incr, including its heading, to read as follows:

D.1 Increment operator with bool operand [depr.incr.bool]

The use of an operand of type bool with the ++ operator is deprecated (see 5.3.2  expr.pre.incr and 5.2.6  expr.post.incr).




106. Creating references to references during template deduction/instantiation

Section: unknown  unknown     Status: ready     Submitter: Bjarne Stroustrup     Date: unknown

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

Proposed resolution:

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






Issues with "Review" Status


113. Visibility of called function

Section: 5.2.2  expr.call     Status: review     Submitter: Christophe de Dinechin     Date: 5 May 1999

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




68. Grammar does not allow "friend class A<int>;"

Section: 7.1.5.3  dcl.type.elab     Status: review     Submitter: Mike Ball     Date: 17 Oct 1998

I can't find the answer to the following in the standard. Does anybody have a reference?

The syntax for elaborated type specifier is

Which does not allow the production

    class foo<int> // foo is a template
On the other hand, a friend declaration seems to require this production,
An elaborated-type-specifier shall be used in a friend declaration for a class.*

[Footnote: The class-key of the elaborated-type-specifier is required. —end footnote]

And in 14.5.3  temp.friend we find the example
[Example:
    template<class T> class task;
    template<class T> task<T>* preempt(task<T>*);

    template<class T> class task {
        // ...
        friend void next_time();
        friend void process(task<T>*);
        friend task<T>* preempt<T>(task<T>*);
        template<class C> friend int func(C);

        friend class task<int>;
        template<class P> friend class frd;
        // ...
    };
Is there some special dispensation somewhere to allow the syntax in this context? Is there something I've missed about elaborated-type-specifier? Is it just another bug in the standard?

An additional problem was reported via comp.std.c++: the grammar does not allow the following example:

    namespace A{
      class B{};
    };

    namespace B{
      class A{};
      class C{
	friend class ::A::B;
      };
    };

Proposed resolution (04/00):

Change the grammar in 7.1.5.3  dcl.type.elab to read

and change the forms allowed in paragraph 1 to




159. Namespace qualification in declarators

Section: 8.3  dcl.meaning     Status: review     Submitter: John Spicer     Date: 23 Aug 1999

8.3  dcl.meaning paragraph 1 says:

In the qualified declarator-id for a class or namespace member definition that appears outside of the member's class or namespace, the nested-name-specifier shall not name any of the namespaces that enclose the member's definition.
This results in the following behavior:
    namespace N {
        namespace M {
            void f();
            void g();
        }
        void M::f(){}     // okay
        void N::M::g(){}  // error
    }
I was very surprised when this rule was pointed out to me. The change appears to have been introduced around the time of the first Santa Cruz meeting, but I don't recall discussion of it and could not find a motion related to it.

Regardless of where it came from, I also can't understand why it is there. Certainly it shouldn't matter how you name a given class or namespace.

For example, the standard permits:

    namespace N {
        namespace M {
            void f();
            void g();
        }
        namespace X = M;
        namespace Y = N::M;
        void X::f(){}  // okay
        void Y::g(){}  // okay
    }
So, it is okay to use an alias for N::M, but not to use N::M directly. Note that it is okay to use N::M in any other context at this point in the program (i.e., the rule is a specific restriction on declarator names, not a general rule on the use of qualified names).

Does anyone recall the intent of this rule or any rationale for its existence?

Proposed resolution: Remove the last sentence of 8.3  dcl.meaning paragraph 1 (cited above) and the example that follows.

Notes from 04/00 meeting:

There was some question as to whether this issue actually constituted a defect in the Standard. John Spicer suggested that machine-generated source code would be likely to run afoul of this prohibition. Francis Glassborow expressed support for a rule that would allow full qualification, or qualification relative to the namespace containing the definition, but not qualification relative to a containing namespace. There was no consensus for moving forward with a DR at this point, so the issue was left in "review" status.




140. Agreement of parameter declarations

Section: 8.3.5  dcl.fct     Status: review     Submitter: Steve Clamage     Date: 15 Jul 1999

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

Proposed resolution (04/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 from "the function types are identical" to "the parameter-type-lists of the functions (8.3.5  dcl.fct) are identical."

  3. In the second and third 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 with a given parameter list shall agree exactly in both the type of the value returned 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."




151. Terminology of zero-initialization

Section: 8.5  dcl.init     Status: review     Submitter: Valentin Bonnard     Date: 4 August 1999

In 3.6.2  basic.start.init paragraph 1 and 8.5  dcl.init paragraphs 5 and 6, the terms "memory" and "storage" are used in connection with zero-initialization. This is inaccurate; it is the variables that are zero-initialized, not the storage. (An all-zero bit pattern in the storage may, in fact, not correspond to the representation of zero converted to the appropriate type, and it is the latter that is being described.)

Suggested resolution: remove the words "storage" and "memory" in these contexts.

Proposed resolution (10/99):

  1. Delete the words "The storage for" from the first sentence of 3.6.2  basic.start.init paragraph 1.
  2. Change 8.5  dcl.init paragraph 5 to
    To zero-initialize an object of type T means:
    • if T is a scalar type, the object is set to the value of 0 (zero) converted to T;
    • if T is a non-union class type, each nonstatic data member and each base-class subobject is zero-initialized;
    • if T is a union type, the first named data member is zero-initialized;
    • if T is an array type, each element is zero-initialized;
    • if T is a reference type, no initialization is performed.
    To default-initialize an object of type T means:...
    • otherwise, the object is zero-initialized.
    A program that calls...
  3. Change 8.5  dcl.init paragraph 6 to read
    Each object of static storage duration shall be zero-initialized...

Additional notes:

  1. Part of the change to 8.5  dcl.init regarding unions relates to issue 57.
  2. References are not objects. Should the bullet describing the (non-)initialization of references be deleted?

Notes from 04/00 meeting:

There is significant overlap between this issue and issue 178; the proposed wording must be reconciled before either resolution can be adopted.




178. More on value-initialization

Section: 8.5  dcl.init     Status: review     Submitter: Andrew Koenig     Date: 25 Oct 1999

When the Committee considered issue 35, another context in which value initialization might be relevant was overlooked: mem-initializers. It would seem reasonable that if T() as an expression invokes value initialization, that the same syntactic construct in a mem-initializer-list would do the same, and the usefulness of value initialization in that context is at least as great as the standalone case.

Proposed resolution:

In 5.2.3  expr.type.conv paragraph 2, replace "whose value is determined by default-initialization" by "which is value-initialized".

In 5.3.4  expr.new paragraph 15,

Replace 8.5  dcl.init paragraph 5 by:

To zero-initialize an object of type T means:

To default-initialize an object of type T means:

To value-initialize an object of type T means:

A program that calls for default-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.

In 8.5  dcl.init paragraph 6, change "The memory occupied by any" to "Every".

In 8.5  dcl.init paragraph 7, replace "default-initialized" by "value-initialized".

In 8.5.1  dcl.init.aggr paragraph 7, replace "default-initialized" by "value-initialized".

In 12.3.1  class.conv.ctor paragraph 2, insert "or value-initialization" after the first occurrence of "default-initialization".

In 12.6  class.init paragraph 1, replace the note by "The object is default-initialized if there is no initializer, or value-initialized if the initializer is ()" [i.e., replace the non-normative note by different, normative text].

In 12.6.1  class.expl.init paragraph 2, replace "default-initialized" by "value-initialized".

In 12.6.2  class.base.init paragraph 3, replace "default-initialized" by "value-initialized" in the first dashed item.

In 12.6.2  class.base.init paragraph 4, replace "default-initialized, nor initialized" by "default-initialized, nor value-initialized, nor assigned".

Notes from 04/00 meeting:

There is significant overlap between this issue and issue 151; the proposed wording must be reconciled before either resolution can be adopted.




9. Clarification of access to base class members

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

11.2  class.access.base paragraph 4 says:

A base class is said to be accessible if an invented public member of the base class is accessible. If a base class is accessible, one can implicitly convert a pointer to a derived class to a pointer to that base class.
Given the above, is the following well-formed?
    class D;
     
    class B
    {
     protected:
       int b1;
 
       friend void foo( D* pd );
    };
     
    class D : protected B { };
     
    void foo( D* pd )
    {
       if ( pd->b1 > 0 ); // Is 'b1' accessible?
    }
Can you access the protected member b1 of B in foo? Can you convert a D* to a B* in foo?

1st interpretation:

A public member of B is accessible within foo (since foo is a friend), therefore foo can refer to b1 and convert a D* to a B*.

2nd interpretation:

B is a protected base class of D, and a public member of B is a protected member of D and can only be accessed within members of D and friends of D. Therefore foo cannot refer to b1 and cannot convert a D* to a B*.

Proposed Resolution (10/99):

  1. Add preceding 11.2  class.access.base paragraph 4:
    A base class B of N is accessible at R, if
    • an invented public member of B would be a public member of N, or
    • R occurs in a member or friend of class N, and an invented public member of B would be a private or protected member of N, or
    • R occurs in a member or friend of a class P derived from N, and an invented public member of B would be a private or protected member of P, or
    • there exists a class S such that B is a base class of S accessible at R and S is a base class of N accessible at R. [Example:
          class B {
          public:
              int m;
          };
      
          class S: private B {
              friend class N;
          };
      
          class N: private S {
              void f() {
      	    B* p = this;  // OK because class S satisfies the
      			// fourth condition above: B is a base
      			// class of N accessible in f() because
      			// B is an accessible base class of S
      			// and S is an accessible base class of N.
              }
          };
      
      end example]
  2. Delete the first sentence of 11.2  class.access.base paragraph 4:
    A base class is said to be accessible if an invented public member of the base class is accessible.
  3. Replace the last sentence ("A member m is accessible...") by the following:
    A member m is accessible at the point R when named in class N if
    • m as a member of N is public, or
    • m as a member of N is private, and R occurs in a member or friend of class N, or
    • m as a member of N is protected, and R occurs in a member or friend of class N, or in a member or friend of a class P derived from N, where m as a member of P is private or protected, or
    • there exists a base class B of N that is accessible at R, and m is accessible at R when named in class B. [Example:...

(See J16/99-0042 = WG21 N1218.)

Notes from 04/00 meeting:

The resolution for issue 207, when it becomes available, should be incorporated into this wording.




16. Access to members of indirect private base classes

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

The text in 11.2  class.access.base paragraph 4 does not seem to handle the following cases:

    class D;
     
    class B {
    private:
        int i;
        friend class D;
    };
     
    class C : private B { };
     
    class D : private C {
        void f() {
            B::i; //1: well-formed?
            i;    //2: well-formed?
        }
    };
The member i is not a member of D and cannot be accessed in the scope of D. What is the naming class of the member i on line //1 and line //2?

Proposed Resolution (10/99): As described for Core issue 9. With that change, it is clear that the example is ill-formed.




45. Access to nested classes

Section: 11.8  class.access.nest     Status: review     Submitter: Daveed Vandevoorde     Date: 29 Sep 1998

Example:

    #include <iostream.h>
    
    class C {  // entire body is private
        struct Parent {
            Parent() { cout << "C::Parent::Parent()\n"; }
        };
    
        struct Derived : Parent {
            Derived() { cout << "C::Derived::Derived()\n"; }
        };
    
        Derived d;
    };
    
    
    int main() {
        C c;      //  Prints message from both nested classes
        return 0;
    }
How legal/illegal is this? Paragraphs that seem to apply here are:

11  class.access paragraph 1:

A member of a class can be
and 11.8  class.access.nest paragraph 1:
The members of a nested class have no special access to members of an enclosing class, nor to classes or functions that have granted friendship to an enclosing class; the usual access rules (clause 11  class.access ) shall be obeyed. [...]
This makes me think that the ': Parent' part is OK by itself, but that the implicit call of 'Parent::Parent()' by 'Derived::Derived()' is not.

From Mike Miller:

I think it is completely legal, by the reasoning given in the (non-normative) 11.8  class.access.nest paragraph 2. The use of a private nested class as a base of another nested class is explicitly declared to be acceptable there. I think the rationale in the comments in the example ("// OK because of injection of name A in A") presupposes that public members of the base class will be public members in a (publicly-derived) derived class, regardless of the access of the base class, so the constructor invocation should be okay as well.

I can't find anything normative that explicitly says that, though.

(See also paper J16/99-0009 = WG21 N1186.)

Proposed Resolution (04/00):

  1. Change 11.8  class.access.nest paragraph 1 from

    The members of a nested class have no special access to members of an enclosing class, nor to classes or functions that have granted friendship to an enclosing class; the usual access rules (clause 11  class.access) shall be obeyed. The members of an enclosing class have no special access to members of a nested class; the usual access rules (clause 11  class.access) shall be obeyed. [Example:
        class E {
            int x;
            class B { };
    
            class I {
                B b;              // error: E::B is private
                int y;
                void f(E* p, int i)
                {
                    p->x = i;     // error: E::x is private
                }
            };
    
            int g(I* p)
            {
                return p->y;      // error: I::y is private
            }
        };
    
    end example]
    to
    A nested class is implicitly a friend of all enclosing classes. However, the members of an enclosing class have no special access to members of a nested class; the usual access rules (clause 11  class.access) shall be obeyed. [Example:
        class E {
            int x;
            class B { };
    
            class I {
                B b;              // OK: I is a friend of E
                int y;
                void f(E* p, int i)
                {
                    p->x = i;     // OK: I is a friend of E
                }
            };
    
            int g(I* p)
            {
                return p->y;      // error: I::y is private
            }
        };
    

  2. Delete 11.8  class.access.nest paragraph 2.

  3. In 11.4  class.friend, change the sentence added to paragraph 1 by the resolution to issue 77 from

    A class that is a member of another class does not gain any special access to the enclosing class. However, such member classes may be declared as friends of the enclosing class.
    to
    Even though a class that is a member of another class is implicitly a friend of the enclosing class (11.8  class.access.nest), such member classes may also be explicitly declared as friends of the enclosing class.

  4. Delete 11  class.access paragraph 6.




124. Lifetime of temporaries in default initialization of class arrays

Section: 12.2  class.temporary     Status: review     Submitter: Jack Rouse     Date: 3 June 1999

Jack Rouse: 12.2  class.temporary states that temporary objects will normally be destroyed at the end of the full expression in which they are created. This can create some unique code generation requirements when initializing a class array with a default constructor that uses a default argument. Consider the code:

    struct T {
       int i;
       T( int );
       ~T();
    };

    struct S {
       S( int = T(0).i );
       ~S();
    };

    S* f( int n )
    {
       return new S[n];
    }
The full expression allocating the array in f(int) includes the default constructor for S. Therefore according to 1.9  intro.execution paragraph 14, it includes the default argument expression for S(int). So evaluation of the full expression should include evaluating the default argument "n" times and creating "n" temporaries of type T. But the destruction of the temporaries must be delayed until the end of the full expression so this requires allocating space at runtime for "n" distinct temporaries. It is unclear how these temporaries are supposed to be allocated and deallocated. They cannot readily be autos because a variable allocation is required.

I believe that many existing implementations will destroy the temporaries needed by the default constructor after each array element is initialized. But I can't find anything in the standard that allows the temporaries to be destroyed early in this case.

I think the standard should allow the early destruction of temporaries used in the default initialization of class array elements. I believe early destruction is the status quo, and I don't think the users of existing C++ compilers have been adversely impacted by it.

Proposed resolution (04/00):

The proposed resolution is contained in the proposal for issue 201.




201. Order of destruction of temporaries in initializers

Section: 12.2  class.temporary     Status: review     Submitter: Alan Nash     Date: 31 Jan 2000

According to 12.2  class.temporary paragraph 4, an expression appearing as the initializer in an object definition constitutes a context "in which temporaries are destroyed at a different point than the end of the full-expression." It goes on to say that the temporary containing the value of the expression persists until after the initialization is complete (see also issue 117). This seems to presume that the end of the full-expression is a point earlier than the completion of the initialization.

However, according to 1.9  intro.execution paragraphs 12-13, the full-expression in such cases is, in fact, the entire initialization. If this is the case, the behavior described for temporaries in an initializer expression is simply the normal behavior of temporaries in any expression, and treating it as an exception to the general rule is both incorrect and confusing.

Proposed resolution (04/00):

[Note: this proposal also addresses issue 124.]

  1. Add to the end of 1.9  intro.execution paragraph 12:

    If the initializer for an object or sub-object is a full-expression, the initialization of the object or sub-object (e.g., by calling a constructor or copying an expression value) is considered to be part of the full-expression.
  2. Replace 12.2  class.temporary paragraph 4 with:

    There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context is when a default constructor is called to initialize an element of an array. If the constructor has one or more default arguments, any temporaries created in the default argument expressions are destroyed immediately upon return from the constructor.



38. Explicit template arguments and operator functions

Section: 14.2  temp.names     Status: review     Submitter: John Wiegley     Date: 17 Aug 1998

It appears from the grammar that explicit template arguments cannot be specified for overloaded operator names. Does this mean that template operators can never be friends?

But assuming that I read things wrong, then I should be able to specify a global template 'operator +' by writing:

    friend A::B operator + <>(char&);
John Spicer:

You should be able to have explicit template arguments on operator function, but the grammar does seem to prohibit it (unless I'm reading it incorrectly). This is an error in the grammar, they should be permitted.

Proposed resolution (04/00):

Change the grammar specified in 13.5  over.oper paragraph 1 from

to




44. Member specializations

Section: 14.7.3  temp.expl.spec     Status: review     Submitter: Nathan Myers     Date: 19 Sep 1998

Some compilers reject the following:

    struct A {
        template <int I> void f();
        template <> void f<0>();
    };
on the basis of 14.7.3  temp.expl.spec paragraph 2:
An explicit specialization shall be declared in the namespace of which the template is a member, or, for member templates, in the namespace of which the enclosing class or enclosing class template is a member. An explicit specialization of a member function, member class or static data member of a class template shall be declared in the namespace of which the class template is a member. ...
claiming that the specialization above is not "in the namespace of which the enclosing class ... is a member". Elsewhere, declarations are sometimes required to be "at" or "in" "namespace scope", which is not what it says here. Paragraph 17 says:
A member or a member template may be nested within many enclosing class templates. If the declaration of an explicit specialization for such a member appears in namespace scope, the member declaration shall be preceded by a template<> for each enclosing class template that is explicitly specialized.
The qualification "if the declaration ... appears in namespace scope", implies that it might appear elsewhere. The only other place I can think of for a member specialization is in class scope.

Was it the intent of the committee to forbid the construction above? (Note that A itself is not a template.) If so, why?

Proposed resolution (10/99): In-class specializations of member templates are not allowed. In 14.7.3  temp.expl.spec paragraph 17, replace

If the declaration of an explicit specialization for such a member appears in namespace scope...
with
In an explicit specialization for such a member...

Notes from 04/00 meeting:

This issue was kept in "review" status for two major reasons:

  1. It's not clear that a change is actually needed. All uses of the phrase "in the namespace" in the IS mean "directly in the namespace," not in a scope nested within the namespace.
  2. There was substantial sentiment for actually adding support for in-class specializations at a future time, and it might be perceived as a reversal of direction to pass a change aimed at reinforcing the absence of the feature, only to turn around afterward and add it.



70. Is an array bound a nondeduced context?

Section: 14.8.2.4  temp.deduct.type     Status: review     Submitter: Jack Rouse     Date: 29 Sep 1998

Paragraph 4 lists contexts in which template formals are not deduced. Were template formals in an expression in the array bound of an array type specification intentionally left out of this list? Or was the intent that such formals always be explicitly specified? Otherwise I believe the following should be valid:

    template <int I> class IntArr {};

    template <int I, int J>
    void concat( int (&d)[I+J], const IntArr<I>& a, const IntArr<J>& b ) {}

    int testing()
    {
        IntArr<2> a;
        IntArr<3> b;
        int d[5];

        concat( d, a, b );
    }
Can anybody shed some light on this?

From John Spicer:

Expressions involving nontype template parameters are nondeduced contexts, even though they are omitted from the list in 14.8.2.4  temp.deduct.type paragraph 4. See 14.8.2.4  temp.deduct.type paragraphs 12-14:

  1. A template type argument cannot be deduced from the type of a non-type template-argument.

     ...

  1. If, in the declaration of a function template with a non-type template-parameter, the non-type template-parameter is used in an expression in the function parameter-list, the corresponding template-argument must always be explicitly specified or deduced elsewhere because type deduction would otherwise always fail for such a template-argument.

Proposed resolution (10/99): In 14.8.2.4  temp.deduct.type paragraph 4, add a third bullet:




208. Rethrowing exceptions in nested handlers

Section: 15.1  except.throw     Status: review     Submitter: Bill Wade     Date: 28 Feb 2000

Paragraph 7 of 15.1  except.throw discusses which exception is thrown by a throw-expression with no operand.

May an expression which has been "finished" (paragraph 7) by an inner catch block be rethrown by an outer catch block?

    catch(...)    // Catch the original exception
    {
      try{ throw; }    // rethrow it at an inner level
                       // (in reality this is probably
                       // inside a function)
      catch (...)
      {
      }   // Here, an exception (the original object)
          // is "finished" according to 15.1p7 wording

      // 15.1p7 says that only an unfinished exception
      // may be rethrown.
      throw;    // Can we throw it again anyway?  It is
                // certainly still alive (15.1p4).
    }

I believe this is ok, since the paragraph says that the exception is finished when the "corresponding" catch clause exits. However since we have two clauses, and only one exception, it would seem that the one exception gets "finished" twice.

Proposed resolution (04/00):

  1. In 15.1  except.throw paragraph 4, change

    When the last handler being executed for the exception exits by any means other than throw; ...
    to
    When the last remaining active handler for the exception exits by any means other than throw; ...

  2. In 15.1  except.throw paragraph 6, change

    A throw-expression with no operand rethrows the exception being handled.
    to
    A throw-expression with no operand rethrows the currently handled exception (15.3  except.handle).

  3. Delete 15.1  except.throw paragraph 7.

  4. Add the following before 15.1  except.throw paragraph 6:

    An exception is considered caught when a handler for that exception becomes active (15.3  except.handle). [Note: an exception can have active handlers and still be considered uncaught if it is rethrown.]

  5. Change 15.3  except.handle paragraph 8 from

    An exception is considered handled upon entry to a handler. [Note: the stack will have been unwound at that point.]
    to

    A handler is consdered ative when initialization is complete for the formal parameter of the catch clause. [Note: the stack will have been unwound at that point.] Also, an implicit handler is considered active when terminate() or unexpected() is entered due to a throw. A handler is no longer considered active when the catch clause exits or when unexpected() exits after being entered due to a throw.

    The exception with the most recently activated handler that is still active is called the currently handled exception.

  6. In 15.3  except.handle paragraph 16, change "exception being handled" to "currently handled exception."






Issues with "Drafting" Status


143. Friends and Koenig lookup

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

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.

Proposed Resolution (10/99): 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.

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




216. Linkage of nameless class-scope enumeration types

Section: 3.5  basic.link     Status: drafting     Submitter: Daveed Vandevoorde     Date: 13 Mar 2000
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.




125. Ambiguity in friend declaration syntax

Section: 5.1  expr.prim     Status: drafting     Submitter: Martin von Loewis     Date: 7 June 1999

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.


118. Calls via pointers to virtual member functions

Section: 5.2.2  expr.call     Status: drafting     Submitter: Martin O'Riordan     Date: 17 May 1999

Martin O'Riordan: Having gone through all the relevant references in the IS, it is not conclusive that a call via a pointer to a virtual member function is polymorphic at all, and could legitimately be interpreted as being static.

Consider 5.2.2  expr.call paragraph 1:

The function called in a member function call is normally selected according to the static type of the object expression (clause 10  class.derived ), but if that function is virtual and is not specified using a qualified-id then the function actually called will be the final overrider (10.3  class.virtual ) of the selected function in the dynamic type of the object expression.
Here it is quite specific that you get the polymorphic call only if you use the unqualified syntax. But, the address of a member function is "always" taken using the qualified syntax, which by inference would indicate that call with a PMF is static and not polymorphic! Not what was intended.

Yet other references such as 5.5  expr.mptr.oper paragraph 4:

If the dynamic type of the object does not contain the member to which the pointer refers, the behavior is undefined.
indicate that the opposite may have been intended, by stating that it is the dynamic type and not the static type that matters. Also, 5.5  expr.mptr.oper paragraph 6:
If the result of .* or ->* is a function, then that result can be used only as the operand for the function call operator (). [Example:
        (ptr_to_obj->*ptr_to_mfct)(10);
calls the member function denoted by ptr_to_mfct for the object pointed to by ptr_to_obj. ]
which also implies that it is the object pointed to that determines both the validity of the expression (the static type of 'ptr_to_obj' may not have a compatible function) and the implicit (polymorphic) meaning. Note too, that this is stated in the non-normative example text.

Andy Sawyer: Assuming the resolution is what I've assumed it is for the last umpteen years (i.e. it does the polymorphic thing), then the follow on to that is "Should there also be a way of selecting the non-polymorphic behaviour"?

Mike Miller: It might be argued that the current wording of 5.2.2  expr.call paragraph 1 does give polymorphic behavior to simple calls via pointers to members. (There is no qualified-id in obj.*pmf, and the IS says that if the function is not specified using a qualified-id, the final overrider will be called.) However, it clearly says the wrong thing when the pointer-to-member itself is specified using a qualified-id (obj.*X::pmf).

Bill Gibbons: The phrase qualified-id in 5.2.2  expr.call paragraph 1 refers to the id-expression and not to the "pointer-to-member expression" earlier in the paragraph:

For a member function call, the postfix expression shall be an implicit (9.3.1  class.mfct.nonstatic , 9.4  class.static ) or explicit class member access (5.2.5  expr.ref ) whose id-expression is a function member name, or a pointer-to-member expression (5.5  expr.mptr.oper ) selecting a function member.

Mike Miller: To be clear, here's an example:

    struct S {
	virtual void f();
    };
    void (S::*pmf)();
    void g(S* sp) {
	sp->f();         // 1: polymorphic
	sp->S::f();      // 2: non-polymorphic
	(sp->S::f)();    // 3: non-polymorphic
	(sp->*pmf)();    // 4: polymorphic
	(sp->*&S::f)();  // 5: polymorphic
    }



4. Does extern "C" affect the linkage of function names with internal linkage?

Section: 7.5  dcl.link     Status: drafting     Submitter: Mike Anderson     Date: unknown

7.5  dcl.link paragraph 6 says the following:

Does this apply to static functions as well? For example, is the following well-formed?
        extern "C" {
            static void f(int) {}
            static void f(float) {}
        };
Can a function with internal linkage "have C linkage" at all (assuming that phrase means "has extern "C" linkage"), for how can a function be extern "C" if it's not extern? The function type can have extern "C" linkage — but I think that's independent of the linkage of the function name. It should be perfectly reasonable to say, in the example above, that extern "C" applies only to the types of f(int) and f(float), not to the function names, and that the rule in 7.5  dcl.link paragraph 6 doesn't apply.

Suggested resolution: The extern "C" linkage specification applies only to the type of functions with internal linkage, and therefore some of the rules that have to do with name overloading don't apply.

Proposed Resolution:

The intent is to distingush implicit linkage from explicit linkage for both name linkage and language (function type) linkage. (It might be more clear to use the terms name linkage and type linkage to distinguish these concepts. A function can have a name with one kind of linkage and a type with a different kind of linkage. The function itself has no linkage: it has no name, only the declaration has a name. This becomes more obvious when you consider function pointers.)

The tentatively agreed proposal is to apply implicit linkage to names declared in brace-enclosed linkage specifications and to non-top-level names declared in simple linkage specifications; and to apply explicit linkage to top-level names declared in simple linkage specifications.

The language linkage of any function type formed through a function declarator is that of the nearest enclosing linkage-specification. For purposes of determining whether the declaration of a namespace-scope name matches a previous declaration, the language linkage portion of the type of a function declaration (that is, the language linkage of the function itself, not its parameters, return type or exception specification) is ignored.

For a linkage-specification using braces, i.e.

extern string-literal { declaration-seqopt }
the linkage of any declaration of a namespace-scope name (including local externs) which is not contained in a nested linkage-specification, is not declared to have no linkage (static), and does not match a previous declaration is given the linkage specified in the string-literal. The language linkage of the type of any function declaration of a namespace-scope name (including local externs) which is not contained in a nested linkage-specification and which is declared with function declarator syntax is the same as that of a matching previous declaration, if any, else is specified by string-literal.

For a linkage-specification without braces, i.e.

extern string-literal declaration

the linkage of the names declared in the top-level declarators of declaration is specified by string-literal; if this conflicts with the linkage of any matching previous declarations, the program is ill-formed. The language linkage of the type of any top-level function declarator is specified by string-literal; if this conflicts with the language linkage of the type of any matching previous function declarations, the program is ill-formed. The effect of the linkage-specification on other (non top-level) names declared in declaration is the same as that of the brace-enclosed form.

Bill Gibbons: In particular, these should be well-formed:

    extern "C" void f(void (*fp)());   // parameter type is pointer to
                                       // function with C language linkage
    extern "C++" void g(void (*fp)()); // parameter type is pointer to
                                       // function with C++ language linkage

    extern "C++" {                     // well-formed: the linkage of "f"
        void f(void(*fp)());           // and the function type used in the
    }                                  // parameter still "C"

    extern "C" {                       // well-formed: the linkage of "g"
        void g(void(*fp)());           // and the function type used in the
    }                                  // parameter still "C++"

but these should not:

    extern "C++" void f(void(*fp)());  // error - linkage of "f" does not
                                       // match previous declaration
                                       // (linkage of function type used in
                                       // parameter is still "C" and is not
                                       // by itself ill-formed)
    extern "C" void g(void(*fp)());    // error - linkage of "g" does not
                                       // match previous declaration
                                       // (linkage of function type used in
                                       // parameter is still "C++" and is not
                                       // by itself ill-formed)

That is, non-top-level declarators get their linkage from matching declarations, if any, else from the nearest enclosing linkage specification. (As already described, top-level declarators in a brace-enclosed linkage specification get the linkage from matching declarations, if any, else from the linkage specifcation; while top-level declarators in direct linkage specifications get their linkage from that specification.)

Mike Miller: This is a pretty significant change from the current specification, which treats the two forms of language linkage similarly for most purposes. I don't understand why it's desirable to expand the differences.

It seems very unintuitive to me that you could have a top-level declaration in an extern "C" block that would not receive "C" linkage.

In the current standard, the statement in 7.5  dcl.link paragraph 4 that

the specified language linkage applies to the function types of all function declarators, function names, and variable names introduced by the declaration(s)

applies to both forms. I would thus expect that in

    extern "C" void f(void(*)());
    extern "C++" {
        void f(void(*)());
    }
    extern "C++" f(void(*)());

both "C++" declarations would be well-formed, declaring an overloaded version of f that takes a pointer to a "C++" function as a parameter. I wouldn't expect that either declaration would be a redeclaration (valid or invalid) of the "C" version of f.

Bill Gibbons: The potential difficulty is the matching process and the handling of deliberate overloading based on language linkage. In the above examples, how are these two declarations matched:

    extern "C" void f(void (*fp1)());

    extern "C++" {
        void f(void(*fp2)());
    }

given that the linkage that is part of fp1 is "C" while the linkage (prior to the matching process) that is part of fp2 is "C++"?

The proposal is that the linkage which is part of the parameter type is not determined until after the match is attempted. This almost always correct because you can't overload "C" and "C++" functions; so if the function names match, it is likely that the declarations are supposed to be the same.

Mike Miller: This seems like more trouble than it's worth. This comparison of function types ignoring linkage specifications is, as far as I know, not found anywhere in the current standard. Why do we need to invent it?

Bill Gibbons: It is possible to construct pathological cases where this fails, e.g.

    extern "C" typedef void (*PFC)();  // pointer to "C" linkage function
    void f(PFC);         // parameter is pointer to "C" function
    void f(void (*)());  // matching declaration or overload based on
                         // difference in linkage type?

It is reasonable to require explicit typedefs in this case so that in the above example the second function declaration gets its parameter type function linkage from the first function declaration.

(In fact, I think you can't get into this situation without having already used typedefs to declare different language linkage for the top-level and parameter linkages.)

For example, if the intent is to overload based on linkage a typedef is needed:

    extern "C" typedef void (*PFC)();  // pointer to "C" linkage function
    void f(PFC);              // parameter is pointer to "C" function
    typedef void (*PFCPP)();  // pointer to "C++" linkage function
    void f(PFCPP);            // parameter is pointer to "C++" function

In this case the two function declarations refer to different functions.

Mike Miller: This seems pretty strange to me. I think it would be simpler to determine the type of the parameter based on the containing linkage specification (implicitly "C++") and require a typedef if the user wants to override the default behavior. For example:

    extern "C" {
        typedef void (*PFC)();    // pointer to "C" function
        void f(void(*)());        // takes pointer to "C" function
    }

    void f(void(*)());            // new overload of "f", taking
                                  // pointer to "C++" function

    void f(PFC);                  // redeclare extern "C" version

Notes from 04/00 meeting:

The following changes were tentatively approved, but because they do not completely implement the proposal above the issue is being kept for the moment in "drafting" status.

  1. Change the first sentence of 7.5  dcl.link paragraph 1 from

    All function types, function names, and variable names have a language linkage.

    to

    All function types, function names with external linkage, and variable names with external linkage have a language linkage.
  2. Change the following sentence of 7.5  dcl.link paragraph 4:
    In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names, and variable names introduced by the declaration(s).

    to

    In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names with external linkage, and variable names with external linkage declared within the linkage-specification.
  3. Add at the end of the final example on 7.5  dcl.link paragraph 4:

        extern "C" {
          static void f4();    // the name of the function f4 has
                               // internal linkage (not C language
                               // linkage) and the function's type
                               // has C language linkage
        }
        extern "C" void f5() {
          extern void f4();    // Okay -- name linkage (internal)
                               // and function type linkage (C
                               // language linkage) gotten from
                               // previous declaration.
        }
        extern void f4();      // Okay -- name linkage (internal)
                               // and function type linkage (C
                               // language linkage) gotten from
                               // previous declaration.
        void f6() {
          extern void f4();    // Okay -- name linkage (internal)
                               // and function type linkage (C
                               // language linkage) gotten from
                               // previous declaration.
        }
    
  4. Change 7.5  dcl.link paragraph 7 from

    Except for functions with internal linkage, a function first declared in a linkage-specification behaves as a function with external linkage. [Example:

        extern "C" double f();
        static double f();     // error
    

    is ill-formed (7.1.1  dcl.stc). ] The form of linkage-specification that contains a braced-enclosed declaration-seq does not affect whether the contained declarations are definitions or not (3.1  basic.def); the form of linkage-specification directly containing a single declaration is treated as an extern specifier (7.1.1  dcl.stc) for the purpose of determining whether the contained declaration is a definition. [Example:

        extern "C" int i;      // declaration
        extern "C" {
    	  int i;           // definition
        }
    

    end example] A linkage-specification directly containing a single declaration shall not specify a storage class. [Example:

        extern "C" static void f(); // error
    

    end example]

    to

    A declaration directly contained in a linkage-specification is treated as if it contains the extern specifier (7.1.1  dcl.stc) for the purpose of determining the linkage of the declared name and whether it is a definition. Such a declaration shall not specify a storage class. [Example:
        extern "C" double f();
        static double f();     // error
        extern "C" int i;      // declaration
        extern "C" {
    	    int i;         // definition
        }
        extern "C" static void g(); // error
    

    end example]




29. Linkage of locally declared functions

Section: 7.5  dcl.link     Status: drafting     Submitter: Mike Ball     Date: 19 Mar 1998

Consider the following:

    extern "C" void foo()
    {
        extern void bar();
        bar();
    }
Does "bar()" have "C" language linkage?

The ARM is explicit and says

A linkage-specification for a function also applies to functions and objects declared within it.
The DIS says
In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names, and variable names introduced by the declaration(s).
Is the body of a function definition part of the declaration?

From Mike Miller:

Yes: from 7  dcl.dcl paragraph 1,

and 8.4  dcl.fct.def paragraph 1: At least that's how I'd read it.

From Dag Brück:

Consider the following where extern "C" has been moved to a separate declaration:

    extern "C" void foo();
    
    void foo() { extern void bar(); bar(); }
I think the ARM wording could possibly be interpreted such that bar() has "C" linkage in my example, but not the DIS wording.

As a side note, I have always wanted to think that placing extern "C" on a function definition or a separate declaration would produce identical programs.

Proposed Resolution:

See the proposed resolution for Core issue 4, which covers this case.

The ODR should also be checked to see whether it addresses name and type linkage.




5. CV-qualifiers and type conversions

Section: 8.5  dcl.init     Status: drafting     Submitter: Josee Lajoie     Date: unknown

The description of copy-initialization in 8.5  dcl.init paragraph 14 says:

Should "destination type" in this last bullet refer to "cv-unqualified destination type" to make it clear that the destination type excludes any cv-qualifiers? This would make it clearer that the following example is well-formed:
     struct A {
       A(A&);
     };
     struct B : A { };
     
     struct C {
       operator B&();
     };
     
     C c;
     const A a = c; // allowed?
The temporary created with the conversion function is an lvalue of type B. If the temporary must have the cv-qualifiers of the destination type (i.e. const) then the copy-constructor for A cannot be called to create the object of type A from the lvalue of type const B. If the temporary has the cv-qualifiers of the result type of the conversion function, then the copy-constructor for A can be called to create the object of type A from the lvalue of type const B. This last outcome seems more appropriate.

Proposed Resolution:

As above.




198. Definition of "use" in local and nested classes

Section: 9.8  class.local     Status: drafting     Submitter: Erwin Unruh     Date: 27 Jan 2000

9.8  class.local paragraph 1 says,

Declarations in a local class can use only type names, static variables, extern variables and functions, and enumerators from the enclosing scope.
The definition of when an object or function is "used" is found in 3.2  basic.def.odr paragraph 2 and essentially says that the operands of sizeof and non-polymorphic typeid operators are not used. (The resolution for issue 48 will add contexts in which integral constant expressions are required to the list of non-uses.)

This definition of "use" would presumably allow code like

    void foo() {
        int i;
        struct S {
            int a[sizeof(i)];
        };
    };
which is required for C compatibility.

However, the restrictions on nested classes in 9.7  class.nest paragraph 1 are very similar to those for local classes, and the example there explicitly states that a reference in a sizeof expression is a forbidden use (abbreviated for exposition):

    class enclose {
    public:
        int x;
        class inner {
            void f(int i)
            {
                int a = sizeof(x);  // error: refers to enclose::x
            }
        };
    };
[As a personal note, I have seen real-world code that was exactly like this; it was hard to persuade the author that the required writearound, sizeof(((enclose*) 0)->x), was an improvement over sizeof(x). —wmm]

Suggested resolution: Add cross-references to 3.2  basic.def.odr following the word "use" in both 9.7  class.nest and 9.8  class.local , and change the example in 9.7  class.nest to indicate that a reference in a sizeof expression is permitted.




39. Conflicting ambiguity rules

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

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

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

Proposed Resolution:

The above example should be well-formed.




207. using-declarations and protected access

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

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.




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

Section: 14.1  temp.param     Status: drafting     Submitter: Martin von Loewis     Date: 13 Mar 2000

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

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

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

Notes from 04/00 meeting:

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

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




62. Unnamed members of classes used as type parameters

Section: 14.3.1  temp.arg.type     Status: drafting     Submitter: Steve Adamczyk     Date: 13 Oct 1998

Section 14.3.1  temp.arg.type paragraph 2 says

A local type, a type with no linkage, an unnamed type or a type compounded from any of these types shall not be used as a template-argument for a template type-parameter.
It probably wasn't intended that classes with unnamed members should be included in this list, but they are arguably compounded from unnamed types.


180. typename and elaborated types

Section: 14.6  temp.res     Status: drafting     Submitter: Mike Miller     Date: 21 Dec 1999

Mike Miller: A question about typename came up in the discussion of issue 68 that is somewhat relevant to the idea of omitting typename in contexts where it is clear that a type is required: consider something like

        template <class T>
        class X {
            friend class T::nested;
        };
Is typename required here? If so, where would it go? (The grammar doesn't seem to allow it anywhere in an elaborated-type-specifier that has a class-key.)

Bill Gibbons: The class applies to the last identifier in the qualified name, since all the previous names must be classes or namespaces. Since the name is specified to be a class it does not need typename. [However,] it looks like 14.6  temp.res paragraph 3 requires typename and the following paragraphs do not exempt this case. This is not what we agreed on.




197. Issues with two-stage lookup of dependent names

Section: 14.6.4.2  temp.dep.candidate     Status: drafting     Submitter: Derek Inglis     Date: 26 Jan 2000

The example in 14.6  temp.res paragraph 9 is incorrect, according to 14.6.4.2  temp.dep.candidate . The example reads,

    void f(char);

    template <class T> void g(T t)
    {
        f(1);        // f(char);
        f(T(1));     // dependent
        f(t);        // dependent
        dd++;        // not dependent
                     // error: declaration for dd not found
    }

    void f(int);

    double dd;
    void h()
    {
        g(2);        // will cause one call of f(char) followed
                     // by two calls of f(int)
        g('a');      // will cause three calls of f(char)
    }
Since 14.6.4.2  temp.dep.candidate says that only Koenig lookup is done from the instantiation context, and since 3.4.2  basic.lookup.koenig says that fundamental types have no associated namespaces, either the example is incorrect (and f(int) will never be called) or the specification in 14.6.4.2  temp.dep.candidate is incorrect.

Notes from 04/00 meeting:

The core working group agreed that the example as written is incorrect and should be reformulated to use a class type instead of a fundamental type. It was also decided to open a new issue dealing more generally with Koenig lookup and fundamental types.

(See also issues 213 and 225.)




87. Exception specifications on function parameters

Section: 15.4  except.spec     Status: drafting     Submitter: Steve Adamczyk     Date: 25 Jan 1999

In 15.4  except.spec paragraph 2:

An exception-specification shall appear only on a function declarator in a function, pointer, reference or pointer to member declaration or definition.
Does that mean in the top-level function declarator, or one at any level? Can one, for example, specify an exception specification on a pointer-to-function parameter of a function?
    void f(int (*pf)(float) throw(A))
Suggested answer: no. The exception specifications are valid only on the top-level function declarators.

However, if exception specifications are made part of a function's type as has been tentatively agreed, they would have to be allowed on any function declaration.

There is already an example of an exception specification for a parameter in the example in 15.4  except.spec paragraph 1.

Proposed resolution: Change text in 15.4  except.spec paragraph 1 from:

An exception-specification shall appear only on a function declarator in a function, pointer, reference or pointer to member declaration or definition.
to:
An exception-specification shall appear only on a function declarator in a function, pointer, reference or pointer to member declaration (including parameter and return type declarations) or definition.

[This wording does not allow exception specifications for elements of arrays. My recollection of the discussion in Kona was that we intended to allow exception specifications on any function declarator except in a typedef. This needs to be decided one way or the other. —wmm]

(See also issues 25, 92, and 133.)






Issues with "Open" Status


129. Stability of uninitialized auto variables

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

Does the Standard require that an uninitialized auto variable have a stable (albeit indeterminate) value? That is, does the Standard require that the following function return true?

    bool f() {
        unsigned char i;  // not initialized
        unsigned char j = i;
        unsigned char k = i;
        return j == k;    // true iff "i" is stable
    }
3.9.1  basic.fundamental paragraph 1 requires that uninitialized unsigned char variables have a valid value, so the initializations of j and k are well-formed and required not to trap. The question here is whether the value of i is allowed to change between those initializations.

Mike Miller: 1.9  intro.execution paragraph 10 says,

An instance of each object with automatic storage duration (3.7.2  basic.stc.auto ) is associated with each entry into its block. Such an object exists and retains its last-stored value during the execution of the block and while the block is suspended...
I think that the most reasonable way to read this is that the only thing that is allowed to change the value of an automatic (non-volatile?) value is a "store" operation in the abstract machine. There are no "store" operations to i between the initializations of j and k, so it must retain its original (indeterminate but valid) value, and the result of the program is well-defined.

The quibble, of course, is whether the wording "last-stored value" should be applied to a "never-stored" value. I think so, but others might differ.

Tom Plum: 7.1.5.1  dcl.type.cv paragraph 8 says,

[Note: volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation. See 1.9  intro.execution for detailed semantics. In general, the semantics of volatile are intended to be the same in C++ as they are in C. ]
From this I would infer that non-volatile means "shall not be changed by means undetectable by an implementation"; that the compiler is entitled to safely cache accesses to non-volatile objects if it can prove that no "detectable" means can modify them; and that therefore i shall maintain the same value during the example above.

Nathan Myers: This also has practical code-generation consequences. If the uninitialized auto variable lives in a register, and its value is really unspecified, then until it is initialized that register can be used as a temporary. Each time it's "looked at" the variable has the value that last washed up in that register. After it's initialized it's "live" and cannot be used as a temporary any more, and your register pressure goes up a notch. Fixing the uninit'd value would make it "live" the first time it is (or might be) looked at, instead.

Mike Ball: I agree with this. I also believe that it was certainly never my intent that an uninitialized variable be stable, and I would have strongly argued against such a provision. Nathan has well stated the case. And I am quite certain that it would be disastrous for optimizers. To ensure it, the frontend would have to generate an initializer, because optimizers track not only the lifetimes of variables, but the lifetimes of values assigned to those variables. This would put C++ at a significant performance disadvantage compared to other languages. Not even Java went this route. Guaranteeing defined behavior for a very special case of a generally undefined operation seems unnecessary.




189. Definition of operator and punctuator

Section: 2.12  lex.operators     Status: open     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.



139. Error in friend lookup example

Section: 3.4.1  basic.lookup.unqual     Status: open     Submitter: Mike Miller     Date: 14 Jul 1999

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




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



218. Specification of Koenig lookup

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

The original intent of the Committee when Koenig lookup was added to the language was apparently something like the following:

  1. The name in the function call expression is looked up like any other unqualified name.
  2. If the ordinary unqualified lookup finds nothing or finds the declaration of a (non-member) function, function template, or overload set, argument-dependent lookup is done and any functions found in associated namespaces are added to the result of the ordinary lookup.

This approach is not reflected in the current wording of the Standard. Instead, the following appears to be the status quo:

  1. Lookup of an unqualified name used as the postfix-expression in the function call syntax always performs Koenig lookup (3.4.1  basic.lookup.unqual paragraph 3).
  2. Unless ordinary lookup finds a class member function, the result of Koenig lookup always includes the declarations found in associated namespaces (3.4.2  basic.lookup.koenig paragraph 2), regardless of whether ordinary lookup finds a declaration and, if so, what kind of entity is found.
  3. The declarations from associated namespaces are not limited to functions and template functions by anything in 3.4.2  basic.lookup.koenig. However, if Koenig lookup results in more than one declaration and at least one of the declarations is a non-function, the program is ill-formed (7.3.4  namespace.udir, paragraph 4; although this restriction is in the description of the using-directive, the wording applies to any lookup that spans namespaces).

John Spicer: Argument-dependent lookup was created to solve the problem of looking up function names within templates where you don't know which namespace to use because it may depend on the template argument types (and was then expanded to permit use in nontemplates). The original intent only concerned functions. The safest and simplest change is to simply clarify the existing wording to that effect.

Bill Gibbons: I see no reason why non-function declarations should not be found. It would take a special rule to exclude "function objects", as well as pointers to functions, from consideration. There is no such rule in the standard and I see no need for one.

There is also a problem with the wording in 3.4.2  basic.lookup.koenig paragraph 2:

If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated namespaces and classes are not considered.

This implies that if the ordinary lookup of the name finds the declaration of a data member which is a pointer to function or function object, argument-dependent lookup is still done.

My guess is that this is a mistake based on the incorrect assumption that finding any member other than a member function would be an error. I would just change "class member function" to "class member" in the quoted sentence.

Mike Miller: In light of the issue of "short-circuiting" Koenig lookup when normal lookup finds a non-function, perhaps it should be written as "...finds the declaration of a class member, an object, or a reference, the associated namespaces..."?

Andy Koenig: I think I have to weigh in on the side of extending argument-dependent lookup to include function objects and pointers to functions. I am particularly concerned about [function objects], because I think that programmers should be able to replace functions by function objects without changing the behavior of their programs in fundamental ways.

Bjarne Stroustrup: I don't think we could seriously argue from first principles that [argument-dependent lookup should find only function declarations]. In general, C++ name lookup is designed to be independent of type: First we find the name(s), then, we consider its(their) meaning. 3.4  basic.lookup states "The name lookup rules apply uniformly to all names ..." That is an important principle.

Thus, I consider text that speaks of "function call" instead of plain "call" or "application of ()" in the context of koenig lookup an accident of history. I find it hard to understand how 5.2.2  expr.call doesn't either disallow all occurrences of x(y) where x is a class object (that's clearly not intended) or requires koenig lookup for x independently of its type (by reference from 3.4  basic.lookup). I suspect that a clarification of 5.2.2  expr.call to mention function objects is in order. If the left-hand operand of () is a name, it should be looked up using koenig lookup.

John Spicer: This approach causes otherwise well-formed programs to be ill-formed, and it does so by making names visible that might be completely unknown to the author of the program. Using-directives already do this, but argument-dependent lookup is different. You only get names from using-directives if you actually use using-directives. You get names from argument-dependent lookup whether you want them or not.

This basically breaks an important reason for having namespaces. You are not supposed to need any knowledge of the names used by a namespace.

But this example breaks if argument-dependent lookup finds non-functions and if the translation unit includes the <list> header somewhere.

    namespace my_ns {
        struct A {};
        void list(std::ostream&, A&);

        void f() {
            my_ns::A a;
            list(cout, a);
        }
    }

This really makes namespaces of questionable value if you still need to avoid using the same name as an entity in another namespace to avoid problems like this.

Erwin Unruh: Before we really decide on this topic, we should have more analysis on the impact on programs. I would also like to see a paper on the possibility to overload functions with function surrogates (no, I won't write one). Since such an extension is bound to wait until the next official update, we should not preclude any outcome of the discussion.

I would like to have a change right now, which leaves open several outcomes later. I would like to say that:

Koenig lookup will find non-functions as well. If it finds a variable, the program is ill-formed. If the primary lookup finds a variable, Koenig lookup is done. If the result contains both functions and variables, the program is ill-formed. [Note: A future standard will assign semantics to such a program.]

I myself are not comfortable with this as a long-time result, but it prepares the ground for any of the following long term solutions:

The note is there to prevent compiler vendors to put their own extensions in here.

(See also issues 113 and 143.)

Notes from 04/00 meeting:

Although many agreed that there were valid concerns motivating a desire for Koenig lookup to find non-function declarations, there was also concern that supporting this capability would be more dangerous than helpful in the absence of overload resolution for mixed function and non-function declarations.

A straw poll of the group revealed 8 in favor of Koenig lookup finding functions and function templates only, while 3 supported the broader result.




225. Koenig lookup and fundamental types

Section: 3.4.2  basic.lookup.koenig     Status: open     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.




141. Non-member function templates in member access expressions

Section: 3.4.5  basic.lookup.classref     Status: open     Submitter: fvali     Date: 31 July 1999

3.4.5  basic.lookup.classref paragraph 1 says,

In a class member access expression (5.2.5  expr.ref ), if the . or -> token is immediately followed by an identifier followed by a <, the identifier must be looked up to determine whether the < is the beginning of a template argument list (14.2  temp.names ) or a less-than operator. The identifier is first looked up in the class of the object expression. If the identifier is not found, it is then looked up in the context of the entire postfix-expression and shall name a class or function template.
There do not seem to be any circumstances in which use of a non-member template function would be well-formed as the id-expression of a class member access expression.


156. Name lookup for conversion functions

Section: 3.4.5  basic.lookup.classref     Status: open     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.




28. 'exit', 'signal' and static object destruction

Section: 3.6.3  basic.start.term     Status: open     Submitter: Martin J. O'Riordan     Date: 19 Oct 1997

The C++ standard has inherited the definition of the 'exit' function more or less unchanged from ISO C.

However, when the 'exit' function is called, objects of static extent which have been initialised, will be destructed if their types posses a destructor.

In addition, the C++ standard has inherited the definition of the 'signal' function and its handlers from ISO C, also pretty much unchanged.

The C standard says that the only standard library functions that may be called while a signal handler is executing, are the functions 'abort', 'signal' and 'exit'.

This introduces a bit of a nasty turn, as it is not at all unusual for the destruction of static objects to have fairly complex destruction semantics, often associated with resource release. These quite commonly involve apparently simple actions such as calling 'fclose' for a FILE handle.

Having observed some very strange behaviour in a program recently which in handling a SIGTERM signal, called the 'exit' function as indicated by the C standard.

But unknown to the programmer, a library static object performed some complicated resource deallocation activities, and the program crashed.

The C++ standard says nothing about the interaction between signals, exit and static objects. My observations, was that in effect, because the destructor called a standard library function other than 'abort', 'exit' or 'signal', while transitively in the execution context of the signal handler, it was in fact non-compliant, and the behaviour was undefined anyway.

This is I believe a plausible judgement, but given the prevalence of this common programming technique, it seems to me that we need to say something a lot more positive about this interaction.

Curiously enough, the C standard fails to say anything about the analogous interaction with functions registered with 'atexit' ;-)

Proposed Resolution (10/98):

The current Committee Draft of the next version of the ISO C standard specifies that the only standard library function that may be called while a signal handler is executing is 'abort'. This would solve the above problem.

[This issue should remain open until it has been decided that the next version of the C++ standard will use the next version of the C standard as the basis for the behavior of 'signal'.]




119. Object lifetime and aggregate initialization

Section: 3.8  basic.life     Status: open     Submitter: Jack Rouse     Date: 20 May 1999

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.




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.




158. Aliasing and qualification conversions

Section: 3.10  basic.lval     Status: open     Submitter: Mike Stump     Date: 20 Aug 1999

3.10  basic.lval paragraph 15 doesn't take into consideration 4.4  conv.qual . Why this matters is because:

    extern "C" int printf(const char *, ... );

    main() {
      int i = 1;
      int *ip = &i;

      const int *const *p1;
      int **p2;

      p1 = &ip;
      p2 = &ip;

      *p2 = (int *)42;
      printf("%d\n", *p1);
    }
won't get 42 without it. 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 .


170. Pointer-to-member conversions

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

The descriptions of explicit (5.2.9  expr.static.cast paragraph 9) and implicit (4.11  conv.mem paragraph 2) pointer-to-member conversions differ in two significant ways:

  1. In a static_cast, a conversion in which the class in the target pointer-to-member type is a base of the class in which the member is declared is permitted and required to work correctly, as long as the resulting pointer-to-member is eventually dereferenced with an object whose dynamic type contains the member. That is, the class of the target pointer-to-member type is not required to contain the member referred to by the value being converted. The specification of implicit pointer-to-member conversion is silent on this question.

    (This situation cannot arise in an implicit pointer-to-member conversion where the source value is something like &X::f, since you can only implicitly convert from pointer-to-base-member to pointer-to-derived-member. However, if the source value is the result of an explicit "up-cast," the target type of the conversion might still not contain the member referred to by the source value.)



  2. The target type in a static_cast is allowed to be more cv-qualified than the source type; in an implicit conversion, however, the cv-qualifications of the two types are required to be identical.
The first difference seems like an oversight. It is not clear whether the latter difference is intentional or not.


222. Sequence points and lvalue-returning operators

Section: expr     Status: open     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?




122. template-ids as unqualified-ids

Section: 5.1  expr.prim     Status: open     Submitter: Mike Miller     Date: 3 June 1999
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?


195. Converting between function and object pointers

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

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

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

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

Several points were raised in opposition to this suggestion:


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

Martin O'Riordan suggested an alternative approach:


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

Notes from 04/00 meeting:

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




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.




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[](size_t) or operator new[](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.




227. How many scopes in an if statement?

Section: 6.4  stmt.select     Status: open     Submitter: Marc Paterno     Date: 21 Apr 2000

The wording of 6.4  stmt.select paragraph 1 is misleading. Instead of

The substatement in a selection-statement (both substatements, in the else form of the if statement) implicitly defines a local scope (3.3  basic.scope).

it should say

... each substatement, in the else form...

As is, one is left with the impression that both "then" and "else" clauses together form a single scope.




157. Omitted typedef declarator

Section: dcl.dcl     Status: open     Submitter: Daveed Vandevoorde     Date: 19 Aug 1999

dcl.dcl paragraph 3 reads,

In a simple-declaration, the optional init-declarator-list can be omitted only when... the decl-specifier-seq contains either a class-specifier, an elaborated-type-specifier with a class-key (9.1  class.name ), or an enum-specifier. In these cases and whenever a class-specifier or enum-specifier is present in the decl-specifier-seq, the identifiers in those specifiers are among the names being declared by the declaration... In such cases, and except for the declaration of an unnamed bit-field (9.6  class.bit ), the decl-specifier-seq shall introduce one or more names into the program, or shall redeclare a name introduced by a previous declaration. [Example:
    enum { };           // ill-formed
    typedef class { };  // ill-formed
—end example]
In the absence of any explicit restrictions in 7.1.3  dcl.typedef , this paragraph appears to allow declarations like the following:
    typedef struct S { };    // no declarator
    typedef enum { e1 };     // no declarator
In fact, the final example in 7  dcl.dcl paragraph 3 would seem to indicate that this is intentional: since it is illustrating the requirement that the decl-specifier-seq must introduce a name in declarations in which the init-declarator-list is omitted, presumably the addition of a class name would have made the example well-formed.

On the other hand, there is no good reason to allow such declarations; the only reasonable scenario in which they might occur is a mistake on the programmer's part, and it would be a service to the programmer to require that such errors be diagnosed.




144. Position of friend specifier

Section: 7.1.5.3  dcl.type.elab     Status: open     Submitter: Daveed Vandevoorde     Date: 22 Jul 1999

7.1.5.3  dcl.type.elab paragraph 1 seems to impose an ordering constraint on the elements of friend class declarations. However, the general rule is that declaration specifiers can appear in any order. Should

    class C friend;
be well-formed?


172. Unsigned int as underlying type of enum

Section: 7.2  dcl.enum     Status: open     Submitter: Bjarne Stroustrup     Date: 26 Sep 1999

According to 7.2  dcl.enum paragraph 5, the underlying type of an enum is an unspecified integral type, which could potentially be unsigned int. The promotion rules in 4.5  conv.prom paragraph 2 say that such an enumeration value used in an expression will be promoted to unsigned int. This means that a conforming implementation could give the value false for the following code:

    enum { zero };
    -1 < zero;       // might be false
This is counterintuitive. Perhaps the description of the underlying type of an enumeration should say that an unsigned underlying type can be used only if the values of the enumerators cannot be represented in the corresponding signed type. This approach would be consistent with the treatment of integral promotion of bitfields (4.5  conv.prom paragraph 3).

On a related note, 7.2  dcl.enum paragraph 5 says,

the underlying type shall not be larger than int unless the value of an enumerator cannot fit in an int or unsigned int.
This specification does not allow for an enumeration like
    enum { a = -1, b = UINT_MAX };
Since each enumerator can fit in an int or unsigned int, the underlying type is required to be no larger than int, even though there is no such type that can represent all the enumerators.

See also issue 58.




138. Friend declaration name lookup

Section: 7.3.1.2  namespace.memdef     Status: open     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.

(See also issues 95, 136, 139, 143, 165, and 166, as well as paper J16/00-0006 = WG21 N1229.)




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 Core issue 56 and Core issue 85.




107. Linkage of operator functions

Section: 7.5  dcl.link     Status: open     Submitter: Stephen Clamage     Date: 21 Apr 1999

Steve Clamage: I can't find anything in the standard that prohibits a language linkage on an operator function. For example:

extern "C" int operator+(MyInt, MyInt) { ... }

Clearly it is a bad idea, you could have only one operator+ with "C" linkage in the entire program, and you can't call the function from C code.

Mike Miller: Well, you can't name an operator function in C code, but if the arguments are compatible (e.g., not references), you can call it from C code via a pointer. In fact, because the language linkage is part of the function type, you couldn't pass the address of an operator function into C code unless you could declare the function to be extern "C".

Fergus Henderson: In the general case, for linkage to languages other than C, this could well make perfect sense.

Steve Clamage:

But is it disallowed (as opposed to being stupid), and if so, where in the standard does it say so?

Mike Miller: I don't believe there's a restriction. Whether that is because of the (rather feeble) justification of being able to call an operator from C code via a pointer, or whether it was simply overlooked, I don't know.

Fergus Henderson: I don't think it is disallowed. I also don't think there is any need to explicitly disallow it.

Steve Clamage: I don't think the standard is clear enough on this point. I'd like to see a clarification.

I think either of these two clarifications would be appropriate:

  1. A linkage specification on an operator function is ill-formed.
  2. A linkage specification on an operator function is well-formed, but the semantics (e.g. name mangling) are implementation-defined. In addition, the rule about multiple functions with the same name having "C" linkage applies.
  3.     extern "C" T operator+(T,T); // ok
        extern "C" T operator-(T,T); // ok
        extern "C" U operator-(U);   // error, two extern "C" operator- 
    

Mike Miller: I think the point here is that something like

    extern "xyzzy" bool operator<(S&,S&)
could well make sense, if language xyzzy is sufficiently compatible with C++, and the one-function rule only applies to extern "C", not to other language linkages. Given that it might make sense to have general language linkages for operators, is it worthwhile to make an exception to the general rule by saying that you can have any language linkage on an operator function except "C" linkage? I don't like exceptions to general rules unless they're very well motivated, and I don't see sufficient motivation to make one here.

Certainly this capability isn't very useful. There are lots of things in C++ that aren't very useful but just weren't worth special-casing out of the language. I think this falls into the same category.

Mike Ball: I DON'T want to forbid operator functions within an extern "C". Rather I want to add operator functions to that sentence in paragraph 4 of 7.5  dcl.link which reads

A C language linkage is ignored for the names of class members and the member function type of class member functions.
My reason is simple: C linkage makes a total hash of scope. Any "C" functions declared with the same name in any namespace scope are the same function. In other words, namespaces are totally ignored.

This provision was added in toward the end of the standardization process, and was, I thought, primarily to make it possible to put the C library in namespace std. Otherwise, it seems an unwarrented attack on the very concept of scope. We (wisely) didn't force this on static member functions, since it would essentially promote them to the global scope.

Now I think that programmers think of operator functions as essentially part of a class. At least for one very common design pattern they are treated as part of the class interface. This pattern is the reason we invented Koenig lookup for operator functions.

What happens when such a class definition is included, deliberately or not, in an extern "C" declaration? The member operators continue to work, but the non-member operators can suddenly get strange and hard to understand messages. Quite possibly, they get the messages only when combined with other classes in other compilation units. You can argue that the programmer shouldn't put the class header in a linkage delaration in the first place, but I can still find books that recommend putting `extern "C"' around entire header files, so it's going to happen.

I think that including operator functions in the general exclusion from extern "C" doesn't remove a capability, rather it ensurs a capability that programmers already think they have.




160. Missing std:: qualification

Section: 8.2  dcl.ambig.res     Status: open     Submitter: Al Stevens     Date: 23 Aug 1999

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.




112. Array types and cv-qualifiers

Section: 8.3.4  dcl.array     Status: open     Submitter: Steve Clamage     Date: 4 May 1999

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




136. Default arguments and friend declarations

Section: 8.3.6  dcl.fct.default     Status: open     Submitter: Daveed Vandevoorde     Date: 9 July 1999

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.




155. Brace initializer for scalar

Section: 8.5  dcl.init     Status: open     Submitter: Steve Clamage     Date: 12 Aug 1999

It is not clear whether the following declaration is well-formed:

    struct S { int i; } s = { { 1 } };
According to 8.5.1  dcl.init.aggr paragraph 2, a brace-enclosed initializer is permitted for a subaggregate of an aggregate; however, i is a scalar, not an aggregate. 8.5  dcl.init paragraph 13 says that a standalone declaration like
    int i = { 1 };
is permitted, but it is not clear whether this says anything about the form of initializers for scalar members of aggregates.

This is (more) clearly permitted by the C89 Standard.




177. Lvalues vs rvalues in copy-initialization

Section: 8.5  dcl.init     Status: open     Submitter: Steve Adamczyk     Date: 25 October 1999

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.

(See also issue 84.)




175. Class name injection and base name access

Section: class     Status: open     Submitter: John Spicer     Date: 21 February 1999

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




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




58. Signedness of bit fields of enum type

Section: 9.6  class.bit     Status: open     Submitter: Steve Adamczyk     Date: 13 Oct 1998

Section 9.6  class.bit paragraph 4 needs to be more specific about the signedness of bit fields of enum type. How much leeway does an implementation have in choosing the signedness of a bit field? In particular, does the phrase "large enough to hold all the values of the enumeration" mean "the implementation decides on the signedness, and then we see whether all the values will fit in the bit field", or does it require the implementation to make the bit field signed or unsigned if that's what it takes to make it "large enough"?

(See also issue 172.)




8. Access to template arguments used in a function return type and in the nested name specifier

Section: 11  class.access     Status: open     Submitter: Mike Ball     Date: unknown

Consider the following example:

    class A {
       class A1{};
       static void func(A1, int);
       static void func(float, int);
       static const int garbconst = 3;
     public:
       template < class T, int i, void (*f)(T, int) > class int_temp {};
       template<> class int_temp<A1, 5, func> { void func1() };
       friend int_temp<A1, 5, func>::func1();
       int_temp<A1, 5, func>* func2();
   };
   A::int_temp<A::A1, A::garbconst + 2, &A::func>* A::func2() {...}
ISSUE 1:

In 11  class.access paragraph 5 we have:

This means, if we take the loosest possible definition of "access from a particular scope", that we have to save and check later the following names

      A::int_temp
      A::A1
      A::garbconst (part of an expression)
      A::func (after overloading is done)
I suspect that member templates were not really considered when this was written, and that it might have been written rather differently if they had been. Note that access to the template arguments is only legal because the class has been declared a friend, which is probably not what most programmers would expect.

Rationale:

Not a defect. This behavior is as intended.

ISSUE 2:

Now consider void A::int_temp<A::A1, A::garbconst + 2, &A::func>::func1() {...} By my reading of 11.8  class.access.nest , the references to A::A1, A::garbconst and A::func are now illegal, and there is no way to define this function outside of the class. Is there any need to do anything about either of these Issues?

This issue needs work.




10. Can a nested class access its own class name as a qualified name if it is a private member of the enclosing class?

Section: 11.8  class.access.nest     Status: open     Submitter: Josee Lajoie     Date: unknown

Paragraph 1 says: "The members of a nested class have no special access to members of an enclosing class..."

This prevents a member of a nested class from being defined outside of its class definition. i.e. Should the following be well-formed?

    class D {
        class E {
            static E* m;
        };
    };
     
    D::E* D::E::m = 1; // ill-formed
This is because the nested class does not have access to the member E in D. 11  class.access paragraph 5 says that access to D::E is checked with member access to class E, but unfortunately that doesn't give access to D::E. 11  class.access paragraph 6 covers the access for D::E::m, but it doesn't affect the D::E access. Are there any implementations that are standard compliant that support this?

Here is another example:

    class C {
        class B
        {
            C::B *t; //2 error, C::B is inaccessible
        };
    };
This causes trouble for member functions declared outside of the class member list. For example:
    class C {
        class B
        {
            B& operator= (const B&);
        };
    };
     
    C::B& C::B::operator= (const B&) { } //3
If the return type (i.e. C::B) is access checked in the scope of class B (as implied by 11  class.access paragraph 5) as a qualified name, then the return type is an error just like referring to C::B in the member list of class B above (i.e. //2) is ill-formed.

This issue depends on the outcome of Core issue 45.




86. Lifetime of temporaries in query expressions

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

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

Here's an example:

    const SFileName &C = noDir ? SFileName("abc") : SFileName("bcd");
Do the temporaries created by the SFileName conversions survive the end of the full expression?


199. Order of destruction of temporaries

Section: 12.2  class.temporary     Status: open     Submitter: Alan Nash     Date: 27 Jan 2000

12.2  class.temporary paragraph 3 simply states the requirement that temporaries created during the evaluation of an expression

are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created.
There is nothing said about the relative order in which these temporaries are destroyed.

Paragraph 5, dealing with temporaries bound to references, says

the temporaries created during the evaluation of the expression initializing the reference, except the temporary to which the reference is bound, are destroyed at the end of the full-expression in which they are created and in the reverse order of the completion of their construction.
Is this difference intentional? May temporaries in expressions other than those initializing references be deleted in non-LIFO order?

Notes from 04/00 meeting:

Steve Adamczyk expressed concern about constraining implementations that are capable of fine-grained parallelism — they may be unable to determine the order of construction without adding undesirable overhead.




111. Copy constructors and cv-qualifiers

Section: 12.8  class.copy     Status: open     Submitter: Jack Rouse     Date: 4 May 1999

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

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

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




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

Section: 13.3.1.1  over.match.call     Status: open     Submitter: Steve Adamczyk     Date: 26 Aug 1999

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.

Suggested resolution: 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.
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.



60. Reference binding and valid conversion sequences

Section: 13.3.3.1.4  over.ics.ref     Status: open     Submitter: Steve Adamczyk     Date: 13 Oct 1998

Does dropping a cv-qualifier on a reference binding prevent the binding as far as overload resolution is concerned? Paragraph 4 says "Other restrictions on binding a reference to a particular argument do not affect the formation of a conversion sequence." This was intended to refer to things like access checking, but some readers have taken that to mean that any aspects of reference binding not mentioned in this section do not preclude the binding.




115. Address of template-id

Section: 13.4  over.over     Status: open     Submitter: John Spicer     Date: 7 May 1999
    template <class T> void f(T);
    template <class T> void g(T);
    template <class T> void g(T,T);

    int main()
    {
        (&f<int>);
        (&g<int>);
    }
The question is whether &f<int> identifies a unique function. &g>int> is clearly ambiguous.

13.4  over.over paragraph 1 says that a function template name is considered to name a set of overloaded functions. I believe it should be expanded to say that a function template name with an explicit template argument list is also considered to name a set of overloaded functions.

In the general case, you need to have a destination type in order to identify a unique function. While it is possible to permit this, I don't think it is a good idea because such code depends on there only being one template of that name that is visible.

The EDG front end issues an error on this use of "f". egcs 1.1.1 allows it, but the most current snapshot of egcs that I have also issues an error on it.

It has been pointed out that when dealing with nontemplates, the rules for taking the address of a single function differ from the rules for an overload set, but this asymmetry is needed for C compatibility. This need does not exist for the template case.

My feeling is that a general rule is better than a general rule plus an exception. The general rule is that you need a destination type to be sure that the operation will succeed. The exception is when there is only one template in the set and only then when you provide values for all of the template arguments.

It is true that in some cases you can provide a shorthand, but only if you encourage a fragile coding style (that will cause programs to break when additional templates are added).

I think the standard needs to specify one way or the other how this case should be handled. My recommendation would be that it is ill-formed.




110. Can template functions and classes be declared in the same scope?

Section: 14  temp     Status: open     Submitter: John Spicer     Date: 28 Apr 1999

According to 14  temp paragraph 5,

Except that a function template can be overloaded either by (non-template) functions with the same name or by other function templates with the same name (14.8.3  temp.over ), a template name declared in namespace scope or in class scope shall be unique in that scope.
3.3.7  basic.scope.hiding paragraph 2 agrees that only functions, not function templates, can hide a class name declared in the same scope:
A class name (9.1  class.name ) or enumeration name (7.2  dcl.enum ) can be hidden by the name of an object, function, or enumerator declared in the same scope.
However, 3.3  basic.scope paragraph 4 treats functions and template functions together in this regard:
Given a set of declarations in a single declarative region, each of which specifies the same unqualified name,

John Spicer: You should be able to take an existing program and replace an existing function with a function template without breaking unrelated parts of the program. In addition, all of the compilers I tried allow this usage (EDG, Sun, egcs, Watcom, Microsoft, Borland). I would recommend that function templates be handled exactly like functions for purposes of name hiding.

Martin O'Riordan: I don't see any justification for extending the purview of what is decidedly a hack, just for the sake of consistency. In fact, I think we should go further and in the interest of consistency, we should deprecate the hack, scheduling its eventual removal from the C++ language standard.

The hack is there to allow old C programs and especially the 'stat.h' file to compile with minimum effort (also several other Posix and X headers). People changing such older programs have ample opportunity to "do it right". Indeed, if you are adding templates to an existing program, you should probably be placing your templates in a 'namespace', so the issue disappears anyway. The lookup rules should be able to provide the behaviour you need without further hacking.




204. Exported class templates

Section: 14  temp     Status: open     Submitter: Robert Klarer     Date: 11 Feb 2000

14  temp paragraph 7 allows class templates to be declared exported, including member classes and member class templates (implicitly by virtue of exporting the containing template class). However, paragraph 8 does not exclude exported class templates from the statement that

An exported template need only be declared (and not necessarily defined) in a translation unit in which it is instantiated.
This is an incorrect implication; however, it is also not dispelled in 14.7.1  temp.inst 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.
This wording says nothing about the translation unit in which the definition must be provided. Contrast this with 14.7.2  temp.explicit paragraph 3:
A definition of a class template or a class member template shall be in scope at the point of the explicit instantiation of the class template or class member template.

Suggested resolution:


(See also issue 212.)

Notes from 04/00 meeting:

John Spicer opined that even though 14  temp paragraph 7 speaks of "declaring a class template exported," that does not mean that the class template is "an exported template" in the sense of paragraph 8. He suggested clarifying paragraph 7 to that effect instead of the change to paragraph 8 suggested above, and questioned the need for a change to 14.7.1  temp.inst.




205. Templates and static data members

Section: 14  temp     Status: open     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.)




184. Default arguments in template template-parameters

Section: 14.1  temp.param     Status: open     Submitter: John Spicer     Date: 11 Nov 1999

John Spicer: Where can default values for the template parameters of template template parameters be specified and where to they apply?

For normal template parameters, defaults can be specified only in class template declarations and definitions, and they accumulate across multiple declarations in the same way that function default arguments do.

I think that defaults for parameters of template template parameters should be handled differently, though. I see no reason why such a default should extend beyond the template declaration with which it is associated. In other words, such defaults are a property of a specific template declaration and are not part of the interface of the template.

    template <class T = float> struct B {};

    template <template <class _T = float> class T> struct A {
        inline void f();
        inline void g();
    };

    template <template <class _T> class T> void A<T>::f() {
        T<> t;  // Okay? (proposed answer - no)
    }

    template <template <class _T = char> class T> // Okay? (proposed answer - yes)
    void A<T>::g() {
        T<> t;  // T<char> or T<float>?  (proposed answer - T<char>)
    }

    int main() {
        A<B> ab;
        ab.f();
    }

I don't think this is clear in the standard.

Gabriel Dos Reis: On the other hand I fail to see the reasons why we should introduce yet another special rule to handle that situation differently. I think we should try to keep rules as uniform as possible. For default values, it has been the case that one should look for any declaration specifying default values. Breaking that rules doesn't buy us anything, at least as far as I can see. My feeling is that [allowing different defaults in different declarations] is very confusing.

Mike Miller: I'm with John on this one. Although we don't have the concept of "prototype scope" for template parameter lists, the analogy with function parameters would suggest that the two declarations of T (in the template class definition and the template member function definition) are separate declarations and completely unrelated. While it's true that you accumulate default arguments on top-level declarations in the same scope, it seems to me a far leap to say that we ought also to accumulate default arguments in nested declarations. I would expect those to be treated as being in different scopes and thus not to share default argument information.

When you look up the name T in the definition of A<T>::f(), the declaration you find has no default argument for the parameter of T, so T<> should not be allowed.




226. Default template arguments for function templates

Section: 14.1  temp.param     Status: open     Submitter: Bjarne Stroustrup     Date: 19 Apr 2000

The prohibition of default template arguments for function templates is a misbegotten remnant of the time where freestanding functions were treated as second class citizens and required all template arguments to be deduced from the function arguments rather than specified.

The restriction seriously cramps programming style by unnecessarily making freestanding functions different from member functions, thus making it harder to write STL-style code.

Suggested resolution:

Replace

A default template-argument shall not be specified in a function template declaration or a function template definition, nor in the template-parameter-list of the definition of a member of a class template.

by

A default template-argument shall not be specified in the template-parameter-list of the definition of a member of a class template.

The actual rules are as stated for arguments to class templates.




96. Syntactic disambiguation using the template keyword

Section: 14.2  temp.names     Status: open     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.




228. Use of template keyword with non-member templates

Section: 14.2  temp.names     Status: open     Submitter: Daveed Vandevoorde     Date: 4 May 2000

Consider the following example:

    template<class T>
    struct X {
       virtual void f();
    };

    template<class T>
    struct Y {
       void g(X<T> *p) {
	  p->template X<T>::f();
       }
    };

This is an error because X is not a member template; 14.2  temp.names paragraph 5 says:

If a name prefixed by the keyword template is not the name of a member template, the program is ill-formed.

In a way this makes perfect sense: X is found to be a template using ordinary lookup even though p has a dependent type. However, I think this makes the use of the template prefix even harder to teach.

Was this intentionally outlawed?




23. Some questions regarding partial ordering of function templates

Section: 14.5.5.2  temp.func.order     Status: open     Submitter: unknown     Date: unknown

Issue 1:

14.5.5.2  temp.func.order paragraph 2 says:

Given two overloaded function templates, whether one is more specialized than another can be determined by transforming each template in turn and using argument deduction (14.8.2  temp.deduct ) to compare it to the other.
14.8.2  temp.deduct now has 4 subsections describing argument deduction in different situations. I think this paragraph should point to a subsection of 14.8.2  temp.deduct .

Rationale:

This is not a defect; it is not necessary to pinpoint cross-references to this level of detail.

Issue 2:

14.5.5.2  temp.func.order paragraph 4 says:

Using the transformed function parameter list, perform argument deduction against the other function template. The transformed template is at least as specialized as the other if, and only if, the deduction succeeds and the deduced parameter types are an exact match (so the deduction does not rely on implicit conversions).
In "the deduced parameter types are an exact match", the terms exact match do not make it clear what happens when a type T is compared to the reference type T&. Is that an exact match?

Issue 3:

14.5.5.2  temp.func.order paragraph 5 says:

A template is more specialized than another if, and only if, it is at least as specialized as the other template and that template is not at least as specialized as the first.
What happens in this case:
    template<class T> void f(T,int);
    template<class T> void f(T, T);
    void f(1,1);
For the first function template, there is no type deduction for the second parameter. So the rules in this clause seem to imply that the second function template will be chosen.

Rationale:

This is not a defect; the standard unambiguously makes the above example ill-formed due to ambiguity.




214. Partial ordering of function templates is underspecified

Section: 14.5.5.2  temp.func.order     Status: open     Submitter: Martin von Loewis/Martin Sebor     Date: 13 Mar 2000

In 14.5.5.2  temp.func.order, partial ordering is explained in terms of template argument deduction. However, the exact procedure for doing so is not specified. A number of details are missing, they are explained as sub-issues below.

  1. 14.5.5.2  temp.func.order paragraph 2 refers to 14.8.2  temp.deduct for argument deduction. This is the wrong reference; it explains how explicit arguments are processed (paragraph 2) and how function parameter types are adjusted (paragraph 3). Neither of these steps is meaningful in the context of partial ordering. Next in deduction follows one of the steps in 14.8.2.1  temp.deduct.call, 14.8.2.2  temp.deduct.funcaddr, 14.8.2.3  temp.deduct.conv, or 14.8.2.4  temp.deduct.type. The standard does not specify which of these contexts apply to partial ordering.
  2. Because 14.8.2.1  temp.deduct.call and 14.8.2.3  temp.deduct.conv both start with actual function parameters, it is meaningful to assume that partial ordering uses 14.8.2.4  temp.deduct.type, which only requires types. With that assumption, the example in 14.5.5.2  temp.func.order paragraph 5 becomes incorrect, considering the two templates
        template<class T> void g(T);  // #1
        template<class T> void g(T&); // #2
    
    Here, #2 is at least as specialized as #1: With a synthetic type U, #2 becomes g(U&); argument deduction against #1 succeeds with T=U&. However, #1 is not at least as specialized as #2: Deducing g(U) against g(T&) fails. Therefore, the second template is more specialized than the first, and the call g(x) is not ambiguous.
  3. According to John Spicer, the intent of the partial ordering was that it uses deduction as in a function call (14.8.2.1  temp.deduct.call), which is indicated by the mentioning of "exact match" in 14.5.5.2  temp.func.order paragraph 4. If that is indeed the intent, it should be specified how values are obtained for the step in 14.8.2.1  temp.deduct.call paragraph 1, where the types of the arguments are determined. Also, since 14.8.2.1  temp.deduct.call paragraph 2 drops references from the parameter type, symmetrically, references should be dropped from the argument type (which is done in 5  expr paragraph 2, for a true function call).
  4. 14.5.5.2  temp.func.order paragraph 4 requires an "exact match" for the "deduced parameter types". It is not clear whether this refers to the template parameters, or the parameters of the template function. Considering the example
        template<class S> void g(S);  // #1
        template<class T> void g(T const &); // #3
    
    Here, #3 is clearly at least as specialized as #1. To determine whether #1 is at least as specialized as #3, a unique type U is synthesized, and deduction of g<U>(U) is performed against #3. Following the rules in 14.8.2.1  temp.deduct.call, deduction succeeds with T=U. Since the template argument is U, and the deduced template parameter is also U, we have an exact match between the template parameters. Even though the conversion from U to U const & is an exact match, it is not clear whether the added qualification should be taken into account, as it is in other places.

Issue 200 covers a related issue, illustrated by the following example:

    template <class T> T f(int);
    template <class T, class U> T f(U);
    void g() {
        f<int>(1);
    }

Even though one template is "obviously" more specialized than the other, deduction fails in both directions because neither function parameter list allows template parameter T to be deduced.




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.


224. Definition of dependent names

Section: 14.6.2.1  temp.dep.type     Status: open     Submitter: Derek Inglis     Date: 30 Nov 1999

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.




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




63. Class instantiation from pointer conversion to void*, null and self

Section: 14.7.1  temp.inst     Status: open     Submitter: Steve Adamczyk     Date: 13 Oct 1998

A template is implicitly instantiated because of a "pointer conversion" on an argument. This was intended to include related-class conversions, but it also inadvertently includes conversions to void*, null pointer conversions, cv-qualification conversions and the identity conversion.

It is not clear whether a reinterpret_cast of a pointer should cause implicit instantiation.

Proposed resolution (10/99): Replace 14.7.1  temp.inst paragraph 4, up to the example, with the following:

A class template specialization is implicitly instantiated if the class type is used in a context that requires a completely-defined object type or if the completeness of the class type affects the semantics of the program. [Note: in particular, if the semantics of an expression depend on the member or base class lists of a class template specialization, the class template specialization is implicitly generated. For instance, deleting a pointer to class type depends on whether or not the class declares a destructor, and conversion between pointer to class types depends on the inheritance relationship between the two classes involved. ]

(See also issue 212.)




212. Implicit instantiation is not described clearly enough

Section: 14.7.1  temp.inst     Status: open     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.)




182. Access checking on explicit specializations

Section: 14.7.3  temp.expl.spec     Status: open     Submitter: John Spicer     Date: 8 Nov 1999

John Spicer: Certain access checks are suppressed on explicit instantiations. 14.7.2  temp.explicit paragraph 8 says:

The usual access checking rules do not apply to names used to specify explicit instantiations. [Note: In particular, the template arguments and names used in the function declarator (including parameter types, return types and exception specifications) may be private types or objects which would normally not be accessible and the template may be a member template or member function which would not normally be accessible. ]
I was surprised that similar wording does not exist (that I could find) for explicit specializations. I believe that the two cases should be handled equivalently in the example below (i.e., that the specialization should be permitted).
    template <class T> struct C {
    void f();
    void g();
    };

    template <class T> void C<T>::f(){}
    template <class T> void C<T>::g(){}

    class A {
    class B {};
    void f();
    };

    template void C<A::B>::f();    // okay
    template <> void C<A::B>::g(); // error - A::B inaccessible

    void A::f() {
    C<B> cb;
    cb.f();
    }

Mike Miller: According to the note in 14.3  temp.arg paragraph 3,

if the name of a template-argument is accessible at the point where it is used as a template-argument, there is no further access restriction in the resulting instantiation where the corresponding template-parameter name is used.
(Is this specified anywhere in the normative text? Should it be?) In the absence of text to the contrary, this blanket permission apparently applies to explicitly-specialized templates as well as to implicitly-generated ones (is that right?). If so, I don't see any reason that an explicit instantiation should be treated differently from an explicit specialization, even though the latter involves new program text and the former is just a placement instruction to the implementation.


92. Should exception specifications be part of the type system?

Section: 15.4  except.spec     Status: open     Submitter: Jonathan Schilling     Date: 2 Feb 1999

It was tentatively agreed at the Santa Cruz meeting that exception specifications should fully participate in the type system. This change would address gaps in the current static checking of exception specifications such as

    void (*p)() throw(int);
    void (**pp)() throw() = &p;   // not currently an error

This is such a major change that it deserves to be a separate issue.

See also issues 25, 87, and 133.




219. Cannot defend against destructors that throw exceptions

Section: 15.5.1  except.terminate     Status: open     Submitter: Herb Sutter     Date: 31 Mar 2000

Destructors that throw can easily cause programs to terminate, with no possible defense. Example: Given

    struct XY { X x; Y y; };

Assume that X::~X() is the only destructor in the entire program that can throw. Assume further that Y construction is the only other operation in the whole program that can throw. Then XY cannot be used safely, in any context whatsoever, period — even simply declaring an XY object can crash the program:

    XY xy; // construction attempt might terminate program:
	   //   1. construct x -- succeeds
	   //   2. construct y -- fails, throws exception
	   //   3. clean up by destroying x -- fails, throws exception,
	   //      but an exception is already active, so call 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 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 terminate() be conforming. I see 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 terminate() becomes non-conforming. In many systems, there are — and/or should be — error-handling and recovery mechanisms beyond what is offered by a single C++ program.

Andy Koenig: If we were to prohibit writing a destructor that can throw, how would I solve the following problem?

I want to write a class that does buffered output. Among the other properties of that class is that destroying an object of that class writes the last buffer on the output device before freeing memory.

What should my class do if writing that last buffer indicates a hardware output error? My user had the option to flush the last buffer explicitly before destroying the object, but didn't do so, and therefore did not anticipate such a problem. Unfortunately, the problem happened anyway. Should I be required to suppress this error indication anyway? In all cases?

In practice, I would rather thrown an exception, even at the risk of crashing the program if we happen to be in the middle of stack unwinding. The reason is that the program would crash only if a hardware error occurred in the middle of cleaning up from some other error that was in the process of being handled. I would rather have such a bizarre coincidence cause a crash, which stands a chance of being diagnosed later, than to be ignored entirely and leave the system in a state where the ignore error could cause other trouble later that is even harder to diagnose.

If I'm not allowed to throw an exception when I detect this problem, what are my options?

Herb Sutter: I understand that some people might feel that "a failed dtor during stack unwinding is preferable in certain cases" (e.g., when recovery can be done beyond the scope of the program), but the problem is "says who?" It is the application program that should be able to decide whether or not such semantics are correct for it, and the problem here is that with the status quo a program cannot defend itself against a 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 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.




223. The meaning of deprecation

Section: depr     Status: open     Submitter: Andrew Koenig     Date: 19 Apr 2000

During the discussion of issues 168 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 guarantee 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.