| Document number: | PL22.16/09-0127 = WG21 N2937 |
| Date: | 2009-08-03 |
| 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 that have been categorized as Defect Reports by the Committee (J16 + WG21), that is, issues with status "DR," "WP," "CD1," and "TC1," along with their proposed resolutions. ONLY RESOLUTIONS FOR ISSUES WITH TC1 STATUS ARE PART OF THE INTERNATIONAL STANDARD FOR C++. The other issues 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.
Section references in this document reflect the section numbering of document PL22.16/09-0104 = WG21 N2914.
[Voted into WP at July, 2009 meeting.]
Paper N2657, adopted at the June, 2008 meeting, removed the prohibition of local and unnamed types as template arguments. As part of the change, 3.5 [basic.link] paragraph 8 was modified to read,
A type without linkage shall not be used as the type of a variable or function with linkage, unless
the variable or function has extern "C" linkage (7.5 [dcl.link]), or
the type without linkage was named using a dependent type (14.7.2.1 [temp.dep.type]).
Because a type without linkage can only be named as a dependent type, there are still some potentially useful things that cannot be done:
template <class T> struct A {
friend void g(A, T); // this can't be defined later
void h(T); // this cannot be explicitly specialized
};
template <class T> void f(T) {
A<T> at;
g(at, (T)0);
}
enum { e };
void g(A<decltype(e)>, decltype(e)){} // not allowed
int main() {
f(e);
}
These deficiencies could be addressed by allowing types without linkage to be used as the type of a variable or function, but with the requirement that any such entity that is used must also be defined in the same translation unit. This would allow issuing a compile-time, instead of a link-time, diagnostic if the definition were not provided, for example. It also seems to be easier to implement than the current rules.
Proposed resolution (March, 2009):
Change 3.5 [basic.link] paragraph 8 as follows:
...A type without linkage shall not be used as the type of a variable or function with linkage, unless
the variable or function has extern "C" linkage (7.5 [dcl.link]), or
the type without linkage was named using a dependent type (14.7.2.1 [temp.dep.type]) the variable or function is not used (3.2 [basic.def.odr]) or is defined in the same translation unit.
[Note: in other words, a type without linkage contains a class or enumeration that cannot be named outside its translation unit. An entity with external linkage declared using such a type could not correspond to any other entity in another translation unit of the program and thus is not permitted must be defined in the translation unit if it is used. Also note that classes with linkage may contain members whose types do not have linkage, and that typedef names are ignored in the determination of whether a type has linkage. —end note] [Example:
void f() { struct A { int x; }; // no linkage extern A a; // ill-formed typedef A B; extern B b; // ill-formed }—end example]
[Example:
template <class T> struct A { // in A<X>, the following is allowed because the type with no linkage // X is named using template parameter T. friend void f(A, T){} }; template <class T> void g(T t) { A<T> at; f(at, t); } int main() { class X {} x; g(x); }template <typename T> struct B { void g(T){} void h(T); friend void i(B, T){} }; void f() { struct A { int x; }; // no linkage A a = {1}; B<A> ba; // declares B<A>::g(A) and B<A>::h(A) ba.g(a); // OK ba.h(a); // error: B<A>::h(A) not defined in the translation unit i(ba, a); // OK }—end example]
[Drafting note: issue 527 also changes part of the same text.]
[Voted into WP at July, 2009 meeting.]
According to 4.5 [conv.prom] paragraph 2,
An rvalue of an unscoped enumeration type (7.2 [dcl.enum]) can be converted to an rvalue of the first of the following types that can represent all the values of the enumeration (i.e. the values in the range bmin to bmax as described in 7.2 [dcl.enum]): int, unsigned int, long int, unsigned long int, long long int, or unsigned long long int.
This wording may have surprising behavior in this case:
enum E: long { e };
void f(int);
void f(long);
void g() {
f(e); // Which f is called?
}
Intuitively, as the programmer has explicitly expressed preference for long as the underlying type, he/she might expect f(long) to be called. However, if long and int happen to have the same size, then e is promoted to int (as it is the first type in the list that can represent all values of E) and f(int) is called instead.
According to 7.2 [dcl.enum] the underlying type of an enumeration is always well-defined for both the fixed and the non-fixed cases, so it makes sense simply to promote to the underlying type unless such a type would itself require promotion.
Suggested resolution:
In 4.5 [conv.prom] paragraph 2, replace all the text from “An rvalue of an unscoped enumeration type” through the end of the paragraph with the following:
An rvalue of an unscoped enumeration type (7.2 [dcl.enum]) is converted to an rvalue of its underlying type if it is different from char16_t, char32_t, wchar_t, or has integer conversion rank greater than or equal to int. Otherwise, it is converted to an rvalue of the first of the following types that can represent all the values of its underlying type: int, unsigned int, long int, unsigned long int, long long int, or unsigned long long int.
(Note that this wording no longer needs to mention extended integer types as special cases.)
Proposed resolution (August, 2008):
Move the following text from 4.5 [conv.prom] paragraph 2 into a separate paragraph, making the indicated changes, and add the following new paragraph after it:
An rvalue of an unscoped enumeration type whose underlying type is not fixed (7.2 [dcl.enum]) can be converted to an rvalue of the first of the following types that can represent all the values of the enumeration (i.e. the values in the range bmin to bmax as described in 7.2 [dcl.enum]): int, unsigned int, long int, unsigned long int, long long int, or unsigned long long int. If none of the types in that list can represent all the values of the enumeration, an rvalue of an unscoped enumeration type can be converted to an rvalue of the extended integer type with lowest integer conversion rank (4.13 [conv.rank]) greater than the rank of long long in which all the values of the enumeration can be represented. If there are two such extended types, the signed one is chosen.
An rvalue of an unscoped enumeration type whose underlying type is fixed (7.2 [dcl.enum]) can be converted to an rvalue of its underlying type. Moreover, if integral promotion can be applied to its underlying type, an rvalue of an unscoped enumeration type whose underlying type is fixed can also be converted to an rvalue of the promoted underlying type.
[Voted into WP at July, 2009 meeting.]
The current wording of 4.9 [conv.fpint] paragraph 2 does not specify what should happen when converting an integer value that is outside the representable range of the target floating point type. The C99 Standard covers this case explicitly in 6.3.1.4 paragraph 2:
When a value of integer type is converted to a real floating type, if the value being converted can be represented exactly in the new type, it is unchanged. If the value being converted is in the range of values that can be represented but cannot be represented exactly, the result is either the nearest higher or nearest lower representable value, chosen in an implementation-defined manner. If the value being converted is outside the range of values that can be represented, the behavior is undefined.
While the current C++ specification requires defined behavior in all cases, the C specification allows for use of NaNs and traps, if those are needed for efficiency.
Notes from the September, 2008 meeting:
The CWG agreed that the C approach should be adopted.
Proposed resolution (March, 2009):
Change 4.9 [conv.fpint] paragraph 2 as indicated:
An rvalue of an integer type or of an unscoped enumeration type can be converted to an rvalue of a floating point type. The result is exact if possible. Otherwise If the value being converted is in the range of values that can be represented but cannot be represented exactly, it is an implementation-defined choice of either the next lower or higher representable value. [Note: loss of precision occurs if the integral value cannot be represented exactly as a value of the floating type. —end note] If the value being converted is outside the range of values that can be represented, the behavior is undefined. If the source type is bool, the value false is converted to zero and the value true is converted to one.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
There is not a single example of a lambda-expression in their specification. The Standard would be clearer if a few judiciously-chosen examples were added.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
Consider an example like:
void f(vector<double> vec) {
double x, y, z;
fancy_algorithm(vec, [&]() { /* use x, y, and z in various ways */ });
}
5.1.2 [expr.prim.lambda] paragraph 8 requires that the closure class for this lambda will have three reference members, and paragraph 12 requires that it be derived from std::reference_closure, implying two additional pointer members. Although 8.3.2 [dcl.ref] paragraph 4 allows a reference to be implemented without allocation of storage, current ABIs require that references be implemented as pointers. The practical effect of these requirements is that the closure object for this lambda expression will contain five pointers. If not for these requirements, however, it would be possible to implement the closure object as a single pointer to the stack frame, generating data accesses in the function-call operator as offsets relative to the frame pointer. The current specification is too tightly constrained.
Lawrence Crowl:
The original intent was that the reference members could be omitted from the closure object by an implementation. The problem we had was that we want the call to f in
extern f(std::reference_closure<void()>);
extern f(std::function<void()>);
f([&](){});
to unambiguously bind to the reference_closure; using reference_closure can be an order of magnitude faster than using function.
(See also issue 751.)
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927. (See also document PL22.16/09-0035 = WG21 N2845, which partially addressed this issue by the removal of std::reference_clossure.)
[Voted into the WP at the July, 2009 meeting as part of N2927.]
During the discussion of issue 750, it was suggested that an implementation might be permitted to omit fields in the closure object of a lambda expression if the implementation does not need them to address the corresponding automatic variables. If permitted, this implementation choice might be visible to the program via inheritance. Consider:
void f() {
int const N = 10;
typedef decltype([&N](){}) F;
struct X: F {
void n() { float z[N]; } // Error?
};
}
If it is implementation-defined or unspecified whether the reference member F::N will exist, then it is unknown whether the the reference to N in X::n() will be an error (because lookup finds F::N, which is private) or well-formed (because there is no F::N, so the reference is to the local automatic variable).
If implementations can omit fields, the implementation dependency might be addressed by either treating the lookup “as if” the fields existed, even if they are not present in the object layout, or by defining the names of the fields in the closure class to be unique identifiers, similar to the names of unnamed namespaces (7.3.1.1 [namespace.unnamed]).
Another suggestion was made that derivation from a closure class should be prohibited, at least for now. However, it was pointed out that inheritance is frequently used to give stateless function objects some state, suggesting a use case along the lines of:
template<class T> struct SomeState: T {
// ...
};
template<class F, typename T< void algo(T functor, ...) {
SomeState<T< state(functor);
...
}
... algo([](int a){ return 2*a; }) ...
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
How does name binding work in nested lambda-expressions? For example,
void f1() {
float v;
[]() { return [v]() { return v; } }
}
void f2() {
float v;
[v]() { return [v]() { return v; } }
}
According to 5.1.2 [expr.prim.lambda] paragraph 3,
A name in the lambda-capture shall be in scope in the context of the lambda expression, and shall be this or shall refer to a local variable or reference with automatic storage duration.
One possible interpretation is that the lambda expression in f1 is ill-formed because v is used in the compound-statement of the outer lambda expression but does not appear in its effective capture set. However, the appearance of v in the inner lambda-capture is not a “use” in the sense of 3.2 [basic.def.odr] paragraph 2, because a lambda-capture is not an expression, and it's not clear whether the reference in the inner lambda expression's return expression should be considered a use of the automatic variable or of the member of the inner lambda expression's closure object.
Similarly, the lambda expression in f2 could be deemed to be ill-formed because the reference to v in the inner lambda expression's lambda-capture would refer to the field of the outer lambda-expression's closure object, not to a local automatic variable; however, it's not clear whether the inner lambda expression should be evaluated in situ or as part of the generated operator() member of the outer lambda expression's closure object.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
The current specification does not adequately describe what happens when an array name is part of the effective capture set of a lambda expression. 5.1.2 [expr.prim.lambda] paragraph 13 says that the array member of the closure object is direct-initialized by the local array; however, 8.5 [dcl.init] paragraph 16 says that such an initialization is ill-formed. There are several possibilities for handling this problem:
This results in an array member of the closure object, which is initialized by copying each element, along the lines of 12.8 [class.copy] paragraph 8.
This results in a pointer member of the closure object, initialized to point to the first element of the array (i.e., the array lvalue decays to a pointer rvalue).
This is ill-formed.
This results in a reference-to-array member of the closure object, initialized to refer to the array, regardless of whether & was used or not.
This is ill-formed unless the capture is “by reference.”
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
Is a lambda expression permitted in a default argument expression for a block-scope function declaration? For example,
void g() {
void f(std::reference_closure<void()> rc = []() {});
f();
}
This was not discussed in either the Evolution Working Group nor in the Core Working Group, and it is possible that some of the same implementation difficulties that led to prohibiting use of automatic variables in such default argument expressions (8.3.6 [dcl.fct.default] paragraph 7) might also apply to closure objects, even though they are not automatic variables.
(See also issue 772.)Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
Consider the following example:
void f() {
int const N = 10;
[=]() mutable { N = 30; } // Okay: this->N has type int, not int const.
N = 20; // Error.
}
That is, the N that is a member of the closure object is not const, even though the captured variable is const. This seems strange, as capturing is basically a means of capturing the local environment in a way that avoids lifetime issues. More seriously, the change of type means that the results of decltype, overload resolution, and template argument deduction applied to a captured variable inside a lambda expression can be different from those in the scope containing the lambda expression, which could be a subtle source of bugs.
On the other hand, the copying involved in capturing has uses beyond avoiding lifetime issues (taking snapshots of values, avoiding data races, etc.), and the value of a cv-qualified object is not cv-qualified.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
The specification of closure objects is missing a couple of important points regarding their destruction. First, although 5.1.2 [expr.prim.lambda] paragraph 11 mentions other implicitly-declared special member functions, it is silent on the destructor, leading to questions about whether the closure class has one or not.
Second, nothing is said about the timing of the destruction of a closure object: is it normally destroyed at the end of the full-expression to which the lambda expression belongs, and is its lifetime extended if the closure object is bound to a reference? These questions would be addressed if paragraph 2 defined the closure object as a temporary instead of just as an rvalue. (It should be noted that 5.2.3 [expr.type.conv] also does not define the conceptually-similar T() as a temporary.)
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927. (The question regarding the failure of 5.2.3 [expr.type.conv] failing to categorize T() as a temporary was split off into a separate issue; see issue 943.)
[Voted into the WP at the July, 2009 meeting as part of N2927.]
According to 5.1.2 [expr.prim.lambda] paragraph 10, the following lambda expressions are ill-formed because the return types of the generated operator() functions are an array type and a function type, respectively:
void f() {
[]{ return ""; };
[]{ return f; };
}
It would seem reasonable to expect the array-to-pointer and function-to-pointer decay to apply to these return values and hence to the inferred return type of operator().
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
The current wording of 5.1.2 [expr.prim.lambda] is not clear as to how name lookup is to be performed for names appearing in the compound-statement of a lambda expression. Consider, for example:
int fac(int n) {
return [=]{ return n <= 1 ? 1 : n*operator()(n-1); }();
}
There is no operator() in scope in the context of the lambda expression. Consequently, according to bullet 5 of paragraph 10, the reference to operator() is not transformed to the class member access syntax but appears untransformed in the closure object's function call operator, where presumably it is interpreted as a recursive call to itself.
A similar question (although it does not involve name lookup per se) arises with respect to use of this in the compound-statement of a lambda expression that does not appear in the body of a non-static member function; for example,
void f() {
float v;
[v]() { return v+this->v; }
}
this cannot refer to anything except the closure object, so are the two references to v equivalent?
The crux of this question is whether the lookups for names in the compound-statement are done in the context of the lambda expression or from the call operator of the closure object. The note at the end of paragraph 10 bullet 5 would tend to support the latter interpretation:
[Note: Reference to captured variables or references within the compound-statement refer to the data members of F. —end note]
Another possible interpretation of the current wording is that there are two distinct compound-statements in view: the compound-statement that is part of the lambda-expression and the body of the closure object's function call operator that is “obtained from” the former. If this is the intended interpretation, one way of addressing the issues regarding the operator() example above would be to state that it is an error if the lookup of a name in the compound-statement fails, making the example ill-formed.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
A lambda expression appearing in local scope presumably creates a local class (in the sense of 9.8 [class.local]) as the type of the closure object, because that class is “considered to be defined at the point where the lambda expression occurs” (5.1.2 [expr.prim.lambda] paragraph 7), and in the absence of any indication to the contrary that class must satisfy the restrictions of 9.8 [class.local] on local classes. One such restriction is that all its member functions must be defined within the class definition, making them inline. However, nothing is said about whether the function call operator for a non-local closure class is inline, and even for the local case it would be better if the specification were explicit.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
5.1.2 [expr.prim.lambda] paragraph 5 says,
The compound-statement of a lambda expression shall use (3.2 [basic.def.odr]) an automatic variable or reference from the context where the lambda expression appears only if the name of the variable or reference is a member of the effective capture set...
The reference to 3.2 [basic.def.odr] makes clear that the technical meaning of “use” is in view here, and that the names of variables can appear without being captured if they are constants used as values or if they are unevaluated operands.
There appears to be a disconnect with the preceding paragraph, however, in the description of which variables are implicitly captured by a capture-default:
for each name v that appears in the lambda expression and denotes a local variable or reference with automatic storage duration in the context where the lambda expression appears and that does not appear in the capture-list or as a parameter name in the lambda-parameter-declaration-list...
It would be more consistent if only variables that were required by paragraph 5 to be captured were implicitly captured, i.e., if “that appears in the lambda expression” were replaced by “that is used (3.2 [basic.def.odr]) in the compound-statement of the lambda expression.” For example,
struct A {
A();
A(const A&);
~A();
};
void f() {
A a;
int i = [=]() { return sizeof a; }();
}
Here, a will be captured (and copied), even though it is not “used” in the lambda expression.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
According to 5.1.2 [expr.prim.lambda] paragraph 7, the appearance of a lambda expression results in the definition of a class “considered to be defined at the point where the lambda expression occurs.” It is not clear whether that means that a lambda expression cannot appear at any point where it is not permitted to define a class type. For example, 8.3.5 [dcl.fct] paragraph 10 says, “Types shall not be defined in return or parameter types.” Does that mean that a function declaration like
void f(int a[sizeof ([]{ return 0; })]);
is ill-formed, because the parameter type defines the closure class for the lambda expression? (Issue 686 lists many contexts in which type definitions are prohibited. Each of these should be examined to see whether a lambda expression should be allowed or prohibited there.)
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
The grammar in 5.1.2 [expr.prim.lambda] for lambda-parameter specifies that a declarator must be present, i.e., that all lambda-parameters must be named. This also has the effect of prohibiting a lambda like [](void){}. It is not clear that there is a good reason for these restrictions; programmers could reasonably expect that lambda-parameters were like ordinary function parameters in these regards.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
The grammar in 5.1.2 [expr.prim.lambda] for lambda-parameter-declaration does not allow for an ellipsis. Is this a desirable restriction?
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
5.1.2 [expr.prim.lambda] paragraph 13 says simply,
The closure object is initialized by direct-initializing each member N of F with the local variable or reference named N; the member t is initialized with this.
The mechanism for this initialization is not specified. In particular, does the closure class have a default constructor that performs this initialization?
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
According to 5.1.2 [expr.prim.lambda] paragraph 11, the closure class “has a public move constructor that performs a member-wise move.” Although the terms “move constructor” and “member-wise move” are not currently defined (see issue 680), this presumably means that a lambda like [&i]{} results in a closure class similar to:
class F {
int& i;
public:
F(&& other):
i(std::move(other.i)) { }
// etc.
};
This constructor is ill-formed because it attempts to initialize an lvalue reference to non-const int with the rvalue returned by std::move.
It is not clear whether this should be handled by:
Not generating the move constructor.
Generating the declaration of the move constructor but only defining it (and giving the corresponding error) if the move constructor would be used, similar to the handling of other implicitly-defined special member functions.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
Assuming that it is permitted to use a lambda as a default argument in a block-scope function declaration (see issue 754), it is presumably ill-formed for such a lambda expression to refer to a local automatic variable (8.3.6 [dcl.fct.default] paragraph 7). What does this mean for capture-defaults? For example,
void f() {
int i = 1;
void f(int = ([i]() { return i; })()); // Definitely an error
void g(int = ([i]() { return 0; })()); // Probably an error
void h(int = ([=]() { return i; })()); // Definitely an error
void o(int = ([=]() { return 0; })()); // Okay?
void p(int = ([]() { return sizeof i; })()); // Presumably okay
}
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
The current wording does not state under what conditions, if ever, a closure class is a POD. It should either be explicitly unspecified or definitively stated that a closure class is never a POD, to allow implementations freedom to determine the contents of closure classes.
Notes from the March, 2009 meeting:
A closure class is neither standard-layout nor trivial.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
According to 5.1.2 [expr.prim.lambda] paragraph 8, the “object type” of a captured function is the type to which the reference refers. That's clearly wrong when the captured reference is a reference to a function, because the resulting data member of the closure class will have a function type:
void f() { }
void g() {
void (&fr)() = f;
[fr]{}; // Oops...
}
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
5.1.2 [expr.prim.lambda] paragraph 8, bullet 2, says of members of a closure class,
if the element is of the form & N, the data member has the name N and type “reference to object type of N”
Is an implementation free to use an rvalue reference as the type of this member, as only a “reference” is specified? (See issue 771; the move constructor would be well-formed if the reference member were an rvalue reference.)
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
Functions and function objects behave differently with respect to argument-dependent lookup. In particular, the associated namespaces of a function's parameters and return types, but not the namespace in which the function is declared, are associated namespaces of the function; the exact opposite is true of a function object. The Committee should consider rectifying that disparity; however, in the absence of such action, an explicit decision should be made as to whether lambdas are more function-like or object-like with respect to argument-dependent lookup. For example:
namespace M {
struct S { };
}
namespace N {
void func(M::S);
struct {
void operator()(M::S);
} fn_obj;
const auto& lambda = [](M::S){};
}
void g() {
f(N::func); // assoc NS == M, not N
f(N::fn_obj); // assoc NS == N, not M
f(N::lambda); // assoc NS == ??
}
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
5.1.2 [expr.prim.lambda] paragraph 13 ties the effective lifetime of a closure object with members captured by reference to the innermost block scope in which the lambda appears, rather than to the lifetime of the objects to which the references are bound. This seems too restrictive.
Notes from the March, 2009 meeting:
Making the suggested change would be problematic for an implementation in which the “reference members” were actually implemented using offsets from a captured stack pointer and in which nested blocks were pushed onto the stack (to optimize space for large local objects, for example).
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into WP at July, 2009 meeting as part of N2932.]
Throwing std::length_error (5.3.4 [expr.new] paragraph 7) for an attempt to allocate a too-large array brings in too much of the Standard library. A simpler exception, like std::bad_alloc, should be thrown instead.
Notes from the March, 2009 meeting:
The CWG was in favor of throwing an exception derived from std::bad_alloc. This would be upwardly compatible; it would be harmless for programs that currently catch std::bad_alloc, but would allow programs to treat the calculation overflow case separately if they wish.
[Voted into WP at July, 2009 meeting.]
The requirements for the operand of the delete operators are given in 5.3.5 [expr.delete] paragraph 2:
In either alternative, the value of the operand of delete may be a null pointer value. If it is not a null pointer value, in the first alternative (delete object), the value of the operand of delete shall be a pointer to a non-array object or a pointer to a subobject (1.8 [intro.object]) representing a base class of such an object (clause 10 [class.derived]). If not, the behavior is undefined. In the second alternative (delete array), the value of the operand of delete shall be the pointer value which resulted from a previous array new-expression. If not, the behavior is undefined.
There are no restrictions on the type of a null pointer, only on a pointer that is not null. That seems wrong.
Proposed resolution (June, 2008):
Change 5.3.5 [expr.delete] paragraph 1 as follows:
...The operand shall have a pointer to object type, or a class type having a single non-explicit conversion function (12.3.2 [class.conv.fct]) to a pointer to object type...
Proposed resolution (September, 2008):
Change 5.3.5 [expr.delete] paragraph 1 as follows:
...The operand shall have a pointer to object type, or a class type having a single non-explicit conversion function (12.3.2) to a pointer to object type. [Footnote: This implies that an object cannot be deleted using a pointer of type void* because void is not an object type. —end footnote] ...
Delete the footnote at the end of 5.3.5 [expr.delete] paragraph 3:
...if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined. [Footnote: This implies that an object cannot be deleted using a pointer of type void* because there are no objects of type void. —end footnote]
[Voted into WP at July, 2009 meeting.]
One effect of the initializer-list proposal is that now we allow
auto x = { 1, 2, 3 }; // decltype(x) is std::initializer_list<int>
but not
auto ar[3] = { 1, 2, 3 }; // ill-formed
This seems unfortunate, as the code for the first could also support the second. Incidentally, I also failed to update the text in 7.1.6.4 [dcl.spec.auto] paragraph 3 which forbids the use of auto with braced-init-lists, so technically the first form above is currently ill-formed but has defined semantics.
Bjarne Stroustrup:
Is this the thin edge of a wedge? How about
vector<auto> v = { 1, 2, 3 };
and
template<class T> void f(vector<T>& v);
f({1, 2, 3 });
(See also issue 625.)
Proposed resolution (March, 2009):
Change 7.1.6.4 [dcl.spec.auto] paragraph 3 as follows:
...The decl-specifier-seq shall be followed by one or more init-declarators, each of which shall have a non-empty initializer. of either of the following forms:= assignment-expression
( assignment-expression )
[Drafting note: This change does not address the original issue of the inability to use auto with an array initializer, only the secondary issue of permitted the braced-init-list. The CWG explicitly decided not to support the array case.]
[Voted into WP at July, 2009 meeting.]
In listing the acceptable contexts in which the auto specifier may appear, 7.1.6.4 [dcl.spec.auto]) paragraph 4 mentions “the type-specifier-seq in a new-type-id” but not the type-id in the parenthesized form; that is, new auto (42) is well-formed but new (auto) (42) is not. This seems an unnecessary restriction, as well as contradicting 5.3.4 [expr.new] paragraph 2:
If the auto type-specifier appears in the type-specifier-seq of a new-type-id or type-id of a new-expression...
Proposed resolution (March, 2009):
Change 7.1.6.4 [dcl.spec.auto] paragraph 4 as follows:
The auto type-specifier can also be used in declaring an object in the condition of a selection statement (6.4 [stmt.select]) or an iteration statement (6.5 [stmt.iter]), in the type-specifier-seq in a the new-type-id or type-id of a new-expression (5.3.4 [expr.new]), in a for-range-declaration...
[Voted into WP at July, 2009 meeting as N2933.]
Parameter packs should be expanded inside attributes. For example, it would be useful to specify the alignment of each element in a pack expansion using a parallel pack expansion.
[Voted into WP at July, 2009 meeting.]
According to 7.6.4 [dcl.attr.final] paragraph 2, overriding a virtual function with the [[final]] attribute renders a program ill-formed, but no diagnostic is required. This is easily diagnosable and a diagnostic should be required in this case.
Notes from the March, 2009 meeting:
This specification was a deliberate decision on the part of the EWG; the general rule was that it should be possible to ignore attributes without changing the meaning of a program. However, the consensus of the CWG was that violation of the [[final]] attribute should require a diagnostic.
Proposed resolution (March, 2009):
Change 7.6.4 [dcl.attr.final] paragraph 2 as follows:
If a virtual member function f in some class B is marked final and in a class D derived from B a function D::f overrides B::f, the program is ill-formed; no diagnostic required. [Footnote: If an implementation does not emit a diagnostic it should execute the program as if final were not present. —end footnote]
[Voted into the WP at the July, 2009 meeting as part of N2927.]
It is currently unspecified whether a declaration like
f() -> struct S { };
should be parsed as a declaration of f whose return type is a class definition (which will be ill-formed according to 7.1.6 [dcl.type] paragraph 3) or as a definition of f whose return type is an elaborated-type-specifier.
Proposed resolution (June, 2009):
See document PL22.16/09-0117 = WG21 N2927.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
The grammar in 8.4 [dcl.fct.def] paragraph 2 incorrectly excludes late-specified return types and should be corrected.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into WP at July, 2009 meeting.]
The recent changes in the handling of initialization have not touched the requirement that the in-class initializer for a const static data member must be of the form = assignment-expression and not a braced-init-list. It would be more consistent and general to allow the braced form as well.
Proposed resolution (March, 2009):
Change 5.19 [expr.const] paragraph 3 as follows:
...as enumerator initializers (7.2 [dcl.enum]), as static member initializers (9.4.2 [class.static.data]), and as integral or enumeration non-type template arguments (14.5 [temp.type]).
Change 9.4.2 [class.static.data] paragraph 3 as follows:
If a static data member is of const effective literal type, its declaration in the class definition can specify a brace-or-equal-initializer with an in which every initializer-clause that is an assignment-expression is a integral constant expression. A static data member of effective literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer with an in which every initializer-clause that is an assignment-expression is a integral constant expression. [Note: In both these cases, the member may appear in integral constant expressions. —end note] The member shall still be defined in a namespace scope if it is used in the program and the namespace scope definition shall not contain an initializer.
[Drafting note: this change also corrects an editorial error resulting from overlapping changes that inadvertently retained the original restriction that only members of integral type could be initialized inside the class definition.]
[Voted into WP at July, 2009 meeting.]
Unions are no longer forbidden to have static data members; however, much of the wording of 9.5 [class.union] (and possibly other places in the Standard) is still written with that assumption and refers only to “data members” when clearly non-static data members are in view. From paragraph 1, for example:
In a union, at most one of the data members can be active at any time... The size of a union is sufficient to contain the largest of its data members...
Proposed resolution (March, 2009):
Change the footnote in 3.9.3 [basic.type.qualifier] paragraph 1 as follows:
The same representation and alignment requirements are meant to imply interchangeability as arguments to functions, return values from functions, and non-static data members of unions.
Change 3.10 [basic.lval] paragraph 15 bullet 6 as follows:
Change 5.9 [expr.rel] paragraph 2 bullet 5 as follows:
Change 7.6.2 [dcl.align] paragraph 8 as follows:
[Note: the alignment of a union type can be strengthened by applying the alignment attribute to any non-static data member of the union. —end note]
Change 8.5.1 [dcl.init.aggr] paragraph 15 as follows:
When a union is initialized with a brace-enclosed initializer, the braces shall only contain an initializer-clause for the first non-static data member of the union...
Change 9.5 [class.union] paragraph 1 as follows:
In a union, at most one of the non-static data members can be active at any time, that is, the value of at most one of the non-static data members can be stored in a union at any time. [Note: one special guarantee is made in order to simplify the use of unions: If a standard-layout union contains several standard-layout structs that share a common initial sequence (9.2 [class.mem]), and if an object of this standard-layout union type contains one of the standard-layout structs, it is permitted to inspect the common initial sequence of any of standard-layout struct members; see 9.2 [class.mem]. —end note] The size of a union is sufficient to contain the largest of its non-static data members. Each non-static data member is allocated as if it were the sole member of a struct. A union can have...
[Voted into WP at July, 2009 meeting as N2928.]
There should be a way to detect errors in overriding a virtual function.
Proposed resolution (July, 2009):
This issue is resolved by paper PL22.16/09-0118 = WG21 N2928.
[Voted into WP at July, 2009 meeting.]
How does copy assignment for unions work? For example,
union U {
int a;
float b;
};
void f() {
union U u = { 5 };
union U v;
v = u; // what happens here?
}
9.5 [class.union] is silent on the issue, therefore it seems that 12.8 [class.copy] applies. There is no special case for unions, thus paragraph 13 (memberwise assignment of subobjects) seems to apply. That would seem to imply these actions in the compiler-generated copy assignment operator:
v.a = u.a; v.b = u.b;
And this is just wrong. For example, the lifetime of v.a ends once the second assignment reuses the memory of v.a.
We should probably prescribe “memcpy” copying for unions (both for the copy constructor and the assignment operator) unless the user provided his own special member function.
Proposed resolution (March, 2008):
Change 12.8 [class.copy] paragraph 8 as follows:
The implicitly-defined or explicitly-defaulted copy constructor for a non-union class X performs a memberwise copy of its subobjects...
Add a new paragraph after 12.8 [class.copy] paragraph 8:
The implicitly-defined or explicitly-defaulted copy constructor for a union X where all members have a trivial copy constructor copies the object representation (3.9 [basic.types]) of X. [Note: The behavior is undefined if X is not a trivial type. —end note]
Change 12.8 [class.copy] paragraph 13 as follows:
The implicitly-defined or explicitly-defaulted copy assignment operator for a non-union class X performs memberwise assignment of its subobjects...
Add a new paragraph after 12.8 [class.copy] paragraph 13:
The implicitly-defined or explicitly-defaulted copy assignment operator for a union X where all members have a trivial copy assignment operator copies the object representation (3.9 [basic.types]) of X. [Note: The behavior is undefined if X is not a trivial type. —end note]
Notes from the September, 2008 meeting:
The proposed wording needs to be updated to reflect the changes adopted in papers N2757 and N2762, resolving issue 683, which require “no non-trivial” special member functions instead of “a trivial” function. Also, the notes regarding undefined behavior are incorrect, because the member functions involved are defined as deleted when there are non-trivial members.
Proposed resolution (October, 2008):
Change 12.8 [class.copy] paragraph 8 as follows:
The implicitly-defined or explicitly-defaulted copy constructor for a non-union class X performs a memberwise copy of its subobjects...
Add a new paragraph following 12.8 [class.copy] paragraph 8:
The implicitly-defined or explicitly-defaulted copy constructor for a union X copies the object representation (3.9 [basic.types]) of X.
Change 12.8 [class.copy] paragraph 13 as follows:
Add a new paragraph following 12.8 [class.copy] paragraph 13:
The implicitly-defined or explicitly-defaulted copy assignment operator for a union X copies the object representation (3.9 [basic.types]) of X.
[Voted into the WP at the July, 2009 meeting as part of N2927.]
Although the term “move constructor” appears multiple times in the library clauses and is referenced in the newly-added text for the lambda feature, it is not defined anywhere.
Notes from the June, 2008 meeting:
The only reference to “move constructor” in the core language clauses of the Standard is in 5.1.2 [expr.prim.lambda] paragraph 10; there are no semantic implications of the term. This issue will be addressed by using a function signature instead of the term, thus allowing the library section to provide a definition that is appropriate for its needs.
Proposed resolution (July, 2009)
See document PL22.16/09-0117 = WG21 N2927.
[Voted into WP at July, 2009 meeting.]
The overload resolution rules for ranking a template against a non-template function differ for conversion functions in a surprising way. 13.3.3 [over.match.best] lists four checks, the last three concern this report. For the non-conversion operator case, checks 2 and 3 are applicable, whereas for the conversion operator case checks 3 and 4 are applicable. Checks 2 and 4 concern the ranking of argument and return value conversion sequences respectively. Check 3 concerns only the templatedness of the functions being ranked, and will prefer a non-template to a template. Notice that this check happens after argument conversion sequence ranking, but before return value conversion sequence ranking. This has the effect of always selecting a non-template conversion operator, as the following example shows:
struct C
{
inline operator int () { return 1; }
template <class T> inline operator T () { return 0; }
};
inline long f (long x) { return x; }
int
main (int argc, char *argv[])
{
return f (C ());
}
The non-templated C::operator int function will be selected, rather than the apparently better C::operator long<long> instantiation. This is a surprise, and resulted in a bug report where the user expected the template to be selected. In addition some C++ compilers have implemented the overload ranking as if checks 3 and 4 were transposed.
Is this ordering accidental, or is there a rationale?
Notes from the April, 2005 meeting:
The CWG agreed that the template/non-template distinction should be the final tie-breaker.
Proposed resolution (March, 2007):
In the second bulleted list of 13.3.3 [over.match.best] paragraph 1, move the second and third bullets to the end of the list, to read as follows:
for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that,
the context is an initialization by user-defined conversion (see 8.5 [dcl.init], 13.3.1.5 [over.match.conv], and 13.3.1.6 [over.match.ref]) and the standard conversion sequence from the return type of F1 to the destination type (i.e., the type of the entity being initialized) is a better conversion sequence than the standard conversion sequence from the return type of F2 to the destination type, [Example: ... —end example] or, if not that,
- F1 is a non-template function and F2 is a function template specialization, or, if not that,
F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in 14.6.6.2 [temp.func.order].
[Voted into WP at July, 2009 meeting.]
We need another bullet in 13.3.3.2 [over.ics.rank], along the lines of:
List-initialization sequence L1 is a better conversion sequence than list-initialization sequence L2 if L1 converts to std::initializer_list<X> for some X and L2 does not.
This is necessary to make the following example work:
#include <initializer_list>
struct string {
string (const char *) {}
template <class Iter> string (Iter, Iter);
};
template <class T, class U>
struct pair {
pair (T t, U u) {}
};
template<class T, class U>
struct map {
void insert (pair<T,U>);
void insert (std::initializer_list<pair<T,U> >) {}
};
int main() {
map<string,string> m;
m.insert({ {"this","that"}, {"me","you"} });
}
Proposed resolution (March, 2009):
Add a new top-level bullet at the end of the current list in 13.3.3.2 [over.ics.rank] paragraph 3:
[Voted into WP at July, 2009 meeting.]
13.6 [over.built] paragraph 7 posits the existence of built-in candidate operator* functions “for every function type T.” However, only non-static member function types can contain a cv-qualifier or ref-qualifier (8.3.5 [dcl.fct] paragraph 7), and a reference to such a type cannot be initialized (5.2.5 [expr.ref] paragraph 4, bullet 3, sub-bullet 2). (See also 14.10.4 [concept.support] paragraph 10, which disallows references to function types with cv-qualifiers but is silent on ref-qualifiers.)
Proposed resolution (March, 2009):
Change 13.6 [over.built] paragraph 7 as follows:
For every function type T that does not have cv-qualifiers or a ref-qualifier, there exist candidate operator functions of the formT & operator*(T*);
Change 14.10.4 [concept.support] paragraph 7 as follows:
Requires: for every type T that is an object type, a function type that does not have cv-qualifiers or a ref-qualifier, or cv void, a concept map PointeeType<T> is implicitly defined in namespace std.
Change 14.10.4 [concept.support] paragraph 11 as follows:
Requires: for every type T that is an object type, a function type that does not have cv-qualifiers or a ref-qualifier, or a reference type, a concept map ReferentType<T> is implicitly defined in namespace std.
[Voted into the WP at the March, 2009 meeting.]
The resolution of issue 33 added the following wording in 3.4.2 [basic.lookup.argdep]:
In addition, if the argument is the name or address of a set of overloaded functions and/or function templates, its associated classes and namespaces are the union of those associated with each of the members of the set: the namespace in which the function or function template is defined and the classes and namespaces associated with its (non-dependent) parameter types and return type.
This wording is self-contradictory: although it claims that the treatment of overload sets is intended to be “the union of those associated with each of the members of the set,” it says that the namespace of which each function or function template is a member is to be considered an associated namespace. That is different from the case of a non-overloaded function argument; in that case, because only the type of the argument is considered, the namespace of which the function is a member is not an associated namespace. This should be rectified so that overloaded and unoverloaded functions really are treated the same.
Proposed resolution (June, 2008):
Change 3.4.2 [basic.lookup.argdep] paragraph 2 as follows:
...In addition, if the argument is the name or address of a set of overloaded functions and/or function templates, its associated classes and namespaces are the union of those associated with each of the members of the set: the namespace in which the function or function template is defined and, i.e., the classes and namespaces associated with its (non-dependent) parameter types and return type.
[Voted into the WP at the March, 2009 meeting.]
According to 3.5 [basic.link] paragraph 3,
A name having namespace scope (3.3.6 [basic.scope.namespace]) has internal linkage if it is the name of
an object, reference, function or function template that is explicitly declared static or,
an object or reference that is explicitly declared const and neither explicitly declared extern nor previously declared to have external linkage;
It is not possible to declare a reference to be const.
Proposed resolution (March, 2008):
Change 3.5 [basic.link] paragraph 3 as indicated (note addition of punctuation in the first bullet):
A name having namespace scope (3.3.6 [basic.scope.namespace]) has internal linkage if it is the name of
an object, reference, function, or function template that is explicitly declared static; or,
an object or reference that is explicitly declared const and neither explicitly declared extern nor previously declared to have external linkage; or
a data member of an anonymous union.
Lisa Lippincott mentioned this case to me:
A[0] = 0; A[A[0]] = 1;
This seems to use the old value of A[0] other than to calculate the new value, which is said to be undefined, but it also seems reasonable, since the old value is used in order to select the object to modify, so there's no ordering ambiguity.
Steve Adamczyk: the ordering rule referred to is in 5 [expr] paragraph 4.
Notes from the March 2004 meeting:
Clark Nelson mentions that the C committee may have done something on this.
Note (July, 2009):
This issue was resolved by the adoption of the “sequenced before” wording.
[Voted into the WP at the March, 2009 meeting.]
At least one implementation accepts the following example as well-formed (returning a null pointer at runtime), although others reject it at compile time:
struct A { virtual ~A(); };
struct B: private A { } b;
A* pa = dynamic_cast<A*>(&b);
Presumably the intent of 5.2.7 [expr.dynamic.cast] paragraph 5 is that all up-casts (converting from derived to base) are to be handled at compile time, regardless of whether the class involved is polymorphic or not:
If T is “pointer to cv1 B” and v has type “pointer to cv2 D” such that B is a base class of D, the result is a pointer to the unique B subobject of the D object pointed to by v. Similarly, if T is “reference to cv1 B” and v has type cv2 D such that B is a base class of D, the result is the unique B subobject of the D object referred to by v... In both the pointer and reference cases, cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2, and B shall be an accessible unambiguous base class of D.
One explanation for the implementation that accepts the example at compile time is that the final sentence is interpreted as part of the condition for the applicability of this paragraph, so that this case falls through into the description of runtime checking that follows. This (mis-)interpretation is buttressed by the example in paragraph 9, which reads in significant part:
class A { virtual void f(); };
class B { virtual void g(); };
class D : public virtual A, private B {};
void g() {
D d;
B* bp;
bp = dynamic_cast<B*>(&d); // fails
}
The “fails” comment is identical to the commentary on the lines in the example where the run-time check fails. If the interpretation that paragraph 5 is supposed to apply to all up-casts, presumably this comment should change to “ill-formed,” or the line should be removed from the example altogether.
It should be noted that this interpretation (that the example is ill-formed and the runtime check applies only to down-casts and cross-casts) rejects some programs that could plausibly be accepted and actually work at runtime. For example,
struct B { virtual ~B(); };
struct D: private virtual B { };
void test(D* pd) {
B* pb = dynamic_cast<B*>(pd); // #1
}
struct D2: virtual B, virtual D {};
void demo() {
D2 d2;
B* pb = dynamic_cast<B*>(&d2); // #2
test(&d2); // #3
}
According to the interpretation that paragraph 5 applies, line #1 is ill-formed. However, converting from D2 to B (line #2) is well-formed; if the alternate interpretation were applied, the conversion in line #1 could succeed when applied to d2 (line #3).
One final note: the wording in 5.2.7 [expr.dynamic.cast] paragraph 8 is incorrect:
The run-time check logically executes as follows:
If, in the most derived object pointed (referred) to by v, v points (refers) to a public base class subobject of a T object, and if only one object of type T is derived from the subobject pointed (referred) to by v the result is a pointer (an lvalue referring) to that T object.
Otherwise, if v points (refers) to a public base class subobject of the most derived object, and the type of the most derived object has a base class, of type T, that is unambiguous and public, the result is a pointer (an lvalue referring) to the T subobject of the most derived object.
Otherwise, the run-time check fails.
All uses of T in this paragraph treat it as if it were a class type; in fact, T is the type to which the expression is being cast and thus is either a pointer type or a reference type, not a class type.
Proposed resolution (June, 2008):
Change 5.2.7 [expr.dynamic.cast] paragraph 5 as follows:
...In both the pointer and reference cases, cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2, and B shall be an accessible unambiguous base class of D the program is ill-formed if cv2 is greater cv-qualification than cv1 or if B is an inaccessible or ambiguous base class of D.
Change the comment in the example in 5.2.7 [expr.dynamic.cast] paragraph 9 as follows:
bp = dynamic_cast<B*>(&d); // fails ill-formed (not a run-time check)
Change 5.2.7 [expr.dynamic.cast] paragraph 8 as follows:
The If C is the class type to which T points or refers, the run-time check logically executes as follows:
If, in the most derived object pointed (referred) to by v, v points (refers) to a public base class subobject of a T C object, and if only one object of type T C is derived from the subobject pointed (referred) to by v the result is a pointer (an lvalue referring) to that T C object.
Otherwise, if v points (refers) to a public base class subobject of the most derived object, and the type of the most derived object has a base class, of type T C, that is unambiguous and public, the result is a pointer (an lvalue referring) to the T C subobject of the most derived object.
Otherwise, the run-time check fails.
[Voted into the WP at the March, 2009 meeting.]
For years I've noticed that people will write code like this to get the address of an object's bytes:
void foo(long* p) {
char* q = reinterpret_cast<char*>(p); // #1
// do something with the bytes of *p by using q
}
When in fact the only portable way to do it according to the standard is:
void foo(long* p) {
char* q = static_cast<char*>(static_cast<void*>(p)); // #2
// do something with the bytes of *p by using q
}
I thought reinterpret_cast existed so that vendors could provide some weird platform-specific things. However, recently Peter Dimov pointed out to me that if we substitute a class type for long above, reinterpret_cast is required to work as expected by 9.2 [class.mem] paragraph 18:
A pointer to a standard-layout struct object, suitably converted using a reinterpret_cast, points to its initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa.
So there isn't a whole lot of flexibility to do something different and useful on non-class types. Are there any implementations for which #1 actually fails? If not, I think it would be a good idea to nail reinterpret_cast down so that the standard says it does what people (correctly) think it does in practice.
Proposed resolution (March, 2008):
Change 5.2.10 [expr.reinterpret.cast] paragraph 7 as indicated:
A pointer to an object can be explicitly converted to a pointer to an object of different type. When an rvalue v of type “pointer to T1” is converted to the type “pointer to cv T2,” the result is static_cast<cv T2*>(static_cast<cv void*>(v)) if both T1 and T2 are standard-layout types (3.9 [basic.types]) and the alignment requirements of T2 are no stricter than those of T1. Except that cConverting an rvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value, t. The result of any other such a pointer conversion is unspecified.
[Voted into the WP at the March, 2009 meeting.]
There appear to be two different specifications for when aliasing is permitted. One is in 3.10 [basic.lval] paragraph 15:
If a program attempts to access the stored value of an object through an lvalue of other than one of the following types the behavior is undefined
the dynamic type of the object,
a cv-qualified version of the dynamic type of the object,
a type similar (as defined in 4.4 [conv.qual]) to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union),
a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
a char or unsigned char type.
There is also a much more restrictive specification in 5.17 [expr.ass] paragraph 8:
If the value being stored in an object is accessed from another object that overlaps in any way the storage of the first object, then the overlap shall be exact and the two objects shall have the same type, otherwise the behavior is undefined.
This affects, for example, the definedness of operations on union members: when may a value be stored into one union member and accessed via another.
It should be noted that this conflict existed in C90 and is unchanged in C99 (see, for example, section 6.5 paragraph 7 and section 6.5.16.1 paragraph 3 of ISO/IEC 9899:1999, which directly parallel the sections cited above).
Notes from the October, 2006 meeting:
This issue is based on a misunderstanding of the intent of the wording in 5.17 [expr.ass] paragraph 8. Instead of being a general statement about aliasing, it's describing the situation in which the source of the value being assigned is storage that overlaps the storage of the target object. The proposed resolution should make that clearer rather than changing the specification.
Proposed resolution (June, 2008):
Add the following note at the end of 5.17 [expr.ass] paragraph 8:
If the value being stored in an object is accessed from another object that overlaps in any way the storage of the first object, then the overlap shall be exact and the two objects shall have the same type, otherwise the behavior is undefined. [Note: This restriction applies to the relationship between the left and right sides of the assignment operation; it is not a statement about how the target of the assignment may be aliased in general. See 3.10 [basic.lval]. —end note]
[Voted into the WP at the March, 2009 meeting.]
It was the intention of the constexpr proposal that implementations be required to evaluate floating-point expressions at compile time. This intention is not reflected in the actual wording of 5.19 [expr.const] paragraph 2, bullet 5:
This restriction has the effect of forbidding the use of floating-point expressions in integral constant expressions.
Proposed resolution (June, 2008):
Delete bullet 6 of 5.19 [expr.const] paragraph 2:
Notes from the June, 2008 meeting:
The CWG agreed with the intent of this issue, that floating-point calculations should be permitted in constant expressions, but acknowledged that this opens the possibility of differing results between compile time and run time. Such issues should be addressed non-normatively, e.g., via a “recommended practice” note like that of C99's 6.4.4.2 or in a technical report.
Proposed resolution (August, 2008):
Delete bullet 6 of 5.19 [expr.const] paragraph 2:
Add a new paragraph after 5.19 [expr.const] paragraph 3:
[Note: Although in some contexts constant expressions must be evaluated during program translation, others may be evaluated during program execution. Since this International Standard imposes no restrictions on the accuracy of floating-point operations, it is unspecified whether the evaluation of a floating-point expression during translation yields the same result as the evaluation of the same expression (or the same operations on the same values) during program execution. [Footnote: Nonetheless, implementations are encouraged to provide consistent results, irrespective of whether the evaluation was actually performed during translation or during program execution. —end footnote] [Example:
bool f() { char array[1 + int(1 + 0.2 - 0.1 - 0.1)]; // Must be evaluated during translation int size = 1 + int(1 + 0.2 - 0.1 - 0.1); // May be evaluated at runtime return sizeof(array) == size; }It is unspecified whether the value of f() will be true or false. —end example] —end note]
[Voted into the WP at the March, 2009 meeting.]
The grammar in 7 [dcl.dcl] paragraph 1 says that a declaration-seq is either declaration or declaration-seq declaration. Some declarations end with semicolons and others (e.g. function definitions and namespace declarations) don't. This means that users who put a semicolon after every declaration are technically writing ill-formed code. The trouble is that in this respect the standard is out of sync with reality. It's convenient to allow semicolons after every declaration, and there's no implementation difficulty in doing so. All existing compilers accept this, except in extra-pedantic mode. When all implementations disagree with the standard, it's time for the standard to change.
Suggested resolution:
In the grammar in 7 [dcl.dcl] paragraph 11, change the second line in the definition of declaration-seq to
Proposed resolution (October, 2006):
Add the indicated lines to the grammar definitions in 7 [dcl.dcl] paragraph 1:
declaration:
...
namespace-definition
empty-declaration
...
static_assert-declaration:
static_assert ( constant-expression , string-literal ) ;
empty-declaration:
;
Add the following as a new paragraph after 7 [dcl.dcl] paragraph 4:
An empty-declaration has no effect.
[Voted into the WP at the March, 2009 meeting.]
7.1.3 [dcl.typedef] paragraph 1 says,
The typedef specifier shall not be used in a function-definition (8.4 [dcl.fct.def])...
Does this mean that the following is ill-formed?
void f() {
typedef int INT;
}
Proposed resolution (March, 2008):
Change 7.1.3 [dcl.typedef] paragraph 1 as follows:
...The typedef specifier shall not be used in a function-definition (8.4 [dcl.fct.def]), and it shall not be combined in a decl-specifier-seq with any other kind of specifier except a type-specifier, and it shall not be used in the declaration of a function parameter nor in the decl-specifier-seq of a function-definition (8.4 [dcl.fct.def])...
Proposed resolution (September, 2008):
Change 7.1.3 [dcl.typedef] paragraph 1 as follows:
...The typedef specifier shall not be used in a function-definition (8.4 [dcl.fct.def]), and it shall not be combined in a decl-specifier-seq with any other kind of specifier except a type-specifier, and it shall be used neither in the decl-specifier-seq of a parameter-declaration (8.3.5 [dcl.fct]) nor in the decl-specifier-seq of a function-definition (8.4 [dcl.fct.def]).
[Voted into the WP at the March, 2009 meeting.]
According to 7.2 [dcl.enum] paragraph 6, the underlying type of an enumeration with an empty enumeration-list is determined as if the enumeration-list contained a single enumerator with value 0. Paragraph 7, which specifies the values of an enumeration and the minimum size of bit-field needed represent those values needs a similar provision for empty enumeration-lists.
Proposed resolution (March, 2008):
Add the indicated sentence to the end of 7.2 [dcl.enum] paragraph 5:
...It is possible to define an enumeration that has values not defined by any of its enumerators. If the enumerator-list is empty, the values of the enumeration are as if the enumeration had a single enumerator with value 0.
[Voted into the WP at the March, 2009 meeting.]
The wording of 7.5 [dcl.link] paragraph 5 is suspect:
If two declarations of the same function or object specify different linkage-specifications (that is, the linkage-specifications of these declarations specify different string-literals), the program is ill-formed if the declarations appear in the same translation unit, and the one definition rule (3.2) applies if the declarations appear in different translation units.
But what if only one of the declarations has a linkage-specification, while the other is left with the default C++ linkage? Shouldn't this restriction be phrased in terms of the functions’ or objects’ language linkage rather than linkage-specifications?
(Additional note [wmm]: Is the ODR the proper vehicle for enforcing this requirement? This is dealing with declarations, not necessarily definitions. Shouldn't this say “ill-formed, no diagnostic required” instead of some vague reference to the ODR?)
Proposed resolution (June, 2008):
Change 7.5 [dcl.link] paragraph 5 as follows:
If two declarations of the same function or object declare functions with the same name and parameter-type-list (8.3.5 [dcl.fct]) to be members of the same namespace or declare objects with the same name to be members of the same namespace specify different linkage-specifications (that is, the linkage-specifications of these declarations specify different string-literals) and the declarations give the names different language linkages, the program is ill-formed if the declarations appear in the same translation unit, and the one definition rule (3.2 [basic.def.odr]) applies; no diagnostic is required if the declarations appear in different translation units.
[Voted into the WP at the March, 2009 meeting.]
The current wording defining a “common initial sequence” in 9.2 [class.mem] paragraph 17 does not address the case in which one member is a bit-field and the corresponding member is not:
Two standard-layout structs share a common initial sequence if corresponding members have layout-compatible types (and, for bit-fields, the same widths) for a sequence of one or more initial members.
Presumably the intent was something like, “(and, if one of the pair is a bit-field, the other is also a bit-field of the same width).”
Proposed Resolution (September, 2008):
Change 9.2 [class.mem] paragraph 18 as follows:
... Two standard-layout structs share a common initial sequence if corresponding members have layout-compatible types (and, for bit-fields, the same widths) and either neither member is a bit-field or both are bit-fields with the same widths for a sequence of one or more initial members.
[Voted into the WP at the March, 2009 meeting.]
In describing the order of destruction of temporaries, 12.2 [class.temporary] paragraphs 4-5 say,
There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression...
The second context is when a reference is bound to a temporary... A temporary bound to the returned value in a function return statement (6.6.3 [stmt.return]) persists until the function exits.
The following example illustrates the issues here:
struct S {
~S();
};
S& f() {
S s; // #1
return
(S(), // #2
S()); // #3
}
If the return type of f() were simply S instead of S&, the two temporaries would be destroyed at the end of the full-expression in the return statement in reverse order of their construction, followed by the destruction of the variable s at block-exit, i.e., the order of destruction of the S objects would be #3, #2, #1.
Because the temporary #3 is bound to the returned value, however, its lifetime is extended beyond the end of the full-expression, so that S object #2 is destroyed before #3.
There are two problems here. First, it is not clear what “until the function exits” means. Does it mean that the temporary is destroyed as part of the normal block-exit destructions, as described in 6.6 [stmt.jump] paragraph 2:
On exit from a scope (however accomplished), destructors (12.4 [class.dtor]) are called for all constructed objects with automatic storage duration (3.7.3 [basic.stc.auto]) (named objects or temporaries) that are declared in that scope, in the reverse order of their declaration.
Or is the point of destruction for #3 after the destruction of the “constructed objects... that are declared [emphasis mine] in that scope” (because temporary #3 was not “declared”)? I.e., should #3 be destroyed before or after #1?
The other problem is that, according to the recollection of one of the participants responsible for this wording, the intent was not to extend the lifetime of #3 but simply to emphasize that its lifetime ended before the function returned, i.e., that the result of f() could not be used without causing undefined behavior. This is also consistent with the treatment of this example by many implementations; MSVC++, g++, and EDG all destroy #3 before #2.
Suggested resolution:
Change 12.2 [class.temporary] paragraph 5 as indicated:
A The lifetime of a temporary bound to the returned value in a function return statement (6.6.3 [stmt.return]) persists until the function exits is not extended; it is destroyed at the end of the full-expression in the return statement.
Proposed resolution (June, 2008):
Change 12.2 [class.temporary] paragraph 5 as follows (converting the running text into a bulleted list and making the indicated edits to the wording):
... The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except: as specified below.
A temporary bound to a reference member in a constructor's ctor-initializer (12.6.2 [class.base.init]) persists until the constructor exits.
A temporary bound to a reference parameter in a function call (5.2.2 [expr.call]) persists until the completion of the full expression containing the call.
A The lifetime of a temporary bound to the returned value in a function return statement (6.6.3 [stmt.return]) persists until the function exits is not extended; the temporary is destroyed at the end of the full-expression in the return statement.
The destruction of a temporary whose lifetime is not extended...
[Voted into the WP at the March, 2009 meeting.]
12.6 [class.init] paragraph 2 says,
When an array of class objects is initialized (either explicitly or implicitly), the constructor shall be called for each element of the array, following the subscript order;
That implies that, given
struct POD {
int x;
};
POD data[10] = {};
this should call the implicitly declared default ctor 10 times, leaving 10 uninitialized ints, rather than value initialize each member of data, resulting in 10 initialized ints (which is required by 8.5.1 [dcl.init.aggr] paragraph 7).
I suggest rephrasing along the lines:
When an array is initialized (either explicitly or implicitly), each element of the array shall be initialized in turn, following the subscript order;
This would allow for PODs and other classes with a dual nature under value/default initialization, and cover copy initialization for arrays too.
Proposed resolution (October, 2006):
Change 12.6 [class.init] paragraph 3 as follows:
When an array of class objects is initialized (either explicitly or implicitly) and the elements are initialized by constructor, the constructor shall be called for each element of the array, following the subscript order; see 8.3.4 [dcl.array].
[Voted into the WP at the March, 2009 meeting.]
12.3.2 [class.conv.fct] paragraph 1 says,
A conversion function is never used to convert a (possibly cv-qualified) object to the (possibly cv-qualified) same object type (or a reference to it), to a (possibly cv-qualified) base class of that type (or a reference to it), or to (possibly cv-qualified) void.
At what point is this enforced, and how is it enforced?
Consider this test case:
struct abc;
struct xyz {
xyz();
xyz(xyz &);
operator xyz& (); // #1
operator abc& (); // #2
};
struct abc : xyz {};
void foo(xyz &);
void bar() {
foo (xyz ());
}
If such conversion functions are part of the overload set, #1 is a better conversion than #2 to convert the temporary xyz object to a non-const reference required for foo's operand. If such conversion functions are not part of the overload set, then #2 would be selected, and AFAICT the program would be well formed.
If the conversion functions are not part of the overload set, then it would seem one cannot take their address. For instance, adding the following line to the above test case would find no suitable function:
xyz &(xyz::*ptr) () = &xyz::operator xyz &;
Notes from the October, 2007 meeting:
The intent of 12.3.2 [class.conv.fct] paragraph 1 is that overload resolution not be attempted at all for the listed cases; that is, if the target type is void, the object's type, or a base of the object's type, the conversion is done directly without considering any conversion functions. Consequently, the questions about whether the conversion function is part of the overload set or not are moot. The wording will be changed to make this clearer.
Proposed Resolution (October, 2007):
Change the footnote in 12.3.2 [class.conv.fct] paragraph 1 as follows:
A conversion function is never used to convert a (possibly cv-qualified) object to the (possibly cv-qualified) same object type (or a reference to it), to a (possibly cv-qualified) base class of that type (or a reference to it), or to (possibly cv-qualified) void. [Footnote: These conversions are considered as standard conversions for the purposes of overload resolution (13.3.3.1 [over.best.ics], 13.3.3.1.4 [over.ics.ref]) and therefore initialization (8.5 [dcl.init]) and explicit casts (5.2.9 [expr.static.cast]). A conversion to void does not invoke any conversion function (5.2.9 [expr.static.cast]). Even though never directly called to perform a conversion, such conversion functions can be declared and can potentially be reached through a call to a virtual conversion function in a base class —end footnote]
Additional note (March, 2008):
A slight change to the example above indicates that there is a need for a normative change as well as the clarification of the rationale in the October, 2007 proposed resolution. If the declaration of foo were changed to
void foo(const xyz&);
with the current wording, the call foo(xyz()) would be interpreted as foo(xyz().operator abc&()) instead of binding the parameter directly to the rvalue, which is clearly wrong.
Proposed resolution (March, 2008):
Change the footnote in 12.3.2 [class.conv.fct] paragraph 1 as described in the October, 2007 proposed resolution.
Change 8.5.3 [dcl.init.ref] paragraph 5 as follows:
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
If the initializer expression
is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2,” or
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an lvalue of type “cv3 T3,” where “cv1 T1” is reference-compatible with “cv3 T3” [Footnote: This requires a conversion function (12.3.2 [class.conv.fct]) returning a reference type. —end footnote] (this conversion is selected by enumerating the applicable conversion functions (13.3.1.6 [over.match.ref]) and choosing the best one through overload resolution (13.3 [over.match])),
then...
[Drafting note: this resolution makes the example in the issue description ill-formed.]
[Voted into the WP at the March, 2009 meeting.]
14.7.2 [temp.dep] paragraph 3 reads,
In the definition of a class template or a member of a class template, if a base class of the class template depends on a template-parameter, the base class scope is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member.
This wording applies only to definitions of class templates and members of class templates. That would make the following program ill-formed (but it probably should be well-formed):
struct B{ void f(int); };
template<class T> struct D: B { };
template<class T> void g() {
struct B{ void f(); };
struct A: D<T> {
B m;
};
A a;
a.m.f(); // Presumably, we want ::g()::B::f(), not ::B::f(int)
}
int main () {
g<int>();
return 0;
}
I suspect the wording should be something like
In the definition of a class template or a class defined (directly or indirectly) within the scope of a class template or function template, if a base class...
That should also include deeply nested classes in templates, local classes of non-template member functions of member classes of class templates, etc.
Proposed resolution (October, 2006):
Change 14.7.2 [temp.dep] paragraph 3 as follows:
In the definition of a class or class template or a member of a class template, if a base class of the class template depends on a template-parameter, the base class scope is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member.
[Voted into the WP at the March, 2009 meeting.]
According to 15.1 [except.throw] paragraph 3,
The type of the throw-expression shall not be an incomplete type, or a pointer to an incomplete type other than (possibly cv-qualified) void.
This disallows cases like the following, because str has an incomplete type (an array of unknown size):
extern const char str[];
void f() {
throw str;
}
The array-to-pointer conversion is applied to the operand of throw, so there's no problem creating the exception object, which is the reason for the restriction on incomplete types. I believe this case should be permitted.
Notes from the April, 2005 meeting:
The CWG agreed that the example should be permitted. Note that the reference to throw-expression in the cited text is incorrect; a throw-expression includes the throw keyword and is always of type void. This wording problem is addressed in the proposed resolution for issue 475.
Proposed resolution (October, 2006)
Change 15.1 [except.throw] paragraph 3 as indicated:
...The type of the throw-expression shall not If the type of the exception object would be an incomplete type, or a pointer to an incomplete type other than (possibly cv-qualified) void the program is ill-formed...
[Voted into the WP at the March, 2009 meeting.]
The destruction of local static objects occurs at the same time as that of non-local objects (3.6.3 [basic.start.term] paragraph 1) and the execution of functions registered with std::atexit (paragraph 3). According to 15.5.1 [except.terminate] paragraph 1, std::terminate is called if a destructor for a non-local object or a function registered with std::atexit exits via an exception, but the Standard is silent about the result of throwing an exception from a destructor for a local static object. Presumably this is an oversight and the same rules should apply to destruction of local static objects.
Proposed resolution (September, 2008):
Change 15.5.1 [except.terminate] paragraph 1, fourth bullet as indicated, and add an additional bullet to follow it:
when construction or destruction of a non-local object with static or thread storage duration exits using an exception (3.6.2 [basic.start.init]), or
when destruction of an object with static or thread storage duration exits using an exception (3.6.3 [basic.start.term]), or
[Voted into the WP at the June, 2008 meeting.]
The C99 and C++ Standards disagree about the validity of two Cyrillic characters for use in identifiers. C++ (_N2691_.E [extendid]) says that 040d is valid in an identifier but that 040e is not; C99 (Annex D) says exactly the opposite. In fact, both characters should be accepted in identifiers; see the Unicode chart.
Proposed resolution (February, 2008):
The reference in paragraph 2 should be changed to ISO/IEC TR 10176:2003 and the table should be changed to conform to the one in that document (beginning on page 34).
[Voted into WP at April, 2007 meeting.]
Section 1.3 [intro.defs], definition of "signature" omits the function name as part of the signature. Since the name participates in overload resolution, shouldn't it be included in the definition? I didn't find a definition of signature in the ARM, but I might have missed it.
Fergus Henderson: I think so. In particular, 17.6.3.3.2 [global.names] reserves certain "function signatures" for use by the implementation, which would be wrong unless the signature includes the name.
-2- Each global function signature declared with external linkage in a header is reserved to the implementation to designate that function signature with external linkage.
-5- Each function signature from the Standard C library declared with external linkage is reserved to the implementation for use as a function signature with both extern "C" and extern "C++" linkage, or as a name of namespace scope in the global namespace.
Other uses of the term "function signature" in the description of the standard library also seem to assume that it includes the name.
James Widman:
Names don't participate in overload resolution; name lookup is separate from overload resolution. However, the word “signature” is not used in clause 13 [over]. It is used in linkage and declaration matching (e.g., 14.6.6.1 [temp.over.link]). This suggests that the name and scope of the function should be part of its signature.
Proposed resolution (October, 2006):
Replace 1.3 [intro.defs] “signature” with the following:
the name and the parameter-type-list (8.3.5 [dcl.fct]) of a function, as well as the class or namespace of which it is a member. If a function or function template is a class member its signature additionally includes the cv-qualifiers (if any) on the function or function template itself. The signature of a function template additionally includes its return type and its template parameter list. The signature of a function template specialization includes the signature of the template of which it is a specialization and its template arguments (whether explicitly specified or deduced). [Note: Signatures are used as a basis for name-mangling and linking. —end note]
Delete paragraph 3 and replace the first sentence of 14.6.6.1 [temp.over.link] as follows:
The signature of a function template specialization consists of the signature of the function template and of the actual template arguments (whether explicitly specified or deduced).
The signature of a function template consists of its function signature, its return type and its template parameter list is defined in 1.3 [intro.defs]. The names of the template parameters are significant...
(See also issue 537.)
[Voted into WP at April, 2007 meeting.]
The standard defines “signature” in two places: 1.3 [intro.defs] and 14.6.6.1 [temp.over.link] paragraphs 3-4. The former seems to be meant as a formal definition (I think it's the only place covering the nontemplate case), yet it lacks some bits mentioned in the latter (specifically, the notion of a “signature of a function template,” which is part of every signature of the associated function template specializations).
Also, I think the 1.3 [intro.defs] words “the information about a function that participates in overload resolution” isn't quite right either. Perhaps, “the information about a function that distinguishes it in a set of overloaded functions?”
Eric Gufford:
In 1.3 [intro.defs] the definition states that “Function signatures do not include return type, because that does not participate in overload resolution,” while 14.6.6.1 [temp.over.link] paragraph 4 states “The signature of a function template consists of its function signature, its return type and its template parameter list.” This seems inconsistent and potentially confusing. It also seems to imply that two identical function templates with different return types are distinct signatures, which is in direct violation of 13.3 [over.match]. 14.6.6.1 [temp.over.link] paragraph 4 should be amended to include verbiage relating to overload resolution.
Either return types are included in function signatures, or they're not, across the board. IMHO, they should be included as they are an integral part of the function declaration/definition irrespective of overloads. Then verbiage should be added about overload resolution to distinguish between signatures and overload rules. This would help clarify things, as it is commonly understood that overload resolution is based on function signature.
In short, the term “function signature” should be made consistent, and removed from its (implicit, explicit or otherwise) linkage to overload resolution as it is commonly understood.
James Widman:
The problem is that (a) if you say the return type is part of the signature of a non-template function, then you have overloading but not overload resolution on return types (i.e., what we have now with function templates). I don't think anyone wants to make the language uglier in that way. And (b) if you say that the return type is not part of the signature of a function template, you will break code. Given those alternatives, it's probably best to maintain the status quo (which the implementors appear to have rendered faithfully).
Proposed resolution (September, 2006):
This issue is resolved by the resolution of issue 357.
[Voted into WP at April, 2006 meeting.]
The standard uses “most derived object” in some places (for example, 1.3 [intro.defs] “dynamic type,” 5.3.5 [expr.delete]) to refer to objects of both class and non-class type. However, 1.8 [intro.object] only formally defines it for objects of class type.
Possible fix: Change the wording in 1.8 [intro.object] paragraph 4 from
an object of a most derived class type is called a most derived object
to
an object of a most derived class type, or of non-class type, is called a most derived object
Proposed resolution (October, 2005):
Add the indicated words to 1.8 [intro.object] paragraph 4:
If a complete object, a data member (9.2 [class.mem]), or an array element is of class type, its type is considered the most derived class, to distinguish it from the class type of any base class subobject; an object of a most derived class type, or of a non-class type, is called a most derived object.
[Voted into the WP at the September, 2008 meeting.]
In 1.9 [intro.execution] paragraph 16, the following expression is still listed as an example of undefined behavior:
i = ++i + 1;
However, it appears that the new sequencing rules make this expression well-defined:
The assignment side-effect is required to be sequenced after the value computations of both its LHS and RHS (5.17 [expr.ass] paragraph 1).
The LHS (i) is an lvalue, so its value computation involves computing the address of i.
In order to value-compute the RHS (++i + 1), it is necessary to first value-compute the lvalue expression ++i and then do an lvalue-to-rvalue conversion on the result. This guarantees that the incrementation side-effect is sequenced before the computation of the addition operation, which in turn is sequenced before the assignment side effect. In other words, it yields a well-defined order and final value for this expression.
It should be noted that a similar expression
i = i++ + 1;
is still not well-defined, since the incrementation side-effect remains unsequenced with respect to the assignment side-effect.
It's unclear whether making the expression in the example well-defined was intentional or just a coincidental byproduct of the new sequencing rules. In either case either the example should be fixed, or the rules should be changed.
Clark Nelson: In my opinion, the poster's argument is perfectly correct. The rules adopted reflect the CWG's desired outcome for issue 222. At the Portland meeting, I presented (and still sympathize with) Tom Plum's case that these rules go a little too far in nailing down required behavior; this is a consequence of that.
One way or another, a change needs to be made, and I think we should seriously consider weakening the resolution of issue 222 to keep this example as having undefined behavior. This could be done fairly simply by having the sequencing requirements for an assignment expression depend on whether it appears in an lvalue context.
James Widman: How's this for a possible re-wording?
In all cases, the side effect of the assignment expression is sequenced after the value computations of the right and left operands. Furthermore, if the assignment expression appears in a context where an lvalue is required, the side effect of the assignment expression is sequenced before its value computation.
Notes from the February, 2008 meeting:
There was no real support in the CWG for weakening the resolution of issue 222 and returning the example to having undefined behavior. No one knew of an implementation that doesn't already do the (newly) right thing for such an example, so there was little motivation to go out of our way to increase the domain of undefined behavior. So the proposed resolution is to change the example to one that definitely does have undependable behavior in existing practice, and undefined behavior under the new rules.
Also, the new formulation of the sequencing rules approved in Oxford contained the wording that by and large resolved issue 222, so with the resolution of this issue, we can also close issue 222.
Proposed resolution (March, 2008):
Change the example in 1.9 [intro.execution] paragraph 16 as follows:
i = v[i++]; // the behavior is undefined
i = 7, i++, i++; // i becomes 9
i = ++i i++ + 1; // the behavior is undefined
i = i + 1; // the value of i is incremented
[Voted into the WP at the September, 2008 meeting.]
Is the behavior undefined in the following example?
void f() {
int n = 0;
n = --n;
}
1.9 [intro.execution] paragraph 16 says,
If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.
It's not clear to me whether the two side-effects in n=--n are “different.” As far as I can tell, it seems that both side-effects involve the assignment of -1 to n, which in a sense makes them non-“different.” But I don't know if that's the intent. Would it be better to say “another” instead of “a different?”
On a related note, can we include this example to illustrate?
void f( int, int );
void g( int a ) { f( a = -1, a = -1 ); } // Undefined?
Proposed resolution (March, 2008):
Change 1.9 [intro.execution] paragraph 16 as follows:
...If a side effect on a scalar object is unsequenced relative to either a different another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. [Example:
void f(int, int); void g(int i, int* v) { i = v[i++]; // the behavior is undefined i = 7, i++, i++; // i becomes 9 i = ++i + 1; // the behavior is undefined i = i + 1; // the value of i is incremented f(i = -1, i = -1); // the behavior is undefined }—end example] When calling...
[Voted into WP at March 2004 meeting.]
Should this program do what its author obviously expects? As far as I can tell, the standard says that the point of instantiation for Fib<n-1>::Value is the same as the point of instantiation as the enclosing specialization, i.e., Fib<n>::Value. What in the standard actually says that these things get initialized in the right order?
template<int n>
struct Fib { static int Value; };
template <>
int Fib<0>::Value = 0;
template <>
int Fib<1>::Value = 1;
template<int n>
int Fib<n>::Value = Fib<n-1>::Value + Fib<n-2>::Value;
int f ()
{
return Fib<40>::Value;
}
John Spicer: My opinion is that the standard does not specify the behavior of this program. I thought there was a core issue related to this, but I could not find it. The issue that I recall proposed tightening up the static initialization rules to make more cases well defined.
Your comment about point of instantiation is correct, but I don't think that really matters. What matters is the order of execution of the initialization code at execution time. Instantiations don't really live in "translation units" according to the standard. They live in "instantiation units", and the handling of instantiation units in initialization is unspecified (which should probably be another core issue). See 2.2 [lex.phases] paragraph 8.
Notes from October 2002 meeting:
We discussed this and agreed that we really do mean the the order is unspecified. John Spicer will propose wording on handling of instantiation units in initialization.
Proposed resolution (April 2003):
TC1 contains the following text in 3.6.2 [basic.start.init] paragraph 1:
Objects with static storage duration defined in namespace scope in the same translation unit and dynamically initialized shall be initialized in the order in which their definition appears in the translation unit.
This was revised by issue 270 to read:
Dynamic initialization of an object is either ordered or unordered. Explicit specializations and definitions of class template static data members have ordered initialization. Other class template static data member instances have unordered initialization. Other objects defined in namespace scope have ordered initialization. Objects defined within a single translation unit and with ordered initialization shall be initialized in the order of their definitions in the translation unit. The order of initialization is unspecified for objects with unordered initialization and for objects defined in different translation units.
This addresses this issue but while reviewing this issue some additional changes were suggested for the above wording:
Dynamic initialization of an object is either ordered or unordered. Definitions of explicitly specialized Explicit specializations and definitions of class template static data members have ordered initialization. Other class template static data members (i.e., implicitly or explicitly instantiated specializations) instances have unordered initialization. Other objects defined in namespace scope have ordered initialization. Objects defined within a single translation unit and with ordered initialization shall be initialized in the order of their definitions in the translation unit. The order of initialization is unspecified for objects with unordered initialization and for objects defined in different translation units.
[Moved to DR at October 2007 meeting.]
C99 and C++ differ in their approach to universal character names (UCNs).
Issue 248 already covers the differences in UCNs allowed for identifiers, but a more fundamental issue is that of UCNs that correspond to codes reserved by ISO 10676 for surrogate pair forms.
Specifically, C99 does not allow UCNs whose short names are in the range 0xD800 to 0xDFFF. I think C++ should have the same constraint. If someone really wants to place such a code in a character or string literal, they should use a hexadecimal escape sequence instead, for example:
wchar_t w1 = L'\xD900'; // Okay.
wchar_t w2 = L'\uD900'; // Error, not a valid character.
(Compare 6.4.3 paragraph 2 in ISO/IEC 9899/1999 with 2.3 [lex.charset] paragraph 2 in the C++ standard.)
Proposed resolution (October, 2007):
This issue is resolved by the adoption of paper J16/07-0030 = WG21 N2170.
[Voted into WP at the October, 2006 meeting.]
The current wording of 2.14.3 [lex.ccon] paragraph 3 states,
If the character following a backslash is not one of those specified, the behavior is undefined.
Paper J16/04-0167=WG21 N1727 suggests that such character escapes be ill-formed. In discussions at the Lillehammer meeting, however, the CWG felt that the newly-approved category of conditionally-supported behavior would be more appropriate.
Proposed resolution (April, 2006):
Change the next-to-last sentence of 2.14.3 [lex.ccon] paragraph 3 from:
If the character following a backslash is not one of those specified, the behavior is undefined.
to:
Escape sequences in which the character following the backslash is not listed in Table 6 are conditionally-supported, with implementation-defined semantics.
[Voted into the WP at the June, 2008 meeting.]
3 [basic] paragraph 8, while not incorrect, does not allow for linkage of operators and conversion functions. It says:
An identifier used in more than one translation unit can potentially refer to the same entity in these translation units depending on the linkage (3.5 [basic.link]) of the identifier specified in each translation unit.
Proposed Resolution (November, 2006):
This issue is resolved by the proposed resolution of issue 485.
[Voted into the WP at the June, 2008 meeting.]
Clause 3 [basic] paragraph 4 says:
A name is a use of an identifier (2.11 [lex.name]) that denotes an entity or label (6.6.4 [stmt.goto], 6.1 [stmt.label]).
Just three paragraphs later, it says
Two names are the same if
- they are identifiers composed of the same character sequence; or
- they are the names of overloaded operator functions formed with the same operator; or
- they are the names of user-defined conversion functions formed with the same type.
The last two bullets contradict the definition of name in paragraph 4 because they are not identifiers.
This definition affects other parts of the Standard, as well. For example, in 3.4.2 [basic.lookup.argdep] paragraph 1,
When an unqualified name is used as the postfix-expression in a function call (5.2.2 [expr.call]), other namespaces not considered during the usual unqualified lookup (3.4.1 [basic.lookup.unqual]) may be searched, and in those namespaces, namespace-scope friend function declarations (11.4 [class.friend]) not otherwise visible may be found.
With the current definition of name, argument-dependent lookup apparently does not apply to function-notation calls to overloaded operators.
Another related question is whether a template-id is a name or not and thus would trigger an argument-dependent lookup. Personally, I have always viewed a template-id as a name, just like operator+.
Proposed Resolution (November, 2006):
Change clause 3 [basic] paragraphs 3-8 as follows:
An entity is a value, object, subobject, base class subobject, array element, variable, reference, function, instance of a function, enumerator, type, class member, template, template specialization, namespace, or parameter pack.
A name is a use of an identifier identifier (2.11 [lex.name]), operator-function-id (13.5 [over.oper]), conversion-function-id (12.3.2 [class.conv.fct]), or template-id (14.3 [temp.names]) that denotes an entity or label (6.6.4 [stmt.goto], 6.1 [stmt.label]). A variable is introduced by the declaration of an object. The variable’s name denotes the object.
Every name that denotes an entity is introduced by a declaration. Every name that denotes a label is introduced either by a goto statement (6.6.4 [stmt.goto]) or a labeled-statement (6.1 [stmt.label]).
A variable is introduced by the declaration of an object. The variable's name denotes the object.
Some names denote types, classes, enumerations, or templates. In general, it is necessary to determine whether or not a name denotes one of these entities before parsing the program that contains it. The process that determines this is called name lookup (3.4 [basic.lookup]).
Two names are the same if
they are identifiers identifiers composed of the same character sequence; or
they are the names of overloaded operator functions operator-function-ids formed with the same operator; or
they are the names of user-defined conversion functions conversion-function-ids formed with the same type., or
they are template-ids that refer to the same class or function (14.5 [temp.type]).
An identifier A name used in more than one translation unit can potentially refer to the same entity in these translation units depending on the linkage (3.5 [basic.link]) of the identifier name specified in each translation unit.
Change 3.3.7 [basic.scope.class] paragraph 1 item 5 as follows:
The potential scope of a declaration that extends to or past the end of a class definition also extends to the regions defined by its member definitions, even if the members are defined lexically outside the class (this includes static data member definitions, nested class definitions, member function definitions (including the member function body and any portion of the declarator part of such definitions which follows the identifier declarator-id, including a parameter-declaration-clause and any default arguments (8.3.6 [dcl.fct.default]).
[Drafting note: This last change is not really mandated by the issue, but it's another case of “identifier” confusion.]
(This proposed resolution also resolves issue 309.)
[Moved to DR at October 2002 meeting.]
3.2 [basic.def.odr] paragraph 2 says that a deallocation function is "used" by a new-expression or delete-expression appearing in a potentially-evaluated expression. 3.2 [basic.def.odr] paragraph 3 requires only that "used" functions be defined.
This wording runs afoul of the typical implementation technique for polymorphic delete-expressions in which the deallocation function is invoked from the virtual destructor of the most-derived class. The problem is that the destructor must be defined, because it's virtual, and if it contains an implicit reference to the deallocation function, the deallocation function must also be defined, even if there are no relevant new-expressions or delete-expressions in the program.
For example:
struct B { virtual ~B() { } };
struct D: B {
void operator delete(void*);
~D() { }
};
Is it required that D::operator delete(void*) be defined, even if no B or D objects are ever created or deleted?
Suggested resolution: Add the words "or if it is found by the lookup at the point of definition of a virtual destructor (12.4 [class.dtor])" to the specification in 3.2 [basic.def.odr] paragraph 2.
Notes from 04/01 meeting:
The consensus was in favor of requiring that any declared non-placement operator delete member function be defined if the destructor for the class is defined (whether virtual or not), and similarly for a non-placement operator new if a constructor is defined.
Proposed resolution (10/01):
In 3.2 [basic.def.odr] paragraph 2, add the indicated text:
An allocation or deallocation function for a class is used by a new expression appearing in a potentially-evaluated expression as specified in 5.3.4 [expr.new] and 12.5 [class.free]. A deallocation function for a class is used by a delete expression appearing in a potentially-evaluated expression as specified in 5.3.5 [expr.delete] and 12.5 [class.free]. A non-placement allocation or deallocation function for a class is used by the definition of a constructor of that class. A non-placement deallocation function for a class is used by the definition of the destructor of that class, or by being selected by the lookup at the point of definition of a virtual destructor (12.4 [class.dtor]). [Footnote: An implementation is not required to call allocation and deallocation functions from constructors or destructors; however, this is a permissible implementation technique.]
[Moved to DR at October 2002 meeting.]
3.2 [basic.def.odr] paragraph 4 has a note listing the contexts that require a class type to be complete. It does not list use as a base class as being one of those contexts.
Proposed resolution (10/01):
In 3.2 [basic.def.odr] paragraph 4 add a new bullet at the end of the note as the next-to-last bullet:
[Voted into WP at March 2004 meeting.]
Consider the following translation unit:
template<class T> struct S {
void f(union U*); // (1)
};
template<class T> void S<T>::f(union U*) {} // (2)
U *p; // (3)
Does (1) introduce U as a visible name in the surrounding namespace scope?
If not, then (2) could presumably be an error since the "union U" in that definition does not find the same type as the declaration (1).
If yes, then (3) is OK too. However, we have gone through much trouble to allow template implementations that do not pre-parse the template definitions, but requiring (1) to be visible would change that.
A slightly different case is the following:
template<typename> void f() { union U *p; }
U *q; // Should this be valid?
Notes from October 2003 meeting:
There was consensus that example 1 should be allowed. (Compilers already parse declarations in templates; even MSVC++ 6.0 accepts this case.) The vote was 7-2.
Example 2, on the other hand, is wrong; the union name goes into a block scope anyway.
Proposed resolution:
In 3.3.2 [basic.scope.pdecl] change the second bullet of paragraph 5 as follows:
for an elaborated-type-specifier of the formclass-key identifierif the elaborated-type-specifier is used in the decl-specifier-seq or parameter-declaration-clause of a function defined in namespace scope, the identifier is declared as a class-name in the namespace that contains the declaration; otherwise, except as a friend declaration, the identifier is declared in the smallest non-class, non-function-prototype scope that contains the declaration. [Note: These rules also apply within templates.] [Note: ...]
[Voted into WP at March 2004 meeting.]
Consider the following example (inspired by a question from comp.lang.c++.moderated):
template<typename> struct B {};
template<typename T> struct D: B<D> {};
Most (all?) compilers reject this code because D is handled as a template name rather than as the injected class name.
9 [class]/2 says that the injected class name is "inserted into the scope of the class."
3.3.7 [basic.scope.class]/1 seems to be the text intended to describe what "scope of a class" means, but it assumes that every name in that scope was introduced using a "declarator". For an implicit declaration such as the injected-class name it is not clear what that means.
So my questions:
John Spicer: I do not believe the injected class name should be available in the base specifier. I think the semantics of injected class names should be as if a magic declaration were inserted after the opening "{" of the class definition. The injected class name is a member of the class and members don't exist at the point where the base specifiers are scanned.
John Spicer: I believe the 3.3.7 [basic.scope.class] wording should be updated to reflect the fact that not all names come from declarators.
Notes from October 2003 meeting:
We agree with John Spicer's suggested answers above.
Proposed Resolution (October 2003):
The answer to question 1 above is No and no change is required.
For question 1, change 3.3.7 [basic.scope.class] paragraph 1 rule 1 to:
1) The potential scope of a name declared in a class consists not only of the declarative region following the name's point of declaration declarator, but also of all function bodies, default arguments, and constructor ctor-initializers in that class (including such things in nested classes). The point of declaration of an injected-class-name (clause 9 [class]) is immediately following the opening brace of the class definition.
(Note that this change overlaps a change in issue 417.)
Also change 3.3.2 [basic.scope.pdecl] by adding a new paragraph 8 for the injected-class-name case:
The point of declaration for an injected-class-name (clause 9 [class]) is immediately following the opening brace of the class definition.
Alternatively this paragraph could be added after paragraph 5 and before the two note paragraphs (i.e. it would become paragraph 5a).
[Moved to DR at 10/01 meeting.]
The example in 3.4.1 [basic.lookup.unqual] paragraph 3 is incorrect:
typedef int f;
struct A {
friend void f(A &);
operator int();
void g(A a) {
f(a);
}
};
Regardless of the resolution of other issues concerning the lookup
of names in friend declarations, this example is ill-formed
(the function and the typedef cannot exist in the same scope).
One possible repair of the example would be to make f a class with a constructor taking either A or int as its parameter.
(See also issues 95, 136, 138, 143, 165, and 166.)
Proposed resolution (04/01):
Change the example in 3.4.1 [basic.lookup.unqual] paragraph 3 to read:
typedef int f;
namespace N {
struct A {
friend int f(A &);
operator int();
void g(A a) {
int i = f(a);
// f is the typedef, not the friend function:
// equivalent to int(a)
}
};
}
Delete the sentence immediately following the example:
The expression f(a) is a cast-expression equivalent to int(a).
[Voted into WP at the October, 2006 meeting.]
Is the following code well-formed?
namespace N {
int i;
extern int j;
}
int N::j = i;
The question here is whether the lookup for i in the initializer of N::j finds the declaration in namespace N or not. Implementations differ on this question.
If N::j were a static data member of a class, the answer would be clear: both 3.4.1 [basic.lookup.unqual] paragraph 12 and 8.5 [dcl.init] paragraph 11 say that the initializer “is in the scope of the member's class.” There is no such provision for namespace members defined outside the namespace, however.
The reasoning given in 3.4.1 [basic.lookup.unqual] may be instructive:
A name used in the definition of a static data member of class X (9.4.2 [class.static.data]) (after the qualified-id of the static member) is looked up as if the name was used in a member function of X.
It is certainly the case that a name used in a function that is a member of a namespace is looked up in that namespace (3.4.1 [basic.lookup.unqual] paragraph 6), regardless of whether the definition is inside or outside that namespace. Initializers for namespace members should probably be looked up the same way.
Proposed resolution (April, 2006):
Add a new paragraph following 3.4.1 [basic.lookup.unqual] paragraph 12:
If a variable member of a namespace is defined outside of the scope of its namespace then any name used in the definition of the variable member (after the declarator-id) is looked up as if the definition of the variable member occurred in its namespace. [Example:
namespace N { int i = 4; extern int j; } int i = 2; int N::j = i; // N::j == 4—end example]
[Moved to DR at 4/02 meeting.]
Paragraphs 1 and 2 of 3.4.2 [basic.lookup.argdep] say, in part,
When an unqualified name is used as the postfix-expression in a function call (5.2.2 [expr.call] )... namespace-scope friend function declarations (11.4 [class.friend] ) not otherwise visible may be found... the set of declarations found by the lookup of the function name [includes] the set of declarations found in the... classes associated with the argument types.The most straightforward reading of this wording is that if a function of namespace scope (as opposed to a class member function) is declared as a friend in a class, and that class is an associated class in a function call, the friend function will be part of the overload set, even if it is not visible to normal lookup.
Consider the following example:
namespace A {
class S;
};
namespace B {
void f(A::S);
};
namespace A {
class S {
int i;
friend void B::f(S);
};
}
void g() {
A::S s;
f(s); // should find B::f(A::S)
}
This example would seem to satisfy the criteria from
3.4.2 [basic.lookup.argdep]
:
A::S is an associated class of the argument, and
A::S has a
friend declaration of the namespace-scope function
B::f(A::S),
so Koenig lookup should include B::f(A::S) as part of the
overload set in the call.
Another interpretation is that, instead of finding the friend declarations in associated classes, one only looks for namespace-scope functions, visible or invisible, in the namespaces of which the the associated classes are members; the only use of the friend declarations in the associated classes is to validate whether an invisible function declaration came from an associated class or not and thus whether it should be included in the overload set or not. By this interpretation, the call f(s) in the example will fail, because B::f(A::S) is not a member of namespace A and thus is not found by the lookup.
Notes from 10/99 meeting: The second interpretation is correct. The wording should be revised to make clear that Koenig lookup works by finding "invisible" declarations in namespace scope and not by finding friend declarations in associated classes.
Proposed resolution (04/01): The "associated classes" are handled adequately under this interpretation by 3.4.2 [basic.lookup.argdep] paragraph 3, which describes the lookup in the associated namespaces as including the friend declarations from the associated classes. Other mentions of the associated classes should be removed or qualified to avoid the impression that there is a lookup in those classes:
In 3.4.2 [basic.lookup.argdep], change
When an unqualified name is used as the postfix-expression in a function call (5.2.2 [expr.call]), other namespaces not considered during the usual unqualified lookup (3.4.1 [basic.lookup.unqual]) may be searched, and namespace-scope friend function declarations (11.4 [class.friend]) not otherwise visible may be found.
to
When an unqualified name is used as the postfix-expression in a function call (5.2.2 [expr.call]), other namespaces not considered during the usual unqualified lookup (3.4.1 [basic.lookup.unqual]) may be searched, and in those namespaces, namespace-scope friend function declarations (11.4 [class.friend]) not otherwise visible may be found.
In 3.4.2 [basic.lookup.argdep] paragraph 2, delete the words and classes in the following two sentences:
If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated namespaces and classes are not considered. Otherwise the set of declarations found by the lookup of the function name is the union of the set of declarations found using ordinary unqualified lookup and the set of declarations found in the namespaces and classes associated with the argument types.
(See also issues 95, 136, 138, 139, 165, 166, and 218.)
[Voted into WP at April, 2007 meeting.]
The original intent of the Committee when Koenig lookup was added to the language was apparently something like the following:
This approach is not reflected in the current wording of the Standard. Instead, the following appears to be the status quo:
John Spicer: Argument-dependent lookup was created to solve the problem of looking up function names within templates where you don't know which namespace to use because it may depend on the template argument types (and was then expanded to permit use in nontemplates). The original intent only concerned functions. The safest and simplest change is to simply clarify the existing wording to that effect.
Bill Gibbons: I see no reason why non-function declarations should not be found. It would take a special rule to exclude "function objects", as well as pointers to functions, from consideration. There is no such rule in the standard and I see no need for one.
There is also a problem with the wording in 3.4.2 [basic.lookup.argdep] paragraph 2:
If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated namespaces and classes are not considered.
This implies that if the ordinary lookup of the name finds the declaration of a data member which is a pointer to function or function object, argument-dependent lookup is still done.
My guess is that this is a mistake based on the incorrect assumption that finding any member other than a member function would be an error. I would just change "class member function" to "class member" in the quoted sentence.
Mike Miller: In light of the issue of "short-circuiting" Koenig lookup when normal lookup finds a non-function, perhaps it should be written as "...finds the declaration of a class member, an object, or a reference, the associated namespaces..."?
Andy Koenig: I think I have to weigh in on the side of extending argument-dependent lookup to include function objects and pointers to functions. I am particularly concerned about [function objects], because I think that programmers should be able to replace functions by function objects without changing the behavior of their programs in fundamental ways.
Bjarne Stroustrup: I don't think we could seriously argue from first principles that [argument-dependent lookup should find only function declarations]. In general, C++ name lookup is designed to be independent of type: First we find the name(s), then, we consider its(their) meaning. 3.4 [basic.lookup] states "The name lookup rules apply uniformly to all names ..." That is an important principle.
Thus, I consider text that speaks of "function call" instead of plain "call" or "application of ()" in the context of koenig lookup an accident of history. I find it hard to understand how 5.2.2 [expr.call] doesn't either disallow all occurrences of x(y) where x is a class object (that's clearly not intended) or requires koenig lookup for x independently of its type (by reference from 3.4 [basic.lookup]). I suspect that a clarification of 5.2.2 [expr.call] to mention function objects is in order. If the left-hand operand of () is a name, it should be looked up using koenig lookup.
John Spicer: This approach causes otherwise well-formed programs to be ill-formed, and it does so by making names visible that might be completely unknown to the author of the program. Using-directives already do this, but argument-dependent lookup is different. You only get names from using-directives if you actually use using-directives. You get names from argument-dependent lookup whether you want them or not.
This basically breaks an important reason for having namespaces. You are not supposed to need any knowledge of the names used by a namespace.
But this example breaks if argument-dependent lookup finds non-functions and if the translation unit includes the <list> header somewhere.
namespace my_ns {
struct A {};
void list(std::ostream&, A&);
void f() {
my_ns::A a;
list(cout, a);
}
}
This really makes namespaces of questionable value if you still need to avoid using the same name as an entity in another namespace to avoid problems like this.
Erwin Unruh: Before we really decide on this topic, we should have more analysis on the impact on programs. I would also like to see a paper on the possibility to overload functions with function surrogates (no, I won't write one). Since such an extension is bound to wait until the next official update, we should not preclude any outcome of the discussion.
I would like to have a change right now, which leaves open several outcomes later. I would like to say that:
Koenig lookup will find non-functions as well. If it finds a variable, the program is ill-formed. If the primary lookup finds a variable, Koenig lookup is done. If the result contains both functions and variables, the program is ill-formed. [Note: A future standard will assign semantics to such a program.]
I myself are not comfortable with this as a long-time result, but it prepares the ground for any of the following long term solutions:
The note is there to prevent compiler vendors to put their own extensions in here.
(See also issues 113 and 143.)
Notes from 04/00 meeting:
Although many agreed that there were valid concerns motivating a desire for Koenig lookup to find non-function declarations, there was also concern that supporting this capability would be more dangerous than helpful in the absence of overload resolution for mixed function and non-function declarations.
A straw poll of the group revealed 8 in favor of Koenig lookup finding functions and function templates only, while 3 supported the broader result.
Notes from the 10/01 meeting:
There was unanimous agreement on one less controversial point: if the normal lookup of the identifier finds a non-function, argument-dependent lookup should not be done.
On the larger issue, the primary point of consensus is that making this change is an extension, and therefore it should wait until the point at which we are considering extensions (which could be very soon). There was also consensus on the fact that the standard as it stands is not clear: some introductory text suggests that argument-dependent lookup finds only functions, but the more detailed text that describes the lookup does not have any such restriction.
It was also noted that some existing implementations (e.g., g++) do find some non-functions in some cases.
The issue at this point is whether we should (1) make a small change to make the standard clear (presumably in the direction of not finding the non-functions in the lookup), and revisit the issue later as an extension, or (2) leave the standard alone for now and make any changes only as part of considering the extension. A straw vote favored option (1) by a strong majority.
Additional Notes (September, 2006):
Recent discussion of this issue has emphasized the following points:
The concept of finding function pointers and function objects as part of argument-dependent lookup is not currently under active discussion in the Evolution Working Group.
The major area of concern with argument-dependent lookup is finding functions in unintended namespaces. There are current proposals to deal with this concern either by changing the definition of “associated namespace” so that fewer namespaces are considered or to provide a mechanism for enabling or disabling ADL altogether. Although this concern is conceptually distinct from the question of whether ADL finds function pointers and function objects, it is related in the sense that the current rules are perceived as finding too many functions (because of searching too many namespaces), and allowing function pointers and function objects would also increase the number of entities found by ADL.
Any expansion of ADL to include function pointers and function objects must necessarily update the overloading rules to specify how they interact with functions and function templates in the overload set. Current implementation experience (g++) is not helpful in making this decision because, although it performs a uniform lookup and finds non-function entities, it diagnoses an error in overload resolution if non-function entities are in the overload set.
There is a possible problem if types are found by ADL: it is not clear that overloading between callable entities (functions, function templates, function pointers, and function objects) and types (where the postfix syntax means a cast or construction of a temporary) is reasonable or useful.
James Widman:
There is a larger debate here about whether ADL should find object names; the proposed wording below is only intended to answer the request for wording to clarify the status quo (option 1 above) and not to suggest the outcome of the larger debate.
Proposed Resolution (October, 2006):
Replace the normative text in 3.4.2 [basic.lookup.argdep] paragraph 3 with the following (leaving the text of the note and example unchanged):
Let X be the lookup set produced by unqualified lookup (3.4.1 [basic.lookup.unqual]) and let Y be the lookup set produced by argument dependent lookup (defined as follows). If X contains
- a declaration of a class member, or
- a block-scope function declaration that is not a using-declaration, or
- a declaration that is neither a function nor a function template
then Y is empty. Otherwise Y is the set of declarations found in the namespaces associated with the argument types as described below. The set of declarations found by the lookup of the name is the union of X and Y.
Change 3.4.1 [basic.lookup.unqual] paragraph 4 as indicated:
When considering an associated namespace, the lookup is the same as the lookup performed when the associated namespace is used as a qualifier (3.4.3.2 [namespace.qual]) except that:
- Any using-directives in the associated namespace are ignored.
- Any namespace-scope friend functions or friend function templates declared in associated classes are visible within their respective namespaces even if they are not visible during an ordinary lookup (11.4 [class.friend]).
- All names except those of (possibly overloaded) functions and function templates are ignored.
[Voted into WP at March 2004 meeting.]
Spun off from issue 384.
3.4.2 [basic.lookup.argdep] 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. ]There is a problem with the term "is a template-id". template-id is a syntactic construct 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 ...".
Proposed Resolution (October 2003):
In 3.4.2 [basic.lookup.argdep], paragraph 2, bullet 8, replace
If T is a template-id ...with
If T is a class template specialization ...
[Voted into WP at the October, 2006 meeting.]
One might assume from 14.8.1 [temp.inst] paragraph 1 that argument-dependent lookup would require instantiation of any class template specializations used in argument types:
Unless a class template specialization has been explicitly instantiated (14.8.2 [temp.explicit]) or explicitly specialized (14.8.3 [temp.expl.spec]), the class template specialization is implicitly instantiated when the specialization is referenced in a context that requires a completely-defined object type or when the completeness of the class type affects the semantics of the program.
A complete class type is required to determine the associated classes and namespaces for the argument type (to determine the class's bases) and to determine the friend functions declared by the class, so the completeness of the class type certainly “affects the semantics of the program.”
This conclusion is reinforced by the second bullet of 3.4.2 [basic.lookup.argdep] paragraph 2:
If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the namespaces in which its associated classes are defined.
A class template specialization is a class type, so the second bullet would appear to apply, requiring the specialization to be instantiated in order to determine its base classes.
However, bullet 8 of that paragraph deals explicitly with class template specializations:
If T is a class template specialization 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 that the class template specialization itself is not listed as an associated class, unlike other class types, and there is no mention of base classes. If bullet 8 were intended as a supplement to the treatment of class types in bullet 2, one would expect phrasing along the lines of, “In addition to the associated namespaces and classes for all class types...” or some such; instead, bullet 8 reads like a self-contained and complete specification.
If argument-dependent lookup does not cause implicit instantiation, however, examples like the following fail:
template <typename T> class C {
friend void f(C<T>*) { }
};
void g(C<int>* p) {
f(p); // found by ADL??
}
Implementations differ in whether this example works or not.
Proposed resolution (April, 2006):
Change bullet 2 of 3.4.2 [basic.lookup.argdep] paragraph 2 as indicated:
If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the namespaces in of which its associated classes are defined members. Furthermore, if T is a class template specialization, its associated namespaces and classes also include: the namespaces and classes associated with the types of the template arguments provided for template type parameters (excluding template template parameters); the namespaces of which any template template arguments are members; and the classes of which any member templates used as template template arguments are members. [Note: Non-type template arguments do not contribute to the set of associated namespaces. —end note]
Delete bullet 8 of 3.4.2 [basic.lookup.argdep] paragraph 2:
If T is a class template specialization 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. —end note]
[Voted into WP at April 2003 meeting.]
Can a typedef T to a cv-qualified class type be used in a qualified name T::x?
struct A { static int i; };
typedef const A CA;
int main () {
CA::i = 0; // Okay?
}
Suggested answer: Yes. All the compilers I tried accept the test case.
Proposed resolution (10/01):
In 3.4.3.1 [class.qual] paragraph 1 add the indicated text:
If the nested-name-specifier of a qualified-id nominates a class, the name specified after the nested-name-specifier is looked up in the scope of the class (10.2 [class.member.lookup]), except for the cases listed below. The name shall represent one or more members of that class or of one of its base classes (clause 10 [class.derived]). If the class-or-namespace-name of the nested-name-specifier names a cv-qualified class type, it nominates the underlying class (the cv-qualifiers are ignored).
Notes from 4/02 meeting:
There is a problem in that class-or-namespace-name does not include typedef names for cv-qualified class types. See 7.1.3 [dcl.typedef] paragraph 4:
Argument and text removed from proposed resolution (October 2002):
7.1.3 [dcl.typedef] paragraph 5:
Here's a good question: in this example, should X be used as a name-for-linkage-purposes (FLP name)?
typedef class { } const X;
Because a type-qualifier is parsed as a decl-specifier, it isn't possible to declare cv-qualified and cv-unqualified typedefs for a type in a single declaration. Also, of course, there's no way to declare a typedef for the cv-unqualified version of a type for which only a cv-qualified version has a name. So, in the above example, if X isn't used as the FLP name, then there can be no FLP name. Also note that a FLP name usually represents a parameter type, where top-level cv-qualifiers are usually irrelevant anyway.
Data points: for the above example, Microsoft uses X as the FLP name; GNU and EDG do not.
My recommendation: for consistency with the direction we're going on this issue, for simplicity of description (e.g., "the first class-name declared by the declaration"), and for (very slightly) increased utility, I think Microsoft has this right.
If the typedef declaration defines an unnamed class type (or enum type), the first typedef-name declared by the declaration to be have that class type (or enum type) or a cv-qualified version thereof is used to denote the class type (or enum type) for linkage purposes only (3.5 [basic.link]). [Example: ...
Proposed resolution (October 2002):
3.4.4 [basic.lookup.elab] paragraphs 2 and 3:
This sentence is deleted twice:
... If this name lookup finds a typedef-name, the elaborated-type-specifier is ill-formed. ...
Note that the above changes are included in N1376 as part of the resolution of issue 245.
5.1.1 [expr.prim.general] paragraph 7:
This is only a note, and it is at least incomplete (and quite possibly inaccurate), despite (or because of) its complexity. I propose to delete it.
... [Note: a typedef-name that names a class is a class-name (9.1 [class.name]). Except as the identifier in the declarator for a constructor or destructor definition outside of a class member-specification (12.1 [class.ctor], 12.4 [class.dtor]), a typedef-name that names a class may be used in a qualified-id to refer to a constructor or destructor. ]
7.1.3 [dcl.typedef] paragraph 4:
My first choice would have been to make this the primary statement about the equivalence of typedef-name and class-name, since the equivalence comes about as a result of a typedef declaration. Unfortunately, references to class-name point to 9.1 [class.name], so it would seem that the primary statement should be there instead. To avoid the possiblity of conflicts in the future, I propose to make this a note.
[Note: A typedef-name that names a class type, or a cv-qualified version thereof, is also a class-name (9.1 [class.name]). If a typedef-name is used following the class-key in an elaborated-type-specifier (7.1.6.3 [dcl.type.elab]), or in the class-head of a class declaration (9 [class]), or is used as the identifier in the declarator for a constructor or destructor declaration (12.1 [class.ctor], 12.4 [class.dtor]), to identify the subject of an elaborated-type-specifier (7.1.6.3 [dcl.type.elab]), class declaration (clause 9 [class]), constructor declaration (12.1 [class.ctor]), or destructor declaration (12.4 [class.dtor]), the program is ill-formed. ] [Example: ...
7.1.6.3 [dcl.type.elab] paragraph 2:
This is the only remaining (normative) statement that a typedef-name can't be used in an elaborated-type-specifier. The reference to template type-parameter is deleted by the resolution of issue 283.
... If the identifier resolves to a typedef-name or a template type-parameter, the elaborated-type-specifier is ill-formed. [Note: ...
8 [dcl.decl] grammar rule declarator-id:
When I looked carefully into the statement of the rule prohibiting a typedef-name in a constructor declaration, it appeared to me that this grammar rule (inadvertently?) allows something that's always forbidden semantically.
declarator-id:
id-expression
::opt nested-name-specifieropt type-name class-name
9.1 [class.name] paragraph 5:
Unlike the prohibitions against appearing in an elaborated-type-specifier or constructor or destructor declarator, each of which was expressed more than once, the prohibition against a typedef-name appearing in a class-head was previously stated only in 7.1.3 [dcl.typedef]. It seems to me that that prohibition belongs here instead. Also, it seems to me important to clarify that a typedef-name that is a class-name is still a typedef-name. Otherwise, the various prohibitions can be argued around easily, if perversely ("But that isn't a typedef-name, it's a class-name; it says so right there in 9.1 [class.name].")
A typedef-name (7.1.3 [dcl.typedef]) that names a class type or a cv-qualified version thereof is also a class-name, but shall not be used in an elaborated-type-specifier; see also 7.1.3 [dcl.typedef]. as the identifier in a class-head.
12.1 [class.ctor] paragraph 3:
The new nonterminal references are needed to really nail down what we're talking about here. Otherwise, I'm just eliminating redundancy. (A typedef-name that doesn't name a class type is no more valid here than one that does.)
A typedef-name that names a class is a class-name (7.1.3 [dcl.typedef]); however, a A typedef-name that names a class shall not be used as the identifier class-name in the declarator declarator-id for a constructor declaration.
12.4 [class.dtor] paragraph 1:
The same comments apply here as to 12.1 [class.ctor].
... A typedef-name that names a class is a class-name (7.1.3); however, a A typedef-name that names a class shall not be used as the identifier class-name following the ~ in the declarator for a destructor declaration.
[Voted into WP at April 2003 meeting.]
A use of an injected-class-name in an elaborated-type-specifier should not name the constructor of the class, but rather the class itself, because in that context we know that we're looking for a type. See issue 147.
Proposed Resolution (revised October 2002):
This clarifies the changes made in the TC for issue 147.
In 3.4.3.1 [class.qual] paragraph 1a replace:
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.
with
In a lookup in which the constructor is an acceptable lookup result, if the nested-name-specifier nominates a class C and the name specified after the nested-name-specifier, when looked up in C, is the injected class name of C (clause 9 [class]), the name is instead considered to name the constructor of class C. [Note: For example, the constructor is not an acceptable lookup result in an elaborated type specifier so the constructor would not be used in place of the injected class name.]
Note that issue 263 updates a part of the same paragraph.
Append to the example:
struct A::A a2; // object of type A
[Voted into WP at March 2004 meeting.]
Consider this code:
struct A { int i; struct i {}; };
struct B { int i; struct i {}; };
struct D : public A, public B { using A::i; void f (); };
void D::f () { struct i x; }
I can't find anything in the standard that says definitively what this means. 7.3.3 [namespace.udecl] says that a using-declaration shall name "a member of a base class" -- but here we have two members, the data member A::i and the class A::i.
Personally, I'd find it more attractive if this code did not work. I'd like "using A::i" to mean "lookup A::i in the usual way and bind B::i to that", which would mean that while "i = 3" would be valid in D::f, "struct i x" would not be. However, if there were no A::i data member, then "A::i" would find the struct and the code in D::f would be valid.
John Spicer: I agree with you, but unfortunately the standard committee did not.
I remembered that this was discussed by the committee and that a resolution was adopted that was different than what I hoped for, but I had a hard time finding definitive wording in the standard.
I went back though my records and found the paper that proposed a resolution and the associated committee motion that adopted the proposed resolution The paper is N0905, and "option 1" from that paper was adopted at the Stockholm meeting in July of 1996. The resolution is that "using A::i" brings in everything named i from A.
3.4.3.2 [namespace.qual] paragraph 2 was modified to implement this resolution, but interestingly that only covers the namespace case and not the class case. I think the class case was overlooked when the wording was drafted. A core issue should be opened to make sure the class case is handled properly.
Notes from April 2003 meeting:
This is related to issue 11. 7.3.3 [namespace.udecl] paragraph 10 has an example for namespaces.
Proposed resolution (October 2003):
Add a bullet to the end of 3.4.3.1 [class.qual] paragraph 1:
Change the beginning of 7.3.3 [namespace.udecl] paragraph 4 from
A using-declaration used as a member-declaration shall refer to a member of a base class of the class being defined, shall refer to a member of an anonymous union that is a member of a base class of the class being defined, or shall refer to an enumerator for an enumeration type that is a member of a base class of the class being defined.
to
In a using-declaration used as a member-declaration, the nested-name-specifier shall name a base class of the class being defined. Such a using-declaration introduces the set of declarations found by member name lookup (10.2 [class.member.lookup], 3.4.3.1 [class.qual]).
[Voted into WP at April 2003 meeting.]
I have some concerns with the description of name lookup for elaborated type specifiers in 3.4.4 [basic.lookup.elab]:
Paragraph 2 has some parodoxical statements concerning looking up names that are simple identifers:
If the elaborated-type-specifier refers to an enum-name and this lookup does not find a previously declared enum-name, the elaborated-type-specifier is ill-formed. If the elaborated-type-specifier refers to an [sic] class-name and this lookup does not find a previously declared class-name... the elaborated-type-specifier is a declaration that introduces the class-name as described in 3.3.2 [basic.scope.pdecl]."
It is not clear how an elaborated-type-specifier can refer to an enum-name or class-name given that the lookup does not find such a name and that class-name and enum-name are not part of the syntax of an elaborated-type-specifier.
The second sentence quoted above seems to suggest that the name found will not be used if it is not a class name. typedef-name names are ill-formed due to the sentence preceding the quote. If lookup finds, for instance, an enum-name then a new declaration will be created. This differs from C, and from the enum case, and can have surprising effects:
struct S {
enum E {
one = 1
};
class E* p; // declares a global class E?
};
Was this really the intent? If this is the case then some more work is needed on 3.4.4 [basic.lookup.elab]. Note that the section does not make finding a type template formal ill-formed, as is done in 7.1.6.3 [dcl.type.elab]. I don't see anything that makes a type template formal name a class-name. So the example in 7.1.6.3 [dcl.type.elab] of friend class T; where T is a template type formal would no longer be ill-formed with this interpretation because it would declare a new class T.
(See also issue 254.)
Notes from the 4/02 meeting:
This will be consolidated with the changes for issue 254. See also issue 298.
Proposed resolution (October 2002):
As given in N1376=02-0034. Note that the inserts and strikeouts in that document do not display correctly in all browsers; <del> --> <strike> and <ins> --> <b>, and the similar changes for the closing delimiters, seem to do the trick.
[Voted into WP at April 2003 meeting.]
The text in 3.4.4 [basic.lookup.elab] paragraph 2 twice refers to the possibility that an elaborated-type-specifier might have the form
class-key identifier ;
However, the grammar for elaborated-type-specifier does not include a semicolon.
In both 3.4.4 [basic.lookup.elab] and 7.1.6.3 [dcl.type.elab], the text asserts that an elaborated-type-specifier that refers to a typedef-name is ill-formed. However, it is permissible for the form of elaborated-type-specifier that begins with typename to refer to a typedef-name.
This problem is the result of adding the typename form to the elaborated-type-name grammar without changing the verbiage correspondingly. It could be fixed either by updating the verbiage or by moving the typename syntax into its own production and referring to both nonterminals when needed.
(See also issue 180. If this issue is resolved in favor of a separate nonterminal in the grammar for the typename forms, the wording in that issue's resolution must be changed accordingly.)
Notes from 04/01 meeting:
The consensus was in favor of moving the typename forms out of the elaborated-type-specifier grammar.
Notes from the 4/02 meeting:
This will be consolidated with the changes for issue 245.
Proposed resolution (October 2002):
As given in N1376=02-0034.
[Voted into the WP at the June, 2008 meeting.]
3.4.5 [basic.lookup.classref] paragraph 1 says,
In a class member access expression (5.2.5 [expr.ref] ), if the . or -> token is immediately followed by an identifier followed by a <, the identifier must be looked up to determine whether the < is the beginning of a template argument list (14.3 [temp.names] ) or a less-than operator. The identifier is first looked up in the class of the object expression. If the identifier is not found, it is then looked up in the context of the entire postfix-expression and shall name a class or function template.
There do not seem to be any circumstances in which use of a non-member template function would be well-formed as the id-expression of a class member access expression.
Proposed Resolution (November, 2006):
Change 3.4.5 [basic.lookup.classref] paragraph 1 as follows:
In a class member access expression (5.2.5 [expr.ref]), if the . or -> token is immediately followed by an identifier followed by a <, the identifier must be looked up to determine whether the < is the beginning of a template argument list (14.3 [temp.names]) or a less-than operator. The identifier is first looked up in the class of the object expression. If the identifier is not found, it is then looked up in the context of the entire postfix-expression and shall name a class or function template...
[Voted into WP at the October, 2006 meeting.]
I believe this program is invalid:
struct A {
};
struct C {
struct A {};
void f ();
};
void C::f () {
::A *a;
a->~A ();
}
The problem is that 3.4.5 [basic.lookup.classref] says that you have to look
up A in both the context of the pointed-to-type (i.e.,
::A), and
in the context of the postfix-expression (i.e., the body of C::f), and
that if the name is found in both places it must name the same type in
both places.
The EDG front end does not issue an error about this program, though.
Am I reading the standardese incorrectly?
John Spicer: I think you are reading it correctly. I think I've been hoping that this would get changed. Unlike other dual lookup contexts, this is one in which the compiler already knows the right answer (the type must match that of the left hand of the -> operator). So I think that if either of the types found matches the one required, it should be sufficient. You can't say a->~::A(), which means you are forced to say a->::A::~A(), which disables the virtual mechanism. So you would have to do something like create a local typedef for the desired type.
See also issues 244, 399, and 466.
Proposed resolution (April, 2006):
Remove the indicated text from 3.4.5 [basic.lookup.classref] paragraph 2:
If the id-expression in a class member access (5.2.5 [expr.ref]) is an unqualified-id, and the type of the object expression is of a class type C (or of pointer to a class type C), the unqualified-id is looked up in the scope of class C...
Change 3.4.5 [basic.lookup.classref] paragraph 3 as indicated:
If the unqualified-id is ~type-name,
the type-name is looked up in the context of the entire
postfix-expression. and If the
type T of the object expression is of a class
type C (or of pointer to a class type C),
the type-name is also looked up in the context of the
entire postfix-expression and in the scope of
class C. The type-name shall refer to
a class-name. If type-name is found in both contexts,
the name shall refer to the same class type. If the type of the object
expression is of scalar type, the type-name is looked up in the
scope of the complete postfix-expression. At least one
of the lookups shall find a name that refers to (possibly
cv-qualified)
T. [Example:
struct A { };
struct B {
struct A { };
void f(::A* a);
};
void B::f(::A* a) {
a->~A(); // OK, lookup in *a finds the injected-class-name
}
—end example]
[Note: this change also resolves issue 414.]
[Voted into WP at October 2004 meeting.]
The example in 3.4.5 [basic.lookup.classref] paragraph 4 is wrong (see 11.2 [class.access.base] paragraph 5; the cast to the naming class can't be done) and needs to be corrected. This was noted when the final version of the algorithm for issue 39 was checked against it.
Proposed Resolution (October 2003):
Remove the entire note at the end of 3.4.5 [basic.lookup.classref] paragraph 4, including the entire example.
[Voted into WP at the October, 2006 meeting.]
By 3.4.5 [basic.lookup.classref] paragraph 3, the following is ill-formed because the two lookups of the destructor name (in the scope of the class of the object and in the surrounding context) find different Xs:
struct X {};
int main() {
X x;
struct X {};
x.~X(); // Error?
}
This is silly, because the compiler knows what the type has to be, and one of the things found matches that. The lookup should require only that one of the lookups finds the required class type.
Proposed resolution (April, 2005):
This issue is resolved by the resolution of issue 305.
[Moved to DR at 10/01 meeting.]
3.5 [basic.link] paragraph 4 says (among other things):A name having namespace scope has external linkage if it is the name ofThat prohibits for example:
- [...]
- a named enumeration (7.2 [dcl.enum]), or an unnamed enumeration defined in a typedef declaration in which the enumeration has the typedef name for linkage purposes (7.1.3 [dcl.typedef])
typedef enum { e1 } *PE;
void f(PE) {} // Cannot declare a function (with linkage) using a
// type with no linkage.
However, the same prohibition was not made for class scope types. Indeed, 3.5 [basic.link] paragraph 5 says:
In addition, a member function, static data member, class or enumeration of class scope has external linkage if the name of the class has external linkage.
That allows for:
struct S {
typedef enum { e1 } *MPE;
void mf(MPE) {}
};
My guess is that this is an unintentional consequence of 3.5 [basic.link] paragraph 5, but I would like confirmation on that.
Proposed resolution:
Change text in 3.5 [basic.link] paragraph 5 from:
In addition, a member function, static data member, class or enumeration of class scope has external linkage if the name of the class has external linkage.to:
In addition, a member function, a static data member, a named class or enumeration of class scope, or an unnamed class or enumeration defined in a class-scope typedef declaration such that the class or enumeration has the typedef name for linkage purposes (7.1.3 [dcl.typedef]), has external linkage if the name of the class has external linkage.
[Voted into WP at October 2004 meeting.]
According to 3.5 [basic.link] paragraph 8, "A name with no linkage ... shall not be used to declare an entity with linkage." This would appear to rule out code such as:
typedef struct {
int i;
} *PT;
extern "C" void f(PT);
[likewise]
static enum { a } e;
which seems rather harmless to me.
See issue 132, which dealt with a closely related issue.
Andrei Iltchenko submitted the same issue via comp.std.c++ on 17 Dec 2001:
Paragraph 8 of Section 3.5 [basic.link] contains the following sentences: "A name with no linkage shall not be used to declare an entity with linkage. If a declaration uses a typedef name, it is the linkage of the type name to which the typedef refers that is considered."
The problem with this wording is that it doesn't cover cases where the type to which a typedef-name refers has no name. As a result it's not clear whether, for example, the following program is well-formed:
#include <vector>
int main()
{
enum { sz = 6u };
typedef int (* aptr_type)[sz];
typedef struct data {
int i, j;
} * elem_type;
std::vector<aptr_type> vec1;
std::vector<elem_type> vec2;
}
Suggested resolution:
My feeling is that the rules for whether or not a typedef-name used in a declaration shall be treated as having or not having linkage ought to be modelled after those for dependent types, which are explained in 14.7.2.1 [temp.dep.type].
Add the following text at the end of Paragraph 8 of Section 3.5 [basic.link] and replace the following example:
In case of the type referred to by a typedef declaration not having a name,
the newly declared typedef-name has linkage if and only if its referred type
comprises no names of no linkage excluding local names that are eligible for
appearance in an integral constant-expression (5.19 [expr.const]).
[Note: if the referred
type contains a typedef-name that does not denote an unnamed class, the
linkage of that name is established by the recursive application of this
rule for the purposes of using typedef names in declarations.] [Example:
void f()
{
struct A { int x; }; // no linkage
extern A a; // ill-formed
typedef A Bl
extern B b; // ill-formed
enum { sz = 6u };
typedef int (* C)[sz]; // C has linkage because sz can
// appear in a constant expression
}
--end example.]
Additional issue (13 Jan 2002, from Andrei Iltchenko):
Paragraph 2 of Section 14.4.1 [temp.arg.type] is inaccurate and unnecessarily prohibits a few important cases; it says "A local type, a type with no linkage, an unnamed type or a type compounded from any of these types shall not be used as a template-argument for a template-parameter." The inaccuracy stems from the fact that it is not a type but its name that can have a linkage.
For example based on the current wording of 14.4.1 [temp.arg.type], the following example is ill-formed.
#include <vector>
struct data {
int i, j;
};
int main()
{
enum { sz = 6u };
std::vector<int(*)[sz]> vec1; // The types 'int(*)[sz]' and 'data*'
std::vector<data*> vec2; // have no names and are thus illegal
// as template type arguments.
}
Suggested resolution:
Replace the whole second paragraph of Section 14.4.1 [temp.arg.type] with the following wording:
A type whose name does not have a linkage or a type compounded from any such
type shall not be used as a template-argument for a template-parameter. In
case of a type T used as a template type argument not having a name,
T
constitutes a valid template type argument if and only if the name of an
invented typedef declaration referring to T would have linkage;
see 3.5.
[Example:
template <class T> class X { /* ... */ };
void f()
{
struct S { /* ... */ };
enum { sz = 6u };
X<S> x3; // error: a type name with no linkage
// used as template-argument
X<S*> x4; // error: pointer to a type name with
// no linkage used as template-argument
X<int(*)[sz]> x5; // OK: since the name of typedef int
// (*pname)[sz] would have linkage
}
--end example] [Note: a template type argument may be an incomplete type
(3.9 [basic.types]).]
Proposed resolution:
This is resolved by the changes for issue 389. The present issue was moved back to Review status in February 2004 because 389 was moved back to Review.
[Voted into WP at October 2004 meeting.]
3.5 [basic.link] paragraph 8 says (among other things):
A name with no linkage (notably, the name of a class or enumeration declared in a local scope (3.3.3 [basic.scope.local])) shall not be used to declare an entity with linkage. If a declaration uses a typedef name, it is the linkage of the type name to which the typedef refers that is considered.
I would expect this to catch situations such as the following:
// File 1:
typedef struct {} *UP;
void f(UP) {}
// File 2:
typedef struct {} *UP; // Or: typedef struct {} U, *UP;
void f(UP);
The problem here is that most implementations must generate the same mangled name for "f" in two translation units. The quote from the standard above isn't quite clear, unfortunately: There is no type name to which the typedef refers.
A related situation is the following:
enum { no, yes } answer;
The variable "answer" is declared as having external linkage, but it is
declared
with an unnamed type. Section 3.5 [basic.link]
talks about the linkage of names, however,
and does therefore not prohibit this. There is no implementation issue
for most
compilers because they do not ordinarily mangle variable names, but I
believe
the intent was to allow that implementation technique.
Finally, these problems are much less relevant when declaring names with internal linkage. For example, I would expect there to be few problems with:
typedef struct {} *UP;
static void g(UP);
I recently tried to interpret 3.5 [basic.link] paragraph 8 with the assumption that types with no names have no linkage. Surprisingly, this resulted in many diagnostics on variable declarations (mostly like "answer" above).
I'm pretty sure the standard needs clarifying words in this matter, but which way should it go?
See also issue 319.
Notes from April 2003 meeting:
There was agreement that this check is not needed for variables and functions with extern "C" linkage, and a change there is desirable to allow use of legacy C headers. The check is also not needed for entities with internal linkage, but there was no strong sentiment for changing that case.
We also considered relaxing this requirement for extern "C++" variables but decided that we did not want to change that case.
We noted that if extern "C" functions are allowed an additional check is needed when such functions are used as arguments in calls of function templates. Deduction will put the type of the extern "C" function into the type of the template instance, i.e., there would be a need to mangle the name of an unnamed type. To plug that hole we need an additional requirement on the template created in such a case.
Proposed resolution (April 2003, revised slightly October 2003 and March 2004):
In 3.5 [basic.link] paragraph 8, change
A name with no linkage (notably, the name of a class or enumeration declared in a local scope (3.3.3 [basic.scope.local])) shall not be used to declare an entity with linkage. If a declaration uses a typedef name, it is the linkage of the type name to which the typedef refers that is considered.
to
A type is said to have linkage if and only ifA type without linkage shall not be used as the type of a variable or function with linkage, unless the variable or function has extern "C" linkage (7.5 [dcl.link]). [Note: in other words, a type without linkage contains a class or enumeration that cannot be named outside of its translation unit. An entity with external linkage declared using such a type could not correspond to any other entity in another translation unit of the program and is thus not permitted. Also note that classes with linkage may contain members whose types do not have linkage, and that typedef names are ignored in the determination of whether a type has linkage.]
- it is a class or enumeration type that is named (or has a name for linkage purposes (7.1.3 [dcl.typedef])) and the name has linkage; or
- it is a specialization of a class template (14 [temp]) [Footnote: a class template always has external linkage, and the requirements of 14.4.1 [temp.arg.type] and 14.4.2 [temp.arg.nontype] ensure that the template arguments will also have appropriate linkage]; or
- it is a fundamental type (3.9.1 [basic.fundamental]); or
- it is a compound type (3.9.2 [basic.compound]) other than a class or enumeration, compounded exclusively from types that have linkage; or
- it is a cv-qualified (3.9.3 [basic.type.qualifier]) version of a type that has linkage.
Change 14.4.1 [temp.arg.type] paragraph 2 from (note: this is the wording as updated by issue 62)
The following types shall not be used as a template-argument for a template type-parameter:
- a type whose name has no linkage
- an unnamed class or enumeration type that has no name for linkage purposes (7.1.3 [dcl.typedef])
- a cv-qualified version of one of the types in this list
- a type created by application of declarator operators to one of the types in this list
- a function type that uses one of the types in this list
to
A type without linkage (3.5 [basic.link]) shall not be used as a template-argument for a template type-parameter.
Once this issue is ready, issue 319 should be moved back to ready as well.
[Voted into WP at October 2005 meeting.]
Consider the following bit of code:
namespace N {
struct S {
void f();
};
}
using namespace N;
void S::f() {
extern void g(); // ::g or N::g?
}
In 3.5 [basic.link] paragraph 7 the Standard says (among other things),
When a block scope declaration of an entity with linkage is not found to refer to some other declaration, then that entity is a member of the innermost enclosing namespace.
The question then is whether N is an “enclosing namespace” for the local declaration of g()?
Proposed resolution (October 2004):
Add the following text as a new paragraph at the end of 7.3.1 [namespace.def]:
The enclosing namespaces of a declaration are those namespaces in which the declaration lexically appears, except for a redeclaration of a namespace member outside its original namespace (e.g., a definition as specified in 7.3.1.2 [namespace.memdef]). Such a redeclaration has the same enclosing namespaces as the original declaration. [Example:namespace Q { namespace V { void f(); // enclosing namespaces are the global namespace, Q, and Q::V class C { void m(); }; } void V::f() { // enclosing namespaces are the global namespace, Q, and Q::V extern void h(); // ... so this declares Q::V::h } void V::C::m() { // enclosing namespaces are the global namespace, Q, and Q::V } }—end example]
[Moved to DR at 4/02 meeting.]
The Standard does not appear to address how the rules for order of initialization apply to static data members of class templates.
Suggested resolution: Add the following verbiage to either 3.6.2 [basic.start.init] or 9.4.2 [class.static.data]:
Initialization of static data members of class templates shall be performed during the initialization of static data members for the first translation unit to have static initialization performed for which the template member has been instantiated. This requirement shall apply to both the static and dynamic phases of initialization.
Notes from 04/01 meeting:
Enforcing an order of initialization on static data members of class templates will result in substantial overhead on access to such variables. The problem is that the initialization be required as the result of instantiation in a function used in the initialization of a variable in another translation unit. In current systems, the order of initialization of static data data members of class templates is not predictable. The proposed resolution is to state that the order of initialization is undefined.
Proposed resolution (04/01, updated slightly 10/01):
Replace the following sentence in 3.6.2 [basic.start.init] paragraph 1:
Objects with static storage duration defined in namespace scope in the same translation unit and dynamically initialized shall be initialized in the order in which their definition appears in the translation unit.
with
Dynamic initialization of an object is either ordered or unordered. Explicit specializations and definitions of class template static data members have ordered initialization. Other class template static data member instances have unordered initialization. Other objects defined in namespace scope have ordered initialization. Objects defined within a single translation unit and with ordered initialization shall be initialized in the order of their definitions in the translation unit. The order of initialization is unspecified for objects with unordered initialization and for objects defined in different translation units.
Note that this wording is further updated by issue 362.
Note (07/01):
Brian McNamara argues against the proposed resolution. The following excerpt captures the central point of a long message on comp.std.c++:
I have a class for representing linked lists which looks something liketemplate <class T> class List { ... static List<T>* sentinel; ... }; template <class T> List<T>* List<T>::sentinel( new List<T> ); // static member definitionThe sentinel list node is used to represent "nil" (the null pointer cannot be used with my implementation, for reasons which are immaterial to this discussion). All of the List's non-static member functions and constructors depend upon the value of the sentinel. Under the proposed resolution for issue #270, Lists cannot be safely instantiated before main() begins, as the sentinel's initialization is "unordered".
(Some readers may propose that I should use the "singleton pattern" in the List class. This is undesirable, for reasons I shall describe at the end of this post at the location marked "[*]". For the moment, indulge me by assuming that "singleton" is not an adequate solution.)
Though this is a particular example from my own experience, I believe it is representative of a general class of examples. It is common to use static data members of a class to represent the "distinguished values" which are important to instances of that class. It is imperative that these values be initialized before any instances of the class are created, as the instances depend on the values.
In a comp.std.c++ posting on 28 Jul 2001, Brian McNamara proposes the following alternative resolution:
Replace the following sentence in 3.6.2 [basic.start.init] paragraph 1:
Objects with static storage duration defined in namespace scope in the same translation unit and dynamically initialized shall be initialized in the order in which their definition appears in the translation unit.with
Objects with static storage duration defined in namespace scope shall be initialized in the order described below.and then after paragraph 1, add this text:
Dynamic initialization is either ordered or quasi-ordered. Explicit specializations of class template static data members have ordered initialization. Other class template static data member instances have quasi-ordered initialization. All other objects defined in namespace scope have ordered initialization. The order of initialization is specified as follows:along with a non-normative note along the lines of
- Objects that are defined within a single translation unit and that have ordered initialization shall be initialized in the order of their definitions in the translation unit.
- Objects that are defined only within a single translation unit and that have quasi-ordered initialization shall also be initialized in the order of their definitions in the translation unit -- that is, as though these objects had ordered initialization.
- Objects that are defined within multiple translation units (which, therefore, must have quasi-ordered initialization) shall be initialized as follows: in exactly one translation unit (which one is unspecified), the object shall be treated as though it has ordered initialization; in the other translation units which define the object, the object will be initialized before all other objects that have ordered initialization in those translation units.
- For any two objects, "X" and "Y", with static storage duration and defined in namespace scope, if the previous bullets do not imply a relationship for the initialization ordering between "X" and "Y", then the relative initialization order of these objects is unspecified.
[ Note: The intention is that translation units can each be compiled separately with no knowledge of what objects may be re-defined in other translation units. Each translation unit can contain a method which initializes all objects (both quasi-ordered and ordered) as though they were ordered. When these translation units are linked together to create an executable program, all of these objects can be initialized by simply calling the initialization methods (one from each translation unit) in any order. Quasi-ordered objects require some kind of guard to ensure that they are not initialized more than once (the first attempt to initialize such an object should succeed; any subsequent attempts should simply be ignored). ]
Erwin Unruh replies: There is a point which is not mentioned with this posting. It is the cost for implementing the scheme. It requires that each static template variable is instantiated in ALL translation units where it is used. There has to be a flag for each of these variables and this flag has to be checked in each TU where the instantiation took place.
I would reject this idea and stand with the proposed resolution of issue 270.
There just is no portable way to ensure the "right" ordering of construction.
Notes from 10/01 meeting:
The Core Working Group reaffirmed its previous decision.
[Voted into WP at April 2005 meeting.]
I have a couple of questions about 3.6.2 [basic.start.init], "Initialization of non-local objects." I believe I recall some discussion of related topics, but I can't find anything relevant in the issues list.
The first question arose when I discovered that different implementations treat reference initialization differently. Consider, for example, the following (namespace-scope) code:
int i; int& ir = i; int* ip = &i;Both initializers, "i" and "&i", are constant expressions, per 5.19 [expr.const] paragraph 4-5 (a reference constant expression and an address constant expression, respectively). Thus, both initializations are categorized as static initialization, according to 3.6.2 [basic.start.init] paragraph 1:
Zero-initialization and initialization with a constant expression are collectively called static initialization; all other initialization is dynamic initialization.
However, that does not mean that both ir and ip must be initialized at the same time:
Objects of POD types (3.9) with static storage duration initialized with constant expressions (5.19) shall be initialized before any dynamic initialization takes place.
Because "int&" is not a POD type, there is no requirement that it be initialized before dynamic initialization is performed, and implementations differ in this regard. Using a function called during dynamic initialization to print the values of "ip" and "&ir", I found that g++, Sun, HP, and Intel compilers initialize ir before dynamic initialization and the Microsoft compiler does not. All initialize ip before dynamic initialization. I believe this is conforming (albeit inconvenient :-) behavior.
So, my first question is whether it is intentional that a reference of static duration, initialized with a reference constant expression, need not be initialized before dynamic initialization takes place, and if so, why?
The second question is somewhat broader. As 3.6.2 [basic.start.init] is currently worded, it appears that there are no requirements on when ir is initialized. In fact, there is a whole category of objects -- non-POD objects initialized with a constant expression -- for which no ordering is specified. Because they are categorized as part of "static initialization," they are not subject to the requirement that they "shall be initialized in the order in which their definition appears in the translation unit." Because they are not POD types, they are not required to be initialized before dynamic initialization occurs. Am I reading this right?
My preference would be to change 3.6.2 [basic.start.init] paragraph 1 so that 1) references are treated like POD objects with respect to initialization, and 2) "static initialization" applies only to POD objects and references. Here's some sample wording to illustrate:
Suggested resolution:
Objects with static storage duration (3.7.1) shall be zero-initialized (8.5) before any other initialization takes place. Initializing a reference, or an object of POD type, of static storage duration with a constant expression (5.19) is called constant initialization. Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place. [Remainder unchanged.]
Proposed Resolution:
Change 3.6.2 [basic.start.init] paragraph 1 as follows:
Objects with static storage duration (3.7.1) shall be zero-initialized (8.5) before any other initialization takes place. Initializing a reference, or an object of POD type, of static storage duration with a constant expression (5.19) is called constant initialization. Together, zero-initialization and constant initialization are Zero-initialization and initialization with a constant expression are collectively called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed Objects of POD types (3.9) with static storage duration initialized with constant expressions (5.19) shall be initialized before any dynamic initialization takes place.
[Voted into the WP at the September, 2008 meeting (resolution in paper N2757).]
Given this literal type,
struct X {
constexpr X() { }
};
and this definition,
static X x;
the current specification does not require that x be statically initialized because it is not “initialized with a constant expression” (3.6.1 [basic.start.main] paragraph 1).
Lawrence Crowl:
This guarantee is essential for atomics.
Jens Maurer:
Suggestion:
A reference with static storage duration or an object of literal type with static storage duration can be initialized with a constant expression (5.19 [expr.const]) or with a constexpr constructor; this is called constant initialization.
(Not spelling out “default constructor” makes it easier to handle multiple-parameter constexpr constructors, where there isn't “a” constant expression but several.)
Peter Dimov:
In addition, there is a need to enforce static initialization for non-literal types: std::shared_ptr, std::once_flag, and std::atomic_* all have nontrivial copy constructors, making them non-literal types. However, we need a way to ensure that a constexpr constructor called with constant expressions will guarantee static initialization, regardless of the nontriviality of the copy constructor.
Proposed resolution (April, 2008):
Change 3.6.2 [basic.start.init] paragraph 1 as follows:
...A reference with static storage duration and an object of trivial or literal type with static storage duration can be initialized with a constant expression (5.19 [expr.const]); this If a reference with static storage duration is initialized with a constant expression (5.19 [expr.const]) or if the initialization of an object with static storage duration satisfies the requirements for the object being declared with constexpr (7.1.5 [dcl.constexpr]), that initialization is called constant initialization...
Change 6.7 [stmt.dcl] paragraph 4 as follows:
...A local object of trivial or literal type (3.9 [basic.types]) with static storage duration initialized with constant-expressions is initialized Constant initialization (3.6.2 [basic.start.init]) of a local entity with static storage duration is performed before its block is first entered...
Change 7.1.5 [dcl.constexpr] paragraph 7 as follows:
A constexpr specifier used in an object declaration declares the object as const. Such an object shall be initialized, and every expression that appears in its initializer (8.5 [dcl.init]) shall be a constant expression. Every implicit conversion used in converting the initializer expressions and every constructor call used for the initialization shall be one of those allowed in a constant expression (5.19 [expr.const])...
Replace 8.5.1 [dcl.init.aggr] paragraph 14 as follows:
When an aggregate with static storage duration is initialized with a brace-enclosed initializer-list, if all the member initializer expressions are constant expressions, and the aggregate is a trivial type, the initialization shall be done during the static phase of initialization (3.6.2 [basic.start.init]); otherwise, it is unspecified whether the initialization of members with constant expressions takes place during the static phase or during the dynamic phase of initialization. [Note: The order of initialization for aggregates with static storage duration is specified in 3.6.2 [basic.start.init] and 6.7 [stmt.dcl]. —end note]
(Note: the change to 3.6.2 [basic.start.init] paragraph 1 needs to be reconciled with the conflicting change in issue 684.)
[Voted into the WP at the June, 2008 meeting.]
The C++ standard has inherited the definition of the 'exit' function more or less unchanged from ISO C.
However, when the 'exit' function is called, objects of static extent which have been initialised, will be destructed if their types posses a destructor.
In addition, the C++ standard has inherited the definition of the 'signal' function and its handlers from ISO C, also pretty much unchanged.
The C standard says that the only standard library functions that may be called while a signal handler is executing, are the functions 'abort', 'signal' and 'exit'.
This introduces a bit of a nasty turn, as it is not at all unusual for the destruction of static objects to have fairly complex destruction semantics, often associated with resource release. These quite commonly involve apparently simple actions such as calling 'fclose' for a FILE handle.
Having observed some very strange behaviour in a program recently which in handling a SIGTERM signal, called the 'exit' function as indicated by the C standard.
But unknown to the programmer, a library static object performed some complicated resource deallocation activities, and the program crashed.
The C++ standard says nothing about the interaction between signals, exit and static objects. My observations, was that in effect, because the destructor called a standard library function other than 'abort', 'exit' or 'signal', while transitively in the execution context of the signal handler, it was in fact non-compliant, and the behaviour was undefined anyway.
This is I believe a plausible judgement, but given the prevalence of this common programming technique, it seems to me that we need to say something a lot more positive about this interaction.
Curiously enough, the C standard fails to say anything about the analogous interaction with functions registered with 'atexit' ;-)
Proposed Resolution (10/98):
The current Committee Draft of the next version of the ISO C standard specifies that the only standard library function that may be called while a signal handler is executing is 'abort'. This would solve the above problem.
[This issue should remain open until it has been decided that the next version of the C++ standard will use the next version of the C standard as the basis for the behavior of 'signal'.]
Notes (November, 2006):
C89 is slightly contradictory here: It allows any signal handler to terminate by calling abort, exit, longjmp, but (for asynchronous signals, i.e. not those produced by abort or raise) then makes calling any library function other than signal with the current signal undefined behavior (C89 7.7.1.1). For synchronous signals, C99 forbids calls to raise, but imposes no other restrictions. For asynchronous signals, C99 allows only calls to abort, _Exit, and signal with the current signal (C99 7.14.1.1). The current C++ WP refers to “plain old functions” and “conforming C programs” (18.10 [support.runtime] paragraph 6).
Proposed Resolution (November, 2006):
Change the footnote in 18.10 [support.runtime] paragraph 6 as follows:
In particular, a signal handler using exception handling is very likely to have problems. Also, invoking std::exit may cause destruction of objects, including those of the standard library implementation, which, in general, yields undefined behavior in a signal handler (see 1.9 [intro.execution]).
[Voted into WP at the October, 2006 meeting.]
According to 3.7.4.1 [basic.stc.dynamic.allocation] paragraph 3,
Any other allocation function that fails to allocate storage shall only indicate failure by throwing an exception of class std::bad_alloc (18.6.2.1 [bad.alloc]) or a class derived from std::bad_alloc.
Shouldn't this statement have the usual requirements for an unambiguous and accessible base class?
Proposed resolution (April, 2006):
Change the last sentence of 3.7.4.1 [basic.stc.dynamic.allocation] paragraph 3 as indicated:
Any other allocation function that fails to allocate storage shall only indicate failure only by throwing an exception of class std::bad_alloc (18.6.2.1 [bad.alloc]) or a class derived from std::bad_alloc a type that would match a handler (15.3 [except.handle]) of type std::bad_alloc (18.6.2.1 [bad.alloc]).
[Voted into the WP at the September, 2008 meeting (resolution in paper N2757).]
[Picked up by evolution group at October 2002 meeting.]
The default global operators delete are specified to not throw, but there is no requirement that replacement global, or class-specific, operators delete must not throw. That ought to be required.
In particular:
We already require that all versions of an allocator's deallocate() must not throw, so that part is okay.
Rationale (04/00):
Note (March, 2008):
The Evolution Working Group has accepted the intent of this issue and referred it to CWG for action for C++0x (see paper J16/07-0033 = WG21 N2173).
Proposed resolution (March, 2008):
Change 3.7.4.2 [basic.stc.dynamic.deallocation] paragraph 3 as follows:
A deallocation function shall not terminate by throwing an exception. The value of the first argument supplied to a deallocation function...
[Voted into WP at October 2005 meeting.]
Standard is clear on behaviour of default allocation/deallocation functions. However, it is surpisingly vague on requirements to the behaviour of user-defined deallocation function and an interaction between delete-expression and deallocation function. This caused a heated argument on fido7.su.c-cpp newsgroup.
Resume:
It is not clear if user-supplied deallocation function is called from delete-expr when the operand of delete-expr is the null pointer (5.3.5 [expr.delete]). If it is, standard does not specify what user-supplied deallocation function shall do with the null pointer operand (18.6.1 [new.delete]). Instead, Standard uses the term "has no effect", which meaning is too vague in context given (5.3.5 [expr.delete]).
Description:
Consider statements
char* p= 0; //result of failed non-throwing ::new char[] ::delete[] p;Argument passed to delete-expression is valid - it is the result of a call to the non-throwing version of ::new, which has been failed. 5.3.5 [expr.delete] paragraph 1 explicitly prohibit us to pass 0 without having the ::new failure.
Standard does NOT specify whether user-defined deallocation function should be called in this case, or not.
Specifically, standard says in 5.3.5 [expr.delete] paragraph 2:
...if the value of the operand of delete is the null pointer the operation has no effect.Standard doesn't specify term "has no effect". It is not clear from this context, whether the called deallocation function is required to have no effect, or delete-expression shall not call the deallocation function.
Furthermore, in para 4 standard says on default deallocation function:
If the delete-expression calls the implementation deallocation function (3.7.4.2 [basic.stc.dynamic.deallocation]), if the operand of the delete expression is not the null pointer constant, ...Why it is so specific on interaction of default deallocation function and delete-expr?
If "has no effect" is a requirement to the deallocation function, then it should be stated in 3.7.4.2 [basic.stc.dynamic.deallocation], or in 18.6.1.1 [new.delete.single] and 18.6.1.2 [new.delete.array], and it should be stated explicitly.
Furthermore, standard does NOT specify what actions shall be performed by user-supplied deallocation function if NULL is given (18.6.1.1 [new.delete.single] paragraph 12):
Required behaviour: accept a value of ptr that is null or that was returned by an earlier call to the default operator new(std::size_t) or operator new(std::size_t, const std::nothrow_t&).
The same corresponds to ::delete[] case.
Expected solution:
Notes from October 2002 meeting:
We believe that study of 18.6.1.1 [new.delete.single] paragraphs 12 and 13, 18.6.1.2 [new.delete.array] paragraphs 11 and 12, and 3.7.4.2 [basic.stc.dynamic.deallocation] paragraph 3 shows that the system-provided operator delete functions must accept a null pointer and ignore it. Those sections also show that a user-written replacement for the system-provided operator delete functions must accept a null pointer. There is no requirement that such functions ignore a null pointer, which is okay -- perhaps the reason for replacing the system-provided functions is to do something special with null pointer values (e.g., log such calls and return).
We believe that the standard should not require an implementation to call a delete function with a null pointer, but it must allow that. For the system-provided delete functions or replacements thereof, the standard already makes it clear that the delete function must accept a null pointer. For class-specific delete functions, we believe the standard should require that such functions accept a null pointer, though it should not mandate what they do with null pointers.
5.3.5 [expr.delete] needs to be updated to say that it is unspecified whether or not the operator delete function is called with a null pointer, and 3.7.4.2 [basic.stc.dynamic.deallocation] needs to be updated to say that any deallocation function must accept a null pointer.
Proposed resolution (October, 2004):
Change 5.3.5 [expr.delete] paragraph 2 as indicated:
If the operand has a class type, the operand is converted to a pointer type by calling the above-mentioned conversion function, and the converted operand is used in place of the original operand for the remainder of this section. In either alternative, if the value of the operand of delete is the null pointer the operation has no effect may be a null pointer value. If it is not a null pointer value, in In the first alternative (delete object), the value of the operand of delete shall be a pointer to a non-array object or a pointer to a sub-object (1.8 [intro.object]) representing a base class of such an object (clause 10 [class.derived])...
Change 5.3.5 [expr.delete] paragraph 4 as follows (note that the old wording reflects the changes proposed by issue 442:
The cast-expression in a delete-expression shall be evaluated exactly once. If the delete-expression calls the implementation deallocation function (3.7.4.2 [basic.stc.dynamic.deallocation]), and if the value of the operand of the delete expression is not a null pointer, the deallocation function will deallocate the storage referenced by the pointer thus rendering the pointer invalid. [Note: the value of a pointer that refers to deallocated storage is indeterminate. —end note]
Change 5.3.5 [expr.delete] paragraphs 6-7 as follows:
The If the value of the operand of the delete-expression is not a null pointer value, the delete-expression will invoke the destructor (if any) for the object or the elements of the array being deleted. In the case of an array, the elements will be destroyed in order of decreasing address (that is, in reverse order of the completion of their constructor; see 12.6.2 [class.base.init]).
The If the value of the operand of the delete-expression is not a null pointer value, the delete-expression will call a deallocation function (3.7.4.2 [basic.stc.dynamic.deallocation]). Otherwise, it is unspecified whether the deallocation function will be called. [Note: The deallocation function is called regardless of whether the destructor for the object or some element of the array throws an exception. —end note]
Change 3.7.4.2 [basic.stc.dynamic.deallocation] paragraph 3 as indicated:
The value of the first argument supplied to one of the a deallocation functions provided in the standard library may be a null pointer value; if so, and if the deallocation function is one supplied in the standard library, the call to the deallocation function has no effect. Otherwise, the value supplied to operator delete(void*) in the standard library shall be one of the values returned by a previous invocation of either operator new(std::size_t) or operator new(std::size_t, const std::nothrow_t&) in the standard library, and the value supplied to operator delete[](void*) in the standard library shall be one of the values returned by a previous invocation of either operator new[](std::size_t) or operator new[](std::size_t, const std::nothrow_t&) in the standard library.
[Note: this resolution also resolves issue 442.]
[Moved to DR at 4/02 meeting.]
Jack Rouse: 3.8 [basic.life] paragraph 1 includes:
The lifetime of an object is a runtime property of the object. The lifetime of an object of type T begins when:Consider the code:
- storage with the proper alignment and size for type T is obtained, and
- if T is a class type with a non-trivial constructor (12.1 [class.ctor] ), the constructor call has completed.
struct B {
B( int = 0 );
~B();
};
struct S {
B b1;
};
int main()
{
S s = { 1 };
return 0;
}
In the code above, class S does have a non-trivial constructor, the
default constructor generated by the compiler. According the text
above, the lifetime of the auto s would never begin because a
constructor for S is never called. I think the second case in the
text needs to include aggregate initialization.
Mike Miller: I see a couple of ways of fixing the problem. One way would be to change "the constructor call has completed" to "the object's initialization is complete."
Another would be to add following "a class type with a non-trivial constructor" the phrase "that is not initialized with the brace notation (8.5.1 [dcl.init.aggr] )."
The first formulation treats aggregate initialization like a constructor call; even POD-type members of an aggregate could not be accessed before the aggregate initialization completed. The second is less restrictive; the POD-type members of the aggregate would be usable before the initialization, and the members with non-trivial constructors (the only way an aggregate can acquire a non-trivial constructor) would be protected by recursive application of the lifetime rule.
Proposed resolution (04/01):
In 3.8 [basic.life] paragraph 1, change
If T is a class type with a non-trivial constructor (12.1 [class.ctor]), the constructor call has completed.
to
If T is a class type with a non-trivial constructor (12.1 [class.ctor]), the initialization is complete. [Note: the initialization can be performed by a constructor call or, in the case of an aggregate with an implicitly-declared non-trivial default constructor, an aggregate initialization (8.5.1 [dcl.init.aggr]).]
[Voted into WP at April 2003 meeting.]
The wording in 3.8 [basic.life] paragraph 6 allows an lvalue designating an out-of-lifetime object to be used as the operand of a static_cast only if the conversion is ultimately to "char&" or "unsigned char&". This description excludes the possibility of using a cv-qualified version of these types for no apparent reason.
Notes on 04/01 meeting:
The wording should be changed to allow cv-qualified char types.
Proposed resolution (04/01):
In 3.8 [basic.life] paragraph 6 change the third bullet:
[Voted into WP at March 2004 meeting.]
3.8 [basic.life] paragraph 1 second bullet says:
if T is a class type with a non-trivial constructor (12.1), the constructor call has completed.
This is confusing; what was intended is probably something like
if T is a class type and the constructor invoked to create the object is non-trivial (12.1), the constructor call has completed.
Proposed Resolution (October 2003):
As given above.
[Voted into the WP at the September, 2008 meeting.]
In ISO/IEC 14882:2003, the second bullet of 3.8 [basic.life] paragraph 1 reads,
if T is a class type with a non-trivial constructor (12.1 [class.ctor]), the constructor call has completed.
Issue 119 pointed out that aggregate initialization can be used with some classes with a non-trivial implicitly-declared default constructor, and that in such cases there is no call to the object's constructor. The resolution for that issue was to change the previously-cited wording to read,
If T is a class type with a non-trivial constructor (12.1 [class.ctor], the initialization is complete.
Later (but before the WP was revised with the wording from the resolution of issue 119), issue 404 changed the 2003 wording to read,
If T is a class type and the constructor invoked to create the object is non-trivial (12.1 [class.ctor]), the constructor call has completed.
thus reversing the effect of issue 119, whose whole purpose was to cover objects with non-trivial constructors that are not invoked.
Through an editorial error, the post-Redmond draft (N1905) still contained the original 2003 wording that should have been replaced by the resolution of issue 119, in addition to the new wording from the resolution:
if T is a class type and the constructor invoked to create the object is non-trivial (12.1 [class.ctor]), the constructor call has completed. the initialization is complete.
Finally, during the application of the edits for delegating constructors (N1986), this editing error was “fixed” by retaining the original 2003 wording (which was needed for the application of the change specified in N1986), so that the current draft (N2009) reads,
if T is a class type and the constructor invoked to create the object is non-trivial (12.1 [class.ctor]), the principal constructor call 12.6.2 [class.base.init]) has completed.
Because the completion of the call to the principal constructor corresponds to the point at which the object is “fully constructed” (15.2 [except.ctor] paragraph 2), i.e., its initialization is complete, I believe that the exact wording of the issue 119 resolution would be correct and should be restored verbatim.
Proposed resolution (June, 2008):
Change 3.8 [basic.life] paragraph 1 as follows:
The lifetime of an object is a runtime property of the object. An object is said to have non-trivial initialization if it is of a class or aggregate type and it or one of its members is initialized by a constructor other than a trivial default constructor. [Note: Initialization by a trivial copy constructor is non-trivial initialization. —end note] The lifetime of an object of type T begins when:
storage with the proper alignment and size for type T is obtained, and
if T is a class type and the constructor invoked to create the object is non-trivial (12.1 [class.ctor]), the principal constructor call (12.6.2 [class.base.init]) has completed. [Note: the initialization can be performed by a constructor call or, in the case of an aggregate with an implicitly-declared non-trivial default constructor, an aggregate initialization 8.5.1 [dcl.init.aggr]. —end note] the object has non-trivial initialization, its initialization is complete.
The lifetime of an object of type T ends when...
[Voted into the WP at the June, 2008 meeting.]
The original proposed wording for 3.9 [basic.types] paragraph 11 required a constexpr constructor for a literal class only “if the class has at least one user-declared constructor.” This wording was dropped during the review by CWG out of a desire to ensure that literal types not have any uninitialized members. Thus, a class like
struct pixel {
int x, y;
};
is not a literal type. However, if an object of that type is aggregate-initialized or value-initialized, there can be no uninitialized members; the missing wording should be restored in order to permit use of expressions like pixel().x as constant expressions.
Proposed resolution (February, 2008):
Change 3.9 [basic.types] paragraph 10 as follows:
A type is a literal type if it is:
- a scalar type; or
- a class type (clause 9 [class]) with
- a trivial copy constructor,
- a trivial destructor,
- a trivial default constructor or at least one constexpr constructor other than the copy constructor,
- no virtual base classes, and
- all non-static data members and base classes of literal types; or
- an array of literal type.
[Moved to DR at 4/02 meeting.]
3.10 [basic.lval] paragraph 15 lists the types via which an lvalue can be used to access the stored value of an object; using an lvalue type that is not listed results in undefined behavior. It is permitted to add cv-qualification to the actual type of the object in this access, but only at the top level of the type ("a cv-qualified version of the dynamic type of the object").
However, 4.4 [conv.qual] paragraph 4 permits a "conversion [to] add cv-qualifiers at levels other than the first in multi-level pointers." The combination of these two rules allows creation of pointers that cannot be dereferenced without causing undefined behavior. For instance:
int* jp;
const int * const * p1 = &jp;
*p1; // undefined behavior!
The reason that *p1 results in undefined behavior is that the type of the lvalue is const int * const", which is not "a cv-qualified version of" int*.
Since the conversion is permitted, we must give it defined semantics, hence we need to fix the wording in 3.10 [basic.lval] to include all possible conversions of the type via 4.4 [conv.qual].
Proposed resolution (04/01):
Add a new bullet to 3.10 [basic.lval] paragraph 15, following "a cv-qualified version of the dynamic type of the object:"
[Voted into the WP at the September, 2008 meeting.]
The requirements on an implementation when presented with an alignment-specifier not supported by that implementation in that context are contradictory: 3.11 [basic.align] paragraph 9 says,
If a request for a specific extended alignment in a specific context is not supported by an implementation, the implementation may reject the request as ill-formed. The implementation may also silently ignore the requested alignment.
In contrast, 7.6.2 [dcl.align] paragraph 2, bullet 4 says simply,
- if the constant expression evaluates to an extended alignment and the implementation does not support that alignment in the context of the declaration, the program is ill-formed
with no provision to “silently ignore” the requested alignment. These two passages need to be reconciled.
If the outcome of the reconciliation is to grant implementations the license to accept and ignore extended alignment requests, the specification should be framed in terms of mechanisms that already exist in the Standard, such as undefined behavior and/or conditionally-supported constructs; “ill-formed” is a category that is defined by the Standard, not something that an implementation can decide.
Notes from the February, 2008 meeting:
The consensus was that such requests should be ill-formed and require a diagnostic. However, it was also observed that an implementation need not reject an ill-formed program; the only requirement is that it issue a diagnostic. It would thus be permissible for an implementation to “noisily ignore” (as opposed to “silently ignoring”) an unsupported alignment request.
Proposed resolution (June, 2008):
Change 3.11 [basic.align] paragraph 9 as follows:
If a request for a specific extended alignment in a specific context is not supported by an implementation, the implementation may reject the request as program is ill-formed. The implementation may also silently ignore the requested alignment. [Note: aAdditionally, a request for runtime allocation of dynamic memory storage for which the requested alignment cannot be honored may shall be treated as an allocation failure. —end note]
[Voted into WP at April, 2006 meeting.]
The C standard says in 6.3.2.3, paragraph 4:
Conversion of a null pointer to another pointer type yields a null pointer of that type. Any two null pointers shall compare equal.
C++ appears to be incompatible with the first sentence in only two areas:
A *a = 0;
void *v = a;
C++ (4.10 [conv.ptr] paragraph 2) says nothing about the value of v.
void *v = 0;
A *b = (A*)v; // aka static_cast<A*>(v)
C++ (5.2.9 [expr.static.cast] paragraph 10) says nothing about the value of b.
Suggested changes:
Add the following sentence to 4.10 [conv.ptr] paragraph 2:
The null pointer value is converted to the null pointer value of the destination type.
Add the following sentence to 5.2.9 [expr.static.cast] paragraph 10:
The null pointer value (4.10 [conv.ptr]) is converted to the null pointer value of the destination type.
Proposed resolution (October, 2005):
Add the indicated words to 4.10 [conv.ptr] paragraph 2:
An rvalue of type “pointer to cv T,” where T is an object type, can be converted to an rvalue of type “pointer to cv void”. The result of converting a “pointer to cv T” to a “pointer to cv void” points to the start of the storage location where the object of type T resides, as if the object is a most derived object (1.8 [intro.object]) of type T (that is, not a base class subobject). The null pointer value is converted to the null pointer value of the destination type.
Add the indicated words to 5.2.9 [expr.static.cast] paragraph 11:
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. The null pointer value is converted to the null pointer value of the destination type. A value of type pointer to object converted to “pointer to cv void” and back, possibly with different cv-qualification, shall have its original value...
[Voted into the WP at the June, 2008 meeting as paper N2656.]
In the interest of promoting use of nullptr instead of the integer literal 0 as the null pointer constant, the proposal accepted by the Committee does not provide for converting a zero-valued integral constant to type std::nullptr_t. However, this omission reduces the utility of the feature for use in the library for smart pointers. In particular, the addition of that conversion (along with a converting constructor accepting a std::nullptr_t) would allow smart pointers to be used just like ordinary pointers in expressions like:
if (p == 0) { }
if (0 == p) { }
if (p != 0) { }
if (0 != p) { }
p = 0;
The existing use of the “unspecified bool type” idiom supports this usage, but being able to use std::nullptr_t instead would be simpler and more elegant.
Jason Merrill: I have another reason to support the conversion as well: it seems to me very odd for nullptr_t to be more restrictive than void*. Anything we can do with an arbitrary pointer, we ought to be able to do with nullptr_t as well. Specifically, since there is a standard conversion from literal 0 to void*, and there is a standard conversion from void* to bool, nullptr_t should support the same conversions.
This changes two of the example lines in the proposal as adopted:
if (nullptr) ; // error, no conversion to bool
if (nullptr == 0) ; // error
become
if (nullptr) ; // evaluates to false
if( nullptr == 0 ); // evaluates to true
And later,
char* ch3 = expr ? nullptr : nullptr; // ch3 is the null pointer value
char* ch4 = expr ? 0 : nullptr; // ch4 is the null pointer value
int n3 = expr ? nullptr : nullptr; // error, nullptr_t can’t be converted to int
int n4 = expr ? 0 : nullptr; // error, nullptr_t can’t be converted to int
I would also allow reinterpret_cast from nullptr_t to integral type, with the same semantics as a reinterpret_cast from the null pointer value to integral type.
Basically, I would like nullptr_t to act like a void* which is constrained to always be (void*)0.
[Voted into WP at the October, 2006 meeting.]
When the Standard refers to a virtual base class, it should be understood to include base classes of virtual bases. However, the Standard doesn't actually say this anywhere, so when 4.11 [conv.mem] (for example) forbids casting to a derived class member pointer from a virtual base class member pointer, it could be read as meaning:
struct B {};
struct D : public B {};
struct D2 : virtual public D {};
int B::*p;
int D::*q;
void f() {
static_cast<int D2::*>(p); // permitted
static_cast<int D2::*>(q); // forbidden
}
Proposed resolution (October, 2005):
Change 4.11 [conv.mem] paragraph 2 as indicated:
...If B is an inaccessible (clause 11 [class.access]), ambiguous (10.2 [class.member.lookup]) or virtual (10.1 [class.mi]) base class of D, or a base class of a virtual base class of D, a program that necessitates this conversion is ill-formed...
Change 5.2.9 [expr.static.cast] paragraph 2 as indicated:
...and B is not neither a virtual base class of D nor a base class of a virtual base class of D...
Change 5.2.9 [expr.static.cast] paragraph 9 as indicated:
...and B is not neither a virtual base class of D nor a base class of a virtual base class of D...
[Voted into the WP at the September, 2008 meeting.]
I believe that the committee has neglected to take into account one of the differences between C and C++ when defining sequence points. As an example, consider
(a += b) += c;
where a, b, and c all have type int. I believe that this expression has undefined behavior, even though it is well-formed. It is not well-formed in C, because += returns an rvalue there. The reason for the undefined behavior is that it modifies the value of `a' twice between sequence points.
Expressions such as this one are sometimes genuinely useful. Of course, we could write this particular example as
a += b; a += c;
but what about
void scale(double* p, int n, double x, double y) {
for (int i = 0; i < n; ++i) {
(p[i] *= x) += y;
}
}
All of the potential rewrites involve multiply-evaluating p[i] or unobvious circumlocations like creating references to the array element.
One way to deal with this issue would be to include built-in operators in the rule that puts a sequence point between evaluating a function's arguments and evaluating the function itself. However, that might be overkill: I see no reason to require that in
x[i++] = y;
the contents of `i' must be incremented before the assignment.
A less stringent alternative might be to say that when a built-in operator yields an lvalue, the implementation shall not subsequently change the value of that object as a consequence of that operator.
I find it hard to imagine an implementation that does not do this already. Am I wrong? Is there any implementation out there that does not `do the right thing' already for (a += b) += c?
5.17 [expr.ass] paragraph 1 says,
The result of the assignment operation is the value stored in the left operand after the assignment has taken place; the result is an lvalue.
What is the normative effect of the words "after the assignment has taken place"? I think that phrase ought to mean that in addition to whatever constraints the rules about sequence points might impose on the implementation, assignment operators on built-in types have the additional constraint that they must store the left-hand side's new value before returning a reference to that object as their result.
One could argue that as the C++ standard currently stands, the effect of x = y = 0; is undefined. The reason is that it both fetches and stores the value of y, and does not fetch the value of y in order to compute its new value.
I'm suggesting that the phrase "after the assignment has taken place" should be read as constraining the implementation to set y to 0 before yielding the value of y as the result of the subexpression y = 0.
Francis Glassborow:
My understanding is that for a single variable:
It is the 3) that is often ignored because in practice the compiler hardly ever codes for the read because it already has that value but in complicated evaluations with a shortage of registers, that is not always the case. Without getting too close to the hardware, I think we both know that a read too close to a write can be problematical on some hardware.
So, in x = y = 0;, the implementation must NOT fetch a value from y, instead it has to "know" what that value will be (easy because it has just computed that in order to know what it must, at some time, store in y). From this I deduce that computing the lvalue (to know where to store) and the rvalue to know what is stored are two entirely independent actions that can occur in any order commensurate with the overall requirements that both operands for an operator be evaluated before the operator is.
Erwin Unruh:
C distinguishes between the resulting value of an assignment and putting the value in store. So in C a compiler might implement the statement x=y=0; either as x=0;y=0; or as y=0;x=0; In C the statement (x += 5) += 7; is not allowed because the first += yields an rvalue which is not allowed as left operand to +=. So in C an assignment is not a sequence of write/read because the result is not really "read".
In C++ we decided to make the result of assignment an lvalue. In this case we do not have the option to specify the "value" of the result. That is just the variable itself (or its address in a different view). So in C++, strictly speaking, the statement x=y=0; must be implemented as y=0;x=y; which makes a big difference if y is declared volatile.
Furthermore, I think undefined behaviour should not be the result of a single mentioning of a variable within an expression. So the statement (x +=5) += 7; should NOT have undefined behaviour.
In my view the semantics could be:
Jerry Schwarz:
My recollection is different from Erwin's. I am confident that the intention when we decided to make assignments lvalues was not to change the semantics of evaluation of assignments. The semantics was supposed to remain the same as C's.
Ervin seems to assume that because assignments are lvalues, an assignment's value must be determined by a read of the location. But that was definitely not our intention. As he notes this has a significant impact on the semantics of assignment to a volatile variable. If Erwin's interpretation were correct we would have no way to write a volatile variable without also reading it.
Lawrence Crowl:
For x=y=0, lvalue semantics implies an lvalue to rvalue conversion on the result of y=0, which in turn implies a read. If y is volatile, lvalue semantics implies both a read and a write on y.
The standard apparently doesn't state whether there is a value dependence of the lvalue result on the completion of the assignment. Such a statement in the standard would solve the non-volatile C compatibility issue, and would be consistent with a user-implemented operator=.
Another possible approach is to state that primitive assignment operators have two results, an lvalue and a corresponding "after-store" rvalue. The rvalue result would be used when an rvalue is required, while the lvalue result would be used when an lvalue is required. However, this semantics is unsupportable for user-defined assignment operators, or at least inconsistent with all implementations that I know of. I would not enjoy trying to write such two-faced semantics.
Erwin Unruh:
The intent was for assignments to behave the same as in C. Unfortunately the change of the result to lvalue did not keep that. An "lvalue of type int" has no "int" value! So there is a difference between intent and the standard's wording.
So we have one of several choices:
I think the last one has the least impact on existing programs, but it is an ugly solution.
Andrew Koenig:
Whatever we may have intended, I do not think that there is any clean way of making
volatile int v;
int i;
i = v = 42;
have the same semantics in C++ as it does in C. Like it or not, the
subexpression v = 42 has the type ``reference to volatile int,''
so if this statement has any meaning at all, the meaning must be to store 42
in v and then fetch the value of v to assign it to i.
Indeed, if v is volatile, I cannot imagine a conscientious programmer writing a statement such as this one. Instead, I would expect to see
v = 42;
i = v;
if the intent is to store 42 in v and then fetch the (possibly
changed) value of v, or
v = 42;
i = 42;
if the intent is to store 42 in both v and i.
What I do want is to ensure that expressions such as ``i = v = 42'' have well-defined semantics, as well as expressions such as (i = v) = 42 or, more realistically, (i += v) += 42 .
I wonder if the following resolution is sufficient:
Append to 5.17 [expr.ass] paragraph 1:
There is a sequence point between assigning the new value to the left operand and yielding the result of the assignment expression.
I believe that this proposal achieves my desired effect of not constraining when j is incremented in x[j++] = y, because I don't think there is a constraint on the relative order of incrementing j and executing the assignment. However, I do think it allows expressions such as (i += v) += 42, although with different semantics from C if v is volatile.
Notes on 10/01 meeting:
There was agreement that adding a sequence point is probably the right solution.
Notes from the 4/02 meeting:
The working group reaffirmed the sequence-point solution, but we will look for any counter-examples where efficiency would be harmed.
For drafting, we note that ++x is defined in 5.3.2 [expr.pre.incr] as equivalent to x+=1 and is therefore affected by this change. x++ is not affected. Also, we should update any list of all sequence points.
Notes from October 2004 meeting:
Discussion centered around whether a sequence point “between assigning the new value to the left operand and yielding the result of the expression” would require completion of all side effects of the operand expressions before the value of the assignment expression was used in another expression. The consensus opinion was that it would, that this is the definition of a sequence point. Jason Merrill pointed out that adding a sequence point after the assignment is essentially the same as rewriting
b += a
as
b += a, b
Clark Nelson expressed a desire for something like a “weak” sequence point that would force the assignment to occur but that would leave the side effects of the operands unconstrained. In support of this position, he cited the following expression:
j = (i = j++)
With the proposed addition of a full sequence point after the assignment to i, the net effect is no change to j. However, both g++ and MSVC++ behave differently: if the previous value of j is 5, the value of the expression is 5 but j gets the value 6.
Clark Nelson will investigate alternative approaches and report back to the working group.
Proposed resolution (March, 2008):
See issue 637.
[Voted into WP at March 2004 meeting.]
I have found what looks like a bug in clause 5 [expr], paragraph 4:
Between the previous and next sequence point a scalar object shall
have its stored value modified at most once by the evaluation of an
expression. Furthermore, the prior value shall be accessed only to
determine the value to be stored. The requirements of this
paragraph shall be met for each allowable ordering of the
subexpressions of a full expression; otherwise the behavior is
undefined. Example:
i = v[i++]; // the behavior is unspecified
i = 7, i++, i++; // i becomes 9
i = ++i + 1; // the behavior is unspecified
i = i + 1; // the value of i is incremented
--end example]
So which is it, unspecified or undefined?
Notes from October 2002 meeting:
We should find out what C99 says and do the same thing.
Proposed resolution (April 2003):
Change the example in clause 5 [expr], paragraph 4 from
[Example:i = v[i++]; // the behavior is unspecified i = 7, i++, i++; // i becomes 9 i = ++i + 1; // the behavior is unspecified i = i + 1; // the value of i is incremented--- end example]
to (changing "unspecified" to "undefined" twice)
[Example:i = v[i++]; // the behavior is undefined i = 7, i++, i++; // i becomes 9 i = ++i + 1; // the behavior is undefined i = i + 1; // the value of i is incremented--- end example]
[Voted into WP at October 2005 meeting.]
Clause 5 [expr] par. 5 of the standard says:
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined, unless such an expression is a constant expression (5.19), in which case the program is ill-formed.
Well, we do know that except in some contexts (e.g. controlling expression of a #if, array bounds), a compiler is not required to evaluate constant-expressions in compile time, right?
Now, let us consider, the following simple snippet:
if (a && 1/0)
...
with a, to fix our attention, being *not* a constant expression. The
quote above seems to say that since 1/0 is a constant
(sub-)expression, the program is ill-formed. So, is it the intent that
such ill-formedness is diagnosable at run-time? Or is it the intent
that the above gives undefined behavior (if 1/0 is evaluated) and is
not ill-formed?
I think the intent is actually the latter, so I propose the following rewording of the quoted section:
If an expression is evaluated but its result is not mathematically defined or not in the range of representable values for its type the behavior is undefined, unless such an expression is a constant expression (5.19) that shall be evaluated during program translation, in which case the program is ill-formed.
Rationale (March, 2004):
We feel the standard is clear enough. The quoted sentence does begin "If during the evaluation of an expression, ..." so the rest of the sentence does not apply to an expression that is not evaluated.
Note (September, 2004):
Gennaro Prota feels that the CWG missed the point of his original comment: unless a constant expression appears in a context that requires a constant expression, an implementation is permitted to defer its evaluation to runtime. An evaluation that fails at runtime cannot affect the well-formedness of the program; only expressions that are evaluated at compile time can make a program ill-formed.
The status has been reset to “open” to allow further discussion.
Proposed resolution (October, 2004):
Change paragraph 5 of 5 [expr] as indicated:
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined, unless such an expression is a constant expression appears where an integral constant expression is required (5.19 [expr.const]), in which case the program is ill-formed.
[Moved to DR at 10/01 meeting.]
5.1.1 [expr.prim.general] paragraph 11 reads,
A template-id shall be used as an unqualified-id only as specified in 14.8.2 [temp.explicit] , 14.8 [temp.spec] , and 14.6.5 [temp.class.spec] .
What uses of template-ids as unqualified-ids is this supposed to prevent? And is the list of referenced sections correct/complete? For instance, what about 14.9.1 [temp.arg.explicit], "Explicit template argument specification?" Does its absence from the list in 5.1.1 [expr.prim.general] paragraph 11 mean that "f<int>()" is ill-formed?
This is even more confusing when you recall that unqualified-ids are contained in qualified-ids:
qualified-id: ::opt nested-name-specifier templateopt unqualified-id
Is the wording intending to say "used as an unqualified-id that is not part of a qualified-id?" Or something else?
Proposed resolution (10/00):
Remove the referenced sentence altogether.
[Voted into WP at March 2004 meeting.]
The example below is ambiguous.
struct A{
struct B{};
};
A::B C();
namespace B{
A C();
}
struct Test {
friend A::B ::C();
};
Here, it is not clear whether the friend declaration denotes
A B::C() or A::B C(), yet the standard does not resolve this
ambiguity.
The ambiguity arises since both the simple-type-specifier (7.1.6.2 [dcl.type.simple] paragra 1) and an init-declararator (8 [dcl.decl] paragraph 1) contain an optional :: and an optional nested-name-specifier (5.1.1 [expr.prim.general] paragraph 1). Therefore, two different ways to analyse this code are possible:
simple-type-specifier = A::Bor
init-declarator = ::C()
simple-declaration = friend A::B ::C();
simple-type-specifier = ASince it is a friend declaration, the init-declarator may be qualified, and start with a global scope.
init-declarator = ::B::C()
simple-declaration = friend A ::B::C();
Suggested Resolution: In the definition of nested-name-specifier, add a sentence saying that a :: token immediately following a nested-name-specifier is always considered as part of the nested-name-specifier. Under this interpretation, the example is ill-formed, and should be corrected as either
friend A (::B::C)(); //or
friend A::B (::C)();
An alternate suggestion — changing 7.1 [dcl.spec] to say that
The longest sequence of tokens that could possibly be a type name is taken as the decl-specifier-seq of a declaration.
— is undesirable because it would make the example well-formed rather than requiring the user to disambiguate the declaration explicitly.
Proposed resolution (04/01):
(See below for problem with this, from 10/01 meeting.)
In 5.1.1 [expr.prim.general] paragraph 7,
Before the grammar for qualified-id, start a new paragraph 7a with the text
A qualified-id is an id-expression that contains the scope resolution operator ::.
Following the grammar fragment, insert the following:
The longest sequence of tokens that could form a qualified-id constitutes a single qualified-id. [Example:
// classes C, D; functions F, G, namespace N; non-class type T friend C ::D::F(); // ill-formed, means friend (C::D::F)(); friend C (::D::F)(); // well-formed friend N::T ::G(); // ill-formed, means friend (N::T::G)(); friend N::T (::G)(); // well-formed—end example]
Start a new paragraph 7b following the example.
(This resolution depends on that of issue 215.)
Notes from 10/01 meeting:
It was pointed out that the proposed resolution does not deal with cases like X::Y where X is a type but not a class type. The working group reaffirmed its decision that the disambiguation should be syntactic only, i.e., it should depend only on whether or not the name is a type.
Jason Merrill :At the Seattle meeting, I suggested that a solution might be to change the class-or-namespace-name in the nested-name-specifier rule to just be "identifier"; there was some resistance to this idea. FWIW, I've tried this in g++. I had to revise the idea so that only the second and subsequent names were open to being any identifier, but that seems to work just fine.
So, instead of
it would be
Or some equivalent but right-associative formulation, if people feel that's important, but it seems irrelevant to me.
Clark Nelson :
Personally, I prefer the left-associative rule. I think it makes it easier to understand. I was thinking about this production a lot at the meeting, considering also some issues related to 301. My formulation was getting kind of ugly, but with a left-associative rule, it gets a lot nicer.
Your proposal isn't complete, however, as it doesn't allow template arguments without an explicit template keyword. You probably want to add an alternative for:
There is admittedly overlap between this alternative and
but I think they're both necessary.
Notes from the 4/02 meeting:
The changes look good. Clark Nelson will merge the two proposals to produce a single proposed resolution.
Proposed resolution (April 2003):
nested-name-specifier is currently defined in 5.1.1 [expr.prim.general] paragraph 7 as:
The proposed definition is instead:
Issue 215 is addressed by using type-name instead of class-name in the first alternative. Issue 125 (this issue) is addressed by using identifier instead of anything more specific in the third alternative. Using left association instead of right association helps eliminate the need for class-or-namespace-name (or type-or-namespace-name, as suggested for issue 215).
It should be noted that this formulation also rules out the possibility of A::template B::, i.e. using the template keyword without any template arguments. I think this is according to the purpose of the template keyword, and that the former rule allowed such a construct only because of the difficulty of formulation of a right-associative rule that would disallow it. But I wanted to be sure to point out this implication.
Notes from April 2003 meeting:
See also issue 96.
The proposed change resolves only part of issue 215.
[Moved to DR at 10/01 meeting.]
Christophe de Dinechin: In 5.2.2 [expr.call] , paragraph 2 reads:
If no declaration of the called function is visible from the scope of the call the program is ill-formed.I think nothing there or in the previous paragraph indicates that this does not apply to calls through pointer or virtual calls.
Mike Miller: "The called function" is unfortunate phraseology; it makes it sound as if it's referring to the function actually called, as opposed to the identifier in the postfix expression. It's wrong with respect to Koenig lookup, too (the declaration need not be visible if it can be found in a class or namespace associated with one or more of the arguments).
In fact, this paragraph should be a note. There's a general rule that says you have to find an unambiguous declaration of any name that is used (3.4 [basic.lookup] paragraph 1); the only reason this paragraph is here is to contrast with C's implicit declaration of called functions.
Proposed resolution:
Change section 5.2.2 [expr.call] paragraph 2 from:If no declaration of the called function is visible from the scope of the call the program is ill-formed.to:
[Note: if a function or member function name is used, and name lookup (3.4 [basic.lookup]) does not find a declaration of that name, the program is ill-formed. No function is implicitly declared by such a call. ]
(See also issue 218.)
[Voted into the WP at the June, 2008 meeting.]
Martin O'Riordan: Having gone through all the relevant references in the IS, it is not conclusive that a call via a pointer to a virtual member function is polymorphic at all, and could legitimately be interpreted as being static.
Consider 5.2.2 [expr.call] paragraph 1:
The function called in a member function call is normally selected according to the static type of the object expression (clause 10 [class.derived] ), but if that function is virtual and is not specified using a qualified-id then the function actually called will be the final overrider (10.3 [class.virtual] ) of the selected function in the dynamic type of the object expression.Here it is quite specific that you get the polymorphic call only if you use the unqualified syntax. But, the address of a member function is "always" taken using the qualified syntax, which by inference would indicate that call with a PMF is static and not polymorphic! Not what was intended.
Yet other references such as 5.5 [expr.mptr.oper] paragraph 4:
If the dynamic type of the object does not contain the member to which the pointer refers, the behavior is undefined.indicate that the opposite may have been intended, by stating that it is the dynamic type and not the static type that matters. Also, 5.5 [expr.mptr.oper] paragraph 6:
If the result of .* or ->* is a function,
then that result can be used
only as the operand for the function
call operator (). [Example:
(ptr_to_obj->*ptr_to_mfct)(10);
calls the member function denoted by ptr_to_mfct
for the object pointed
to by ptr_to_obj. ]
which also implies that it is the object pointed to that determines both the
validity of the expression (the static type of 'ptr_to_obj'
may not have a
compatible function) and the implicit (polymorphic) meaning. Note too,
that this is stated in the non-normative example text.
Andy Sawyer: Assuming the resolution is what I've assumed it is for the last umpteen years (i.e. it does the polymorphic thing), then the follow on to that is "Should there also be a way of selecting the non-polymorphic behaviour"?
Mike Miller: It might be argued that the current wording of 5.2.2 [expr.call] paragraph 1 does give polymorphic behavior to simple calls via pointers to members. (There is no qualified-id in obj.*pmf, and the IS says that if the function is not specified using a qualified-id, the final overrider will be called.) However, it clearly says the wrong thing when the pointer-to-member itself is specified using a qualified-id (obj.*X::pmf).
Bill Gibbons: The phrase qualified-id in 5.2.2 [expr.call] paragraph 1 refers to the id-expression and not to the "pointer-to-member expression" earlier in the paragraph:
For a member function call, the postfix expression shall be an implicit (9.3.1 [class.mfct.non-static] , 9.4 [class.static] ) or explicit class member access (5.2.5 [expr.ref] ) whose id-expression is a function member name, or a pointer-to-member expression (5.5 [expr.mptr.oper] ) selecting a function member.
Mike Miller: To be clear, here's an example:
struct S {
virtual void f();
};
void (S::*pmf)();
void g(S* sp) {
sp->f(); // 1: polymorphic
sp->S::f(); // 2: non-polymorphic
(sp->S::f)(); // 3: non-polymorphic
(sp->*pmf)(); // 4: polymorphic
(sp->*&S::f)(); // 5: polymorphic
}
Notes from October 2002 meeting:
This was moved back to open for lack of a champion. Martin O'Riordan is not expected to be attending meetings.
Proposed resolution (February, 2008):
Change 5.2.2 [expr.call] paragraph 1 as follows:
... For a member function call, the postfix expression shall be an implicit (9.3.1 [class.mfct.non-static], 9.4 [class.static]) or explicit class member access (5.2.5 [expr.ref]) whose id-expression is a function member name, or a pointer-to-member expression (5.5 [expr.mptr.oper]) selecting a function member. The first expression in the postfix expression is then called the object expression, and; the call is as a member of the object pointed to or referred to by the object expression (5.2.5 [expr.ref], 5.5 [expr.mptr.oper]). In the case of an implicit class member access, the implied object is the one pointed to by this. [Note: a member function call of the form f() is interpreted as (*this).f() (see 9.3.1 [class.mfct.non-static]). —end note] If a function or member function name is used, the name can be overloaded (clause 13 [over]), in which case the appropriate function shall be selected according to the rules in 13.3 [over.match]. The function called in a member function call is normally selected according to the static type of the object expression (clause 10 [class.derived]), but if that function is virtual and is not specified using a qualified-id then the function actually called will be the final overrider (10.3 [class.virtual]) of the selected function in the dynamic type of the object expression If the selected function is non-virtual, or if the id-expression in the class member access expression is a qualified-id, that function is called. Otherwise, its final overrider (10.3 [class.virtual]) in the dynamic type of the object expression is called. ...
Change 5.5 [expr.mptr.oper] paragraph 4 as follows:
The first operand is called the object expression. If the dynamic type of the object expression does not contain the member to which the pointer refers, the behavior is undefined.
[Voted into WP at the October, 2006 meeting.]
The current wording of 5.2.2 [expr.call] paragraph 7 states:
When there is no parameter for a given argument, the argument is passed in such a way that the receiving function can obtain the value of the argument by invoking va_arg (18.10 [support.runtime]). The lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), and function-to-pointer (4.3 [conv.func]) standard conversions are performed on the argument expression. After these conversions, if the argument does not have arithmetic, enumeration, pointer, pointer to member, or class type, the program is ill-formed. If the argument has a non-POD class type (clause 9 [class]), the behavior is undefined.
Paper J16/04-0167=WG21 N1727 suggests that passing a non-POD object to ellipsis be ill-formed. In discussions at the Lillehammer meeting, however, the CWG felt that the newly-approved category of conditionally-supported behavior would be more appropriate.
Proposed resolution (October, 2005):
Change 5.2.2 [expr.call] paragraph 7 as indicated:
...After these conversions, if the argument does not have arithmetic, enumeration, pointer, pointer to member, or class type, the program is ill-formed. If the argument has a non-POD class type (clause 9), the behavior is undefined. Passing an argument of non-POD class type (clause 9) with no corresponding parameter is conditionally-supported, with implementation-defined semantics.
[Voted into the WP at the September, 2008 meeting.]
Issue 506 changed passing a non-POD class type to an ellipsis from undefined behavior to conditionally-supported behavior. As a result, an implementation could conceivably reject code like the following:
struct two {char _[2];};
template <class From, class To>
struct is_convertible
{
private:
static From f;
template <class U> static char test(const U&);
template <class U> static two test(...);
public:
static const bool value = sizeof(test<To>(f)) == 1;
};
struct A
{
A();
};
int main()
{
const bool b = is_convertible<A,int>::value; // b == false
}
This technique has become popular in template metaprogramming, and no non-POD object is actually passed at runtime. Concepts will eliminate much (perhaps not all) of the need for this kind of programming, but legacy code will persist.
Perhaps this technique should be officially supported by allowing implementations to reject passing a non-POD type to ellipsis only if it appears in a potentially-evaluated expression?
Notes from the July, 2007 meeting:
The CWG agreed with the suggestion to allow such calls in unevaluated contexts.
Proposed resolution (September, 2007):
Change 5.2.2 [expr.call] paragraph 7 as follows:
...Passing an a potentially-evaluated argument of non-trivial class type (clause 9 [class]) with no corresponding parameter is conditionally-supported, with implementation-defined semantics...
[Voted into WP at April, 2006 meeting.]
5.2.4 [expr.pseudo] paragraph 2 says both:
The type designated by the pseudo-destructor-name shall be the same as the object type.and also:
The cv-unqualified versions of the object type and of the type designated by the pseudo-destructor-name shall be the same type.Which is it? "The same" or "the same up to cv-qualifiers"? The second sentence is more generous than the first. Most compilers seem to implement the less restrictive form, so I guess that's what I think we should do.
Proposed resolution (October, 2005):
Change 5.2.4 [expr.pseudo] paragraph 2 as follows:
The left-hand side of the dot operator shall be of scalar type. The left-hand side of the arrow operator shall be of pointer to scalar type. This scalar type is the object type. The type designated by the pseudo-destructor-name shall be the same as the object type. The cv-unqualified versions of the object type and of the type designated by the pseudo-destructor-name shall be the same type. Furthermore, the two type-names in a pseudo-destructor-name of the form::opt nested-name-specifieropt type-name ::~ type-name
shall designate the same scalar type. The cv-unqualified versions of the object type and of the type designated by the pseudo-destructor-name shall be the same type.
[Voted into WP at March 2004 meeting.]
Consider
typedef
struct {
int a;
} A;
A f(void)
{
A a;
return a;
}
int main(void)
{
int* p = &f().a; // #1
}
Should #1 be rejected? The standard is currently silent.
Mike Miller: I don't believe the Standard is silent on this. I will agree that the wording of 5.2.5 [expr.ref] paragraph 4 bullet 2 is unfortunate, as it is subject to misinterpretation. It reads,
If E1 is an lvalue, then E1.E2 is an lvalue.The intent is, "and not otherwise."
Notes from October 2003 meeting:
We agree the reference should be an rvalue, and a change along the lines of that recommended by Mike Miller is reasonable.
Proposed Resolution (October 2003):
Change the second bullet of 5.2.5 [expr.ref] paragraph 4 to read:
If E1 is an lvalue, then E1.E2 is an lvalue; otherwise, it is an rvalue.
[Voted into WP at April, 2006 meeting.]
There is an inconsistency between the normative text in section 5.2.8 [expr.typeid] and the example that follows.
Here is the relevant passage (starting with paragraph 4):
When typeid is applied to a type-id, the result refers to a std::type_info object representing the type of the type-id. If the type of the type-id is a reference type, the result of the typeid expression refers to a std::type_info object representing the referenced type.
The top-level cv-qualifiers of the lvalue expression or the type-id that is the operand of typeid are always ignored.
and the example:
typeid(D) == typeid(const D&); // yields true
The second paragraph above says the “type-id that is the operand”. This would be const D&. In this case, the const is not at the top-level (i.e., applied to the operand itself).
By a strict reading, the above should yield false.
My proposal is that the strict reading of the normative test is correct. The example is wrong. Different compilers here give different answers.
Proposed resolution (April, 2005):
Change the second sentence of 5.2.8 [expr.typeid] paragraph 4 as follows:
If the type of the type-id is a reference to a possibly cv-qualified type, the result of the typeid expression refers to a std::type_info object representing the cv-unqualified referenced type.
[Voted into WP at October 2004 meeting.]
Is it okay to use a static_cast to cast from a private base class to a derived class? That depends on what the words "valid standard conversion" in paragraph 8 mean — do they mean the conversion exists, or that it would not get an error if it were done? I think the former was intended — and therefore a static_cast from a private base to a derived class would be allowed.
Rationale (04/99): A static_cast from a private base to a derived class is not allowed outside a member from the derived class, because 4.10 [conv.ptr] paragraph 3 implies that the conversion is not valid. (Classic style casts work.)
Reopened September 2003:
Steve Adamczyk: It makes some sense to disallow the inverse conversion that is pointer-to-member of derived to pointer-to-member of private base. There's less justification for the pointer-to-private-base to pointer-to-derived case. EDG, g++ 3.2, and MSVC++ 7.1 allow the pointer case and disallow the pointer-to-member case. Sun disallows the pointer case as well.
struct B {};
struct D : private B {};
int main() {
B *p = 0;
static_cast<D *>(p); // Pointer case: should be allowed
int D::*pm = 0;
static_cast<int B::*>(pm); // Pointer-to-member case: should get error
}
There's a tricky case with old-style casts: because the static_cast interpretation is tried first, you want a case like the above to be considered a static_cast, but then issue an error, not be rejected as not a static cast; if you did the latter, you would then try the cast as a reinterpret_cast.
Ambiguity and casting to a virtual base should likewise be errors after the static_cast interpretation is selected.
Notes from the October 2003 meeting:
There was lots of sentiment for making things symmetrical: the pointer case should be the same as the pointer-to-member case. g++ 3.3 now issues errors on both cases.
We decided an error should be issued on both cases. The access part of the check should be done later; by some definition of the word the static_cast is valid, and then later an access error is issued. This is similar to the way standard conversions work.
Proposed Resolution (October 2003):
Replace paragraph 5.2.9 [expr.static.cast]/6:
The inverse of any standard conversion sequence (clause 4 [conv]), other than the lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), function-to-pointer (4.3 [conv.func]), and boolean (4.12 [conv.bool]) conversions, 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 the explicit conversion does not cast away constness (5.2.11 [expr.const.cast]), and the following additional rules for specific cases:
with two paragraphs:
The inverse of any standard conversion sequence (clause 4 [conv]), other than the lvalue-to-rvalue (4.1 [conv.lval]), array-to-pointer (4.2 [conv.array]), function-to-pointer (4.3 [conv.func]), and boolean (4.12 [conv.bool]) conversions, can be performed explicitly using static_cast. A program is ill-formed if it uses static_cast to perform the inverse of an ill-formed standard conversion sequence.[Example:--- end example]struct B {}; struct D : private B {}; void f() { static_cast<D*>((B*)0); // Error: B is a private base of D. static_cast<int B::*>((int D::*)0); // Error: B is a private base of D. }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 the explicit conversion does not cast away constness (5.2.11 [expr.const.cast]), and the following additional rules for specific cases:
In addition, modify the second sentence of 5.4 [expr.cast]/5. The first two sentences of 5.4 [expr.cast]/5 presently read:
The conversions performed bycan be performed using the cast notation of explicit type conversion. The same semantic restrictions and behaviors apply.
- a const_cast (5.2.11),
- a static_cast (5.2.9),
- a static_cast followed by a const_cast,
- a reinterpret_cast (5.2.10), or
- a reinterpret_cast followed by a const_cast,
Change the second sentence to read:
The same semantic restrictions and behaviors apply, with the exception that in performing a static_cast in the following situations the conversion is valid even if the base class is inaccessible:
- a pointer to an object of derived class type or an lvalue of derived class type may be explicitly converted to a pointer or reference to an unambiguous base class type, respectively;
- a pointer to member of derived class type may be explicitly converted to a pointer to member of an unambiguous non-virtual base class type;
- a pointer to an object of an unambiguous non-virtual base class type, an lvalue of an unambiguous non-virtual base class type, or a pointer to member of an unambiguous non-virtual base class type may be explicitly converted to a pointer, a reference, or a pointer to member of a derived class type, respectively.
Remove paragraph 5.4 [expr.cast]/7, which presently reads:
In addition to those conversions, the following static_cast and reinterpret_cast operations (optionally followed by a const_cast operation) may be performed using the cast notation of explicit type conversion, even if the base class type is not accessible:
- a pointer to an object of derived class type or an lvalue of derived class type may be explicitly converted to a pointer or reference to an unambiguous base class type, respectively;
- a pointer to member of derived class type may be explicitly converted to a pointer to member of an unambiguous non-virtual base class type;
- a pointer to an object of non-virtual base class type, an lvalue of non-virtual base class type, or a pointer to member of non-virtual base class type may be explicitly converted to a pointer, a reference, or a pointer to member of a derived class type, respectively.
[Voted into WP at October 2004 meeting.]
Consider this code:
struct B {};
struct D : public B {
D(const B&);
};
extern B& b;
void f() {
static_cast<const D&>(b);
}
The rules for static_cast permit the conversion to "const D&" in two ways:
The first alternative is 5.2.9 [expr.static.cast]/5; the second is 5.2.9 [expr.static.cast]/2.
Presumably the first alternative is better -- it's the "simpler" conversion. The standard does not seem to make that clear.
Steve Adamczyk: I take the "Otherwise" at the beginning of 5.2.9 [expr.static.cast]/3 as meaning that the paragraph 2 interpretation is used if available, which means in your example above interpretation 2 would be used. However, that's not what EDG's compiler does, and I agree that it's not the "simpler" conversion.
Proposed Resolution (October 2003):
Move paragraph 5.2.9/5:
An lvalue of type ``cv1 B'', where B is a class type, can be cast to type ``reference to cv2 D'', where D is a class derived (clause 10 [class.derived]) from B, if a valid standard conversion from ``pointer to D'' to ``pointer to B'' exists (4.10 [conv.ptr]), cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and B is not a virtual base class of D. The result is an lvalue of type ``cv2 D.'' If the lvalue of type ``cv1 B'' is actually a sub-object of an object of type D, the lvalue refers to the enclosing object of type D. Otherwise, the result of the cast is undefined. [Example:
struct B {}; struct D : public B {}; D d; B &br = d; static_cast<D&>(br); // produces lvalue to the original d object--- end example]
before paragraph 5.2.9 [expr.static.cast]/2.
Insert Otherwise, before the text of paragraph 5.2.9 [expr.static.cast]/2 (which will become 5.2.9 [expr.static.cast]/3 after the above insertion), so that it reads:
Otherwise, an expression e can be explicitly converted to a type T using a static_cast of the form static_cast<T>(e) if the declaration "T t(e);" is well-formed, for some invented temporary variable t (8.5 [dcl.init]). The effect of such an explicit conversion is the same as performing the declaration and initialization and then using the temporary variable as the result of the conversion. The result is an lvalue if T is a reference type (8.3.2 [dcl.ref]), and an rvalue otherwise. The expression e is used as an lvalue if and only if the initialization uses it as an lvalue.
[Voted into WP at April 2005 meeting.]
Paragraph 5.2.9 [expr.static.cast] paragraph 10 says that:
A value of type pointer to object converted to "pointer to cv void" and back to the original pointer type will have its original value.
That guarantee should be stronger. In particular, given:
T* p1 = new T;
const T* p2 = static_cast<const T*>(static_cast<void *>(p1));
if (p1 != p2)
abort ();
there should be no call to "abort". The last sentence of Paragraph
5.2.9 [expr.static.cast] paragraph 10 should be changed to read:
A value of type pointer to object converted to "pointer to cv void" and back to the original pointer type (or a variant of the original pointer type that differs only in the cv-qualifiers applied to the object type) will have its original value. [Example:---end example.]T* p1 = new T; const T* p2 = static_cast<const T*>(static_cast<void *>(p1)); bool b = p1 == p2; // b will have the value true.
Proposed resolution:
Change 5.2.9 [expr.static.cast] paragraph 10 as indicated:
A value of type pointer to object converted to "pointer to
cv void" and back to the original pointer
type, possibly with different cv-qualification, will have
its original value. [Example:
T* p1 = new T;
const T* p2 = static_cast<const T*>(static_cast<void *>(p1));
bool b = p1 == p2; // b will have the value true.
---end example]
Rationale: The wording "possibly with different cv-qualification" was chosen over the suggested wording to allow for changes in cv-qualification at different levels in a multi-level pointer, rather than only at the object type level.
[Voted into the WP at the September, 2008 meeting.]
There appears to be no provision in the Standard for explicit conversion of a value of a scoped enumeration type to an integral type, even though the inverse conversion is permitted. That is,
enum class E { e };
static_cast<E>(0); // #1: OK
static_cast<int>(E::e); // #2: error
This is because values of scope enumeration types (intentionally) cannot be implicitly converted to integral types (4.5 [conv.prom] and 4.7 [conv.integral]) and 5.2.9 [expr.static.cast] was not updated to permit #2, although #1 is covered by paragraph 8.
Proposed resolution (June, 2008):
Add the following as a new paragraph following 5.2.9 [expr.static.cast] paragraph 8:
A value of a scoped enumeration type (7.2 [dcl.enum]) can be explicitly converted to an integral type. The value is unchanged if the original value can be represented by the specified type. Otherwise, the resulting value is unspecified.
[Voted into WP at April 2005 meeting.]
It is currently not permitted to cast directly between a pointer to function type and a pointer to object type. This conversion is not listed in 5.2.9 [expr.static.cast] and 5.2.10 [expr.reinterpret.cast] and thus requires a diagnostic to be issued. However, if a sufficiently long integral type exists (as is the case in many implementations), it is permitted to cast between pointer to function types and pointer to object types using that integral type as an intermediary.
In C the cast results in undefined behavior and thus does not require a diagnostic, and Unix C compilers generally do not issue one. This fact is used in the definition of the standard Unix function dlsym, which is declared to return void* but in fact may return either a pointer to a function or a pointer to an object. The fact that C++ compilers are required to issue a diagnostic is viewed as a "competitive disadvantage" for the language.
Suggested resolution: Add wording to 5.2.10 [expr.reinterpret.cast] allowing conversions between pointer to function and pointer to object types, if the implementation has an integral data type that can be used as an intermediary.
Several points were raised in opposition to this suggestion:
Martin O'Riordan suggested an alternative approach:
The advantage of this approach is that it would permit writing portable, well-defined programs involving such conversions. However, it breaks the current degree of compatibility between old and new casts, and it adds functionality to dynamic_cast which is not obviously related to its current meaning.
Notes from 04/00 meeting:
Andrew Koenig suggested yet another approach: specify that "no diagnostic is required" if the implementation supports the conversion.
Later note:
It was observed that conversion between function and data pointers is listed as a "common extension" in C99.
Notes on the 10/01 meeting:
It was decided that we want the conversion defined in such a way that it always exists but is always undefined (as opposed to existing only when the size relationship is appropriate, and being implementation-defined in that case). This would allow an implementation to issue an error at compile time if the conversion does not make sense.
Bill Gibbons notes that the definitions of the other similar casts are inconsistent in this regard. Perhaps they should be updated as well.
Proposed resolution (April 2003):
After 5.2.10 [expr.reinterpret.cast] paragraph 6, insert:
A pointer to a function can be explicitly converted to a pointer to a function of a different type. The effect of calling a function through a pointer to a function type (8.3.5 [dcl.fct]) that is not the same as the type used in the definition of the function is undefined. Except that converting an rvalue of type ``pointer to T1'' to the type ``pointer to T2'' (where T1 and T2 are function types) and back to its original type yields the original pointer value, the result of such a pointer conversion is unspecified. [Note: see also 4.10 [conv.ptr] for more details of pointer conversions. ] It is implementation defined whether a conversion from pointer to object to pointer to function and/or a conversion from pointer to function to pointer to object exist.and in paragraph 10:
An lvalue expression of type T1 can be cast to the type ``reference to T2'' if T1 and T2 are object types and an expression of type ``pointer to T1'' can be explicitly converted to the type ``pointer to T2'' using a reinterpret_cast. That is, a reference cast reinterpret_cast< T& >(x) has the same effect as the conversion *reinterpret_cast< T* >(&x) with the built-in & and * operators. The result is an lvalue that refers to the same object as the source lvalue, but with a different type. No temporary is created, no copy is made, and constructors (12.1 [class.ctor]) or conversion functions (12.3 [class.conv]) are not called.
Drafting Note:
If either conversion exists, the implementation already has to define the behavior (paragraph 3).
Notes from April 2003 meeting:
The new consensus is that if the implementation allows this cast, pointer-to-function converted to pointer-to-object converted back to the original pointer-to-function should work; anything else is undefined behavior. If the implementation does not allow the cast, it should be ill-formed.
Tom Plum is investigating a new concept, that of a "conditionally-defined" feature, which may be applicable here.
Proposed Resolution (October, 2004):
(See paper J16/04-0067 = WG21 N1627 for background material and rationale for this resolution. The resolution proposed here differs only editorially from the one in the paper.)
Insert the following into 1.3 [intro.defs] and renumber all following definitions accordingly:
1.3.2 conditionally-supported behavior
behavior evoked by a program construct that is not a mandatory requirement of this International Standard. If a given implementation supports the construct, the behavior shall be as described herein; otherwise, the implementation shall document that the construct is not supported and shall treat a program containing an occurrence of the construct as ill-formed (1.3 [intro.defs]).
Add the indicated words to 1.4 [intro.compliance] paragraph 2, bullet 2:
If a program contains a violation of any diagnosable rule, or an occurrence of a construct described herein as “conditionally-supported” or as resulting in “conditionally-supported behavior” when the implementation does not in fact support that construct, a conforming implementation shall issue at least one diagnostic message, except that
Insert the following as a new paragraph following 5.2.10 [expr.reinterpret.cast] paragraph 7:
Converting a pointer to a function to a pointer to an object type or vice versa evokes conditionally-supported behavior. In any such conversion supported by an implementation, converting from an rvalue of one type to the other and back (possibly with different cv-qualification) shall yield the original pointer value; mappings between pointers to functions and pointers to objects are otherwise implementation-defined.
Change 7.4 [dcl.asm] paragraph 1 as indicated:
The meaning of an An asm declaration evokes conditionally-supported behavior. If supported, its meaning is implementation-defined.
Change 7.5 [dcl.link] paragraph 2 as indicated:
The string-literal indicates the required language linkage. The meaning of the string-literal is implementation-defined. A linkage-specification with a string that is unknown to the implementation is ill-formed. This International Standard specifies the semantics of C and C++ language linkage. Other values of the string-literal evoke conditionally-supported behavior, with implementation-defined semantics. [Note: Therefore, a linkage-specification with a string-literal that is unknown to the implementation requires a diagnostic. When the string-literal in a linkage-specification names a programming language, the spelling of the programming language's name is implementation-defined. [Note: It is recommended that the spelling be taken from the document defining that language, for example Ada (not ADA) and Fortran or FORTRAN (depending on the vintage). The semantics of a language linkage other than C++ or C are implementation-defined. ]
Change 14 [temp] paragraph 4 as indicated:
A template, a template explicit specialization (14.8.3 [temp.expl.spec]), or a class template partial specialization shall not have C linkage. If the linkage of one of these is something other than C or C++, the behavior is implementation-defined result is conditionally-supported behavior, with implementation-defined semantics.
[Voted into WP at April, 2006 meeting.]
Is reinterpret_cast<T*>(null_pointer_constant) guaranteed to yield the null pointer value of type T*?
I think a committee clarification is needed. Here's why: 5.2.10 [expr.reinterpret.cast] par. 8 talks of "null pointer value", not "null pointer constant", so it would seem that
reinterpret_cast<T*>(0)is a normal int->T* conversion, with an implementation-defined result.
However a little note to 5.2.10 [expr.reinterpret.cast] par. 5 says:
Converting an integral constant expression (5.19) with value zero always yields a null pointer (4.10), but converting other expressions that happen to have value zero need not yield a null pointer.Where is this supported in normative text? It seems that either the footnote or paragraph 8 doesn't reflect the intent.
SUGGESTED RESOLUTION: I think it would be better to drop the footnote #64 (and thus the special case for ICEs), for two reasons:
a) it's not normative anyway; so I doubt anyone is relying on the guarantee it hints at, unless that guarantee is given elsewhere in a normative part
b) users expect reinterpret_casts to be almost always implementation dependent, so this special case is a surprise. After all, if one wants a null pointer there's static_cast. And if one wants reinterpret_cast semantics the special case requires doing some explicit cheat, such as using a non-const variable as intermediary:
int v = 0; reinterpret_cast<T*>(v); // implementation defined reinterpret_cast<T*>(0); // null pointer value of type T* const int w = 0; reinterpret_cast<T*>(w); // null pointer value of type T*
It seems that not only that's providing a duplicate functionality, but also at the cost to hide what seems the more natural one.
Notes from October 2004 meeting:
This footnote was added in 1996, after the invention of reinterpret_cast, so the presumption must be that it was intentional. At this time, however, the CWG feels that there is no reason to require that reinterpret_cast<T*>(0) produce a null pointer value as its result.
Proposed resolution (April, 2005):
Delete the footnote in 5.2.10 [expr.reinterpret.cast] paragraph 5 reading,
Converting an integral constant expression (5.19 [expr.const]) with value zero always yields a null pointer (4.10 [conv.ptr]), but converting other expressions that happen to have value zero need not yield a null pointer.
Add the indicated note to 5.2.10 [expr.reinterpret.cast] paragraph 8:
The null pointer value (4.10 [conv.ptr]) is converted to the null pointer value of the destination type. [Note: A null pointer constant, which has integral type, is not necessarily converted to a null pointer value. —end note]
[Voted into WP at October 2003 meeting.]
An assignment returns an lvalue for its left operand. If that operand refers to a bit field, can the "&" operator be applied to the assignment? Can a reference be bound to it?
struct S { int a:3; int b:3; int c:3; };
void f()
{
struct S s;
const int *p = &(s.b = 0); // (a)
const int &r = (s.b = 0); // (b)
int &r2 = (s.b = 0); // (c)
}
Notes from the 4/02 meeting:
The working group agreed that this should be an error.
Proposed resolution (October 2002):
In 5.3.2 [expr.pre.incr] paragraph 1 (prefix "++" and "--" operators), change
The value is the new value of the operand; it is an lvalue.to
The result is the updated operand; it is an lvalue, and it is a bit-field if the operand is a bit-field.
In 5.16 [expr.cond] paragraph 4 ("?" operator), add the indicated text:
If the second and third operands are lvalues and have the same type, the result is of that type and is an lvalue and it is a bit-field if the second or the third operand is a bit-field, or if both are bit-fields.
In 5.17 [expr.ass] paragraph 1 (assignment operators) add the indicated text (the original text is as updated by issue 221, which is DR but not in TC1):
The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue with the type and value of the left operand after the assignment has taken place. The result in all cases is a bit-field if the left operand is a bit-field.
Note that issue 222 adds (non-conflicting) text at the end of this same paragraph (5.17 [expr.ass] paragraph 1).
In 5.18 [expr.comma] paragraph 1 (comma operator), change:
The type and value of the result are the type and value of the right operand; the result is an lvalue if its right operand is.to
The type and value of the result are the type and value of the right operand; the result is an lvalue if the right operand is an lvalue, and is a bit-field if the right operand is an lvalue and a bit-field.
Relevant related text (no changes required):
5.3.1 [expr.unary.op] paragraph 4:
The operand of & shall not be a bit-field.
8.5.3 [dcl.init.ref] paragraph 5, bullet 1, sub-bullet 1 (regarding binding a reference to an lvalue):
... is an lvalue (but is not a bit-field) ...
[Voted into the WP at the September, 2008 meeting.]
[Picked up by evolution group at October 2002 meeting.]
(See also issue 476.)
The size requested by an array allocation is computed by multiplying the number of elements requested by the size of each element and adding an implementation-specific amount for overhead. It is possible for this calculation to overflow. Is an implementation required to detect this situation and, for instance, throw std::bad_alloc?
On one hand, the maximum allocation size is one of the implementation limits specifically mentioned in Annex B [implimits], and, according to 1.4 [intro.compliance] paragraph 2, an implementation is only required to "accept and correctly execute" programs that do not violate its resource limits.
On the other hand, it is difficult or impossible for user code to detect such overflows in a portable fashion, especially given that the array allocation overhead is not fixed, and it would be a service to the user to handle this situation gracefully.
Rationale (04/01):
Each implementation is required to document the maximum size of an object (Annex B [implimits]). It is not difficult for a program to check array allocations to ensure that they are smaller than this quantity. Implementations can provide a mechanism in which users concerned with this problem can request extra checking before array allocations, just as some implementations provide checking for array index and pointer validity. However, it would not be appropriate to require this overhead for every array allocation in every program.
(See issue 624 for a request to reconsider this resolution.)
Note (March, 2008):
The Evolution Working Group has accepted the intent of this issue and referred it to CWG for action for C++0x (see paper J16/07-0033 = WG21 N2173).
Proposed resolution (September, 2008):
This issue is resolved by the resolution of issue 624, given in paper N2757.
[Voted into WP at October 2005 meeting.]
In 5.3.4 [expr.new], the standard says that the expression in an array-new has to have integral type. There's already a DR (issue 74) that says it should also be allowed to have enumeration type. But, it should probably also say that it can have a class type with a single conversion to integral type; in other words the same thing as in 6.4.2 [stmt.switch] paragraph 2.
Suggested resolution:
In 5.3.4 [expr.new] paragraph 6, replace "integral or enumeration type (3.9.1 [basic.fundamental])" with "integral or enumeration type (3.9.1 [basic.fundamental]), or a class type for which a single conversion function to integral or enumeration type exists".
Proposed resolution (October, 2004):
Change 5.3.4 [expr.new] paragraph 6 as follows:
Every constant-expression in a direct-new-declarator shall be an integral constant expression (5.19 [expr.const]) and evaluate to a strictly positive value. The expression in a direct-new-declarator shall have be of integral type, or enumeration type (3.9.1), or a class type for which a single conversion function to integral or enumeration type exists (12.3 [class.conv]). If the expression is of class type, the expression is converted by calling the conversion function, and the result of the conversion is used in place of the original expression. The value of the expression shall bewith a non-negative value. [Example: ...
Proposed resolution (April, 2005):
Change 5.3.4 [expr.new] paragraph 6 as follows:
Every constant-expression in a direct-new-declarator shall be an integral constant expression (5.19 [expr.const]) and evaluate to a strictly positive value. The expression in a direct-new-declarator shall have integral or enumeration type (3.9.1 [basic.fundamental]) with a non-negative value be of integral type, enumeration type, or a class type for which a single conversion function to integral or enumeration type exists (12.3 [class.conv]). If the expression is of class type, the expression is converted by calling that conversion function, and the result of the conversion is used in place of the original expression. If the value of the expression is negative, the behavior is undefined. [Example: ...
[Voted into WP at October 2004 meeting.]
What does this example do?
#include <stdio.h>
#include <stdlib.h>
struct A {
void* operator new(size_t alloc_size, size_t dummy=0) {
printf("A::operator new()\n");
return malloc(alloc_size);
};
void operator delete(void* p, size_t s) {
printf("A::delete %d\n", s);
};
A() {printf("A constructing\n"); throw 2;};
};
int
main() {
try {
A* ap = new A;
delete ap;
}
catch(int) {printf("caught\n"); return 1;}
}
The fundamental issue here is whether the deletion-on-throw is driven by the syntax of the new (placement or non-placement) or by signature matching. If the former, the operator delete would be called with the second argument equal to the size of the class. If the latter, it would be called with the second argument 0.
Core issue 127 (in TC1) dealt with this topic. It removed some wording in 15.2 [except.ctor] paragraph 2 that implied a syntax-based interpretation, leaving wording in 5.3.4 [expr.new] paragraph 19 that is signature-based. But there is no accompanying rationale to confirm an explicit choice of the signature-based approach.
EDG and g++ get 0 for the second argument, matching the presumed core issue 127 resolution. But maybe this should be revisited.
Notes from October 2003 meeting:
There was widespread agreement that the compiler shouldn't just silently call the delete with either of the possible values. In the end, we decided it's smarter to issue an error on this case and force the programmer to say what he means.
Mike Miller's analysis of the status quo: 3.7.4.2 [basic.stc.dynamic.deallocation] paragraph 2 says that "operator delete(void*, std::size_t)" is a "usual (non-placement) deallocation function" if the class does not declare "operator delete(void*)." 3.7.4.1 [basic.stc.dynamic.allocation] does not use the same terminology for allocation functions, but the most reasonable way to understand the uses of the term "placement allocation function" in the Standard is as an allocation function that has more than one parameter and thus can (but need not) be called using the "new-placement" syntax described in 5.3.4 [expr.new]. (In considering issue 127, the core group discussed and endorsed the position that, "If a placement allocation function has default arguments for all its parameters except the first, it can be called using non-placement syntax.")
5.3.4 [expr.new] paragraph 19 says that any non-placement deallocation function matches a non-placement allocation function, and that a placement deallocation function matches a placement allocation function with the same parameter types after the first -- i.e., a non-placement deallocation function cannot match a placement allocation function. This makes sense, because non-placement ("usual") deallocation functions expect to free memory obtained from the system heap, which might not be the case for storage resulting from calling a placement allocation function.
According to this analysis, the example shows a placement allocation function and a non-placement deallocation function, so the deallocation function should not be invoked at all, and the memory will just leak.
Proposed Resolution (October 2003):
Add the following text at the end of 5.3.4 [expr.new] paragraph 19:
If the lookup finds the two-parameter form of a usual deallocation function (3.7.4.2 [basic.stc.dynamic.deallocation]), and that function, considered as a placement deallocation function, would have been selected as a match for the allocation function, the program is ill-formed. [Example:--- end example]struct S { // Placement allocation function: static void* operator new(std::size_t, std::size_t); // Usual (non-placement) deallocation function: static void operator delete(void*, std::size_t); }; S* p = new (0) S; // ill-formed: non-placement deallocation function matches // placement allocation function
[Voted into the WP at the September, 2008 meeting (resolution in paper N2757).]
Issue 256 was closed without action, principally on the the grounds that an implementation could provide a means (command-line option, #pragma, etc.) for requesting that the allocation size be checked for validity, but that “it would not be appropriate to require this overhead for every array allocation in every program.”
This rationale may be giving too much weight to the overhead such a check would add, especially when compared to the likely cost of actually doing the storage allocation. In particular, the test essentially amounts to something like
if (max_allocation_size / sizeof(T) < num_elements)
throw std::bad_alloc();
(noting that max_allocation_size/sizeof(T) is a compile-time constant). It might make more sense to turn the rationale around and require the check, assuming that implementations could provide a mechanism for suppressing it if needed.
Suggested resolution:
In 5.3.4 [expr.new] paragraph 7, add the following words before the example:
If the value of the expression is such that the size of the allocated object would exceed the implementation-defined limit, an exception of type std::bad_alloc is thrown and no storage is obtained.
Note (March, 2008):
The Evolution Working Group has accepted the intent of issue 256 and referred it to CWG for action for C++0x (see paper J16/07-0033 = WG21 N2173).
Proposed resolution (March, 2008):
As suggested.
Notes from the June, 2008 meeting:
The CWG felt that this situation should not be treated like an out-of-memory situation and thus an exception of type std::bad_alloc (or, alternatively, returning a null pointer for a throw() allocator) would not be appropriate.
Proposed resolution (June, 2008):
Change 5.3.4 [expr.new] paragraph 8 as follows:
If the value of the expression in a direct-new-declarator is such that the size of the allocated object would exceed the implementation-defined limit, no storage is obtained and the new-expression terminates by throwing an exception of a type that would match a handler (15.3 [except.handle]) of type std::length_error (19.2.4 [length.error]). Otherwise, if When the value of the that expression in a direct-new-declarator is zero, the allocation function is called to allocate an array with no elements.
[Drafting note: std::length_error is thrown by std::string and std::vector and thus appears to be the right choice for the exception to be thrown here.]
[Voted into the WP at the June, 2008 meeting.]
For delete expressions, 5.3.5 [expr.delete] paragraph 1 says
The operand shall have a pointer type, or a class type having a single conversion function to a pointer type.
However, paragraph 3 of that same section says:
if the static type of the operand is different from its dynamic type, the static type shall be a base class of the operand's dynamic type and the static type shall have a virtual destructor or the behavior is undefined.
Since the operand must be of pointer type, its static type is necessarily the same as its dynamic type. That clause is clearly referring to the object being pointed at, and not to the pointer operand itself.
Correcting the wording gets a little complicated, because dynamic and static types are attributes of expressions, not objects, and there's no sub-expression of a delete-expression which has the relevant types.
Suggested resolution:
then there is a static type and a dynamic type that the hypothetical expression (* const-expression) would have. If that static type is different from that dynamic type, then that static type shall be a base class of that dynamic type, and that static type shall have a virtual destructor, or the behavior is undefined.
There's precedent for such use of hypothetical constructs: see 5.10 [expr.eq] paragraph 2, and 8.1 [dcl.name] paragraph 1.
10.3 [class.virtual] paragraph 3 has a similar problem. It refers to
the type of the pointer or reference denoting the object (the static type).
The type of the pointer is different from the type of the reference, both of which are different from the static type of '*pointer', which is what I think was actually intended. Paragraph 6 contains the exact same wording, in need of the same correction. In this case, perhaps replacing "pointer or reference" with "expression" would be the best fix. In order for this fix to be sufficient, pointer->member must be considered equivalent to (*pointer).member, in which case the "expression" referred to would be (*pointer).
12.5 [class.free] paragraph 4 says thatif a delete-expression is used to deallocate a class object whose static type has...
This should be changed to
if a delete-expression is used to deallocate a class object through a pointer expression whose dereferenced static type would have...
The same problem occurs later, when it says that the
static and dynamic types of the object shall be identical
In this case you could replace "object" with "dereferenced pointer expression".
Footnote 104 says that
5.3.5 [expr.delete] requires that ... the static type of the delete-expression's operand be the same as its dynamic type.
This would need to be changed to
the delete-expression's dereferenced operand
Proposed resolution (December, 2006):
Change 5.3.5 [expr.delete] paragraph 3 as follows:
In the first alternative (delete object), if the static type of the operand object to be deleted is different from its dynamic type, the static type shall be a base class of the operand’s dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined. In the second alternative (delete array) if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined.
Change the footnote in 12.5 [class.free] paragraph 4 as follows:
A similar provision is not needed for the array version of operator delete because 5.3.5 [expr.delete] requires that in this situation, the static type of the delete-expression’s operand object to be deleted be the same as its dynamic type.
Change the footnote in 12.5 [class.free] paragraph 5 as follows:
If the static type in the delete-expression of the object to be deleted is different from the dynamic type and the destructor is not virtual the size might be incorrect, but that case is already undefined; see 5.3.5 [expr.delete].
[Drafting notes: No change is required for 10.3 [class.virtual] paragraph 7 because “the type of the pointer” includes the pointed-to type. No change is required for 12.5 [class.free] paragraph 4 because “...used to deallocate a class object whose static type...” already refers to the object (and not the operand expression).]
[Voted into WP at April 2003 meeting.]
In a couple of comp.std.c++ threads, people have asked whether the Standard guarantees that the deallocation function will be called in a delete-expression if the destructor throws an exception. Most/all people have expressed the opinion that the deallocation function must be called in this case, although no one has been able to cite wording in the Standard supporting that view.
#include <new.h>
#include <stdio.h>
#include <stdlib.h>
static int flag = 0;
inline
void operator delete(void* p) throw()
{
if (flag)
printf("in deallocation function\n");
free(p);
}
struct S {
~S() { throw 0; }
};
void f() {
try {
delete new S;
}
catch(...) { }
}
int main() {
flag=1;
f();
flag=0;
return 0;
}
Proposed resolution (October 2002):
Add to 5.3.5 [expr.delete] paragraph 7 the highlighted text:
The delete-expression will call a deallocation function (3.7.4.2 [basic.stc.dynamic.deallocation]) [Note: The deallocation function is called regardless of whether the destructor for the object or some element of the array throws an exception. ]
[Voted into WP at October 2005 meeting.]
After some discussion in comp.lang.c++.moderated we came to the conclusion that there seems to be a defect in 5.3.5 [expr.delete]/4, which says:
The cast-expression in a delete-expression shall be evaluated exactly once. If the delete-expression calls the implementation deallocation function (3.7.3.2), and if the operand of the delete expression is not the null pointer constant, the deallocation function will deallocate the storage referenced by the pointer thus rendering the pointer invalid. [Note: the value of a pointer that refers to deallocated storage is indeterminate. ]
In the second sentence, the term "null pointer constant" should be changed to "null pointer". In its present form, the passage claims that the deallocation function will deallocate the storage refered to by a null pointer that did not come from a null pointer constant in the delete expression. Besides, how can the null pointer constant be the operand of a delete expression, as "delete 0" is an error because delete requires a pointer type or a class type having a single conversion function to a pointer type?
See also issue 348.
Proposed resolution:
Change the indicated sentence of 5.3.5 [expr.delete] paragraph 4 as follows:
If the delete-expression calls the implementation deallocation function (3.7.4.2 [basic.stc.dynamic.deallocation]), and if the value of the operand of the delete expression is not the a null pointer constant, the deallocation function will deallocate the storage referenced by the pointer thus rendering the pointer invalid.
Notes from October 2004 meeting:
This wording is superseded by, and this issue will be resolved by, the resolution of issue 348.
Proposed resolution (April, 2005):
This issue is resolved by the resolution of issue 348.
[Voted into the WP at the September, 2008 meeting.]
The specification for the alignof operator (5.3.6 [expr.alignof]) does not forbid function types as operands, although it probably should.
Proposed resolution (March, 2008):
The issue, as described, is incorrect. The requirement in 5.3.6 [expr.alignof] is for “a complete object type,” so a function type is already forbidden. However, the existing text does have a problem in this requirement in that it does not allow a reference type, as anticipated by paragraph 3. Consequently, the proposal is to change 5.3.6 [expr.alignof] paragraph 1 as indicated:
An alignof expression yields the alignment requirement of its operand type. The operand shall be a type-id representing a complete object type or a reference to a complete object type.
[Voted into WP at April, 2007 meeting.]
5.4 [expr.cast] paragraph 6 says,
The operand of a cast using the cast notation can be an rvalue of type “pointer to incomplete class type”. The destination type of a cast using the cast notation can be “pointer to incomplete class type”. In such cases, even if there is a inheritance relationship between the source and destination classes, whether the static_cast or reinterpret_cast interpretation is used is unspecified.
The wording seems to allow the following:
casting from void pointer to incomplete type
struct A;
struct B;
void *v;
A *a = (A*)v; // allowed to choose reinterpret_cast
variant application of static or reinterpret casting
B *b = (B*)a; // compiler can choose static_cast here
A *aa = (A*)b; // compiler can choose reinterpret_cast here
assert(aa == a); // might not hold
ability to somehow choose static_cast
It's not entirely clear how a compiler can
choose static_cast as 5.4 [expr.cast] paragraph 6
seems to allow. I believe the intent of 5.4 [expr.cast]
paragraph 6 is to force the use of reinterpret_cast when
either are incomplete class types and static_cast iff the
compiler knows both types and there is a non-ambiguous
hierarchy-traversal between that cast (or maybe not, core issue 242 talks about this). I cannot see any
other interpretation because it isn't intuitive, every compiler I've
tried agrees with me, and neither standard pointer conversions
(4.10 [conv.ptr] paragraph 3) nor static_cast
(5.2.9 [expr.static.cast] paragraph 5) talk about incomplete
class types. If the committee agrees with me, I would like to see
Proposed resolution (April, 2006):
Change 5.4 [expr.cast] paragraph 6 as indicated:
The operand of a cast using the cast notation can be an rvalue of type “pointer to incomplete class type.” The destination type of a cast using the cast notation can be “pointer to incomplete class type.” In such cases, even if there is a inheritance relationship between the source and destination classes, whether the static_cast or reinterpret_cast interpretation is used is unspecified. If both the operand and destination types are class types and one or both are incomplete, it is unspecified whether the static_cast or the reinterpret_cast interpretation is used, even if there is an inheritance relationship between the two classes. [Note: For example, if the classes were defined later in the translation unit, a multi-pass compiler would be permitted to interpret a cast between pointers to the classes as if the class types were complete at that point. —end note]
[Voted into WP at October 2005 meeting.]
5.5 [expr.mptr.oper] paragraph 5 contains the following example:
struct S {
mutable int i;
};
const S cs;
int S::* pm = &S::i; // pm refers to mutable member S::i
cs.*pm = 88; // ill-formed: cs is a const object
The const object cs is not explicitly initialized, and class S does not have a user-declared default constructor. This makes the code ill-formed as per 8.5 [dcl.init] paragraph 9.
Proposed resolution (April, 2005):
Change the example in 5.5 [expr.mptr.oper] paragraph 5 to read as follows:
struct S {
S() : i(0) { }
mutable int i;
};
void f()
{
const S cs;
int S::* pm = &S::i; // pm refers to mutable member S::i
cs.*pm = 88; // ill-formed: cs is a const object
}
[Voted into the WP at the September, 2008 meeting as part of paper N2757.]
The current Standard leaves it implementation-defined whether integer division rounds the result toward 0 or toward negative infinity and thus whether the result of % may be negative. C99, apparently reflecting (nearly?) unanimous hardware practice, has adopted the rule that integer division rounds toward 0, thus requiring that the result of -1 % 5 be -1. Should the C++ Standard follow suit?
On a related note, does INT_MIN % -1 invoke undefined behavior? The % operator is defined in terms of the / operator, and INT_MIN / -1 overflows, which by 5 [expr] paragraph 5 causes undefined behavior; however, that is not the “result” of the % operation, so it's not clear. The wording of 5.6 [expr.mul] paragraph 4 appears to allow % to cause undefined behavior only when the second operand is 0.
Proposed resolution (August, 2008):
Change 5.6 [expr.mul] paragraph 4 as follows:
The binary / operator yields the quotient, and the binary % operator yields the remainder from the division of the first expression by the second. If the second operand of / or % is zero the behavior is undefined; otherwise (a/b)*b + a%b is equal to a. If both operands are nonnegative then the remainder is nonnegative; if not, the sign of the remainder is implementation-defined. [Footnote: According to work underway toward the revision of ISO C, the preferred algorithm for integer division follows the rules defined in the ISO Fortran standard, ISO/IEC 1539:1991, in which the quotient is always rounded toward zero. —end footnote]. For integral operands, the / operator yields the algebraic quotient with any fractional part discarded; [Footnote: This is often called “truncation towards zero.” —end footnote] if the quotient a/b is representable in the type of the result, (a/b)*b + a%b is equal to a.
[Drafting note: see C99 6.5.5 paragraph 6.]
[Voted into the WP at the June, 2008 meeting.]
The actual semantics of arithmetic comparison — e.g., whether 1 < 2 yields true or false — appear not to be specified anywhere in the Standard. The C Standard has a general statement that
Each of the operators < (less than), > (greater than), <= (less than or equal to), and >= (greater than or equal to) shall yield 1 if the specified relation is true and 0 if it is false.
There is no corresponding statement in the C++ Standard.
Proposed resolution (February, 2008):
Append the following paragraph to the end of 5.9 [expr.rel]:
If both operands (after conversions) are of arithmetic type, each of the operators shall yield true if the specified relation is true and false if it is false.
Append the following paragraph to the end of 5.10 [expr.eq]:
Each of the operators shall yield true if the specified relation is true and false if it is false.
[Voted into WP at October 2005 meeting.]
The problem occurs when the value of the operator is determined to be an rvalue, the selected argument is an lvalue, the type is a class type and a non-const member is invoked on the modifiable rvalue result.
struct B {
int v;
B (int v) : v(v) { }
void inc () { ++ v; }
};
struct D : B {
D (int v) : B(v) { }
};
B b1(42);
(0 ? B(13) : b1).inc();
assert(b1.v == 42);
The types of the second and third operands are the same and one is an rvalue. Nothing changes until p6 where an lvalue to rvalue conversion is performed on the third operand. 12.2 [class.temporary] states that an lvalue to rvalue conversion produces a temporary and there is nothing to remove it. It seems clear that the assertion must pass, yet most implementations fail.
There seems to be a defect in p3 b2 b1. First, the conditions to get here and pass the test.
If E1 and E2 have class type, and the underlying class types are the same or one is a base class of the other: E1 can be converted to match E2 if the class of T2 is the same type as, or a base class of, the class of T1, and the cv-qualification of T2 is the same cv-qualification as, or a greater cv-qualification than, the cv-qualification of T1.
If both E1 and E2 are lvalues, passing the conditions here also passes the conditions for p3 b1. Thus, at least one is an rvalue. The case of two rvalues is not interesting and the action is covered by the case when E1 is an rvalue.
(0 ? D(13) : b1).inc();
assert(b1.v == 42);
E1 is changed to an rvalue of type T2 that still refers to the original source class object (or the appropriate subobject thereof). [Note: that is, no copy is made. ]
Having changed the rvalue to base type, we are back to the above case where an lvalue to rvalue conversion is required on the third operand at p6. Again, most implementations fail.
The remaining case, E1 an lvalue and E2 an rvalue, is the defect.
D d1(42);
(0 ? B(13) : d1).inc();
assert(d1.v == 42);
The above quote states that an lvalue of type T1 is changed to an rvalue of type T2 without creating a temporary. This is in contradiction to everything else in the standard about lvalue to rvalue conversions. Most implementations pass in spite of the defect.
The usual accessible and unambiguous is missing from the base class.
There seems to be two possible solutions. Following other temporary creations would produce a temporary rvalue of type T1 and change it to an rvalue of type T2. Keeping the no copy aspect of this bullet intact would change the lvalue of type T1 to an lvalue of type T2. In this case the lvalue to rvalue conversion would happen in p6 as usual.
Suggested wording for p3 b2 b1
The base part:
If E1 and E2 have class type, and the underlying class types are the same or one is a base class of the other: E1 can be converted to match E2 if the class of T2 is the same type as, or an accessible and unambiguous base class of, the class of T1, and the cv-qualification of T2 is the same cv-qualification as, or a greater cv-qualification than, the cv-qualification of T1. If the conversion is applied:
The same type temporary version:
If E1 is an lvalue, an lvalue to rvalue conversion is applied. The resulting or original rvalue is changed to an rvalue of type T2 that refers to the same class object (or the appropriate subobject thereof). [Note: that is, no copy is made in changing the type of the rvalue. ]
The never copy version:
The lvalue(rvalue) E1 is changed to an lvalue(rvalue) of type T2 that refers to the original class object (or the appropriate subobject thereof). [Note: that is, no copy is made. ]
The test case was posted to clc++m and results for implementations were reported.
#include <cassert>
struct B {
int v;
B (int v) : v(v) { }
void inc () { ++ v; }
};
struct D : B {
D (int v) : B(v) { }
};
int main () {
B b1(42);
D d1(42);
(0 ? B(13) : b1).inc();
assert(b1.v == 42);
(0 ? D(13) : b1).inc();
assert(b1.v == 42);
(0 ? B(13) : d1).inc();
assert(d1.v == 42);
}
// CbuilderX(EDG301) FFF Rob Williscroft
// ICC-8.0 FFF Alexander Stippler
// COMO-4.301 FFF Alexander Stippler
// BCC-5.4 FFP Rob Williscroft
// BCC32-5.5 FFP John Potter
// BCC32-5.65 FFP Rob Williscroft
// VC-6.0 FFP Stephen Howe
// VC-7.0 FFP Ben Hutchings
// VC-7.1 FFP Stephen Howe
// OpenWatcom-1.1 FFP Stephen Howe
// Sun C++-6.2 PFF Ron Natalie
// GCC-3.2 PFP John Potter
// GCC-3.3 PFP Alexander Stippler
// GCC-2.95 PPP Ben Hutchings
// GCC-3.4 PPP Florian Weimer
I see no defect with regards to lvalue to rvalue conversions; however, there seems to be disagreement about what it means by implementers. It may not be surprising because 5.16 and passing a POD struct to an ellipsis are the only places where an lvalue to rvalue conversion applies to a class type. Most lvalue to rvalue conversions are on basic types as operands of builtin operators.
Notes from the March 2004 meeting:
We decided all "?" operators that return a class rvalue should copy the second or third operand to a temporary. See issue 86.
Proposed resolution (October 2004):
Change 5.16 [expr.cond] paragraph 3 bullet 2 sub-bullet 1 as follows:
if E1 and E2 have class type, and the underlying class types are the same or one is a base class of the other: E1 can be converted to match E2 if the class of T2 is the same type as, or a base class of, the class of T1, and the cv-qualification of T2 is the same cv-qualification as, or a greater cv-qualification than, the cv-qualification of T1. If the conversion is applied, E1 is changed to an rvalue of type T2 that still refers to the original source class object (or the appropriate subobject thereof). [Note: that is, no copy is made. —end note] by copy-initializing a temporary of type T2 from E1 and using that temporary as the converted operand.
Change 5.16 [expr.cond] paragraph 6 bullet 1 as follows:
The second and third operands have the same type; the result is of that type. If the operands have class type, the result is an rvalue temporary of the result type, which is copy-initialized from either the second operand or the third operand depending on the value of the first operand.
Change 4.1 [conv.lval] paragraph 2 as follows:
The value contained in the object indicated by the lvalue is the rvalue result. When an lvalue-to-rvalue conversion occurs within the operand of sizeof (5.3.3 [expr.sizeof]) the value contained in the referenced object is not accessed, since that operator does not evaluate its operand. Otherwise, if the lvalue has a class type, the conversion copy-initializes a temporary of type T from the lvalue and the result of the conversion is an rvalue for the temporary. Otherwise, the value contained in the object indicated by the lvalue is the rvalue result.
[Note: this wording partially resolves issue 86. See also issue 462.]
[Voted into the WP at the June, 2008 meeting as paper N2634.]
I've seen some pieces of code recently that put complex expressions involving overload resolution inside sizeof operations in constant expressions in templates.
5.19 [expr.const] paragraph 1 implies that some kinds of nonconstant expressions are allowed inside a sizeof in a constant expression, but it's not clear that this was intended to extend all the way to things like overload resolution. Allowing such things has some hidden costs. For example, name mangling has to be able to represent all operators, including calls, and not just the operators that can appear in constant expressions.
template <int I> struct A {};
char xxx(int);
char xxx(float);
template <class T> A<sizeof(xxx((T)0))> f(T){}
int main()
{
f(1);
}
If complex expressions are indeed allowed, it should be because of an explicit committee decision rather than because of some looseness in this section of the standard.
Notes from the 4/02 meeting:
Any argument for restricting such expressions must involve a cost/benefit ratio: a restriction would be palatable only if it causes minimum hardship for users and allows a substantial reduction in implementation cost. If we propose a restriction, it must be one that library writers can live with.
Lots of these cases fail with current compilers, so there can't be a lot of existing code using them. We plan to find out what cases there are in libraries like Loki and Boost.
We noted that in many cases one can move the code into a class to get the same result. The implementation problem comes up when the expression-in-sizeof is in a template deduction context or part of a template signature. The problem cases are ones where an error causes deduction to fail, as opposed to contexts where an error causes a diagnostic. The latter contexts are easier to handle; however, there are situations where "fail deduction" may be the desired behavior.
Notes from the April 2003 meeting:
Here is a better example:
extern "C" int printf(const char *, ...);
char f(int);
int f(...);
// Approach 1 -- overload resolution in template class
// No problem
template <class T> struct conv_int {
static const bool value = (sizeof(f(T())) == 1);
};
// Approach 2 -- overload resolution in type deduction
// Difficult
template <int I> struct A {
static const int value = I;
};
template <class T> bool conv_int2(A<sizeof(f(T()))> p) {
return p.value == 1;
}
template<typename T>
A<sizeof(f(T()))> make_A() {
return A<sizeof(f(T()))>();
}
int main() {
printf("short: %d\n", conv_int<short>::value);
printf("int *: %d\n", conv_int<int *>::value);
printf("short: %d\n", conv_int2<short>(make_A<short>()));
printf("int *: %d\n", conv_int2<int *>(make_A<int*>()));
}
The core working group liked the idea of a restriction that says that expressions inside sizeof in template signature contexts must be otherwise valid as nontype template argument expressions (i.e., integer operations only, limited casts). This of course is subject to whether users can live with that restriction. This topic was brought up in full committee, but there was limited feedback from other groups.
It was also noted that if typeof (whatever it is called) is added, there may be a similar issue there.
Note (March, 2005):
Dave Abrahams (quoting a Usenet posting by Vladimir Marko): The decltype and auto proposal (revision 3: N1607) presents
template <class T,class U>
decltype((*(T*)0)+(*(U*)0)) add(const T& t,const U& u);
as a valid declaration (if the proposal is accepted). If [the restrictions in the April, 2003 note] really applied to decltype, the declaration above would be invalid. AFAICT every non-trivial use of decltype in a template function declaration would be invalid. And for me this would render my favorite proposal useless.
I would propose to allow any kind of expression inside sizeof (and decltype) and explicitly add sizeof (and decltype) expressions involving template-parameters to non-deduced contexts (add a bullet to 14.9.2.4 [temp.deduct.partial] paragraph 4).
Jaakko Jarvi: Just reinforcing that this is important and hope for insights. The topic is discussed a bit on page 10 of the latest revision of the proposal (N1705). Here's a quote from the proposal:
However, it is crucial that no restrictions are placed on what kinds of expressions are allowed inside decltype, and therefore also inside sizeof. We suggest that issue 339 is resolved to require the compiler to fail deduction (apply the SFINAE principle), and not produce an error, for as large set of invalid expressions in operands of sizeof or decltype as is possible to comfortably implement. We wish that implementors aid in classifying the kinds of expressions that should produce errors, and the kinds that should lead to failure of deduction.
Notes from the April, 2007 meeting:
The CWG is pursuing a compromise proposal, to which the EWG has tentatively agreed, which would allow arbitrary expressions in the return types of function templates but which would restrict the expressions that participate in the function signature (and thus in overload resolution) to those that can be used as non-type template arguments. During deduction and overload resolution, these complex return types would be ignored; that is, there would be no substitution of the deduced template arguments into the return type at this point. If such a function were selected by overload resolution, however, a substitution failure in the return type would produce a diagnostic rather than a deduction failure.
This approach works when doing overload resolution in the context of a function call, but additional tricks (still being defined) are needed in other contexts such as friend function declaration matching and taking the address of a function, in which the return type does play a part.
Notes from the July, 2007 meeting:
The problem is whether arbitrary expressions (for example, ones that include overload resolution) are allowed in template deduction contexts, and, if so, which expression errors are SFINAE failures and which are hard errors.
This issue deals with arbitrary expressions inside sizeof in deduction contexts. That's a fringe case right now (most compilers don't accept them). decltype makes the problem worse, because the standard use case is one that involves overload resolution. Generalized constant expressions make it worse yet, because they allow overload resolution and class types to show up in any constant expression in a deduction context.
Why is this an issue? Why don't we just say everything is allowed and be done with it?
At the April, 2007 meeting, we were headed toward a solution that imposed a restriction on expressions in deduction contexts, but such a restriction seems to really hamper uses of constexpr functions. So we're now proposing that fully general expressions be allowed, and that most errors in such expressions be treated as SFINAE failures rather than errors.
One issue with writing Standard wording for that is how to define “most.” There's a continuum of errors, some errors being clearly SFINAE failures, and some clearly “real” errors, with lots of unclear cases in between. We decided it's easier to write the definition by listing the errors that are not treated as SFINAE failures, and the list we came up with is as follows:
Everything else produces a SFINAE failure rather than a hard error.
There was broad consensus that this felt like a good solution, but that feeling was mixed with trepidation on several fronts:
We will be producing wording for the Working Draft for the October, 2007 meeting.
(See also issue 657.)
[Voted into WP at October 2003 meeting.]
According to 16.1 [cpp.cond] paragraph 1, the if-group
#if "Hello, world"
is well-formed, since it is an integral constant expression. Since that may not be obvious, here is why:
5.19 [expr.const] paragraph 1 says that an integral constant expression may involve literals (2.14 [lex.literal]); "Hello, world" is a literal. It restricts operators to not use certain type conversions; this expression does not use type conversions. It further disallows functions, class objects, pointers, ... - this expression is none of those, since it is an array.
However, 16.1 [cpp.cond] paragraph 6 does not explain what to do with this if-group, since the expression evaluates neither to false(zero) nor true(non-zero).
Proposed resolution (October 2002):
Change the beginning of the second sentence of 5.19 [expr.const] paragraph 1 which currently reads
An integral constant-expression can involve only literals (2.14 [lex.literal]), ...to say
An integral constant-expression can involve only literals of arithmetic types (2.14 [lex.literal], 3.9.1 [basic.fundamental]), ...
[Voted into WP at the October, 2006 meeting.]
The following translation unit appears to be well-formed.
int x[true?throw 4:5];
According to 5.19 [expr.const], this appears to be an integral constant expression: it is a conditional expression, involves only literals, and no assignment, increment, decrement, function-call, or comma operators. However, if this is well-formed, the standard gives no meaning to this declaration, since the array bound (8.3.4 [dcl.array] paragraph 1) cannot be computed.
I believe the defect is that throw expressions should also be banned from constant expressions.
Notes from October 2002 meeting:
We should also check on new and delete.
Notes from the April, 2005 meeting:
Although it could be argued that all three of these operators potentially involve function calls — throw to std::terminate, new and delete to the corresponding allocation and deallocation functions — and thus would already be excluded from constant expressions, this reasoning was considered to be too subtle to allow closing the issue with no change. A modification that explicitly clarifies the status of these operators will be drafted.
Proposed resolution (October, 2005):
Change the last sentence of 5.19 [expr.const] as indicated:
In particular, except in sizeof expressions, functions, class objects, pointers, or references shall not be used, and assignment, increment, decrement, function-call function call (including new-expressions and delete-expressions), or comma operators, or throw-expressions shall not be used.
Note: this sentence is also changed by the resolution of issue 530.
[Voted into WP at April 2005 meeting.]
I'm looking at 5.19 [expr.const]. I see:
An integral constant-expression can involve only ... const variables or static data members of integral or enumeration types initialized with constant expressions ...
Shouldn't that be "const non-volatile"?
It seems weird to say that:
const volatile int i = 3; int j[i];is valid.
Steve Adamczyk: See issue 76, which made the similar change to 7.1.6.1 [dcl.type.cv] paragraph 2, and probably should have changed this one as well.
Proposed resolution (October, 2004):
Change the first sentence in the second part of 5.19 [expr.const] paragraph 1 as follows:
An integral constant-expression can involve only literals of arithmetic types (2.14 [lex.literal], 3.9.1 [basic.fundamental]), enumerators, non-volatile const variables or static data members of integral or enumeration types initialized with constant expressions (8.5 [dcl.init]), non-type template parameters of integral or enumeration types, and sizeof expressions.
[Voted into the WP at the April, 2007 meeting as part of paper J16/07-0095 = WG21 N2235.]
Consider:
template <int* p> struct S {
static const int I = 3;
};
int i;
int a[S<&i>::I];
Clearly this should be valid, but a pedantic reading of 5.19 [expr.const] would suggest that this is invalid because “&i” is not permitted in integral constant expressions.
Proposed resolution (October, 2005):
Change the last sentence of 5.19 [expr.const] paragraph 1 as indicated:
In particular, except in non-type template-arguments or 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.
(Note: the same text is changed by the resolution of issue 367.)
Notes from April, 2006 meeting:
The proposed resolution could potentially be read as saying that the prohibited operations and operators would be permitted in integral constant expressions that are non-type template-arguments. John Spicer is investigating an alternate approach, to say that expressions in non-type template arguments are not part of the expression in which the template-id appears (in contrast to the operand of sizeof, which is part of the containing expression).
Additional note (May, 2008):
This issue is resolved by the rewrite of
[Voted into the WP at the September, 2008 meeting (resolution in paper N2757).]
The expressions that are excluded from being constant expressions in 5.19 [expr.const] paragraph 2 does not address an example like the following:
void f() {
int a;
constexpr int* p = &a; // should be ill-formed, currently isn't
}
Suggested resolution:
Add the following bullet to the list in 5.19 [expr.const] paragraph 2:
an id-expression that refers to a variable with automatic storage duration unless a permitted lvalue-to-rvalue conversion is applied (see above)
Proposed resolution (June, 2008):
Change 3.6.2 [basic.start.init] paragraph 1 as follows:
Objects with static storage duration (3.7.1 [basic.stc.static]) or thread storage duration (3.7.2) shall be zero-initialized (8.5 [dcl.init]) before any other initialization takes place. A reference with static or thread storage duration and an object of trivial or literal type with static or thread storage duration can be initialized with a constant expression (5.19 [expr.const]); this is called constant initialization. Constant initialization is performed:Together, zero-initialization and constant initialization...
if an object of trivial or literal type with static or thread storage duration is initialized with a constant expression (5.19 [expr.const]), or
if a reference with static or thread storage duration is initialized with a constant expression that is not an lvalue designating an object with thread or automatic storage duration.
Add the following in 5.19 [expr.const] paragraph 2:
an lvalue-to-rvalue conversion (4.1) unless it is applied to...
an array-to-pointer conversion (4.2 [conv.array]) that is applied to an lvalue that designates an object with thread or automatic storage duration
a unary operator & (5.3.1 [expr.unary.op]) that is applied to an lvalue that designates an object with thread or automatic storage duration
an id-expression that refers to a variable or data member of reference type;
...
(Note: the change to 3.6.2 [basic.start.init] paragraph 1 needs to be reconciled with the conflicting change in issue 688.)
[Voted into the WP at the June, 2008 meeting.]
According to 6.6 [stmt.jump] paragraph 2,
On exit from a scope (however accomplished), destructors (12.4 [class.dtor]) are called for all constructed objects with automatic storage duration (3.7.3 [basic.stc.auto]) (named objects or temporaries) that are declared in that scope, in the reverse order of their declaration.
This wording is problematic for temporaries and for parameters. First, temporaries are not "declared," so this requirement does not apply to them, in spite of the assertion in the quoted text that it does.
Second, although the parameters of a function are declared in the called function, they are constructed and destroyed in the calling context, and the order of evaluation of the arguments is unspecified (cf 5.2.2 [expr.call] paragraphs 4 and 8). The order of destruction of the parameters might, therefore, be different from the reverse order of their declaration.
Notes from 04/01 meeting:
Any resolution of this issue should be careful not to introduce requirements that are redundant or in conflict with those of other parts of the IS. This is especially true in light of the pending issues with respect to the destruction of temporaries (see issues 86, 124, 199, and 201). If possible, the wording of a resolution should simply reference the relevant sections.
It was also noted that the temporary for a return value is also destroyed "out of order."
Note that issue 378 picks a nit with the wording of this same paragraph.
Proposed Resolution (November, 2006):
Change 6.6 [stmt.jump] paragraph 2 as follows:
On exit from a scope (however accomplished), destructors (12.4 [class.dtor]) are called for all constructed objects with automatic storage duration (3.7.3 [basic.stc.auto]) (named objects or temporaries) that are declared in that scope, in the reverse order of their declaration. variables with automatic storage duration (3.7.3 [basic.stc.auto]) that have been constructed in that scope are destroyed in the reverse order of their construction. [Note: For temporaries, see 12.2 [class.temporary]. —end note] Transfer out of a loop...
[Moved to DR at October 2002 meeting.]
There is currently no restriction on the use of the inline specifier in friend declarations. That would mean that the following usage is permitted:
struct A {
void f();
};
struct B {
friend inline void A::f();
};
void A::f(){}
I believe this should be disallowed because a friend declaration in one class should not be able to change attributes of a member function of another class.
More generally, I think that the inline attribute should only be permitted in friend declarations that are definitions.
Notes from the 04/01 meeting:
The consensus agreed with the suggested resolution. This outcome would be similar to the resolution of issue 136.
Proposed resolution (10/01):
Add to the end of 7.1.2 [dcl.fct.spec] paragraph 3:
If the inline specifier is used in a friend declaration, that declaration shall be a definition or the function shall have previously been declared inline.
[Voted into WP at October 2005 meeting.]
Steve Clamage: Consider this sequence of declarations:
void foo() { ... }
inline void foo();
The non-inline definition of foo precedes the inline declaration.
It seems to me this code should be ill-formed, but I could not find
anything in the standard to cover the situation.
Bjarne Stroustrup: Neither could I, so I looked in the ARM, which addressed this case (apparently for member function only) in some detail in 7.1.2 (pp103-104).
The ARM allows declaring a function inline after its initial declaration, as long as it has not been called.
Steve Clamage: If the above code is valid, how about this:
void foo() { ... } // define foo
void bar() { foo(); } // use foo
inline void foo(); // declare foo inline
Bjarne Stroustrup: ... and [the ARM] disallows declaring a function inline after it has been called.
This may still be a good resolution.
Steve Clamage: But the situation in the ARM is the reverse: Declare a function inline, and define it later (with no intervening call). That's a long-standing rule in C++, and allows you to write member function definitions outside the class.
In my example, the compiler could reasonably process the entire function as out-of-line, and not discover the inline declaration until it was too late to save the information necessary for inline generation. The equivalent of another compiler pass would be needed to handle this situation.
Bjarne Stroustrup: I see, and I think your argument it conclusive.
Steve Clamage: I'd like to open a core issue on this point, and I recommend wording along the lines of: "A function defined without an inline specifier shall not be followed by a declaration having an inline specifier."
I'd still like to allow the common idiom
class T {
int f();
};
inline int T::f() { ... }
Martin Sebor: Since the inline keyword is just a hint to the compiler, I don't see any harm in allowing the construct. Your hypothetical compiler can simply ignore the inline on the second declaration. On the other hand, I feel that adding another special rule will unnecessarily complicate the language.
Steve Clamage: The inline specifier is more than a hint. You can have multiple definitions of inline functions, but only one definition of a function not declared inline. In particular, suppose the above example were in a header file, and included multiple times in a program.
Proposed resolution (October, 2004):
Add the indicated words to 7.1.2 [dcl.fct.spec] paragraph 4:
An inline function shall be defined in every translation unit in which it is used and shall have exactly the same definition in every case (3.2 [basic.def.odr]). [Note: a call to the inline function may be encountered before its definition appears in the translation unit. —end note] If the definition of a function appears in a translation unit before its first declaration as inline, the program is ill-formed. If a function with external linkage is declared inline in one translation unit...
[Voted into WP at March 2004 meeting.]
BTW, I noticed that the following note in 7.1.1 [dcl.stc] paragraph 2 doesn't seem to have made it onto the issues list or into the TR:
[Note: hence, the auto specifier is almost always redundant and not often used; one use of auto is to distinguish a declaration-statement from an expression-statement (stmt.ambig) explicitly. --- end note]
I thought that this was well known to be incorrect, because using auto does not disambiguate this. Writing:
auto int f();is still a declaration of a function f, just now with an error since the function's return type may not use an auto storage class specifier. I suppose an error is an improvement over a silent ambiguity going the wrong way, but it's still not a solution for the user who wants to express the other in a compilable way.
Proposed resolution: Replace that note with the following note:
[Note: hence, the auto specifier is always redundant and not often used. --- end note]
John Spicer: I support the proposed change, but I think the disambiguation case is not the one that you describe. An example of the supposed disambiguation is:
int i;
int j;
int main()
{
int(i); // declares i, not reference to ::i
auto int(j); // declares j, not reference to ::j
}
cfront would take "int(i)" as a cast of ::i, so the auto would force what it would otherwise treat as a statement to be considered a declaration (cfront 3.0 warned that this would change in the future).
In a conforming compiler the auto is always redundant (as you say) because anything that could be considered a valid declaration should be treated as one.
Proposed resolution (April 2003):
Replace 7.1.1 [dcl.stc] paragraph 2
[Note: hence, the auto specifier is almost always redundant and not often used; one use of auto is to distinguish a declaration-statement from an expression-statement (6.8 [stmt.ambig]) explicitly. --- end note]with
[Note: hence, the auto specifier is always redundant and not often used. One use of auto is to distinguish a declaration-statement from an expression-statement explicitly rather than relying on the disambiguation rules (6.8 [stmt.ambig]), which may aid readers. --- end note]
[Voted into WP at April, 2007 meeting.]
Are string literals from default arguments used in extern inlines supposed to have the same addresses across all translation units?
void f(const char* = "s")
inline g() {
f();
}
Must the "s" strings be the same in all copies of the inline function?
Steve Adamczyk: The totality of the standard's wisdom on this topic is (7.1.2 [dcl.fct.spec] paragraph 4):
A string literal in an extern inline function is the same object in different translation units.
I'd hazard a guess that a literal in a default argument expression is not "in" the extern inline function (it doesn't appear in the tokens of the function), and therefore it need not be the same in different translation units.
I don't know that users would expect such strings to have the same address, and an equally valid (and incompatible) expectation would be that the same string literal would be used for every expansion of a given default argument in a single translation unit.
Notes from April 2003 meeting:
The core working group feels that the address of a string literal should be guaranteed to be the same only if it actually appears textually within the body of the inline function. So a string in a default argument expression in a block extern declaration inside the body of a function would be the same in all instances of the function. On the other hand, a string in a default argument expression in the header of the function (i.e., outside of the body) would not be the same.
Proposed resolution (April 2003):
Change the last sentence and add the note to the end of 7.1.2 [dcl.fct.spec] paragraph 4:
A string literal in the body of an extern inline function is the same object in different translation units. [Note: A string literal that is encountered only in the context of a function call (in the default argument expression of the called function), is not “in” the extern inline function.]
Notes from October 2003 meeting:
We discussed ctor-initializer lists and decided that they are also part of the body. We've asked Clark Nelson to work on syntax changes to give us a syntax term for the body of a function so we can refer to it here. See also issue 452, which could use this term.
(October, 2005: moved to “review” status in concert with issue 452. With that resolution, the wording above needs no further changes.)
Proposed resolution (April, 2006):
Change the last sentence and add the note to the end of 7.1.2 [dcl.fct.spec] paragraph 4:
A string literal in the body of an extern inline function is the same object in different translation units. [Note: A string literal appearing in a default argument expression is not considered to be “in the body” of an inline function merely by virtue of the expression’s use in a function call from that inline function. —end note]
[Voted into WP at the October, 2006 meeting.]
I couldn't find wording that makes it invalid to say friend virtual... The closest seems to be 7.1.2 [dcl.fct.spec] paragraph 5, which says:
The virtual specifier shall only be used in declarations of nonstatic class member functions that appear within a member-specification of a class definition; see 10.3 [class.virtual].
I don't think that excludes a friend declaration (which is a valid member-specification by 9.2 [class.mem]).
John Spicer: I agree that virtual should not be allowed on friend declarations. I think the wording in 7.1.2 [dcl.fct.spec] is intended to be the declaration of a function within its class, although I think the wording should be improved to make it clearer.
Proposed resolution (October, 2005):
Change 7.1.2 [dcl.fct.spec] paragraphs 5-6 as indicated:
The virtual specifier shall only be used only in declarations the initial declaration of a non-static class member functions that appear within a member-specification of a class definition function; see
10.3 [class.virtual] .The explicit specifier shall be used only in declarations the declaration of constructors a constructor within a its class definition; see 12.3.1 [class.conv.ctor].
[Voted into WP at March 2004 meeting.]
I wonder if perhaps the core issue 56 change in 7.1.3 [dcl.typedef] paragraph 2 wasn't quite careful enough. The intent was to remove the allowance for:
struct S {
typedef int I;
typedef int I;
};
but I think it also disallows the following:
class B {
typedef struct A {} A;
void f(struct B::A*p);
};
See also issue 407.
Proposed resolution (October 2003):
At the end of 7.1.3 [dcl.typedef] paragraph 2, add the following:
In a given class scope, a typedef specifier can be used to redefine
any class-name declared in that scope that is not
also a typedef-name
to refer to the type to which it already refers. [Example:
struct S {
typedef struct A {} A; // OK
typedef struct B B; // OK
typedef A A; // error
};
]
[Voted into the WP at the September, 2008 meeting.]
According to 7.1.5 [dcl.constexpr] paragraph 5,
If the instantiated template specialization of a constexpr function template would fail to satisfy the requirements for a constexpr function, the constexpr specifier is ignored and the specialization is not a constexpr function.
One would expect to see a similar provision for an instantiated constructor template (because the requirements for a constexpr function [paragraph 3] are different from the requirements for a constexpr constructor [paragraph 4]), but there is none; constexpr constructor templates are not mentioned.
Suggested resolution:
Change the wording of 7.1.5 [dcl.constexpr] paragraph 5 as indicated:
If the instantiated template specialization of a constexpr function template would fail to satisfy the requirements for a constexpr function or constexpr constructor, as appropriate to the function template, the constexpr specifier is ignored and the specialization is not a constexpr function or constexpr constructor.
Proposed resolution (June, 2008):
[Drafting note: This resolution goes beyond the problem described in the issue discussion, which is one aspect of the general failure of the existing wording to deal consistently with the distinctions between constexpr functions and constexpr constructors. The wording below attempts to rectify that problem systematically.]
Change 7.1.5 [dcl.constexpr] paragraph 2 as follows:
A constexpr specifier used in a function declaration the declaration of a function that is not a constructor declares that function to be a constexpr function. Similarly, a constexpr specifier used in a constructor declaration declares that constructor to be a constexpr constructor. Constexpr functions and constexpr constructors are implicitly inline (7.1.2 [dcl.fct.spec]). A constexpr function shall not be virtual (10.3).
Change 7.1.5 [dcl.constexpr] paragraph 3 as follows:
The definition of a constexpr function shall satisfy the following constraints:
it shall not be virtual (10.3 [class.virtual])
its return type shall be a literal type
each of its parameter types shall be a literal type
its function-body shall be a compound-statement of the form
{ return expression ; }
where expression is a potential constant expression (5.19 [expr.const])
every implicit conversion used in converting expression to the function return type (8.5 [dcl.init]) shall be one of those allowed in a constant expression (5.19 [expr.const]).
[Example:...
Change 7.1.5 [dcl.constexpr] paragraph 4 as follows:
The definition of a constexpr constructor shall satisfy the following constraints:
each of its parameter types shall be a literal type
its function-body shall not be a function-try-block
the compound-statement of its function-body shall be empty
every non-static data member and base class sub-object shall be initialized (12.6.2 [class.base.init])
every constructor involved in initializing non-static data members and base class sub-objects invoked by a mem-initializer shall be a constexpr constructor invoked with potential constant expression arguments, if any.
every constructor argument and full-expression in a mem-initializer shall be a potential constant expression
every implicit conversion used in converting a constructor argument to the corresponding parameter type and converting a full-expression to the corresponding member type shall be one of those allowed in a constant expression.
A trivial copy constructor is also a constexpr constructor. [Example: ...
Change 7.1.5 [dcl.constexpr] paragraph 5 as follows:
If the instantiated template specialization of a constexpr function template would fail to satisfy the requirements for a constexpr function or constexpr constructor, the constexpr specifier is ignored and the specialization is not a constexpr function.
Change 7.1.5 [dcl.constexpr] paragraph 6 as follows:
A constexpr specifier used in for a non-static member function definition that is not a constructor declares that member function to be const (9.3.1 [class.mfct.non-static]). [Note: ...
[Voted into the WP at the September, 2008 meeting.]
The current wording of 7.1.5 [dcl.constexpr] paragraph 7 seems not quite correct. It reads,
A constexpr specifier used in an object declaration declares the object as const. Such an object shall be initialized, and every expression that appears in its initializer (8.5 [dcl.init]) shall be a constant expression.
The phrase “every expression” is intended to cover multiple arguments to a constexpr constructor and multiple expressions in an aggregate initializer. However, it could be read (incorrectly) as saying that non-constant expressions cannot appear as subexpressions in such initializers, even in places where they do not render the full-expression non-constant (i.e., as unevaluated operands and in the unselected branches of &&, ||, and ?:). Perhaps this problem could be remedied by replacing “every expression” with “every full-expression?”
Proposed resolution (June, 2008):
Change 7.1.5 [dcl.constexpr] paragraph 7 as follows:
A constexpr specifier used in an object declaration declares the object as const. Such an object shall be initialized, and every expression that appears in its initializer (8.5) initialized. If it is initialized by a constructor call, the constructor shall be a constexpr constructor and every argument to the constructor shall be a constant expression. Otherwise, every full-expression that appears in its initializer shall be a constant expression. Every implicit conversion used...
[Voted into WP at April 2003 meeting.]
Although 14.2 [temp.param] paragraph 3 contains an assertion that
A type-parameter defines its identifier to be a type-name (if declared with class or typename)
the grammar in 7.1.6.2 [dcl.type.simple] paragraph 1 says that a type-name is either a class-name, an enum-name, or a typedef-name. The identifier in a template type-parameter is none of those. One possibility might be to equate the identifier with a typedef-name instead of directly with a type-name, which would have the advantage of not requiring parallel treatment of the two in situations where they are treated the same (e.g., in elaborated-type-specifiers, see issue 245). See also issue 215.
Proposed resolution (Clark Nelson, March 2002):
In 14.2 [temp.param] paragraph 3, change "A type-parameter defines its identifier to be a type-name" to "A type-parameter defines its identifier to be a typedef-name"
In 7.1.6.3 [dcl.type.elab] paragraph 2, change "If the identifier resolves to a typedef-name or a template type-parameter" to "If the identifier resolves to a typedef-name".
This has been consolidated with the edits for some other issues. See N1376=02-0034.
[Voted into WP at the October, 2006 meeting.]
7.1.6.2 [dcl.type.simple] paragraph 3 reads,
It is implementation-defined whether bit-fields and objects of char type are represented as signed or unsigned quantities. The signed specifier forces char objects and bit-fields to be signed; it is redundant with other integral types.
The last sentence in that quote is misleading w.r.t. bit-fields. The first sentence in that quote is correct but incomplete.
Proposed fix: change the two sentences to read:
It is implementation-defined whether objects of char type are represented as signed or unsigned quantities. The signed specifier forces char objects signed; it is redundant with other integral types except when declaring bit-fields (9.6 [class.bit]).
Proposed resolution (October, 2005):
Change 7.1.6.2 [dcl.type.simple] paragraph 3 as indicated:
When multiple simple-type-specifiers are allowed, they can be freely intermixed with other decl-specifiers in any order. [Note: It is implementation-defined whether bit-fields and objects of char type and certain bit-fields (9.6 [class.bit]) are represented as signed or unsigned quantities. The signed specifier forces bit-fields and char objects and bit-fields to be signed; it is redundant with other integral types in other contexts. —end note]
[Voted into the WP at the September, 2008 meeting.]
The second bullet of 7.1.6.2 [dcl.type.simple] paragraph 4 reads,
- otherwise, if e is a function call (5.2.2 [expr.call]) or an invocation of an overloaded operator (parentheses around e are ignored), decltype(e) is the return type of that function;
The reference to “that function” is imprecise; it is not the actual function called at runtime but the statically chosen function (ignoring covariant return types in virtual functions).
Also, the examples in this paragraph have errors:
The declaration of struct A should end with a semicolon.
The lines of the form decltype(...); are ill-formed; they need a declarator.
Proposed Resolution (October, 2007):
Change 7.1.6.2 [dcl.type.simple] paragraph 4 as follows:
The type denoted by decltype(e) is defined as follows:
if e is an id-expression or a class member access (5.2.5 [expr.ref]), decltype(e) is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;
otherwise, if e is a function call (5.2.2 [expr.call]) or an invocation of an overloaded operator (parentheses around e are ignored), decltype(e) is the return type of that the statically chosen function;
otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;
otherwise, decltype(e) is the type of e.
The operand of the decltype specifier is an unevaluated operand (clause 5 [expr]).
[Example:
const int&& foo(); int i; struct A { double x; }; const A* a = new A(); decltype(foo()) x1; // type is const int&& decltype(i) x2; // type is int decltype(a->x) x3; // type is double decltype((a->x)) x4; // type is const double&—end example]
[Voted into the WP at the February, 2008 meeting as paper J16/08-0056 = WG21 N2546.]
We've found an interesting parsing ambiguity with the new meaning of auto. Consider:
typedef int T;
void f() {
auto T = 42; // Valid or not?
}
The question here is whether T should be a type specifier or a storage class? 7.1.6.4 [dcl.spec.auto] paragraph 1 says,
The auto type-specifier has two meanings depending on the context of its use. In a decl-specifier-seq that contains at least one type-specifier (in addition to auto) that is not a cv-qualifier, the auto type-specifier specifies that the object named in the declaration has automatic storage duration.
In this case, T is a type-specifier, so the declaration is ill-formed: there is no declarator-id. Many, however, would like to see auto work “just like int,” i.e., forcing T to be redeclared in the inner scope. Concerns cited included hijacking of the name in templates and inline function bodies over the course of time if a program revision introduces a type with that name in the surrounding context. Although it was pointed out that enclosing the name in parentheses in the inner declaration would prevent any such problems, this was viewed as unacceptably ugly.
Notes from the April, 2007 meeting:
The CWG wanted to avoid a rule like, “if auto can be a type-specifier, it is” (similar to the existing “if it can be a declaration, it is” rule) because of the lookahead and backtracking difficulties such an approach would pose for certain kinds of parsing techniques. It was noted that the difficult lookahead cases all involve parentheses, which would not be a problem if only the “=” form of initializer were permitted in auto declarations; only very limited lookahead is required in that case. It was also pointed out that the “if it can be a type-specifier, it is” approach results in a quiet change of meaning for cases like
typedef int T;
int n = 3;
void f() {
auto T(n);
}
This currently declares n to be an int variable in the inner scope but would, under the full lookahead approach, declare T to be a variable, quitely changing uses of n inside f() to refer to the outer variable.
The consensus of the CWG was to pursue the change to require the “=” form of initializer for auto.
Notes from the July, 2007 meeting:
See paper J16/07-0197 = WG21 N2337. There was no consensus among the CWG for either of the approaches recommended in the paper; additional input and direction is required.
[Moved to DR at October 2002 meeting.]
According to 7.2 [dcl.enum] paragraph 5, the underlying type of an enum is an unspecified integral type, which could potentially be unsigned int. The promotion rules in 4.5 [conv.prom] paragraph 2 say that such an enumeration value used in an expression will be promoted to unsigned int. This means that a conforming implementation could give the value false for the following code:
enum { zero };
-1 < zero; // might be false
This is counterintuitive. Perhaps the description of the underlying
type of an enumeration should say that an unsigned underlying type
can be used only if the values of the enumerators cannot be
represented in the corresponding signed type. This approach would
be consistent with the treatment of integral promotion of
bitfields (4.5 [conv.prom]
paragraph 3).
On a related note, 7.2 [dcl.enum] paragraph 5 says,
the underlying type shall not be larger than int unless the value of an enumerator cannot fit in an int or unsigned int.
This specification does not allow for an enumeration like
enum { a = -1, b = UINT_MAX };
Since each enumerator can fit in an int or unsigned int, the underlying type is required to be no larger than int, even though there is no such type that can represent all the enumerators.
Proposed resolution (04/01; obsolete, see below):
Change 7.2 [dcl.enum] paragraph 5 as follows:
It is implementation-defined which integral type is used as the underlying type for an enumeration except that the underlying type shall not be larger than int unless the value of an enumerator cannot fit in an int or unsigned int neither int nor unsigned int can represent all the enumerator values. Furthermore, the underlying type shall not be an unsigned type if the corresponding signed type can represent all the enumerator values.
See also issue 58.
Notes from 04/01 meeting:
It was noted that 4.5 [conv.prom] promotes unsigned types smaller than int to (signed) int. The signedness chosen by an implementation for small underlying types is therefore unobservable, so the last sentence of the proposed resolution above should apply only to int and larger types. This observation also prompted discussion of an alternative approach to resolving the issue, in which the bmin and bmax of the enumeration would determine the promoted type rather than the underlying type.
Proposed resolution (10/01):
Change 4.5 [conv.prom] paragraph 2 from
An rvalue of type wchar_t (3.9.1 [basic.fundamental]) or an enumeration type (7.2 [dcl.enum]) can be converted to an rvalue of the first of the following types that can represent all the values of its underlying type: int, unsigned int, long, or unsigned long.to
An rvalue of type wchar_t (3.9.1 [basic.fundamental]) can be converted to an rvalue of the first of the following types that can represent all the values of its underlying type: int, unsigned int, long, or unsigned long. An rvalue of an enumeration type (7.2 [dcl.enum]) can be converted to an rvalue of the first of the following types that can represent all the values of the enumeration (i.e., the values in the range bmin to bmax as described in 7.2 [dcl.enum]): int, unsigned int, long, or unsigned long.
[Voted into WP at April 2003 meeting.]
7.2 [dcl.enum] defines the underlying type of an enumeration as an integral type "that can represent all the enumerator values defined in the enumeration".
What does the standard say about this code:
enum E { a = LONG_MIN, b = ULONG_MAX };
?
I think this should be ill-formed.
Proposed resolution:
In 7.2 [dcl.enum] paragraph 5 after
The underlying type of an enumeration is an integral type that can represent all the enumerator values defined in the enumeration.insert
If no integral type can represent all the enumerator values, the enumeration is ill-formed.
[Voted into WP at April, 2006 meeting.]
The C language (since C99), and some C++ compilers, accept:
enum { FOO, };
as syntactically valid. It would be useful
for machine generated code
for minimising changes when editing
to allow a distinction between the final item being intended as an ordinary item or as a limit:
enum { red, green, blue, num_colours }; // note no comma
enum { fred, jim, sheila, }; // last is not special
This proposed change is to permit a trailing comma in enum by adding:
enum identifieropt { enumerator-list , }
as an alternative definition for the enum-specifier nonterminal
in
Proposed resolution (October, 2005):
Change the grammar in 7.2 [dcl.enum] paragraph 1 as indicated:
enum-specifier:enum identifieropt { enumerator-listopt }
enum identifieropt { enumerator-list , }
[Voted into the WP at the September, 2008 meeting.]
The current specification of scoped enumerations does not appear to forbid an example like the following, even though the enumerator e cannot be used:
enum class { e };
This might be covered by 7 [dcl.dcl] paragraph 3,
In a simple-declaration, the optional init-declarator-list can be omitted only when declaring a class (clause 9 [class]) or enumeration (7.2 [dcl.enum]), that is, when the decl-specifier-seq contains either a class-specifier, an elaborated-type-specifier with a class-key (9.1 [class.name]), or an enum-specifier. In these cases and whenever a class-specifier or enum-specifier is present in the decl-specifier-seq, the identifiers in these specifiers are among the names being declared by the declaration (as class-names, enum-names, or enumerators, depending on the syntax). In such cases, and except for the declaration of an unnamed bit-field (9.6 [class.bit]), the decl-specifier-seq shall introduce one or more names into the program, or shall redeclare a name introduced by a previous declaration.
which, when combined with paragraph 2,
A declaration occurs in a scope (3.3 [basic.scope]); the scope rules are summarized in 3.4 [basic.lookup]. A declaration that declares a function or defines a class, namespace, template, or function also has one or more scopes nested within it. These nested scopes, in turn, can have declarations nested within them. Unless otherwise stated, utterances in clause 7 [dcl.dcl] about components in, of, or contained by a declaration or subcomponent thereof refer only to those components of the declaration that are not nested within scopes nested within the declaration.
appears to rule out the similar class definition,
struct { int m; };
However, a scoped enumeration is not listed in paragraph 2 among the constructs containing a nested scope (although 3.3.10 [basic.scope.enum] does describe “enumeration scope”); furthermore, an enumerator-definition is not formally a “nested declaration.” If unusable scoped enumeration definitions are to be banned, these shortcomings in 7 [dcl.dcl] paragraph 2 must be addressed. (A note in 7.2 [dcl.enum] mentioning that unnamed scoped enumerations are not allowed would also be helpful.)
Notes from the February, 2008 meeting:
The consensus was to require that the identifier be present in an enum-specifier unless the enum-key is enum.
Proposed resolution (June, 2008):
Change 7.2 [dcl.enum] paragraph 2 as follows:
...The enum-keys enum class and enum struct are semantically equivalent; an enumeration type declared with one of these is a scoped enumeration, and its enumerators are scoped enumerators. The optional identifier shall not be omitted in the declaration of a scoped enumeration. The type-specifier-seq of an enum-base...
[Voted into the WP at the October, 2006 meeting as part of paper J16/06-0188 = WG21 N2118.]
The resolution of issue 106 specifies that an attempt to create a type “reference to cv1 T,” where T is a typedef or template parameter of the type “reference to cv2 S,” actually creates the type “reference to cv12 S,” where cv12 is the union of the two sets of cv-qualifiers.
One objection that has been raised to this resolution is that it is inconsistent with the treatment of cv-qualification and references specified in 8.3.2 [dcl.ref] paragraph 1, which says that cv-qualifiers applied to a typedef or template argument that is a reference type are ignored. For example:
typedef int& intref;
const intref r1; // reference to int
const intref& r2; // reference to const int
In fact, however, these two declarations are quite different. In the declaration of r1, const applies to a “top-level” reference, while in the declaration of t2, it occurs under a reference. In general, cv-qualifiers that appear under a reference are preserved, even if the type appears in a context in which top-level cv-qualification is removed, for example, in determining the type of a function from parameter types (8.3.5 [dcl.fct] paragraph 3) and in template argument deduction (14.9.2.1 [temp.deduct.call] paragraph 2).
Another objection to the resolution is that type composition gives different results in a single declaration than it does when separated into two declarations. For example:
template <class T>
struct X {
typedef T const T_const;
typedef T_const& type1;
typedef T const& type2;
};
X<int&>::type1 t1; // int&
X<int&>::type2 t2; // int const&
The initial motivation for the propagation of cv-qualification during reference-to-reference collapse was to prevent inadvertent loss of cv-qualifiers in contexts in which it could make a difference. For example, if the resolution were changed to discard, rather than propagate, embedded cv-qualification, overload resolution could surprisingly select a non-const version of a member function:
struct X {
void g();
void g() const;
};
template <typename T> struct S {
static void f(const T& t) {
t.g(); // const or non-const???
}
};
X x;
void q() {
S<X>::f(x); // calls X::g() const
S<X&>::f(x); // calls X::g()
}
Another potentially-surprising outcome of dropping embedded cv-qualifiers would be:
template <typename T> struct A {
void f(T&); // mutating version
void f(const T&); // non-mutating version
};
A<int&> ai; // Ill-formed: A<int&> declares f(int&) twice
On the other hand, those who would like to see the resolution changed to discard embedded cv-qualifiers observe that these examples are too simple to be representative of real-world code. In general, it is unrealistic to expect that a template written with non-reference type parameters in mind will automatically work correctly with reference type parameters as a result of applying the issue 106 resolution. Instead, template metaprogramming allows the template author to choose explicitly whether cv-qualifiers are propagated or dropped, according to the intended use of the template, and it is more important to respect the reasonable intuition that a declaration involving a template parameter will not change the type that the parameter represents.
As a sample of real-world code, tr1::tuple was examined. In both cases — the current resolution of issue 106 and one in which embedded cv-qualifiers were dropped — some metaprogramming was required to implement the intended interface, although the version reflecting the revised resolution was somewhat simpler.
Notes from the October, 2005 meeting:
The consensus of the CWG was that the resolution of issue 106 should be revised not to propagate embedded cv-qualification.
Note (February, 2006):
The wording included in the rvalue-reference paper, J16/06-0022 = WG21 N1952, incorporates changes intended to implement the October, 2005 consensus of the CWG.
[Voted into WP at March 2004 meeting.]
Issue 1:
The working paper is not clear about how the typename/template keywords interact with using-declarations:
template<class T> struct A {
typedef int X;