| Document number: | J16/06-0069 = WG21 N1999 |
| Date: | 2006-04-22 |
| Project: | Programming Language C++ |
| Reference: | ISO/IEC IS 14882:2003 |
| Reply to: | William M. Miller |
| Edison Design Group, Inc. | |
| wmm@edg.com |
This document contains the C++ core language issues for which the Committee (J16 + WG21) has decided that no action is required, that is, issues with status "NAD" ("Not A Defect"), "dup" (duplicate), and "extension."
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.
The standard is inconsistent in its use of a hyphen on the following: nontype vs. non-type, non-dependent vs. nondependent, non-deduced vs. nondeduced, and non-template vs. nontemplate. We should pick a preferred form.
Notes from the March 2004 meeting:
If this isn't a purely editorial issue, nothing is. We're referring this to the editor. We prefer the hyphenated forms.
In 3.2 basic.def.odr paragraph 4 bullet 4, it's presumably the case that a conversion to T* requires that T be complete only if the conversion is from a different type. One could argue that there is no conversion (and therefore the text is accurate as it stands) if a cast does not change the type of the expression, but it's probably better to be more explicit here.
On the other hand, this text is non-normative (it's in a note).
Rationale (04/99): The relevant normative text makes this clear. Implicit conversion and static_cast are defined (in 4 conv and 5.2.9 expr.static.cast , respectively) as equivalent to declaration with initialization, which permits pointers to incomplete types, and dynamic_cast (5.2.7 expr.dynamic.cast ) explicitly prohibits pointers to incomplete types.
Consider this code:
struct Base {
enum { a, b, c, next };
};
struct Derived : public Base {
enum { d = Base::next, e, f, next };
};
The idea is that the enumerator "next" in each class is the next available
value for enumerators in further derived classes.
If we had written
enum { d = next, e, f, next };
I think we would run afoul of 3.3.6
basic.scope.class
:
A name N used in a class S shall refer to the same declaration in its context and when re-evaluated in the completed scope of S. No diagnostic is required for a violation of this rule.But in the original code, we don't have an unqualified "next" that refers to anything but the current scope. I think the intent was to allow the code, but I don't find the wording clear on on that point.
Is there another section that makes it clear whether the original code is valid? Or am I being obtuse? Or should the quoted section say "An unqualified name N used in a class ..."?
Rationale (04/99): It is sufficiently clear that "name" includes qualified names and hence the usual lookup rules make this legal.
The wording of 3.4.1 basic.lookup.unqual paragraph 2 is misleading. It says:
The declarations from the namespace nominated by a using-directive become visible in a namespace enclosing the using-directive; see 7.3.4 namespace.udir.
According to 7.3.4 namespace.udir paragraph 1, that namespace is
the nearest enclosing namespace which contains both the using-directive and the nominated namespace.
That would seem to imply the following:
namespace outer {
namespace inner {
int i;
}
void f() {
using namespace inner;
}
int j = i; // inner::i is "visible" in namespace outer
}
Suggested resolution: Change the first sentence of 3.4.1 basic.lookup.unqual paragraph 2 to read:
The declarations from the namespace nominated by a using-directive become visible in the scope in which the using-directive appears after the using-directive.
Notes from the 4/02 meeting:
After a lot of discussion of possible wording changes, we decided the wording should be left alone. 3.4.1 basic.lookup.unqual paragraph 2 is not intended to be a full specification; that's in 7.3.4 namespace.udir paragraph 1. See also 3.3.5 basic.scope.namespace paragraph 1.
When a union is used in argument-dependent lookup, the union's type is not an associated class type. Consequently, code like this will fail to work.
union U {
friend void f(U);
};
int main() {
U u;
f(u); // error: no matching f — U is not an associated class
}
Is this an error in the description of unions in argument-dependent lookup?
Also, this section is written as if unions were distinct from classes. So adding unions to the "associated classes" requires either rewriting the section so that "associated classes" can include unions, or changing the term to be more inclusive, e.g. "associated classes and unions" or "associated types".
Jason Merrill: Perhaps in both cases, the standard text was intended to only apply to anonymous unions.
Liam Fitzpatrick: One cannot create expressions of an anonymous union type.
Rationale (04/99): Unions are class types, so the example is well-formed. Although the wording here could be improved, it does not rise to the level of a defect in the Standard.
I believe the following code example should unambiguously call the member operator+. Am I right?
//--- some library header ---
//
namespace N1 {
template<class T> struct Base { };
template<class T> struct X {
struct Y : public Base<T> { // here's a member operator+
Y operator+( int _Off ) const { return Y(); }
};
Y f( unsigned i ) { return Y() + i; } // the "+" in question
};
}
//--- some user code ---
//
namespace N2 {
struct Z { };
template<typename T> // here's another operator+
int* operator+( T , unsigned ) { static int i ; return &i ; }
}
int main() {
N1::X< N2::Z > v;
v.f( 0 );
}
My expectation is that 3.4.2 basic.lookup.argdep would govern, specifically:
If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated namespaces and classes are not considered.So I think the member should hide the otherwise-better-matching one in the associated namespace. Here's what compilers do:
Agree with me and call the member operator+: Borland 5.5, Comeau 4.3.0.1, EDG 3.0.1, Metrowerks 8.0, MSVC 6.0
Disagree with me and try to call N2::operator+: gcc 2.95.3, 3.1.1, and 3.2; MSVC 7.0
Simple so far, but someone tells me that 13.3.1.2 over.match.oper muddies the waters. There, paragraph 10 summarizes that subclause:
[Note: the lookup rules for operators in expressions are different than the lookup rules for operator function names in a function call, ...In particular, consider the above call to "Y() + unsigned" and please help me step through 13.3.1.2 over.match.oper paragraph 3:
... for a binary operator @ with a left operand of a type whose cv-unqualified version is T1 and a right operand of a type whose cv-unqualified version is T2,OK so far, here @ is +, and T1 is N1::X::Y.
three sets of candidate functions, designated member candidates, non-member candidates and built-in candidates, are constructed as follows:[and later are union'd together to get the candidate list]
If T1 is a class type, the set of member candidates is the result of the qualified lookup of T1::operator@ (over.call.func); otherwise, the set of member candidates is empty.So there is one member candidate, N1::X::Y::operator+.
The set of non-member candidates is the result of the unqualified lookup of operator@ in the context of the expression according to the usual rules for name lookup in unqualified function calls (basic.lookup.argdep) except that all member functions are ignored.
*** This is the question: What does that last phrase mean? Does it mean:
a) first apply the usual ADL rules to generate a candidate list, then ignore any member functions in that list (this is what I believe and hope it means, and in particular it means that the presence of a member will suppress names that ADL would otherwise find in the associated namespaces); or
b) something else?
In short, does N2::operator+ make it into the candidate list? I think it shouldn't. Am I right?
John Spicer: I believe that the answer is sort-of "a" above. More specifically, the unqualified lookup consists of a "normal" unqualified lookup and ADL. ADL always deals with only namespace members, so the "ignore members functions" part must affect the normal lookup, which should ignore class members when searching for an operator.
I suspect that the difference between compilers may have to do with details of argument-dependent lookup. In the example given, the argument types are "N1::X<N2::Z>::Y" and "unsigned int". In order for N2::operator+ to be a candidate, N2 must be an associated namespace.
N1::X<N2::Z>::Y is a class type, so 3.4.2 basic.lookup.argdep says that its associated classes are its direct and indirect base classes, and its namespaces are the namespaces of those classes. So, its associated namespace is just N1.
3.4.2 basic.lookup.argdep also says:
If T is a template-id, its associated namespaces and classes are the namespace in which the template is defined; for member templates, the member template's class; the namespaces and classes associated with the types of the template arguments provided for template type parameters (excluding template template parameters); the namespaces in which any template template arguments are defined; and the classes in which any member templates used as template template arguments are defined. [Note: non-type template arguments do not contribute to the set of associated namespaces. ]First of all, there is a problem with the term "is a template-id". template-id is a syntactic constuct and you can't really talk about a type being a template-id. Presumably, this is intended to mean "If T is the type of a class template specialization ...". But does this apply to N1::X<N2::Z>::Y? Y is a class nested within a class template specialization. In addition, its base class is a class template specialization.
I think this raises two issues:
Notes from the April 2003 meeting:
The ADL rules in the standard sort of look at if they are fully recursive, but in fact they are not; in some cases, enclosing classes and base classes are considered, and in others they are not. Microsoft and g++ did fully-recursive implementations, and EDG and IBM did it the other way. Jon Caves reports that Microsoft saw no noticeable difference (e.g., no complaints from customers internal or external) when they made this change, so we believe that even if the rules are imperfect the way they are in the standard, they are clear and the imperfections are small enough that programmers will not notice them. Given that, it seemed prudent to make no changes and just close this issue.
The template-id issue is spun off as issue 403.
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.5 basic.link 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
Rationale (10/00): The proposed change would have introduced an incompatibility with the C language. For example, the global declaration
static enum { A, B, C } abc;
represents an idiom that is used in C but would be prohibited under this resolution.
According to 3.2 basic.def.odr paragraph 5, it is possible for a static data member of a class template to be defined more than once in a given program provided that each such definition occurs in a different translation unit and the ODR is met.
Now consider the following example:
src1.cpp:
#include <iostream>
int initializer()
{
static int counter;
return counter++;
}
int g_data1 = initializer();
template<class T>
struct exp {
static int m_data;
};
template<class T>
int exp<T>::m_data = initializer();
int g_data2 = initializer();
extern int g_data3;
int main()
{
std::cout << exp<char>::m_data << ", " << g_data1 << ", "
<< g_data2 << ", " << g_data3 << std::endl;
return 0;
}
src2.cpp:
extern int initializer();
int g_data3 = initializer();
template<class T>
struct exp {
static int m_data;
};
template<class T>
int exp<T>::m_data = initializer();
void func()
{
exp<char>::m_data++;
}
The specialization exp<char>::m_data is implicitly instaniated in both translation units, hence (14.7.1 temp.inst paragraph 1) its initialization occurs. And for both definitions of exp<T>::m_data the ODR is met. According to 3.6.2 basic.start.init paragraph 1:
Objects with static storage duration defined in namespace scope in the same translation unit and dynamically initialized shall be initialized in the order in which their definition appears in the translation unit.
But for exp<T>::m_data we have two definitions. Does it mean that both g_data1 and g_data3 are guaranteed to be dynamically initialized before exp<char>::m_data?
Suggested Resolution: Insert the following sentence before the last two sentences of 3.2 basic.def.odr paragraph 5:
In the case of D being a static data member of a class template the following shall also hold:
- for a given (not explicit) specialization of D initialized dynamically (3.6.2 basic.start.init), the accumulated set of objects initialized dynamically in namespace scope before the specialization of D shall be the same in every translation unit that contains the definition for this specialization.
Notes from 10/01 meeting:
It was decided that this issue is not linked to issue 270 and that there is no problem, because there is only one instantiation (see 2.1 lex.phases paragraph 8).
The subject line pretty much says it all. It's a possibility that hadn't ever occurred to me. I don't see any prohibition in the standard, and I also don't think the possibility introduces any logical inconsistencies. The proper behavior, presumably, would be to go through the list of already-constructed objects (not including the current one, since its constructor wouldn't have finished executing) and destroy them in reverse order. Not fundamentally hard, and I'm sure lots of existing implementations already do that.
I'm just not sure whether the standard was intended to support this, or whether it's just that nobody else thought of it either. If the former, then a non-normative note somewhere in 3.6.2 basic.start.init might be nice.
Rationale (October 2004):
There is nothing in the Standard to indicate that this usage is prohibited, so it must be presumed to be permitted.
3.8 basic.life and 12.4 class.dtor discuss explicit management of object lifetime. It seems clear that most object lifetime issues apply to sub-objects (array elements, and data members) as well. The standard supports
struct X { T t } x;
T* pt = &x.t;
pt->~T();
new(pt) T;
and this kind of behavior is useful in allocators.
However the standard does not seem to prohibit the same operations on base sub-objects.
struct D: B{ ... } d;
B* pb = &d;
pb->~B();
new(pb) B;
However if B and/or D have virtual member functions or virtual bases, it is unlikely that this code will result in a well-formed D object in current implementations (note that the various lines may be in different functions).
Suggested resolution: 12.4 class.dtor should be modified so that explicit destruction of base-class sub-objects be made illegal, or legal only under some restrictive conditions.
Rationale (04/01):
Reallocation of a base class subobject is already disallowed by 3.8 basic.life paragraph 7.
Following the definition in 9 class paragraph 4 the following is a valid POD (actually a POD-struct):
struct test
{
const int i;
};
The legality of PODs with const members is also implied by the text of 5.3.4 expr.new paragraph 15 bullet 1, sub-bullet 2 and 12.6.2 class.base.init paragraph 4 bullet 2.
3.9 basic.types paragraph 3 states that
For any POD type T, if two pointers to T point to distinct 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.
[Note: this text was changed by TC1, but the essential point stays the same.]
This implies that the following is required to work:
test obj1 = { 1 };
test obj2 = { 2 };
memcpy( &obj2, &obj1, sizeof(test) );
The memcpy of course changes the value of the const member, surely something that shouldn't be allowed.
Suggested resolution:
It is recommended that 3.9 basic.types paragraph 3 be reworded to exclude PODs which contain (directly or indirectly) members of const-qualified type.
Rationale (October, 2004):
7.1.5.1 dcl.type.cv paragraph 4 already forbids modifying a const member of a POD struct. The prohibition need not be repeated in 3.9 basic.types.
Paragraph 3 of section 4.5 conv.prom contains a statement saying that if a bit-field is larger than int or unsigned int, no integral promotions apply to it. This phrase needs further clarification, as it is hardly possible to fugure out what it means. See below.
Assuming a machine with a size of general-purpose register equal 32 bits (where a byte takes up 8 bits) and a C++ implementation where an int is 32 bits and a long is 64 bits. And the following snippet of code:
struct ExternalInterface {
long field1:36, field2:28;
};
int main() {
ExternalInterface myinstance = { 0x100000001L, 0x12,};
if(myinstance.field1 < 0x100000002L) { //do something }
}
Does the standard prohibit the implementation from promoting field1's value into two general purpose registers? And imposes a burden of using shift machine instructions to work with the field's value? What else could that phrase mean?
Either alternative is implementation specific, so I don't understand why the phrase "If the bit-field is larger yet, no integral promotions apply to it" made it to the standard.
Notes from 10/01 meeting:
The standard of course does not dictate what an implementation might do with regard to use of registers or shift instructions in the generated code. The phrase cited means only that a larger bit-field does not undergo integral promotions, and therefore it retains the type with which it was declared (long in the above example). The Core Working Group judged that this was sufficiently clear in the standard.
Note that 9.6 class.bit paragraph 1 indicates that any bits in excess of the size of the underlying type are padding bits and do not participate in the value representation. Therefore the field1 bit field in the above example is not capable of holding the indicated values, which require more than 32 bits.
In the following code, I expect both "null" and "FALSE" to be null pointer constants -- and that the code should compile and output the string "int*" twice to cout:
#include <iostream>
using namespace std;
void foo(int* p)
{
cout << "int*" << endl;
}
int main(void)
{
const int null = 0;
foo(null);
const bool FALSE = false;
foo(FALSE);
}
ISO/IEC 14882-1998 4.10 conv.ptr states:
An integral constant expression rvalue of integer type that evaluates to zero (called a /null pointer constant/) can be converted to a pointer type.
Stroustrup appears to agree with me -- he states (3rd edition page 88):
In C, it has been popular to define a macro NULL to represent the zero pointer. Because of C++`s tighter type checking, the use of plain 0, rather than any suggested NULL macro, leads to fewer problems. If you feel you must define NULL, use:const int NULL = 0;
However gcc 3.3.1 rejects this code with the errors:
bug.cc:17: error: invalid conversion from `int' to `int*'
bug.cc:19: error: cannot convert `const bool' to `int*' for argument `1' to `
void foo(int*)'
I have reported this as a bug (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=13867), but the gcc team states that 4.10 requires that a null pointer constant must be an rvalue -- and no implicit conversion from an lvalue to an rvalue is required (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=396):
a null pointer constant is an integral constant expression rvalue that evaluates to zero [4.10/1] in this case `null' is an lvalue. The standard does not specify that lvalue->rvalue decay happens here, so `null' is not a null pointer constant.
I disagree with the gcc teams interpretation -- I don't see why 3.10 basic.lval doesn't apply:
Whenever an lvalue appears in a context where an rvalue is expected, the lvalue is converted to an rvalue;
The insertion of the word rvalue appears to have occurred during standardization -- it is not present in either Stroustrup 2nd edition or the 3rd edition. Does the committee deliberately intend to exclude an lvalue as a null pointer constant by adding the word rvalue? If so, it leads to the rather bizarre fact that "null" is not a null pointer constant, but "null + 0" is!
Notes from the March 2004 meeting:
We think this is just a bug in gcc. The const variable does get converted to an rvalue in this context. This case is not really any different than cases like
const int null = 0; int i = null;or
const int i = 1; int a[i];(which are accepted by gcc). No one would argue that the second lines of those examples are invalid because the variables are lvalues, and yet the conversions to rvalue happen implicitly for the same reason cited above -- the contexts require an rvalue.
An operator expression can, according to 5 expr paragraph 2, require transformation into function call syntax. The reference in that paragraph is to 13.5 over.oper , but it should be to 13.3.1.2 over.match.oper .
Rationale (04/99): The subsections 13.5.1 over.unary , 13.5.2 over.binary , etc. of the referenced section are in fact relevant.
Section 12.5 class.free paragraph 4 says:
If a delete-expression begins with a unary :: operator, the deallocation function's name is looked up in global scope. Otherwise, if the delete-expression is used to deallocate a class object whose static type has a virtual destructor, the deallocation function is the one found by the lookup in the definition of the dynamic type's virtual destructor (12.4 class.dtor ). Otherwise, if the delete-expression is used to deallocate an object of class T or array thereof, the static and dynamic types of the object shall be identical and the deallocation function's name is looked up in the scope of T. If this lookup fails to find the name, the name is looked up in the global scope. If the result of the lookup is ambiguous or inaccessible, or if the lookup selects a placement deallocation function, the program is ill-formed.I contrast that with 5.3.4 expr.new paragraphs 16 and 17:
If the new-expression creates an object or an array of objects of class type, access and ambiguity control are done for the allocation function, the deallocation function (12.5 class.free ), and the constructor (12.1 class.ctor ). If the new-expression creates an array of objects of class type, access and ambiguity control are done for the destructor (12.4 class.dtor ).I think nothing in the latter paragraphs implies that the deallocation function found is the same as that for a corresponding delete-expression. I suspect that may not have been intended and that the lookup should occur "as if for a delete-expression".If any part of the object initialization described above terminates by throwing an exception and a suitable deallocation function can be found, the deallocation function is called to free the memory in which the object was being constructed, after which the exception continues to propagate in the context of the new-expression. If no unambiguous matching deallocation function can be found, propagating the exception does not cause the object's memory to be freed. [Note: This is appropriate when the called allocation function does not allocate memory; otherwise, it is likely to result in a memory leak. ]
Rationale:
Paragraphs 16 through 18 are sufficiently correct and unambiguous as written.
Clause 5 expr paragraph 4 appears to grant an implementation the right to generate code for a function call like
f(new T1, new T2)
in the order
Suggested resolution: either forbid the ordering above or expand the requirement for reclaiming storage to include exceptions thrown in all operations between the allocation and the completion of the constructor.
Rationale (10/99): Even in the "traditional" ordering of the calls to allocation functions and constructors, memory can still leak. For instance, if T1 were successfully constructed and then the construction of T2 were terminated by an exception, the memory for T1 would be lost. Programmers concerned about memory leaks will avoid this kind of construct, so it seems unnecessary to provide special treatment for it to avoid the memory leaks associated with one particular implementation strategy.
An expression of the form pointer + enum (see paragraph 5) is not given meaning, and ought to be, given that paragraph 2 of this section makes it valid. Presumably, the enum value should be converted to an integral value, and the rest of the processing done on that basis. Perhaps we want to invoke the integral promotions here.
[Should this apply to (pointer - enum) too?]
Rationale (04/99): Paragraph 1 invokes "the usual arithmetic conversions" for operands of enumeration type.
(It was later pointed out that the builtin operator T* operator+(T*, ptrdiff_t) (13.6 over.built paragraph 13) is selected by overload resolution. Consequently, according to 13.3.1.2 over.match.oper paragraph 7, the operand of enumeration type is converted to ptrdiff_t before being interpreted according to the rules in 5.7 expr.add .)
Consider:
int* p = false; // Well-formed?
int* q = !1; // What about this?
>From 3.9.1
basic.fundamental
paragraph 6:
"As described below, bool values behave as integral types."
From 4.10 conv.ptr paragraph 1: "A null pointer constant is an integral constant expression rvalue of integer type that evaluates to zero."
From 5.19 expr.const paragraph 1: "An integral constant-expression can involve only literals, enumerators, const variables or static members of integral or enumeration types initialized with constant expressions, ..."
In 2.13.1 lex.icon : No mention of true or false as an integer literal.
From 2.13.5 lex.bool : true and false are Boolean literals.
So the definition of q is certainly valid, but the validity of p depends on how the sentence in 5.19 expr.const is parsed. Does it mean
If the latter, then (3.0 < 4.0) is a constant expression, which I don't think we ever wanted. If the former, though, we have the anomalous notion that true and false are not constant expressions.
Now, you may argue that you shouldn't be allowed to convert false to a pointer. But what about this?
static const bool debugging = false;
// ...
int table[debugging? n+1: n];
Whether the definition of table is well-formed hinges on whether
false is an integral constant expression.
I think that it should be, and that failure to make it so was just an oversight.
Rationale (04/99): A careful reading of 5.19 expr.const indicates that all types of literals can appear in integral constant expressions, but floating-point literals must immediately be cast to an integral type.
According to 5.19 expr.const paragraph 1,
In particular, except in sizeof expressions, functions, class objects, pointers, or references shall not be used, and assignment, increment, decrement, function-call, or comma operators shall not be used.
Given a case like
enum E { e };
int operator+(int, E);
int i[4 + e];
does this mean that the overloaded operator+ is not considered (because it can't be called), or is it selected by overload resolution, thus rendering the program ill-formed?
Rationale (April, 2005):
All expressions, including constant expressions, are subject to overload resolution. The example is ill-formed.
When jumping past initialization of a local static variable the value of the static becomes indeterminate. Seems like this behavior should be illegal just as it is for local variables with automatic linkage.
Here is an example:
struct X {
X(int i) : x(i) {}
int x;
};
int f(int c) {
if (c)
goto ly; // error here for jumping past next stmt.
static X a = 1;
ly:
return a.x; // either 1 or 0 depending on implementation.
}
6.7 stmt.dcl P3 should be changed to:
A program that jumps from a point where a local variable with automatic or static storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has POD type (3.9) and is declared without an initializer (8.5).This would imply "static X a = 1;" should be flagged as an error. Note that this behavior a may be a "quality of implementation issue" which may be covered in 6.7 P4. Paragraph 4 seems to make the choice of static/dynamic initialization indeterminate. Making this an error and thus determinate seems the correct thing to do since that is what is already required of automatic variables.
Steve Adamczyk: Some version of this may be appropriate, but it's common to have code that is executed only the first time it is reached, and to have an initialization of a static variable inside such a piece of code. In such a case, on executions after the first there is indeed a jump over the declaration, but the static variable is correctly initialized -- it was initialized the first time the routine was called.
void f() {
static bool first_time = true;
if (!first_time) goto after_init;
static int i = g();
first_time = false;
after_init:
...
}
Rationale (October, 2004):
The CWG sees no reason to change this specification. Local static variables are different from automatic variables: automatic variables, if not explicitly initialized, can have indeterminate (“garbage”) values, including trap representations, while local static variables are subject to zero initialization and thus cannot have garbage values.
The latitude granted to implementations regarding performing dynamic initialization of local static objects as if it were static initialization is exactly parallel to namespace scope objects (3.6.2 basic.start.init), as are the restrictions on programmer assumptions.
Because a definition is also a declaration, it might make sense to change uses of "declaration or definition" to simply "declaration".
Notes from the March 2004 meeting:
Jens Maurer prepared drafting for this issue, but we find ourselves reluctant to actually make the changes. Though correct, they seemed more likely to be misread than the existing wording.
Proposed resolution:
Remove in 1.3.9 defns.parameter the indicated words:
an object or reference declared as part of a function declarationor definition, or in the catch clause of an exception handler, that acquires a value on entry to the function or handler; ...
Remove in 14.1 temp.param paragraph 10 the indicated words:
The set of default template-arguments available for use with a template declarationor definitionis obtained by merging the default arguments from the definition (if in scope) and all declarations in scope in the same way default function arguments are (...).
Remove in 14.6 temp.res paragraph 2 the indicated words:
A name used in a template declarationor definitionand that is dependent on a template-parameter is assumed not to name a type unless the applicable name lookup finds a type name or the name is qualified by the keyword typename.
Remove in 14.6.4.1 temp.point paragraph 1 the indicated words:
Otherwise, the point of instantiation for such a specialization immediately follows the namespace scope declarationor definitionthat refers to the specialization.
Remove in 14.6.4.1 temp.point paragraph 3 the indicated words:
Otherwise, the point of instantiation for such a specialization immediately precedes the namespace scope declarationor definitionthat refers to the specialization.
Remove in 14.7.3 temp.expl.spec paragraph 21 the indicated words:
Default function arguments shall not be specified in a declarationor a definitionfor one of the following explicit specializations:[Note: default function arguments may be specified in the declaration
- ...
or definitionof a member function of a class template specialization that is explicitly specialized. ]
Remove in 14.8.2.5 temp.deduct.type paragraph 18 the indicated words:
[Note: a default template-argument cannot be specified in a function template declarationor definition; ...]
Remove in 17.4.2.1 lib.using.headers paragraph 3 the indicated words:
A translation unit shall include a header only outside of any external declarationor definition, and shall include the header lexically before the first reference to any of the entities it declaresor first definesin that translation unit.
Rationale (October, 2004):
CWG felt that readers might misunderstand “declaration” as meaning “non-definition declaration.”
9.5 class.union paragraph 3 implies that anonymous unions in unnamed namespaces need not be declared static (it only places that restriction on anonymous unions "declared in a named namespace or in the global namespace").
However, 7.1.1 dcl.stc paragraph 1 says that "global anonymous unions... shall be declared static." This could be read as prohibiting anonymous unions in unnamed namespaces, which are the preferred alternative to the deprecated use of static.
Rationale (10/99): An anonymous union in an unnamed namespace is not "a global anonymous union," i.e., it is not a member of the global namespace.
In clause 7.1.2 dcl.fct.spec, para. 3, the following sentence
A function defined within a class definition is an inline function.
should, if I am not mistaken, instead be:
A function defined within a class declaration is an inline function."
Notes from October 2002 meeting:
This is not a defect. Though there is a long history, going back to the ARM, of use of the term "class declaration" to mean the definition of the class, we believe "class definition" is clearer. We have opened issue 379 to deal with changing all other uses of "class declaration" to "class definition" where appropriate.
A customer reports that when he attempts to replace ::operator new with a user-defined function, the standard library calls the default function by preference if the user-defined function is inline. I believe that our compiler is correct, and that such a replacement function isn't allowed to be inline, but I'm not sure there's sufficiently explicit language in the standard.
In general, of course, the definition of an inline function must be present in every translation unit where the function is called. (7.1.2 dcl.fct.spec, par 4) It could be argued that this requirement doesn't quite address replacement functions: what we're dealing with is the odd case where we've already got one definition and the user is supplying a different one. I'd like to see something specifically addressing the case of a replacement function.
So what do we have? I see discussion of requirement for a replacement ::operator new in three places: 17.4.3.4 lib.replacement.functions, 18.4.1.1 lib.new.delete.single par 2, and 3.7.3 basic.stc.dynamic par 2-3. I don't see anything explicitly saying that the replacement function may not be inline. The closest I can find is 18.4.1.1 lib.new.delete.single par 2, which says that "a C++ program may define a function with this function signature that displaces the default version defined by the C++ Standard library". One might argue that "with this function signature" rules out inline, but that strikes me as a slight stretch.
Have I missed anything?
Andrew Koenig: I think you've turned up a problem in 7.1.2 dcl.fct.spec paragraph 4. Consider:
// Translation unit 1
#include <iostream>
extern void foo(void (*)());
inline void bar() {
std::cout << "Hello, world!" << std::endl;
}
int main() {
foo(bar);
}
// Translation unit 2
void foo(void (*f)()) { (*f)(); }
Are you really trying to tell me that this program is ill-formed because the definition of bar is not available in translation unit 2?
I think not. The actual words in 7.1.2 dcl.fct.spec par 4 are
An inline function shall be defined in every translation unit in which it is used...and I think at in this context, ``used'' should be interpreted to mean that foo is used only in translation unit 1, where it is converted to a value of type void(*)().
Notes from October 2003 meeting:
We don't think Andy Koenig's comment requires any action; "used" is already defined appropriately.
We agree that this replacement should not be allowed, but we think it's a library issue (in the rules for allowed replacements). Forwarded to library group; it's issue 404 on the library issues list.
Is the following valid?
template <class T> void f(T) {
typedef int x;
typedef T x;
}
int main() {
f(1);
}
There is an instantiation where the function is valid. Is an implementation allowed to issue an error on the template declaration because the types on the typedef are not the same (7.1.3 dcl.typedef)?
How about
typedef T x; typedef T2 x;?
It can be argued that these cases should be allowed because they aren't necessarily wrong, but it can also be argued that there's no reason to write things like the first case above, and if such a case appears it's more likely to be a mistake than some kind of intentional test that int and T are the same type.
Notes from the October 2003 meeting:
We believe that all these cases should be allowed, and that errors should be required only when an instance of the template is generated. The current standard wording does not seem to disallow such cases, so no change is required.
I received an inquiry/complaint that you cannot re-open a namespace using a qualified name. For example, the following program is ok, but if you uncomment the commented lines you get an error:
namespace A {
namespace N {
int a;
}
int b;
namespace M {
int c;
}
}
//namespace A::N {
// int d;
//}
namespace A {
namespace M {
int e;
}
}
int main()
{
A::N::a = 1;
A::b = 2;
A::M::c = 3;
// A::N::d = 4;
A::M::e = 5;
}
Andrew Koenig: There's a name lookup issue lurking here. For example:
int x;
namespace A {
int x;
namespace N {
int y;
};
}
namespace A::N {
int* y = &x; // which x?
}
Jonathan Caves: I would assume that any rule would state that:
namespace A::B {
would be equivalent to:
namespace A {
namespace B {
so in your example 'x' would resolve to A::x
BTW: we have received lots of bug reports about this "oversight".
Lawrence Crowl: Even worse is
int x;
namespace A {
int x;
}
namespace B {
int x;
namespace ::A {
int* y = &x;
}
}
I really don't think that the benefits of qualified names here is worth
the cost.
Notes from April 2003 meeting:
We're closing this because it's on the Evolution working group list.
A change was introduced into the language that made names first declared in friend declarations "invisible" to normal lookups until such time that the identifier was declared using a non-friend declaration. This is described in 7.3.1.2 namespace.memdef paragraph 3 and 11.4 class.friend paragraph 9 (and perhaps other places).
The standard gives examples of how this all works with friend declarations, but there are some cases with nonfriend elaborated type specifiers for which there are no examples, and which might yield surprising results.
The problem is that an elaborated type specifier is sometimes a declaration and sometimes a reference. The meaning of the following code changes depending on whether or not friend class names are injected (visibly) into the enclosing namespace scope.
struct A;
struct B;
namespace N {
class X {
friend struct A;
friend struct B;
};
struct A *p; // N::A with friend injection, ::A without
struct B; // always N::B
}
Is this the desired behavior, or should
all elaborated type specifiers (and not just those of the form
"class-key identifier;") have the effect of finding
previously declared "invisible"
names and making them visible?
Mike Miller: That's not how I would categorize the effect of "struct B;". That declaration introduces the name "B" into namespace N in exactly the same fashion as if the friend declaration did not exist. The preceding friend declaration simply stated that, if a class N::B were ever defined, it would have friendly access to the members of N::X. In other words, the lookups in both "struct A*..." and "struct B;" ignore the friend declarations.
(The standard is schizophrenic on the issue of whether such friend declarations introduce names into the enclosing namespace. 3.3 basic.scope paragraph 4 says,
John Spicer: The previous declaration of B is not completely ignored though, because certainly changing "friend struct B;" to "friend union B;" would result in an error when B was later redeclared as a struct, wouldn't it?
Bill Gibbons: Right. I think the intent was to model this after the existing rule for local declarations of functions (which dates back to C), where the declaration is introduced into the enclosing scope but the name is not. Getting this right requires being somewhat more rigorous about things like the ODR because there may be declaration clashes even when there are no name clashes. I suspect that the standard gets this right in most places but I would expect there to be a few that are still wrong, in addition to the one Mike pointed out.
Mike Miller: Regarding would result in an error when B was later redeclared
I don't see any reason why it should. The restriction that the class-key must agree is found in 7.1.5.3 dcl.type.elab and is predicated on having found a matching declaration in a lookup according to 3.4.4 basic.lookup.elab . Since a lookup of a name declared only (up to that point) in a friend declaration does not find that name (regardless of whether you subscribe to the "does-not-introduce" or "introduces-invisibly" school of thought), there can't possibly be a mismatch.
I don't think that the Standard's necessarily broken here. There is no requirement that a class declared in a friend declaration ever be defined. Explicitly putting an incompatible declaration into the namespace where that friend class would have been defined is, to me, just making it impossible to define — which is no problem, since it didn't have to be defined anyway. The only error would occur if the same-named but unbefriended class attempted to use the nonexisting grant of friendship, which would result in an access violation.
(BTW, I couldn't find anything in the Standard that forbids defining a class with a mismatched class-key, only using one in an elaborated-type-specifier. Is this a hole that needs to be filled?)
John Spicer: This is what 7.1.5.3 dcl.type.elab paragraph 3 says:
class B;
union B {};
and
union B {};
class B;
are both invalid. I think this paragraph is intended to say that. I'm
not so sure it actually does say that, though.
Mike Miller: Regarding I think the intent was to model this after the existing rule for local declarations of functions (which dates back to C)
Actually, that's not the C (1989) rule. To quote the Rationale from X3.159-1989:
Regarding Getting this right requires being somewhat more rigorous
Yes, I think if this is to be made illegal, it would have to be done with the ODR; the name-lookup-based current rules clearly (IMHO) don't apply. (Although to be fair, the [non-normative] note in 3.3 basic.scope paragraph 4 sounds as if it expects friend invisible injection to trigger the multiple-declaration provisions of that paragraph; it's just that there's no normative text implementing that expectation.)
Bill Gibbons: Nor does the ODR currently disallow:
translation unit #1 struct A;
translation unit #2 union A;
since it only refers to class definitions, not declarations.
But the obvious form of the missing rule (all declarations of a class within a program must have compatible struct/class/union keys) would also answer the original question.
The declarations need not be visible. For example:
translation unit #1 int f() { return 0; }
translation unit #2: void g() {
extern long f();
}
is ill-formed even though the second "f" is not a visible declaration.
Rationale (10/99): The main issue (differing behavior of standalone and embedded elaborated-type-specifiers) is as the Committee intended. The remaining questions mentioned in the discussion may be addressed in dealing with related issues.
(See also issues 136, 138, 139, 143, 165, and 166.)
7.3.1.2 namespace.memdef paragraph 2 says,
Members of a named namespace can also be defined outside that namespace by explicit qualification (3.4.3.2 namespace.qual ) of the name being defined, provided that the entity being defined was already declared in the namespace...It is not clear whether block-scope extern declarations and friend declarations are sufficient to permit the named entities to be defined outside their namespace. For example,
namespace NS {
struct A { friend struct B; };
void foo() { extern void bar(); }
}
struct NS::B { }; // 1) legal?
void NS::bar() { } // 2) legal?
Rationale (10/99): Entities whose names are "invisibly injected" into a namespace as a result of friend declarations are not "declared" in that namespace until an explicit declaration of the entity appears at namespace scope. Consequently, the definitions in the example are ill-formed.
(See also issues 95, 136, 138, 139, 143, and 166.)
Consider the following example:
class C {
public: enum E {};
friend void* operator new(size_t, E);
friend void operator delete(void*, E);
};
void foo() {
C::E e;
C* ptr = new(e) C();
}
This code, which is valid in global scope, becomes ill-formed when the class definition is moved into a namespace, and there is no way to make it valid:
namespace N {
class C {
public: enum E {};
friend void* operator new(size_t, E);
friend void operator delete(void*, E);
};
}
void foo() {
N::C::E e;
N::C* ptr = new(e) N::C();
}
The reason for this is that non-member allocation and deallocation functions are required to be members of the global scope (3.7.3.1 basic.stc.dynamic.allocation paragraph 1, 3.7.3.2 basic.stc.dynamic.deallocation paragraph 1), unqualified friend declarations declare names in the innermost enclosing namespace (7.3.1.2 namespace.memdef paragraph 3), and these functions cannot be declared in global scope at a point where the friend declarations could refer to them using qualified-ids because their second parameter is a member of the class and thus can't be named before the class containing the friend declarations is defined.
Possible solutions for this conundrum include invention of some mechanism to allow a friend declaration to designate a namespace scope other than the innermost enclosing namespace in which the friend class or function is to be declared or to relax the innermost enclosing namespace lookup restriction in 7.3.1.2 namespace.memdef paragraph 3 for friend declarations that nominate allocation and deallocation functions.
Rationale (April, 2006):
The CWG acknowledged that it is not always possible to move code from the global scope into a namespace but felt that this problem was not severe enough to warrant changing the language to accommodate it. Possible solutions include moving the enumeration outside the class or defining member allocation and deallocation functions.
Daveed Vandevoorde : While reading Core issue 11 I thought it implied the following possibility:
template<typename T>
struct B {
template<int> void f(int);
};
template<typename T>
struct D: B<T> {
using B<T>::template f;
void g() { this->f<1>(0); } // OK, f is a template
};
However, the grammar for a using-declaration reads:
and nested-name-specifier never ends in "template".
Is that intentional?
Bill Gibbons :
It certainly appears to be, since we have:
Rationale (04/99): Any semantics associated with the template keyword in using-declarations should be considered an extension.
Notes from the April 2003 meeting:
We decided to make no change and to close this issue as not-a-defect. This is not needed functionality; the example above, for example, can be written with ->template. This issue has been on the issues list for years as an extension, and there has been no clamor for it.
It was also noted that knowing that something is a template is not enough; there's still the issue of knowing whether it is a class or function template.
7.3.3 namespace.udecl paragraph says,
A using-declaration shall not name a template-id.It is not clear whether this prohibition applies to the entity for which the using-declaration is a synonym or to any name that appears in the using-declaration. For example, is the following code well-formed?
template <typename T>
struct base {
void bar ();
};
struct der : base<int>
{
using base<int>::bar; // ill-formed ?
};
Rationale (10/99): 7.3.3 namespace.udecl paragraph 1 says, "A using-declaration introduces a name..." It is the name that is thus introduced that cannot be a template-id.
Now that the concept of "conditionally-supported" is available (see N1564), perhaps asm should not be required of every implementation.
Rationale (October, 2004):
This is covered in paper N1627. We would like to keep asm as a keyword for all implementations, however, to enhance portability by preventing programmers from inadvertently using it as an identifier.
Issue 1
7.5 dcl.link paragraph 6 says the following:
extern "C" int f(void);
namespace A {
extern "C" int f(void);
};
using namespace A;
int i = f(); // Ok because only one function f() or
// ill-formed
For name lookup, both declarations of f are visible and overloading cannot
distinguish between them. Has the compiler to check that these functions
are really the same function or is the program in error?
Rationale: These are the same function for all purposes.
Issue 2
A similar question may arise with typedefs:
// vendor A
typedef unsigned int size_t;
// vendor B
namespace std {
typedef unsigned int size_t;
}
using namespace std;
size_t something(); // error?
Is this valid because the typedef size_t refers to the same type in both
namespaces?
Rationale (04/99): In 7.3.4 namespace.udir paragraph 4:
If name lookup finds a declaration for a name in two different namespaces, and the declarations do not declare the same entity and do not declare functions, the use of the name is ill-formed.The term entity applied to typedefs refers to the underlying type or class (3 basic , paragraph 3); therefore both declarations of size_t declare the same entity and the above example is well-formed.
Is this code valid:
extern "C" void f();
namespace N
{
int var;
extern "C" void f(){ var = 10; }
}
The two declarations of f refer to the same external function, but is this a valid way to declare and define f?
And is the definition of f considered to be in namespace N or in the global namespace?
Notes from October 2002 meeting:
Yes, this example is valid. See 7.5 dcl.link paragraph 6, which contains a similar example with the definition in the global namespace instead. There is only one f, so the question of whether the definition is in the global namespace or the namespace N is not meaningful. The same function is found by name lookup whether it is found from the declaration in namespace N or the declaration in the global namespace, or both (7.3.4 namespace.udir paragraph 4).
In deciding whether a construct is an object declaration or a function declaration, 8.2 dcl.ambig.res contains the following gem: "In that context, the choice is between a function declaration [...] and an object declaration [...] Just as for the ambiguities mentioned in 6.8 stmt.ambig, the resolution is to consider any construct that could possibly be a declaration a declaration."
To what declaration do the last two "declarations" refer? Object, function, or (following from the syntax) possibly parameter declarations?
Notes from the 4/02 meeting:
This is not a defect. Section 8.2 dcl.ambig.res reads:
The ambiguity arising from the similarity between a function-style cast and a declaration mentioned in 6.8 stmt.ambig can also occur in the context of a declaration. In that context, the choice is between a function declaration with a redundant set of parentheses around a parameter name and an object declaration with a function-style cast as the initializer. Just as for the ambiguities mentioned in 6.8 stmt.ambig, the resolution is to consider any construct that could possibly be a declaration a declaration.
The wording "any construct" in the last sentence is not limited to top-level constructs. In particular, the function declaration encloses a parameter declaration, whereas the object declaration encloses an expression. Therefore, in case of ambiguity between these two cases, the declaration is parsed as a function declaration.
Consider the following program:
struct Point
{
Point(int){}
};
struct Lattice
{
Lattice(Point, Point, int){}
};
int main(void)
{
int a, b;
Lattice latt(Point(a), Point(b), 3); /* Line X */
}
The problem concerns the line marked /* Line X */, which is an ambiguous declarations for either an object or a function. The clause that governs this ambiguity is 8.2 dcl.ambig.res paragraph 1, and reads:
The ambiguity arising from the similarity between a function-style cast and a declaration mentioned in 6.8 stmt.ambig can also occur in the context of a declaration. In that context, the choice is between a function declaration with a redundant set of parentheses around a parameter name and an object declaration with a function-style cast as the initializer. Just as for the ambiguities mentioned in 6.8 stmt.ambig, the resolution is to consider any construct that could possibly be a declaration a declaration. [Note: a declaration can be explicitly disambiguated by a nonfunction-style cast, by a = to indicate initialization or by removing the redundant parentheses around the parameter name. ]
Based on this clause there are two possible interpretations of the declaration in line X:
Note that the last sentence before the "[Note:" is not much help, because both options are declarations.
Steve Adamczyk: a number of people replied to this posting on comp.std.c++ saying that they did not see a problem. The original poster replied:
I can't do anything but agree with your argumentation. So there is only one correct interpretation of clause 8.2 dcl.ambig.res paragraph 1, but I have to say that with some rewording, the clause can be made a lot clearer, like stating explicitly that the entire declaration must be taken into account and that function declarations are preferred over object declarations.
I would like to suggest the following as replacement for the current clause 8.2 dcl.ambig.res paragraph 1:
The ambiguity arising from the similarity between a functionstyle cast and a declaration mentioned in 6.8 stmt.ambig can also occur in the context of a declaration. In that context, the choice is between a function declaration with a redundant set of parentheses around a parameter name and an object declaration with a function-style cast as the initializer. The resolution is to consider any construct that could possibly be a function declaration a function declaration. [Note: To disambiguate, the whole declaration might have to be examined to determine if it is an object or a function declaration.] [Note: a declaration can be explicitly disambiguated by a nonfunction-style cast, by a = to indicate initialization or by removing the redundant parentheses around the parameter name. ]
Notes from the 4/02 meeting:
The working group felt that the current wording is clear enough.
Consider the following example:
struct S {
virtual void v() = 0;
};
void f(S sa[10]); // permitted?
8.3.4 dcl.array paragraph 1 says that a declaration like that of sa is ill-formed:
T is called the array element type; this type shall not be a reference type, the (possibly cv-qualified) type void, a function type or an abstract class type.
On the other hand, 8.3.5 dcl.fct paragraph 3 says that the type of sa is adjusted to S*, which would be permitted:
The type of each parameter is determined from its own decl-specifier-seq and declarator. After determining the type of each parameter, any parameter of type “array of T” or “function returning T” is adjusted to be “pointer to T” or “pointer to function returning T,” respectively.
It is not clear whether the parameter adjustment trumps the prohibition on declaring an array of an abstract class type or not. Implementations differ in this respect: EDG 2.4.2 and MSVC++ 7.1 reject the example, while g++ 3.3.3 and Sun Workshop 8 accept it.
Rationale (April, 2005):
The prohibition in 8.3.4 dcl.array is absolute and does not allow for exceptions. Even though such a type in a parameter declaration would decay to an allowed type, the prohibition applies to the type before the decay.
This interpretation is consistent with the resolution of issue 337, which causes template type deduction to fail if such types are deduced. It was also observed that pointer arithmetic on pointers to abstract classes is very likely to fail, and the fact that the programmer used array notation to declare the pointer type is a strong indication that he/she expected to use subscripting.
8.3.5 dcl.fct paragraph 2 says:
If the parameter-declaration-clause is empty, the function takes no arguments. The parameter list (void) is equivalent to the empty parameter list.Can a typedef to void be used instead of the type void in the parameter list?
Rationale: The IS is already clear that this is not allowed.
Paragraph 9 of says that extra default arguments added after a using-declaration but before a call are usable in the call, while 7.3.3 namespace.udecl paragraph 9 says that extra function overloads are not. This seems inconsistent, especially given the similarity of default arguments and overloads.
Rationale (10/99): The Standard accurately reflects the intent of the Committee.
In section 8.5.3 dcl.init.ref, paragraph 5, there is following note:
Note: the usual lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are not needed, and therefore are suppressed, when such direct bindings to lvalues are done.
I believe that this note is misleading. There should be either:
The problem:
int main()
{
const int ci = 10;
int * pi = NULL;
const int * & rpci = pi;
rpci = &ci;
*pi = 12; // circumvent constness of "ci"
}
int main()
{
int * pi = NULL;
const int * const & rcpci = pi; // 1
int i = 0;
pi = &i; // 2
if (pi == rcpci)
std::cout << "bound to lvalue" << std::endl;
else
std::cout << "bound to temporary rvalue" << std::endl;
}
There has been discussion on this issue on comp.lang.c++.moderated month ago, see http://groups.google.pl/groups?threadm=9bed99bb.0308041153.1c79e882%40posting.google.com and there seems to be some confusion about it. I understand that note is not normative, but apparently even some compiler writers are misled (try above code snippets on few different compilers, and using different compilation options - notably GCC 3.2.3 with -Wall -pedantic), thus it should be cleared up.
My proposal is to change wording of discussed note to:
Note: result of every standard conversion is never an lvalue, and therefore all standard conversions (clause 4) are suppressed, when such direct bindings to lvalues are done.
Rationale (April, 2005):
As acknowledged in the description of the issue, the referenced text is only a note and has no normative impact. Furthermore, the examples cited do not involve the conversions mentioned in the note, and the normative text is already sufficiently clear that the types in the examples are not reference-compatible.
Another instance to consider is that of invoking a member function from a null pointer:
struct A { void f () { } };
int main ()
{
A* ap = 0;
ap->f ();
}
Which is explicitly noted as undefined in 9.3.1 class.mfct.nonstatic, even though one could argue that since f() is empty, there is no lvalue->rvalue conversion.
If f is static, however, there seems to be no such rule, and the call is only undefined if the dereference implicit in the -> operator is undefined. IMO it should be.
Incidentally, another thing that ought to be cleaned up is the inconsistent use of "indirection" and "dereference". We should pick one. (This terminology issue has been broken out as issue 342.)
This is related to issue 232
Rationale (October 2003):
We agreed the example should be allowed. p->f() is rewritten as (*p).f() according to 5.2.5 expr.ref. *p is not an error when p is null unless the lvalue is converted to an rvalue (4.1 conv.lval), which it isn't here.
class Foo { public: Foo() {} ~Foo() {} };
class A : virtual private Foo { public: A() {} ~A() {} };
class Bar : public A { public: Bar() {} ~Bar() {} };
~Bar() calls ~Foo(), which is ill-formed due to access
violation, right? (Bar's constructor has the same problem since it
needs to call Foo's constructor.) There seems to be some disagreement
among compilers. Sun, IBM and g++ reject the testcase, EDG and HP
accept it. Perhaps this case should be clarified by a note in the
draft.
In short, it looks like a class with a virtual private base can't be derived from.
Rationale: This is what was intended.
Footnote 98 says:
As specified previously in clause 11 class.access , private members of a base class remain inaccessible even to derived classes unless friend declarations within the base class declaration are used to grant access explicitly.This footnote does not fit with the algorithm provided in 11.2 class.access.base paragraph 4 because it does not take into account the naming class concept introduced in this paragraph.
(See also paper J16/99-0002 = WG21 N1179.)
Rationale (10/99): The footnote should be read as referring to immediately-derived classes, and is accurate in that context.
The Standard does not appear to specify how to handle cases in which conflicting access specifications for a member are inherited from different base classes. For example,
struct A {
public:
int i;
};
struct B : virtual public A {
protected:
using A::i;
};
struct C : virtual public A, public B {
// "i" is protected from B, public from A
};
This question affects both the existing wording of 11.2 class.access.base paragraph 4 (“m as a member of N is public ... m as a member of N is private ... m as a member of N is protected”) and the proposed wording for issue 385 (“when a nonstatic data member or nonstatic member function is a protected member of its naming class”).
One possible definition of “is public” would be something like, “if any visible declaration of the entity has public access.” One could also plausibly define the access of m in N to be the minimum of all the visible declarations, or even an error if the visible declarations are inconsistent.
11.2 class.access.base paragraph 1 describes the access of inherited members, so a clarifying statement resolving this issue might plausibly be inserted at the end of that paragraph.
Proposed resolution (October, 2004):
Add the following text as a new paragraph after 11.2 class.access.base paragraph 1:
If a given base class can be reached along more than one path through a derived class's sub-object lattice (10.1 class.mi), a member of that base class could have different accessibility in the derived class along different paths. In such cases, the most permissive access prevails. [Example:
struct B { static int i; }; class I: protected B { }; class D1: public B, public I { }; class D2: public I, private B { };i is accessible as a public member of D1 and as a protected member of D2. —end example]
Rationale (03/2005): This question is already covered, in almost identical words, in 11.7 class.paths.
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.
Proposed resolution (10/00):
Remove the first sentence of 11.4 class.friend, paragraph 7.
Rationale (04/01):
After the 10/00 vote to accept this issue as a DR with the proposed resolution, it was noted that the first two sentences of 11 class.access paragraph 3 cause the proposed change to have no effect:
Access control is applied uniformly to all names, whether the names are referred to from declarations or expressions. [Note: access control applies to names nominated by friend declarations (11.4 class.friend) and using-declarations (7.3.3 namespace.udecl). ]
In addition to the obvious editing to the text of the note, an exception to the blanket statement in the first sentence would also be required. However, discussion during the 04/01 meeting failed to produce consensus on exactly which names in the friend declaration should be exempted from the requirements of access control.
One possibility would be that only the name nominated as friend should be exempt. However, that approach would make it impossible to name a function as a friend if it used private types in its parameters or return type. Another suggestion was to ignore access for every name used in a friend declaration. That approach raised a question about access within the body of a friend function defined inline in the class body — the body is part of the declaration of a function, but references within the body of a friend function should still be subject to the usual access controls.
Other possibilities were raised, such as allowing the declaration of a friend member function if the declaration were permissible in its containing class, or taking the union of the access within the befriending class and the befriended entity. However, these suggestions would have been complex and difficult to specify correctly.
Ultimately it was decided that the original perceived defect was not sufficiently serious as to warrant the degree of complexity required to resolve it satisfactorily and the issue was consequently declared not to be a defect. It was observed that most of the problems involved with the current state of affairs result from inability to declare a particular member function as a friend; in such cases, an easy workaround is simply to befriend the entire class rather than the specific member function.
Thus says the section 11.4 class.friend/7 in ISO 14882 C++ standard:
A name nominated by a friend declaration shall be accessible in the scope of the class containing the friend declaration.
The obvious intention of this is to allow a friend declaration to specify a function (or nested class, enum, etc.) that is declared "private" or "protected" in its enclosing class. However, literal interpretation seems to allow a broader access to the befriended function by the whole class that is declaring the friendship.
If the rule were interpreted literally as it is currently written, this would compile (when it, of course, shouldn't be allowed at all):
class C
{
private:
static void f();
};
class D
{
friend void C::f(); // A name nominated by friend declaration...
D()
{
C::f(); // ... shall be accessible in scope of class declaring friendship
}
};
Suggested fix is to reword "in the scope of the class containing the friend declaration" to exclude all other references from the scope of the declaring class, except the friend-declaration itself.
Notes from the March 2004 meeting:
We considered this and concluded that the standard is clear enough.
I just received a query from a user of why line #1 in the following snippet is ill-formed:
void g(int (*)(int));
template<class T>
class A {
friend int f(int) { return 0; }
void h() {
g(f); // #1
}
};
I believe that the special invisibility rule about friends is too complicated and makes life too complicated, especially considering that friends in templates are not templates, nor can they be conveniently rewritten with a “first declare at the namespace scope” rule. I can understand the rules when they make programming easier or prevent some obvious “silly” mistakes; but that does not seem to be the case here.
John Spicer: See two papers that discuss this issue: N0878 by Bill Gibbons, which ultimately gave rise to our current rules, and N0913 by me as an alternative to N0878.
Rationale (April, 2005):
The Standard is clear and consistent; this rule is the result of an explicit decision by the Committee.
11.5 class.protected paragraph 1 says:
When a friend or a member function of a derived class references a protected nonstatic member of a base class, an access check applies in addition to ...Instead of saying "references a protected nonstatic member of a base class", shouldn't this be rewritten to use the concept of naming class as 11.2 class.access.base paragraph 4 does?
Rationale (04/99): This rule is orthogonal to the specification in 11.2 class.access.base paragraph 4.
12.2 class.temporary paragraph 4 seems self-contradictory:
the temporary that holds the result of the expression shall persist until the object's initialization is complete... the temporary is destroyed after it has been copied, before or when the initialization completes.How can it be destroyed "before the initialization completes" if it is required to "persist until the object's initialization is complete?"
Rationale (04/00):
It was suggested that "before the initialization completes" refers to the case in which some part of the initialization terminates by throwing an exception. In that light, the apparent contradiction does not apply.
Is the following code well-formed?
struct A { /* */ };
int main()
{
A a=a;
}
Note, that { int a=a; } is pretty legal.
And if so, what is the semantics of the self-initialization of UDT? For example
#include <stdio.h>
struct A {
A() { printf("A::A() %p\n", this); }
A(const A& a) { printf("A::A(const A&) %p %p\n", this, &a); }
~A() { printf("A::~A() %p\n", this); }
};
int main()
{
A a=a;
}
can be compiled and prints:
A::A(const A&) 0253FDD8 0253FDD8 A::~A() 0253FDD8
(on some implementations).
Notes from October 2002 meeting:
3.8 basic.life paragraph 6 indicates that the references here are valid. It's permitted to take the address of a class object before it is fully initialized, and it's permitted to pass it as an argument to a reference parameter as long as the reference can bind directly. Except for the failure to cast the pointers to void * for the %p in the printfs, these examples are standard-conforming.
The second paragraph of section 12.7 class.cdtor contains the following text:
To explicitly or implicitly convert a pointer (an lvalue) referring to an object of class X to a pointer (reference) to a direct or indirect base class B of X, the construction of X and the construction of all of its direct or indirect bases that directly or indirectly derive from B shall have started and the destruction of these classes shall not have completed, otherwise the conversion results in undefined behavior.
Now suppose we have the following piece of code:
struct a {
a() : m_a_data(0) { }
a(const a& rhsa) : m_a_data(rhsa.m_a_data) { }
int m_a_data;
};
struct b : virtual a {
b() : m_b_data(0) { }
b(const b& rhsb) : a(rhsb), m_b_data(rhsb.m_b_data) { }
int m_b_data;
};
struct c : b {
c() : m_c_data(0) { }
c(const c& rhsc) : a(rhsc),// Undefined behaviour when constru-
// cting an object of type 'c'
b(rhsc), m_c_data(rhsc.m_c_data) { }
int m_c_data;
};
int main()
{ c ac1, ac2(ac1); }
The problem with the above snippet is that when the value 'ac2' is being created and its construction gets started, c's copy constructor has first to initialize the virtual base class subobject 'a'. Which requires that the lvalue expression 'rhsc' be converted to the type of the parameter of a's copy constructor, which is 'const a&'. According to the wording quoted above, this can be done without undefined behaviour if and only if b's construction has already started, which is not possible since 'a', being a virtual base class, has to be initialized first by a constructor of the most derived object (12.6.2 class.base.init).
The issue could in some cases be alleviated when 'c' has a user-defined copy constuctor. The constructor could default-initialize its 'a' subobject and then initialize a's members as needed taking advantage of the latitude given in paragraph 2 of 12.6.2 class.base.init.
But if 'c' ends up having the implicitly-defined copy constuctor, there's no way to evade undefined behaviour.
struct c : b {
c() : m_c_data(0) { }
int m_c_data;
};
int main()
{ c ac1, ac2(ac1); }
Paragraph 8 of 12.8 class.copy states
The implicitly-defined copy constructor for class X performs a memberwise copy of its subobjects. The order of copying is the same as the order of initialization of bases and members in a user-defined constructor (see 12.6.2 class.base.init). Each subobject is copied in the manner appropriate to its type:
- if the subobject is of class type, the copy constructor for the class is used;
Which effectively means that the implicitly-defined copy constructor for 'c' will have to initialize its 'a' base class subobject first and that must be done with a's copy constructor, which will always require a conversion of an lvalue expression of type 'const c' to an lvalue of type 'const a&'. The situation would be the same if all the three classes shown had implicitly-defined copy constructors.
Suggested resolution:
Prepend to paragraph 2 of 12.7 class.cdtor the following:
Unless the conversion happens in a mem-initializer whose mem-initializer-id designates a virtual base class of X, to explicitly or implicitly convert ...
Notes from the 10/01 meeting:
There is no problem in this example. ac1 is fully initialized before it is used in the initialization of ac2.
The working paper is quite explicit about
struct X {
X(X, X const& = X());
};
being illegal (because of the chicken & egg problem wrt copying.)
Shouldn't it be as explicit about the following?
struct Y {
Y(Y const&, Y = Y());
};
Rationale:
There is no need for additional wording. This example leads to
a program which either fails to compile (due to resource limits on recursive
inlining) or fails to run (due to unterminated recursion). In either case
the implementation may generate an error when the program is compiled.
Section 12.8 class.copy paragraph 8 says the compiler-generated copy constructor copies scalar elements via the built-in assignment operator. Seems inconsistent. Why not the built-in initialization?
Notes from October 2002 meeting:
The Core Working Group believes this should not be changed. The standard already mentions built-in operators and the assignment operator does clearly define what must be done for scalar types. There is currently no concept of built-in initialization.
EDG (and g++, for that matter) picks the explicit copy assignment operator, which we think is wrong in this case:
#include <stdio.h>
struct D; // fwd declaration
struct B {
D& operator=(D&);
};
struct D : B {
D() {}
D(int ii) { s = ii; }
using B::operator=;
int s;
};
int main() {
D od, od1(10);
od = od1; // implicit D::operator=(D&) called, not BASE::operator=(D&)
}
D& B::operator=(D& d) {
printf("B::operator called\n");
return d;
}
If you look at 12.8 class.copy paragraph 10 it explicitly states that in such a case the "using B::operator=" will not be considered.
Steve Adamczyk: The fact that the operator= you declared is (D&) and not (const D&) is fooling you. As the standard says, the operator= introduced by the using-declaration does not suppress the generation of the implicit operator=. However, the generated operator= has the (const D&) signature, so it does not hide B::operator=; it overloads it.
Kerch Holt: I'm not sure this is correct. Going by 12.8 P10 first paragraph we think that the other form "operator=(D&)" is generated because the two conditions mentioned were not met in this case. 1) there is no direct base with a "const [volatile] B&" or "B" operator 2) And no member has a operator= either. This implies the implicit operator is "operator=(D&)". So, if that is the case the "hiding" should happen.
Also, in the last paragraph it seems to state that operators brought in from "using", no matter what the parameter is, are always hidden.
Steve Adamczyk: Not really. I think this section is pretty clear about the fact that the implicit copy assignment operator is generated. The question is whether it hides or overloads the one imported by the using-declaration.
Notes from the March 2004 meeting:
(a) Class B does get an implicitly-generated operator=(const B&); (b) the using-declaration brings in two operator= functions from B, the explicitly-declared one and the implicitly-generated one; (c) those two functions overload with the implicitly-generated operator=(const D&) in the derived class, rather than being hidden by the derived-class function, because it does not match either of their signatures; (d) overload resolution picks the explicitly-declared function from the base class because it's the best match in this case. We think the standard wording says this clearly enough.
The following example does not work as one might expect:
namespace N { class C {}; }
int operator +(int i, N::C) { return i+1; }
#include <numeric>
int main() {
N::C a[10];
std::accumulate(a, a+10, 0);
}
According to
3.4.1
basic.lookup.unqual
paragraph 6, I would expect that the "+" call inside
std::accumulate would find the global operator+.
Is this true, or am I
missing a rule? Clearly, the operator+ would be found by Koenig lookup
if it were in namespace N.
Daveed Vandevoorde: But doesn't unqualified lookup of the operator+ in the definition of std::accumulate proceed in the namespace where the implicit specialization is generated; i.e., in namespace std?
In that case, you may find a non-empty overload set for operator+ in namespace std and the surrounding (global) namespace is no longer considered?
Nathan Myers: Indeed, <string> defines operator+, as do <complex>, <valarray>, and <iterator>. Any of these might hide the global operator.
Herb Sutter: These examples fail for the same reason:
struct Value { int i; };
typedef map<int, Value > CMap;
typedef CMap::value_type CPair;
ostream & operator<< ( ostream &os, const CPair &cp )
{ return os << cp.first << "/" << cp.second.i; }
int main() {
CMap courseMap;
copy( courseMap.begin(), courseMap.end(),
ostream_iterator<CPair>( cout, "\n" ) );
}
template<class T, class S>
ostream& operator<< (ostream& out, pair<T,S> pr)
{ return out << pr.first << " : " << pr.second << endl; }
int main() {
map <int, string> pl;
copy( pl.begin(), pl.end(),
ostream_iterator <places_t::value_type>( cout, "\n" ) );
}
This technique (copying from a map to another container or stream)
should work. If it really cannot be made to work, that would seem broken
to me. The reason is does not work is that copy and pair are in
namespace std and the name lookup rules do not permit the global
operator<< to be found because the other operator<<'s
in namespace std
hide the global operator. (Aside: FWIW, I think most programmers don't
realize that a typedef like CPair is actually in na