| Document number: | J16/01-0036 = WG21 N1322 |
| Date: | 12 September, 2001 |
| Project: | Programming Language C++ |
| Reference: | ISO/IEC IS 14882:1998(E) |
| Reply to: | J. Stephen Adamczyk |
| jsa@edg.com |
This document contains the C++ core language issues that have been categorized as Defect Reports by the C++ Standard Committee (J16 + WG21), along with their proposed resolutions. THESE RESOLUTIONS ARE NOT YET PART OF THE INTERNATIONAL STANDARD FOR C++. They are provided for informational purposes only, as an indication of the intent of the Committee. They should not be considered definitive until or unless they appear in an approved Technical Corrigendum or revised International Standard for C++.
This document is part of a group of related documents that together describe the issues that have been raised regarding the C++ Standard. The other documents in the group are:
For more information, including a description of the meaning of the issue status codes and instructions on reporting new issues, please see the Active Issues List.
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/00):
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.
From reflector message core-7838.
Footnotes 26 and 29 both use the phrase "following the function declarator" incorrectly: the function declarator includes the parameter list, but the footnotes make clear that they intend what's said to apply to names inside the parameter list. Presumably the phrase should be "following the function declarator-id."
Proposed Resolution (04/99): Change the text in 3.4.1 basic.lookup.unqual paragraph 6 from:
A name used in the definition of a function [footnote: This refers to unqualified names following the function declarator; such a name may be used as a type or as a default argument name in the parameter-declaration-clause, or may be used in the function body. end footnote] that is ...to:
A name used in the definition of a function following the function's declarator-id [footnote: This refers to unqualified names that occur, for instance, in a type or default argument expression in the parameter-declaration-clause or used in the function body. end footnote] that is ...Change the text in 3.4.1 basic.lookup.unqual paragraph 8 from:
A name used in the definition of a function that is a member function (9.3 class.mfct ) [footnote: That is, an unqualified name following the function declarator; such a name may be used as a type or as a default argument name in the parameter-declaration-clause, or may be used in the function body, or, if the function is a constructor, may be used in the expression of a mem-initializer. end footnote] of class X shall be ...to:
A name used in the definition of a member function (9.3 class.mfct ) of class X following the function's declarator-id [footnote: That is, an unqualified name that occurs, for instance, in a type or default argument expression in the parameter-declaration-clause, in the function body, or in an expression of a mem-initializer in a constructor definition. end footnote] shall be ...
From reflector message core-7768.
If an argument used for lookup is the address of a group of overloaded functions, are there any associated namespaces or classes? What if it's the address of a function template?
My inclination is to say no to both.
From Mike Miller:
We discussed this on the reflector a few weeks ago. I'll leave the template case for the Core III experts, but I'd find it surprising if the overload case weren't handled as the obvious generalization of the single-function case. For a single function, the associated namespaces are those of the types used in the parameters and return type; I would expect that using an overloaded function name would simply be the union of the namespaces from the members of the overload set. That would be the simplest and most intuitive, IMHO — is there an argument for doing it differently?
Proposed Resolution (04/99): In 3.4.2 basic.lookup.koenig paragraph 2, add following the last bullet in the list of associated classes and namespaces for various argument types (not a bullet itself because overload sets and templates do not have a type):
In addition, if the argument is the name or address of a set of overloaded functions and/or function templates, its associated classes and namespaces are the union of those associated with each of the members of the set: the namespace in which the function or function template is defined and the classes and namespaces associated with its (non-dependent) parameter types and return type.
From reflector message core-7952.
Section 3.4.2 basic.lookup.koenig includes the following:
struct A {
union U {};
friend void f(U);
};
struct B {
struct S {};
friend void f(S);
};
int main() {
A::U u;
f(u); // okay: A is an associated class
B::S s;
f(s); // error: no matching f(), B is not an associated class
}
Certainly the enclosing class should also be an associated class for nested
class types, shouldn't it?
Proposed Resolution (10/99): Change the two referenced bullets to read:
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/00):
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.]
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 (10/00): Remove the line
struct Data; // OK: Redeclares nested Data
See also Core issue 36 and Core issue 56.
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 (10/00):
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..."):
From reflector message core-7956.
The text of 3.8 basic.life paragraph 2 currently reads,
The phrase "an object of type" is obviously incorrect. I believe it should read "an object of POD type." Does anyone disagree?
Proposed Resolution (10/99): As suggested.
From reflector message core-7850.
Can you use memcpy on non-member POD subobjects of non-POD objects?
In 3.9 basic.types paragraphs 2 and 3 we have:
For any complete POD object type T, whether or not the object holds a valid value of type T, the underlying bytes (1.7 intro.memory ) making up the object can be copied into an array of char or unsigned char*. If the content of the array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value. [Example elided]Paragraph 3 doesn't repeat the restriction of paragraph 2. Should it be assumed? Otherwise only complete POD types are copyable to an array of char and back, but scribbling over subobjects is OK. (Or perhaps a "distinct T object" is a complete object...)*[Footnote: By using, for example, the library functions (17.4.1.2 lib.headers ) memcpy or memmove. end footnote]For any POD type T, if two pointers to T point to distinct T objects obj1 and obj2, if the value of obj1 is copied into obj2, using the memcpy library function, obj2 shall subsequently hold the same value as obj1.
Proposed Resolution (04/99): Change the text in 3.9 basic.types paragraph 2 from:
For any complete POD object type T, ...to:
For any object (other than a base class subobject) of POD type T, ...Change the text in 3.9 basic.types paragraph 3 from:
For any POD type T, if two pointers to T point to distinct T objects obj1 and obj2,to:
For any POD type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base class subobject, ...
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/00):
From reflector message core-8092.
The cross-reference is incorrect in the first sentence after the grammar in 5.1 expr.prim paragraph 7:A nested-name-specifier that names a class, optionally followed by the keyword template (14.8.1 temp.arg.explicit ), ...The use of the template keyword in this context is discussed in 14.2 temp.names , not 14.8.1 temp.arg.explicit .
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/00):
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.)
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 (10/00):
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).
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.]
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
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.]
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 (10/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), ...
From reflector messages core-8096 through 8100.
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/00): 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 ).
From reflector message core-8198.
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/00):
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.
From reflector message core-7911.
5.3.4 expr.new paragraph 6 says:
The expression in a direct-new-declarator shall have integral type (3.9.1 basic.fundamental ) with a non-negative value.I assume the intent was to also allow enumeral types, as we do in 5.2.1 expr.sub ?
Proposed Resolution (10/99): Replace "integral type" by "integral or enumeration type" in 5.3.4 expr.new paragraph 6.
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 (10/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.
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.
Proposed resolution (10/00):
Remove the words or function from paragraph 8.
From reflector messages core-7890, 7895, 7896, 7904, 8101-8106.
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.
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 (10/00):
A valid value of an object pointer type represents either the address of a byte in memory (1.7 intro.memory) or a null pointer (4.10 conv.ptr). If an object of type T is located at an address A, a pointer of type cv T* whose value is the address A is said to point to that object, regardless of how the value was obtained. [Note: for instance, the address one past the end of an array (5.7 expr.add) would be considered to point to an unrelated object of the array's element type that might be located at that address.]
Two pointers of the same type compare equal if and only if they are both null, both point to the same function, or both represent the same address (3.9.2 basic.compound).
(See also paper J16/00-0011 = WG21 N1234.)
From reflector messages 8396-7.
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 (10/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.
From reflector messages core-7960, 7961, 7962 and 7965.
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.)
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.
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.
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 (10/00):
Remove the phrase "within its scope" in 9.4.2 class.static.data paragraph 4.
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.
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.
Change the referenced 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."
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.
Proposed resolution (10/00): As suggested.
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.
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.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.John: Agreed.
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."export" is an additional attribute that is separate from linkage, but that can only be applied to templates with external linkage.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.
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 (10/00):
Change the 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.
Can a typedef redeclaration be done within a class?
class X {
typedef int I;
typedef int I;
};
See also
9.2
class.mem
,
Core issue 36,
and
Core issue 85.
Proposed Resolution (10/99): Change 7.1.3 dcl.typedef paragraph 2 from "In a given scope" to "In a given non-class scope."
The following code does not compile with the EDG compiler:
volatile const int a = 5;
int b[a];
The standard,
7.1.5.1
dcl.type.cv
, says:
A variable of const-qualified integral or enumeration type initialized by an integral constant expression can be used in integral constant expressions.This doesn't say it can't be const volatile-qualified, although I think that was what was intended.
Proposed Resolution (10/99): Change the referenced text in paragraph 2 of 7.1.5.1 dcl.type.cv to read:
I can't find the answer to the following in the standard. Does anybody have a reference?
The syntax for elaborated type specifier is
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.*And in 14.5.3 temp.friend we find the example[Footnote: The class-key of the elaborated-type-specifier is required. —end footnote]
[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 (10/00):
Change the grammar in 7.1.5.3
dcl.type.elab to read
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/00):
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).
In the last sentence of the same paragraph, change "Names declared in the global namespace scope" to "Names with global namespace scope."
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.
From reflector messages 8321-3.
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/00):
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.)
From reflector message core-7994:
Consider the following:
extern "C" void f();
namespace N {
extern "C" void f();
}
using N::f;
According to
7.3.3
namespace.udecl
paragraph 11, the using-declaration is an error:
If a function declaration in namespace scope or block scope has the same name and the same parameter types as a function introduced by a using-declaration, the program is ill-formed.Based on the context (7.3.3 namespace.udecl paragraph 10 simply reiterates the requirements of 3.3 basic.scope ), one might wonder if the failure to exempt extern "C" functions was intentional or an oversight. After all, there is only one function f() involved, because it's extern "C", so ambiguity is not a reason to prohibit the using-declaration.
This also breaks the relatively strong parallel between extern "C" functions and typedefs established in our discussion of Core issue 14 in Santa Cruz. There the question was for using-directives:
typedef unsigned int size_t;
extern "C" int f();
namespace N {
typedef unsigned int size_t;
extern "C" int f();
}
using namespace N;
int i = f(); // ambiguous "f"?
size_t x; // ambiguous "size_t"?
We decided for both that there was no ambiguity because each
pair of declarations declares the same entity. (According to
3
basic
paragraph 3, a typedef name is not an entity, but a type is; thus the
declarations of size_t declare the same entity "unsigned int".)
In the context of using-declarations, there is no explicit extension of the restrictions in 3.3 basic.scope paragraph 4 except as noted above for function declarations; thus the parallel scenario for a typedef is not ill-formed:
typedef unsigned int size_t;
namespace N {
typedef unsigned int size_t;
};
using N::size_t; // okay, both declarations
// refer to the same entity
I think the first sentence of
7.3.3
namespace.udecl
paragraph 11 ought to be rewritten as:
If a function declaration in namespace scope or block scope has the same name and the same parameter types as a function introduced by a using-declaration, and the declarations do not declare the same function, the program is ill-formed.
Proposed Resolution (10/99): As suggested.
From reflector messages core-8001 and core-8003.
Section 7.3.4 namespace.udir paragraph 3 uses the term extended-namespace-definition three times:
If a namespace is extended by an extended-namespace-definition after a using-directive for that namespace is given, the additional members of the extended namespace and the members of namespaces nominated by using-directives in the extended-namespace-definition can be used after the extended-namespace-definition.I think the intent is clear, but unfortunately I cannot find any other mention (or definition) of this term.
Mike Miller: True enough; in Section 7.3.1 namespace.def [the grammar] it's called an extension-namespace-definition.
Proposed Resolution (10/99): Systematically replace "extended-namespace-definition" by "extension-namespace-definition".
(Previously numbered 864.)
7.5 dcl.link paragraph 6 says the following:
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.
[The following discussion is from messages 8722 and 8724.]
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.
Notes from 10/00 meeting:
After further discussion, the core language working group determined that the more extensive proposal described above is not needed and that the following changes are sufficient.
Proposed resolution (04/01):
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.
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.
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.
}
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(); // erroris 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]
From reflector message core-7714.
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,
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 (04/01):
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.
(From J16/99-0005 = WG21 N1182, "Proposed Resolutions for Core Language Issues 6, 14, 20, 40, and 89")
There are two sub-issues. The first concerns the statement in 8.3 dcl.meaning paragraph 1,
The id-expression of a declarator-id shall be a simple identifier except for the declaration of some special functions (12.3 class.conv , 12.4 class.dtor , 13.5 over.oper ) and for the declaration of template specializations or partial specializations (14.7 temp.spec ).The second sub-issue is regarding another statement in the same paragraph:
A declarator-id shall not be qualified except for the definition of a member function (9.3 class.mfct ) or static data member (9.4 class.static ) or nested class (9.7 class.nest ) outside of its class, the definition or explicit instantiation of a function, variable or class member of a namespace outside of its namespace, or...Analysis
The problem in the first sub-issue is that the wrong syntactic non-terminal is mentioned. The relevant portions of the grammar are:
If an unqualified-id is used as the id-expression of a declarator-id, it shall be a simple identifier except...However, it does not appear that this restriction has any meaning; all of the possible cases of unqualified-ids are represented in the list of exceptions! Rather than recasting the sentence into a correct but useless form, it would be better to remove it altogether.
The second sub-issue deals with the conditions under which a qualified-id can be used in a declarator, including "the definition of a...nested class" and "the definition or explicit instantiation of a...class member of a namespace." However, the name in a class definition is not part of a declarator; these constructs do not belong in a list of declarator contexts.
Proposed Resolution for sub-issue 1 (04/99):
The suggested resolution for the first sub-issue overlooked the fact that the existing wording has the additional effect of prohibiting the use of the non-identifier syntax for declaring other than the listed entities. Thus the proposed wording for the first sub-issue is:
Change 8.3 dcl.meaning paragraph 1 from:
The id-expression of a declarator-id shall be a simple identifier except...to:
An unqualified-id occurring in a declarator-id shall be a simple identifier except...
Proposed Resolution for sub-issue 2 (10/99):
Change 8.3 dcl.meaning paragraph 1 from:
A declarator-id shall not be qualified except for the definition of a member function (9.3 class.mfct ) or static data member (9.4 class.static ) or nested class (9.7 class.nest ) outside of its class, the definition or explicit instantiation of a function, variable or class member of a namespace outside of its namespace, or...to
A declarator-id shall not be qualified except for the definition of a member function (9.3 class.mfct ) or static data member (9.4 class.static ) outside of its class, the definition or explicit instantiation of a function or variable member of a namespace outside of its namespace, or...
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?
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.
Proposed resolution (10/00):
Remove the last sentence of 8.3 dcl.meaning paragraph 1 (cited above) and the example that follows.
From reflector messages 8167, 8170-76.
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/00): 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).
(Previously numbered 689.)
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:
- they shall all refer to the same entity, or all refer to functions ...
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 (10/00):
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]
Proposed Resolution (04/99): Change the text in the example of section 8.3.6 dcl.fct.default paragraph 5 from:
... g will be called with the value f(1).to:
... g will be called with the value f(2).
From reflector messages 8602-4.
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 (10/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.
(Previously numbered 866.)
The description of copy-initialization in 8.5 dcl.init paragraph 14 says:
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.
Steve Adamczyk: (See message 8897.)
Because of late changes to this area, the relevant text is now the third sub-bullet of the fourth bullet of 8.5 dcl.init paragraph 14:
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated... The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the destination type. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization.
The issue still remains whether the wording should refer to "the cv-unqualified version of the destination type." I think it should.
Notes from 10/00 meeting:
The original example does not illustrate the remaining problem. The following example does:
struct C { };
C c;
struct A {
A(const A&);
A(const C&);
};
const volatile A a = c; // Okay
Proposed Resolution (04/01):
In 8.5 dcl.init, paragraph 14, bullet 4, sub-bullet 3, change
if the function is a constructor, the call initializes a temporary of the destination type.
to
if the function is a constructor, the call initializes a temporary of the cv-unqualified version of the destination type.
From reflector message core-7780.
Given:
struct S1 {
int x;
};
struct S2 {
int x;
double y;
};
struct S3 {
int x;
double y;
string s;
};
Once upon a time, we went through a fairly protracted discussion to ensure
that S1().x would be guaranteed to be 0. Note that if we declare
void f()
{
S1 s1;
// ...
}
there is no guarantee of the value of s1.x, and that is intentional.
But S1().x is different, because S1() is an rvalue, and
unless all of its members are defined, the effect of copying it is undefined.
Similarly, S2().x and S2().y are also defined to be equal to zero, and here it really matters for many implementations, because if S2().y is just a bunch of random bits, it is entirely possible that trying to copy S2().y will yield a floating-point trap.
However, rather to my surprise, the standard does not define the value of S3().x or S3().y, because S3 is not a POD. It does define S3().s (by running the string constructor), but once a structure is no longer a POD, the values of uninitialized members are no longer guaranteed in expressions of the form T().
In my opinion, this definition is a mistake, and the committee's intention was to zero-initialize all members that do not have an explicitly defined constructor, whether or not the class is a POD.
See core reflector messages 7780, 7782, 7785-7787, 7789-7791, 7796 and 7798-7805 for a spirited discussion of this topic. See also paper J16/99-0014 = WG21 N1191.
[Note: this issue is resolved by the resolution of issue 178.]
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/00): 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...
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/00):
Delete the words "The storage for" from the first sentence of 3.6.2 basic.start.init paragraph 1.
[Note: Revised wording in 8.5 dcl.init relating to this issue is also found in issue 178.]
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 t