I use the following in my C++ code:
int a = 0, b = a;
I would like to know if this behaviour is reliable and well defined (left to right order of name declaration) and that my code will not break with other compilers with an undeclared name error.
If not reliable, I would break the statement:
int a = 0;
int b = a;
Thank you.
I believe the answer is no.
It is subject to core active issue 1342 which says:
It is not clear what, if anything, in the existing specification requires that the initialization of multiple init-declarators within a single declaration be performed in declaration order.
We have non-normative note in [dcl.decl]p3 which says:
...[ Note: A declaration with several declarators is usually
equivalent to the corresponding sequence of declarations each with a
single declarator. That is
T D1, D2, ... Dn;
is usually equivalent to
T D1; T D2; ... T Dn;
...
but it is non-normative and it does not cover the initialization case at all and as far as I can tell no normative wording says the same thing.
Although the standard does cover the scope of names in [basic.scope.pdecl]p1 which says:
The point of declaration for a name is immediately after its complete
declarator and before its initializer (if any), except as noted below.
[ Example:
unsigned char x = 12;
{ unsigned char x = x; }
Here the second x is initialized with its own (indeterminate) value.
— end example ]
The fact that you thought to ask this question suggests that the style is not great. Even though the one-line version is almost guaranteed† to work, I would still go with the two-line approach for the greater clarity to human readers.
† I initially said it was guaranteed, but I will step back from that. After reviewing the relevant portion of the spec, I can see how language lawyers would complain that this guarantee is not explicitly stated. (As Shafik Yaghmour points out, core active issue 1342 notes the lack of an explicit guarantee, albeit with phrasing that suggests that such a guarantee should be present.)
I will step back only to "almost guaranteed", though, as it is strongly implied by "Each init-declarator in a declaration is analyzed separately as if it was in a declaration by itself.". That is, the analysis of int a = 0, b = a; has two parts: one where a variable named a is initialized to 0, and one where a variable named b is initialized to the value of a. If you are truly keeping these parts separate, then the first part would have to finish before the second part begins (otherwise they are not as if each was in a declaration by itself), so a would have the value 0 before b is initialized. I accept that this might be not definite enough for the language lawyers, but it should be good enough for a compiler's bug report if there is a compiler for which that line does not work as intended.
My apologies for not looking up the spec earlier. The "language-lawyer" tag was not present when I initially answered.
A declaration statement that defines multiple variables separated by comma is exactly equivalent to multiple declaration statements that defines a single variable in the same order because the scope of a variable begins just after it's name, but there are (at least) two exceptions:
1) When a variable declaration hides a type with the same name, as in:
struct S {};
S S, T;
Is different from
struct S {};
S S;
S T; //error: S is a variable name
But
struct S {};
S S, T{S};
Is equivalent to
struct S{};
S S;
struct S T{S};
2) When using the auto and decltype(auto) specifiers:
auto i{0}, j{i}, k{2.0}; // error: deduction for auto fails, double or int?
Is different from
auto i{0};
auto j{i};
auto k{2.0};
In any case, evaluation order is always from left to right.
Related
Recent versions of clang (since clang-11) issue a warning when compiled with -pedantic for the following segment of code:
namespace foo {
template <int A>
struct Bar {
~Bar();
};
} // namespace foo
template <int A>
foo::Bar<A>::~Bar(){}
With the generated warning (and error with -Werror) being:
<source>:10:12: error: ISO C++ requires the name after '::~' to be found in the same scope as the name before '::~' [-Werror,-Wdtor-name]
foo::Bar<A>::~Bar(){}
~~~~~~~~~~~^~
::Bar
Live Example
clang is the first compiler I've seen to issue this diagnostic, and as far as I'm aware, what was written above is completely valid C++. Two different ways that seem to suppress this is to either define within the same namespace or explicitly qualify the destructor name -- such as:
...
template <int A>
foo::Bar<A>::~Bar<A>(){}
Is Clang's diagnostic here correct? It's my understanding that the type's name would absolutely be in the correct name-scope as foo::Bar<A>::~ -- and that the qualification of ~Bar<A>() should be unnecessary.
From what I have learned since posting the question, this warning is, strictly-speaking, correct -- though it most likely a defect in the wording of the standard.
According to Richard Smith in LLVM Bug 46979:
The diagnostic is correct, per the standard rules as written; strictly-speaking, the C++ rules require this destructor to be written as
template<typename T>
A::B<T>::~B<T>()
Per C++ [basic.lookup.qual]p6:
In a qualified-id of the form:
nested-name-specifier[opt] type-name :: ~ type-name
the second type-name is looked up in the same scope as the first.
This means that the second B is looked up in class A, which finds only the class template B and not the injected-class-name.
This is not an especially useful rule, and likely isn't the intended rule, which is why this diagnostic (along with a bunch of similar diagnostics for plausible but formally incorrect destructor names) is disabled by default but included in -pedantic.
Looking further into this, I can find the mentioned passage for [basic.lookup.qual]/6 in the C++20 standard, but it appears drafts for C++23 have changed this -- which points towards this most likely being a defect.
In drafts for [basic.lookup.qual] for C++23, this whole section has been overhauled and is currently, at the time of writing, replaced with [basic.lookup.qual]/4 which states:
If a qualified name Q follows a ~:
(4.1) If Q is a member-qualified name, it undergoes unqualified lookup as well as qualified lookup.
(4.2) Otherwise, its nested-name-specifier N shall nominate a type.
If N has another nested-name-specifier S, Q is looked up as if its lookup context were that nominated by S.
(4.3) Otherwise, if the terminal name of N is a member-qualified name M, Q is looked up as if ~Q appeared in place of M (as above).
(4.4) Otherwise, Q undergoes unqualified lookup.
(4.5) Each lookup for Q considers only types (if Q is not followed by a <) and templates whose specializations are types.
If it finds nothing or is ambiguous, it is discarded.
(4.6) The type-name that is or contains Q shall refer to its (original) lookup context (ignoring cv-qualification) under the interpretation established by at least one (successful) lookup performed.
[Example 4:
struct C {
typedef int I;
};
typedef int I1, I2;
extern int* p;
extern int* q;
void f() {
p->C::I::~I(); // I is looked up in the scope of C
q->I1::~I2(); // I2 is found by unqualified lookup
}
struct A {
~A();
};
typedef A AB;
int main() {
AB* p;
p->AB::~AB(); // explicitly calls the destructor for A
}
— end example]
(The full quote has been posted here since this is from a draft, and the wording may be subject to change in the future)
This appears to explicitly correct this issue by ensuring that lookup is performed as one would expect.
So basically the diagnostic is correct under older versions of C++ due to a defect in the wording -- but C++23 appears to change that. I am not sure whether this will be retroactively fixed in older versions as a defect given that it appears no compiler actually follows this pedantic requirement.
Recent versions of clang (since clang-11) issue a warning when compiled with -pedantic for the following segment of code:
namespace foo {
template <int A>
struct Bar {
~Bar();
};
} // namespace foo
template <int A>
foo::Bar<A>::~Bar(){}
With the generated warning (and error with -Werror) being:
<source>:10:12: error: ISO C++ requires the name after '::~' to be found in the same scope as the name before '::~' [-Werror,-Wdtor-name]
foo::Bar<A>::~Bar(){}
~~~~~~~~~~~^~
::Bar
Live Example
clang is the first compiler I've seen to issue this diagnostic, and as far as I'm aware, what was written above is completely valid C++. Two different ways that seem to suppress this is to either define within the same namespace or explicitly qualify the destructor name -- such as:
...
template <int A>
foo::Bar<A>::~Bar<A>(){}
Is Clang's diagnostic here correct? It's my understanding that the type's name would absolutely be in the correct name-scope as foo::Bar<A>::~ -- and that the qualification of ~Bar<A>() should be unnecessary.
From what I have learned since posting the question, this warning is, strictly-speaking, correct -- though it most likely a defect in the wording of the standard.
According to Richard Smith in LLVM Bug 46979:
The diagnostic is correct, per the standard rules as written; strictly-speaking, the C++ rules require this destructor to be written as
template<typename T>
A::B<T>::~B<T>()
Per C++ [basic.lookup.qual]p6:
In a qualified-id of the form:
nested-name-specifier[opt] type-name :: ~ type-name
the second type-name is looked up in the same scope as the first.
This means that the second B is looked up in class A, which finds only the class template B and not the injected-class-name.
This is not an especially useful rule, and likely isn't the intended rule, which is why this diagnostic (along with a bunch of similar diagnostics for plausible but formally incorrect destructor names) is disabled by default but included in -pedantic.
Looking further into this, I can find the mentioned passage for [basic.lookup.qual]/6 in the C++20 standard, but it appears drafts for C++23 have changed this -- which points towards this most likely being a defect.
In drafts for [basic.lookup.qual] for C++23, this whole section has been overhauled and is currently, at the time of writing, replaced with [basic.lookup.qual]/4 which states:
If a qualified name Q follows a ~:
(4.1) If Q is a member-qualified name, it undergoes unqualified lookup as well as qualified lookup.
(4.2) Otherwise, its nested-name-specifier N shall nominate a type.
If N has another nested-name-specifier S, Q is looked up as if its lookup context were that nominated by S.
(4.3) Otherwise, if the terminal name of N is a member-qualified name M, Q is looked up as if ~Q appeared in place of M (as above).
(4.4) Otherwise, Q undergoes unqualified lookup.
(4.5) Each lookup for Q considers only types (if Q is not followed by a <) and templates whose specializations are types.
If it finds nothing or is ambiguous, it is discarded.
(4.6) The type-name that is or contains Q shall refer to its (original) lookup context (ignoring cv-qualification) under the interpretation established by at least one (successful) lookup performed.
[Example 4:
struct C {
typedef int I;
};
typedef int I1, I2;
extern int* p;
extern int* q;
void f() {
p->C::I::~I(); // I is looked up in the scope of C
q->I1::~I2(); // I2 is found by unqualified lookup
}
struct A {
~A();
};
typedef A AB;
int main() {
AB* p;
p->AB::~AB(); // explicitly calls the destructor for A
}
— end example]
(The full quote has been posted here since this is from a draft, and the wording may be subject to change in the future)
This appears to explicitly correct this issue by ensuring that lookup is performed as one would expect.
So basically the diagnostic is correct under older versions of C++ due to a defect in the wording -- but C++23 appears to change that. I am not sure whether this will be retroactively fixed in older versions as a defect given that it appears no compiler actually follows this pedantic requirement.
void f(int x) {
int (x), 1;
}
Clang compiles it, GCC doesn't. Which compiler is correct?
IMO, the wording in [stmt.ambig] is clear enough on this:
An expression-statement with a function-style explicit type conversion as its leftmost subexpression can be indistinguishable from a declaration where the first declarator starts with a (. In those cases the statement is a declaration.
[Note: If the statement cannot syntactically be a declaration, there is no ambiguity, so this rule does not apply. The whole statement might need to be examined to determine whether this is the case.
The wording speaks of an entire (expression-)statement.
Your statement cannot be parsed as a declaration, because the lexeme 1 is grammatically not a declarator. There is no ambiguity: it might look ambiguous if we looked solely at int(x), but the standard quite explicitly denies that if some prefix of the statement parses as a declaration, the entire statement is considered a potential declaration.
In fact, core experts had a highly similar discussion back in 2002 over core issue 340---I highlighted the important bits. Here, again, we have a supposed declaration that contains an incompatible sub-construct.
Consider the following program:
struct Point {
Point(int){}
};
struct Lattice {
Lattice(Point, Point, int){}
};
int main(void) {
int a, b;
Lattice latt(Point(a), Point(b), 3); /* Line X */
}
The problem concerns the line marked /* Line X */, which is an ambiguous
declarations for either an object or a function. The clause that
governs this ambiguity is 8.2 [dcl.ambig.res] paragraph 1, and reads:
The ambiguity arising from the similarity between a function-style
cast and a declaration mentioned in 6.8 [stmt.ambig] [..]
Based on this clause there are two
possible interpretations of the declaration in line X:
The declaration of latt declares a function with a return value of the
type Lattice and taking three arguments. The type of the first two
arguments is Point and each of these arguments is followed by a
parameter name in redundant parentheses. The type of the third
argument can not be determined, because it is a literal. This will
result in a syntax error.
The declaration of latt declares an object,
because the other option (a function declaration) would result in a
syntax error. Note that the last sentence before the "[Note:" is not
much help, because both options are declarations.
Steve Adamczyk: a number of people replied to this posting on
comp.std.c++ saying that they did not see a problem.
The original
poster replied:
I can't do anything but agree with your argumentation. So there is
only one correct interpretation of clause 8.2 [dcl.ambig.res]
paragraph 1, but I have to say that with some rewording, the clause
can be made a lot clearer, like stating explicitly that the entire
declaration must be taken into account and that function declarations
are preferred over object declarations.
I would like to suggest the following as replacement for the current
clause 8.2 [dcl.ambig.res] paragraph 1:
The ambiguity arising from the similarity between a functionstyle cast
and a declaration mentioned in 6.8 [stmt.ambig] […]
The working group felt that the current wording is clear enough.
In this code:
typedef int foo;
struct S
{
foo foo;
};
int main() {}
all versions of clang -std=c++14 accept this code, however all versions of g++ -std=c++14 report:
5 : error: declaration of 'foo S::foo' [-fpermissive]
foo foo;
^
1 : error: changes meaning of 'foo' from 'typedef int foo' [-fpermissive]
Is the code correct?
The code is wrong. typedef is a new name for a existing type. So you can not create a variable with a type's name like foo foo; is equal to int int.
g++ -std=c++14 is the correct one.
Also refer this question
According to the C++ standard, declaring a variable with the same name as a type is correct code in general, but invalid code within a class definition. The class case is specific, because names declared in a class definition are visible within the whole class definition, before and after the point of declaration of that name. In other scopes (global, namespace, function, ...) declared names are visible only after the point of declaration.
For the example given in the question: If you have another reference to foo before the member declaration foo foo;,
struct S { foo another_member; foo foo; };
to which foo should it refer? To the type or the member foo? In this case it would be possible to deduce from the context that the type is meant, but the C++ standard probably avoided complexity for treating corner cases.
But foo foo; is valid code outside of class definitions: Additionally to the excerpt from section [dcl.spec], which Serge Ballesta already citied in his answer, section [dcl.decl] is useful in this context. It gives a valid example for this case (in older versions of the C++ standard text in a note below the text, in later versions as part of the main text) - here from the N3242 draft (final draft for C++11):
A declaration with several declarators is usually equivalent to the corresponding sequence of declarations each with a single declarator. That is
T D1, D2, ... Dn;
is usually equvalent to
T D1; T D2; ... T Dn;
where T is a decl-specifier-seq and each Di is an init-declarator.
The exception occurs when a name introduced by one of the declarators
hides a type name used by the decl-specifiers, so that when the same
decl-specifiers are used in a subsequent declaration, they do not have
the same meaning, as in
struct S ... ;
S S, T; // declare two instances of struct S
which is not equivalent to
struct S ... ;
S S;
S T; // error
The excerpt from section [dcl.spec] is useful as well, as it describes, when a name in a declaration of a variable is interpreted as type name and when as the variable name (long quote is given in Serge Ballesta's answer):
... it is interpreted as part of the decl-specifier-seq if and only if
...
The relevant section for the class' case, which is given in the original question, is [basic.scope.class]:
... A name N used in a class S shall refer to the same declaration in its context and when re-evaluated in
the completed scope of S. No diagnostic is required for a violation of this rule. ...
This means that the code of the original question is invalid, but the compiler does not need to give an error. So both clang and gcc behave correctly (according to the C++ standard).
I would say CLang is correct here - even if I would never use this.
C++ 14 draft N4296 says in 7.1 Specifiers [dcl.spec] 3
If a type-name is encountered while parsing a decl-specifier-seq, it is interpreted as part of the decl-specifier-
seq if and only if there is no previous type-specifier other than a cv-qualifier in the decl-specifier-seq. The
sequence shall be self-consistent as described below. [ Example:
typedef char* Pc;
static Pc; // error: name missing
Here, the declaration static Pc is ill-formed because no name was specified for the static variable of type Pc.
To get a variable called Pc, a type-specifier (other than const or volatile) has to be present to indicate that
the typedef-name Pc is the name being (re)declared, rather than being part of the decl-specifier sequence.
For another example,
void f(const Pc); // void f(char* const) (not const char*)
void g(const int Pc); // void g(const int)
(emphasize mine)
Even if an example is not normative, it let think that for the writers of C++ specification, a variable can redeclare the name of a typedef.
But g++ is simply more conservative what looks more reasonable. If I ever see such a construct in production code, the programmer would soon learn not to do it again, even I the compiler accepted it...
I do not understand the need to specify the type of an extern/static variable at initialization. For example:
struct Test{
static int i;
};
Test::i = 2; //error
int Test::i = 2; //ok
Doesn't the compiler know that i is of type int? Is it just a particularity of the compilers, or why is specification of the type,,int" needed?
I do not understand the need to specify the type of an extern/static variable at initialization.
Because the language designers chose to use the same syntax for variable declarations and definitions. That syntax includes the type name. You're correct that, in some cases, that type name is redundant. But allowing you to leave it out might be somewhat confusing: it would look like an assignment, not a definition.
Doesn't the compiler know that i is of type int?
Only if the variable has already been declared. That has to be the case for a static member like this, but not necessarily for a global variable. You could declare it in one source file:
extern int i;
and define it in another
int i = 42;
without making the declaration available to the definition.
It's a matter of basic syntax.
A C++ file consists of a series of declarations. The basic syntax for a variable declaration is something like:
type identifier initializeropt ;
I'm leaving out a lot of details, but that's the basic idea: what's at file scope has to be a declaration, and a variable declaration has to start with the name of a type.
Without the name of a type on the beginning, you have a simple assignment statement--but those are only allowed inside of functions, not at file scope as you've tried to do it here.
This "limitation" can be reduced to simple grammar: The statement
Test::i = 2; //error
is an expression-statement consisting of an assignment-expression. This is never actually parsed as a declaration, whatever the entity Test::i may be, and adjusting the grammar to cover this would be extremely complicated and have no great benefit whatsoever.
Here's an example of valid, runnable code that would become ambiguous if the type weren't specified in the definition of the integer N::X::i, given the use of the disambiguating struct is possible for structures but there's nothing equivalent to force the int X::i definition not to match the struct under your proposed syntax.
#include <iostream>
namespace N
{
struct X
{
static int i;
struct i { int n_; };
};
}
int n = 2;
namespace N
{
int X::i (n); // take int out and under current C++ rules
// it will be equivalent to the next line
// (which is equivalent to struct X::i n; btw)
struct X::i (n); // but under your proposed rules, ambiguous
}
int main()
{
std::cout << N::n.n_ << ' ' << N::X::i << '\n';
}
That's a bit convoluted, but the bottom line is: not having types mentioned could break stuff.
Other answers basically amount to consistency with variable definitions appearing in other situations - that's desirable but not a sound technical driver to decide against the simplification proposed in the question. Of course, it may not be enough of a simplification, or may be seen as more of an obfuscation, preventing it ever having been seriously considered for Standardisation.