As far as I can see in the standard, the following code is valid. It compiles in MSVC1025.
const struct omg;
struct omg volatile;
int main()
{
return 0;
}
The qualifiers const and volatile seem purposeless in those declarations. They do not help nor hurt neither the compiler nor the programmer.
The standard does not seem bent on weeding out these "empty ambiguities". In the case of the empty declaration ;, it is explicitly allowed.
Are there other cases of tokens that, after preprocessing, are irrelevant for the meaning of the expression?
Both clang and gcc reject this code using -pedantic-errors. clang provides the following error:
error: 'const' is not permitted on a declaration of a type [-Werror,-Wmissing-declarations]
const struct omg;
^
error: 'volatile' is not permitted on a declaration of a type [-Werror,-Wmissing-declarations]
the draft C++ standard section 7.1.6.1 The cv-qualifiers [dcl.type.cv] says:
[...]If a cv-qualifier appears in a decl-specifier-seq, the init-declarator-list of the declaration shall
not be empty.[...]
Related
I was writing an out-of-class destructor definition for a class template when I noticed that the program compiles with clang with c++17 and c++20 and also with gcc with c++17 but rejected with gcc c++20. Demo.
template<typename T>
struct C
{
~C();
};
template<typename T>
C<T>::~C<T>() //accepted by compilers
{
}
int main()
{
C<int> c;;
}
The result of the above program is summarized in the below table:
Compiler
C++ Version
Accepts-Code
GCC
C++17
Yes
GCC
C++20
No
GCC
C++2b
No
Clang
C++17
Yes
Clang
C++20
Yes
Clang
C++2b
Yes
MSVC
C++17
Yes
MSVC
C++20
Yes
As we can see in the above both of the compilers accept the code except that gcc with c++20 and onwards reject it with the error error: template-id not allowed for destructor.
So, my question is which compiler is right here(if any).
The program is ill-formed atleast starting from c++20 and clang and msvc are wrong in accepting the code with c++20 and onwards.
Note that the change in wording for class.dtor was introduced in C++23 via p1787r6-class.dtor and seems to be a DR for C++20.
So, the code is ill-formed from C++20 and onwards which can be seen from: class.dtor#1.2 which states that:
1 A declaration whose declarator-id has an unqualified-id that begins with a ~ declares a prospective destructor; its declarator shall be a function declarator ([dcl.fct]) of the form
ptr-declarator ( parameter-declaration-clause ) noexcept-specifieropt attribute-specifier-seqopt
where the ptr-declarator consists solely of an id-expression, an optional attribute-specifier-seq, and optional surrounding parentheses, and the id-expression has one of the following forms:
1.2 otherwise, the id-expression is nested-name-specifier ~class-name and the class-name is the injected-class-name of the class nominated by the nested-name-specifier.
(emphasis mine)
And since the class-name is the injected-class-name C and not C<T> in our example, the correct way to write an out of class implementation for the destructor would be as shown below:
template<typename T>
struct C
{
~C(); //this is an ordinary destructor(meaning it is not templated)
};
template<typename T>
//-----v----------------> C is the injected-class-name and not C<T>
C<T>::~C()
{
}
int main()
{
C<int> c;;
}
Demo
Here is the clang bug report:
Clang accepts invalid out of class definition for destructor
Here is msvc bug report:
MSVC accepts invalid out of class definition for a destructor of a class template with c++20
For further reading
One can also refer to:
Why destructor cannot be template?
Error: Out-of-line constructor cannot have template arguments C++.
template<typename T> someclass<T>::someclass<T>() is not allowed when providing an out of class definition for a constructor and a destructor with any c++ version.
I don't think it's true that this was invalid in C++17.
CWG 1435 introduced the wording that allowed the code (that wording can still be seen in the context for the [class.dtor] changes in CWG 2337):
in a declaration at namespace scope or in a friend declaration, the id-expression is nested-name-specifier ~class-name and the class-name names the same class as the nested-name-specifier."
This allows the code, because C<T>::~C<T> is an id-expression of that form. The class-name in the destructor ~C<T> names the same class as in C<T>::. That wording was present in C++17 and C++20.
P1787R6 Declarations and where to find them changed that wording to:
otherwise, the id-expression is nested-name-specifier ~class-name and the class-name is the injected-class-name of the class nominated by the nested-name-specifier."
This no longer allows ~C<T> because the injected-class-name is just C.
This seems like a breaking change that was not obvious from the revision history of the r5 paper, which includes:
Required destructor declarations to use the injected-class-name, avoiding name lookup
Simplified lookup for destructors
Specified that < begins a template argument list in a destructor name
All compilers (GCC, Clang, EDG and MSVC) accept the program in C++17 mode, and I don't think it's at all clear that the code was always invalid in older standards, as OP claims. It seems to be a possibly-unintentional change in C++23. Edit: I'm reliably informed by Jason Merrill that it was an intentional change for C++23, but not a DR for C++17. See his comment on the other answer.
The following code snippet:
struct a
{
[[nodiscard]] friend int b();
};
Produces this error when compiling on clang++ (trunk 342102) with -std=c++17:
<source>:3:5: error: an attribute list cannot appear here
[[nodiscard]] friend int b();
^~~~~~~~~~~~~
Removing friend or adding a body to b prevents the error.
g++ (trunk) compiles the code just fine.
Live example on godbolt: https://gcc.godbolt.org/z/ttTDuZ
Is this a clang++ bug? Or is there some rule in the Standard that makes this code ill-formed?
If clang++ is correct, what's the proper way of marking a friend member function as [[nodiscard]]?
Per [dcl.attr.grammar]/5
Each attribute-specifier-seq is said to appertain to some entity or statement, identified by the syntactic context where it appears ([stmt.stmt], [dcl.dcl], [dcl.decl]). If an attribute-specifier-seq that appertains to some entity or statement contains an attribute or alignment-specifier that is not allowed to apply to that entity or statement, the program is ill-formed. If an attribute-specifier-seq appertains to a friend declaration, that declaration shall be a definition. No attribute-specifier-seq shall appertain to an explicit instantiation.
emphasis mine
So, clang is right here. If you have an attribute, the function must have a definition if it is a friend function.
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...
The following namespace definition fails to compile when the first declaration is commented out. If the first declaration of foo is uncommented, then it compiles just fine.
namespace Y
{
//void foo();
void ::Y::foo(){}
}
The relevant part in the standard (§8.3¶1) says:
When the declarator-id is qualified, the declaration shall refer to a previously declared member
I understand that this rule prevents the introduction of names into other namespaces. I wonder if that rule could be relaxed to allow for qualified-ids referring to the current namespace.
CWG #482 is relevant:
According to 8.3 [dcl.meaning] paragraph 1, […]
This restriction prohibits examples like the following:
void f();
void ::f(); // error: qualified declarator
namespace N {
void f();
void N::f() { } // error: qualified declarator
}
There doesn't seem to be any good reason for disallowing such
declarations, and a number of implementations accept them in spite of
the Standard's prohibition. Should the Standard be changed to allow
them?
Notes from the April, 2006 meeting:
In discussing issue 548, the CWG agreed that the prohibition of
qualified declarators inside their namespace should be removed.
So your code is valid if the first declaration of foo is present (as of about 2012; GCC has an open bug report). If not, however, your quoted wording still applies and renders the qualified declaration ill-formed. I see no reason to permit that case; it intuitively implies that the name has been declared already, since qualified name lookup must determine what it refers to.
class Test
{
enum{};
...
};
Is this empty enum definition portable? Compiles in gcc and and msvc.
such an enum is specifically listed in clause 7 paragraph 3 of the C++ standard as
ill-formed. gcc does not accept it. there was a bug fix for this in gcc:
http://gcc.gnu.org/bugzilla/show_bug.cgi?id=29018
According to the following snippet from the c++ standard we can deduce that it's indeed a valid statement:
7.2/1 Enumeration declarations (C++03)...
enum-specifier:
enum identifieropt { enumerator-listopt }
Note that both the identifier and the enumerator-list are optional, and therefor a statement as enum {} is valid (if you ask the standard).
But doesn't the standard also say that empty declarations are ill-formed?
Yes, and there is even an example of enum { }; in the below snippet from the standard.
7/3 Specifiers (C++03)
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), the decl-specifier-seq shall introduce one or more names into
the program, or shall redeclare a name introduced by a previous
declaration.
*Example [
enum { }; // ill-formed
typedef class { }; // ill-formed
*end example]
Conclusion
The statement seems to be ill-formed after a careful look at the standard, though compilers are written by humans - and humans tend to make mistakes and sometimes overlook things.
TL;DR You should not use an empty declaration such as enum { };, even though it compiles