GCC likes to tell me that I'm missing a specifier-qualifier-list in its error messages.
I know that this means I didn't put in a correct type of something.
But what exactly is a specifier-qualifier-list?
Edit:
Example C code that causes this:
#include <stdio.h>
int main(int argc, char **argv) {
struct { undefined_type *foo; } bar;
printf("Hello, world!");
}
Gives these errors from GCC:
Lappy:code chpwn$ gcc test.c
test.c: In function ‘main’:
test.c:4: error: expected specifier-qualifier-list before ‘undefined_type’
It's a list of specifiers and qualifiers :-) Specifiers are things like void, char, struct Foo, etc., and qualifiers are keywords like const and volatile. See this C grammar for the definition.
In your case, undefined_type was not defined yet, so the parser saw it as an identifier, not a specifier-qualifier-list like it expected. If you were to typedef ... undefined_type; before its occurrence, then undefined_type would become a specifier.
If you think in terms of parsing C with a context-free grammar, the way the compiler handles typedefs and such may be bothersome. If I understand correctly, it games the parser generator by sneaking in symbol table operations so it can use context to parse the source code.
Related
Background
Everybody agrees that
using <typedef-name> = <type>;
is equivalent to
typedef <type> <typedef-name>;
and that the former is to be preferred to the latter for various reasons (see Scott Meyers, Effective Modern C++ and various related questions on stackoverflow).
This is backed by [dcl.typedef]:
A typedef-name can also be introduced by an alias-declaration. The identifier following the using keyword
becomes a typedef-name and the optional attribute-specifier-seq following the identifier appertains to that
typedef-name. Such a typedef-name has the same semantics as if it were introduced by the typedef specifier.
However, consider a declaration such as
typedef struct {
int val;
} A;
For this case, [dcl.typedef] specifies:
If the typedef declaration defines an unnamed class (or enum), the first typedef-name declared by the
declaration to be that class type (or enum type) is used to denote the class type (or enum type) for linkage
purposes only (3.5).
The referenced section 3.5 [basic.link] says
A name having namespace scope that has not
been given internal linkage above has the same linkage as the enclosing namespace if it is the name of
[...]
an unnamed class defined in a typedef declaration in which the class has
the typedef name for linkage purposes [...]
Assuming the typedef declaration above is done in the global namespace, the struct A would have external linkage, since the global namespace has external linkage.
Question
The question is now whether the same is true, if the typedef declaration is replaced by an alias declaration according to the common notion that they are equivalent:
using A = struct {
int val;
};
In particular, does the type A declared via the alias declaration ("using") have the same linkage as the one declared via the typedef declaration?
Note that [decl.typedef] does not say that an alias declaration is a typedef declaration (it only says that both introduce a typedef-name) and that [decl.typedef] speaks only of a typedef declaration (not an alias declaration) having the property of introducing a typedef name for linkage purposes.
If the alias declaration is not capable of introducing a typedef name for linkage purposes, A would just be an alias for an anonymous type and have no linkage at all.
IMO, that's at least one possible, albeit strict, interpretation of the standard. Of course, I may be overlooking something.
This raises the subsequent questions:
If there is indeed this subtle difference, is it by intention or is
it an oversight in the standard?
What is the expected behavior of compilers/linkers?
Research
The following minimal program consisting of three files (we need at least two separate compilation units) is used to investigate the issue.
a.hpp
#ifndef A_HPP
#define A_HPP
#include <iosfwd>
#if USING_VS_TYPEDEF
using A = struct {
int val;
};
#else
typedef struct {
int val;
} A;
#endif
void print(std::ostream& os, A const& a);
#endif // A_HPP
a.cpp
#include "a.hpp"
#include <iostream>
void print(std::ostream& os, A const& a)
{
os << a.val << "\n";
}
main.cpp
#include "a.hpp"
#include <iostream>
int main()
{
A a;
a.val = 42;
print(std::cout, a);
}
GCC
Compiling this with gcc 7.2 with the "typedef" variant compiles cleanly and provides the expected output:
> g++ -Wall -Wextra -pedantic-errors -DUSING_VS_TYPEDEF=0 a.cpp main.cpp
> ./a.out
42
The compilation with the "using" variant produces a compile error:
> g++ -Wall -Wextra -pedantic-errors -DUSING_VS_TYPEDEF=1 a.cpp main.cpp
a.cpp:4:6: warning: ‘void print(std::ostream&, const A&)’ defined but not used [-Wunused-function]
void print(std::ostream& os, A const& a)
^~~~~
In file included from main.cpp:1:0:
a.hpp:16:6: error: ‘void print(std::ostream&, const A&)’, declared using unnamed type, is used but never defined [-fpermissive]
void print(std::ostream& os, A const& a);
^~~~~
a.hpp:9:2: note: ‘using A = struct<unnamed>’ does not refer to the unqualified type, so it is not used for linkage
};
^
a.hpp:16:6: error: ‘void print(std::ostream&, const A&)’ used but never defined
void print(std::ostream& os, A const& a);
^~~~~
This looks like GCC follows the strict interpretation of the standard above and makes a difference concerning linkage between the typedef and the alias declaration.
Clang
Using clang 6, both variants compile and run cleanly without any warnings:
> clang++ -Wall -Wextra -pedantic-errors -DUSING_VS_TYPEDEF=0 a.cpp main.cpp
> ./a.out
42
> clang++ -Wall -Wextra -pedantic-errors -DUSING_VS_TYPEDEF=1 a.cpp main.cpp
> ./a.out
42
One could therefore also ask
Which compiler is correct?
This looks to me like a bug in GCC.
Note that [decl.typedef] does not say that an alias declaration is a typedef declaration
You're right, [dcl.dcl]p9 gives a definition of the term typedef declaration which excludes alias-declarations. However, [dcl.typedef] does explicitly say, as you quoted in your question:
2 A typedef-name can also be introduced by an alias-declaration. The identifier following the using keyword becomes a typedef-name and the optional attribute-specifier-seq following the identifier appertains to that typedef-name. It has the same semantics as if it were introduced by the typedef specifier. [...]
"The same semantics" doesn't leave any doubt. Under GCC's interpretation, typedef and using have different semantics, therefore the only reasonable conclusion is that GCC's interpretation is wrong. Any rules applying to typedef declarations must be interpreted as applying to alias-declarations as well.
It looks like the standard is unclear on this.
On one hand,
[dcl.typedef] A typedef-name can also be introduced by an alias-declaration. [...] Such a typedef-name has the same semantics as if it were introduced by the typedef specifier.
On the other hand, the standard clearly separates the notions of typedef declaration and alias-declaration (the latter term is a grammar production name, so it is italicised and hyphenated; the former is not). In some contexts it talks about "a typedef declaration or alias-declaration", making them equivalent in these contexts; and sometimes it talks solely about "a typedef declaration". In particular, whenever the standard talks about linkage and typedef declarations, it only talks about typedef declarations and does not mention alias-declaration. This includes the key passage
[dcl.typedef] If the typedef declaration defines an unnamed class (or enum), the first typedef-name declared by the declaration to be that class type (or enum type) is used to denote the class type (or enum type) for linkage
purposes only.
Note the standard insists on the first typedef-name being used for linkage. This means that in
typedef struct { int x; } A, B;
only A is used for linkage, and B is not. Nothing in the standard indicates that a name introduced by alias-declaration should behave like A and not like B.
It is my opinion that the standard is insufficiently clear in this area. If the intent is to make only typedef declaration work for linkage, then it would be appropriate to state explicitly in [dcl.typedef] that alias-declaration does not. If the intent is to make alias-declaration work for linkage, this should be stated explicitly too, as is done in other contexts.
Background
Everybody agrees that
using <typedef-name> = <type>;
is equivalent to
typedef <type> <typedef-name>;
and that the former is to be preferred to the latter for various reasons (see Scott Meyers, Effective Modern C++ and various related questions on stackoverflow).
This is backed by [dcl.typedef]:
A typedef-name can also be introduced by an alias-declaration. The identifier following the using keyword
becomes a typedef-name and the optional attribute-specifier-seq following the identifier appertains to that
typedef-name. Such a typedef-name has the same semantics as if it were introduced by the typedef specifier.
However, consider a declaration such as
typedef struct {
int val;
} A;
For this case, [dcl.typedef] specifies:
If the typedef declaration defines an unnamed class (or enum), the first typedef-name declared by the
declaration to be that class type (or enum type) is used to denote the class type (or enum type) for linkage
purposes only (3.5).
The referenced section 3.5 [basic.link] says
A name having namespace scope that has not
been given internal linkage above has the same linkage as the enclosing namespace if it is the name of
[...]
an unnamed class defined in a typedef declaration in which the class has
the typedef name for linkage purposes [...]
Assuming the typedef declaration above is done in the global namespace, the struct A would have external linkage, since the global namespace has external linkage.
Question
The question is now whether the same is true, if the typedef declaration is replaced by an alias declaration according to the common notion that they are equivalent:
using A = struct {
int val;
};
In particular, does the type A declared via the alias declaration ("using") have the same linkage as the one declared via the typedef declaration?
Note that [decl.typedef] does not say that an alias declaration is a typedef declaration (it only says that both introduce a typedef-name) and that [decl.typedef] speaks only of a typedef declaration (not an alias declaration) having the property of introducing a typedef name for linkage purposes.
If the alias declaration is not capable of introducing a typedef name for linkage purposes, A would just be an alias for an anonymous type and have no linkage at all.
IMO, that's at least one possible, albeit strict, interpretation of the standard. Of course, I may be overlooking something.
This raises the subsequent questions:
If there is indeed this subtle difference, is it by intention or is
it an oversight in the standard?
What is the expected behavior of compilers/linkers?
Research
The following minimal program consisting of three files (we need at least two separate compilation units) is used to investigate the issue.
a.hpp
#ifndef A_HPP
#define A_HPP
#include <iosfwd>
#if USING_VS_TYPEDEF
using A = struct {
int val;
};
#else
typedef struct {
int val;
} A;
#endif
void print(std::ostream& os, A const& a);
#endif // A_HPP
a.cpp
#include "a.hpp"
#include <iostream>
void print(std::ostream& os, A const& a)
{
os << a.val << "\n";
}
main.cpp
#include "a.hpp"
#include <iostream>
int main()
{
A a;
a.val = 42;
print(std::cout, a);
}
GCC
Compiling this with gcc 7.2 with the "typedef" variant compiles cleanly and provides the expected output:
> g++ -Wall -Wextra -pedantic-errors -DUSING_VS_TYPEDEF=0 a.cpp main.cpp
> ./a.out
42
The compilation with the "using" variant produces a compile error:
> g++ -Wall -Wextra -pedantic-errors -DUSING_VS_TYPEDEF=1 a.cpp main.cpp
a.cpp:4:6: warning: ‘void print(std::ostream&, const A&)’ defined but not used [-Wunused-function]
void print(std::ostream& os, A const& a)
^~~~~
In file included from main.cpp:1:0:
a.hpp:16:6: error: ‘void print(std::ostream&, const A&)’, declared using unnamed type, is used but never defined [-fpermissive]
void print(std::ostream& os, A const& a);
^~~~~
a.hpp:9:2: note: ‘using A = struct<unnamed>’ does not refer to the unqualified type, so it is not used for linkage
};
^
a.hpp:16:6: error: ‘void print(std::ostream&, const A&)’ used but never defined
void print(std::ostream& os, A const& a);
^~~~~
This looks like GCC follows the strict interpretation of the standard above and makes a difference concerning linkage between the typedef and the alias declaration.
Clang
Using clang 6, both variants compile and run cleanly without any warnings:
> clang++ -Wall -Wextra -pedantic-errors -DUSING_VS_TYPEDEF=0 a.cpp main.cpp
> ./a.out
42
> clang++ -Wall -Wextra -pedantic-errors -DUSING_VS_TYPEDEF=1 a.cpp main.cpp
> ./a.out
42
One could therefore also ask
Which compiler is correct?
This looks to me like a bug in GCC.
Note that [decl.typedef] does not say that an alias declaration is a typedef declaration
You're right, [dcl.dcl]p9 gives a definition of the term typedef declaration which excludes alias-declarations. However, [dcl.typedef] does explicitly say, as you quoted in your question:
2 A typedef-name can also be introduced by an alias-declaration. The identifier following the using keyword becomes a typedef-name and the optional attribute-specifier-seq following the identifier appertains to that typedef-name. It has the same semantics as if it were introduced by the typedef specifier. [...]
"The same semantics" doesn't leave any doubt. Under GCC's interpretation, typedef and using have different semantics, therefore the only reasonable conclusion is that GCC's interpretation is wrong. Any rules applying to typedef declarations must be interpreted as applying to alias-declarations as well.
It looks like the standard is unclear on this.
On one hand,
[dcl.typedef] A typedef-name can also be introduced by an alias-declaration. [...] Such a typedef-name has the same semantics as if it were introduced by the typedef specifier.
On the other hand, the standard clearly separates the notions of typedef declaration and alias-declaration (the latter term is a grammar production name, so it is italicised and hyphenated; the former is not). In some contexts it talks about "a typedef declaration or alias-declaration", making them equivalent in these contexts; and sometimes it talks solely about "a typedef declaration". In particular, whenever the standard talks about linkage and typedef declarations, it only talks about typedef declarations and does not mention alias-declaration. This includes the key passage
[dcl.typedef] If the typedef declaration defines an unnamed class (or enum), the first typedef-name declared by the declaration to be that class type (or enum type) is used to denote the class type (or enum type) for linkage
purposes only.
Note the standard insists on the first typedef-name being used for linkage. This means that in
typedef struct { int x; } A, B;
only A is used for linkage, and B is not. Nothing in the standard indicates that a name introduced by alias-declaration should behave like A and not like B.
It is my opinion that the standard is insufficiently clear in this area. If the intent is to make only typedef declaration work for linkage, then it would be appropriate to state explicitly in [dcl.typedef] that alias-declaration does not. If the intent is to make alias-declaration work for linkage, this should be stated explicitly too, as is done in other contexts.
In a very simple situation with a constrained constructor, testing for convertibility of the argument, an error is produced in clang, but not in g++:
#include <type_traits>
template <class T, class U>
constexpr bool Convertible = std::is_convertible<T,U>::value && std::is_convertible<U,T>::value;
template <class T>
struct A
{
template <class S, class = std::enable_if_t<Convertible<S,T>> >
A(S const&) {}
};
int main()
{
A<double> s = 1.0;
}
Maybe this issue is related to Is clang's c++11 support reliable?
The error clang gives, reads:
error: no member named 'value' in 'std::is_convertible<double, A<double> >'
constexpr bool Convertible = std::is_convertible<T,U>::value && std::is_convertible<U,T>::value;
~~~~~~~~~~~~~~~~~~~~~~~~~~^
I've tried
g++-5.4, g++-6.2 (no error)
clang++-3.5, clang++-3.8, clang++-3.9 (error)
with argument -std=c++1y and for clang either with -stdlib=libstdc++ or -stdlib=libc++.
Which compiler is correct? Is it a bug in clang or gcc? Or is the behavior for some reasons undefined and thus both compilers correct?
First of all, note that it works fine if you use:
A<double> s{1.0};
Instead, the error comes from the fact that you are doing this:
A<double> s = 1.0;
Consider the line below (extracted from the definition of Convertible):
std::is_convertible<U,T>::value
In your case, this is seen as it follows (once substitution has been performed):
std::is_convertible<double, A<double>>::value
The compiler says this clearly in the error message.
This is because a temporary A<double> is constructed from 1.0, then it is assigned to s.
Note that in your class template you have defined a (more or less) catch-all constructor, so a const A<double> & is accepted as well.
Moreover, remember that a temporary binds to a const reference.
That said, the error happens because in the context of the std::enable_if_t we have that A<double> is an incomplete type and from the standard we have this for std::is_convertible:
From and To shall be complete types [...]
See here for the working draft.
Because of that, I would say that it's an undefined behavior.
As a suggestion, you don't need to use std::enable_if_t in this case.
You don't have a set of functions from which to pick the best one up in your example.
A static_assert is just fine and error messages are nicer:
template <class S>
A(S const&) { static_assert(Convertible<S,T>, "!"); }
Why am I getting a warning about initialization in one case, but not the other? The code is in a C++ source file, and I am using GCC 4.7 with -std=c++11.
struct sigaction old_handler, new_handler;
The above doesn't produce any warnings with -Wall and -Wextra.
struct sigaction old_handler={}, new_handler={};
struct sigaction old_handler={0}, new_handler={0};
The above produces warnings:
warning: missing initializer for member ‘sigaction::__sigaction_handler’ [-Wmissing-field-initializers]
warning: missing initializer for member ‘sigaction::sa_mask’ [-Wmissing-field-initializers]
warning: missing initializer for member ‘sigaction::sa_flags’ [-Wmissing-field-initializers]
warning: missing initializer for member ‘sigaction::sa_restorer’ [-Wmissing-field-initializers]
I've read through How should I properly initialize a C struct from C++?, Why is the compiler throwing this warning: "missing initializer"? Isn't the structure initialized?, and bug reports like Bug 36750. Summary: -Wmissing-field-initializers relaxation request. I don't understand why the uninitialized struct is not generating a warning, while the initialized struct is generating a warning.
Why is the uninitialized structs not generating a warning; and why is the initialized structs generating a warning?
Here is a simple example:
#include <iostream>
struct S {
int a;
int b;
};
int main() {
S s { 1 }; // b will be automatically set to 0
// and that's probably(?) not what you want
std::cout<<"s.a = "<<s.a<<", s.b = "<<s.b<<std::endl;
}
It gives the warning:
missing.cpp: In function ‘int main()’:
missing.cpp:9:11: warning: missing initializer for member 'S::b' [-Wmissing-field-initializers]
The program prints:
s.a = 1, s.b = 0
The warning is just a reminder from the compiler that S has two members but you only explicitly initialized one of them, the other will be set to zero. If that's what you want, you can safely ignore that warning.
In such a simple example, it looks silly and annoying; if your struct has many members, then this warning can be helpful (catching bugs: miscounting the number of fields or typos).
Why is the uninitialized structs not generating a warning?
I guess it would simply generate too much warnings. After all, it is legal and it is only a bug if you use the uninitialized members. For example:
int main() {
S s;
std::cout<<"s.a = "<<s.a<<", s.b = "<<s.b<<std::endl;
}
missing.cpp: In function ‘int main()’:
missing.cpp:10:43: warning: ‘s.S::b’ is used uninitialized in this function [-Wuninitialized]
missing.cpp:10:26: warning: ‘s.S::a’ is used uninitialized in this function [-Wuninitialized]
Even though it did not warn me about the uninitialized members of s, it did warn me about using the uninitialized fields. All is fine.
Why is the initialized structs generating a warning?
It warns you only if you explicitly but partially initialize the fields. It is a reminder that the struct has more fields than you enumerated. In my opinion, it is questionable how useful this warning is: It can indeed generate too much false alarms. Well, it is not on by default for a reason...
That's a defective warning. You did initialize all the members, but you just didn't have the initializers for each member separately appear in the code.
Just ignore that warning if you know what you are doing. I regularly get such warnings too, and I'm upset regularly. But there's nothing I can do about it but to ignore it.
Why is the uninitialized struct not giving a warning? I don't know, but most probably that is because you didn't try to initialize anything. So GCC has no reason to believe that you made a mistake in doing the initialization.
You're solving the symptom but not the problem. Per my copy of "Advanced Programming in the UNIX Environment, Second Edition" in section 10.15:
Note that we must use sigemptyset() to initialize the sa_mask member of the structure. We're not guaranteed that act.sa_mask = 0 does the same thing.
So, yes, you can silence the warning, and no this isn't how you initialize a struct sigaction.
The compiler warns that all members are not initialized when you initialize the struct. There is nothing to warn about declaring an uninitialized struct. You should get the same warnings when you (partially) initialize the uninitialized structs.
struct sigaction old_handler, new_handler;
old_handler = {};
new_handler = {};
So, that's the difference. Your code that doesn't produce the warning is not an initialization at all. Why gcc warns about zero initialized struct at all is beyond me.
The only way to prevent that warning (or error, if you or your organization is treating warnings as errors (-Werror option)) is to memset() it to an init value. For example:
#include <stdio.h>
#include <memory.h>
typedef struct {
int a;
int b;
char c[12];
} testtype_t;
int main(int argc, char* argv[]) {
testtype_t type1;
memset(&type1, 0, sizeof(testtype_t));
printf("%d, %s, %d\n", argc, argv[0], type1.a);
return 0;
}
It is not very clean, however, it seems like that for GCC maintainers, there is only one way to initialize a struct and code beauty is not on top of their list.
Clang's own diagnostics propaganda contains this exerpt:
Since Clang has range highlighting, it never needs to pretty print your code back out to you. This is particularly bad in G++ (which often emits errors containing lowered vtable references), but even GCC can produce inscrutible error messages in some cases when it tries to do this.
Googling this phrase doesn't give anything very helpful, and the subsequent example is completely unrelated.
Can someone please post an example of what it's talking about?
Thanks.
Here is an example:
struct a {
virtual int bar();
};
struct foo : public virtual a {
};
void test(foo *P) {
return P->bar()+*P;
}
Clang produces:
t.cc:9:18: error: invalid operands to binary expression ('int' and 'foo')
return P->bar()+*P;
~~~~~~~~^~~
GCC 4.2 produces:
t.cc: In function ‘void test(foo*)’:
t.cc:9: error: no match for ‘operator+’ in ‘(((a*)P) + (*(long int*)(P->foo::<anonymous>.a::_vptr$a + -0x00000000000000020)))->a::bar() + * P’
t.cc:9: error: return-statement with a value, in function returning 'void'
GCC does this because its C++ frontend is bolted on top of the C frontend in many cases. Instead of building C++-specific Abstract Syntax Trees (ASTs) for various C++ operations, the parser just lowers them immediately to their C equivalent. In this case, GCC synthesizes an struct to contain the vtable, and the pointer dereference to bar is then lowered into a series of C pointer dereferences, casts, pointer arithmetic etc.
Clang does not have this problem, because it has a very clean AST that directly represents the source code. If you change the example to:
struct a {
virtual int bar();
};
struct foo : public virtual a {
};
void test(foo *P) {
P->bar();
}
.. so that the code is valid, then ask clang to dump its ast with "clang -cc1 -ast-dump t.cc", you get:
...
void test(foo *P)
(CompoundStmt 0x10683cae8 <t.cc:8:19, line:10:1>
(CXXMemberCallExpr 0x10683ca78 <line:9:3, col:10> 'int'
(MemberExpr 0x10683ca40 <col:3, col:6> '<bound member function type>' ->bar 0x10683bef0
(ImplicitCastExpr 0x10683cac8 <col:3> 'struct a *' <UncheckedDerivedToBase (virtual a)>
(ImplicitCastExpr 0x10683ca28 <col:3> 'struct foo *' <LValueToRValue>
(DeclRefExpr 0x10683ca00 <col:3> 'struct foo *' lvalue ParmVar 0x10683c8a0 'P' 'struct foo *'))))))
-Chris