extern struct forward declaration - c++

I am trying to compile some old code that include a mix of C and C++. I found following syntax:
extern struct Edge;
typedef struct Edge
{
...
Edge* edges;
...
} Edge;
When I try to compile with GCC I get an error:
a storage class can only be specified for objects and functions
extern struct Edge;
After I remove extern it compiles. I might be mistaken, but it does look to me like a forward declaration of Edge, but why there an extern keyword at the front of struct?

In C, extern struct S; is valid but not useful: it means exactly the same thing as struct S; In extern struct S a, b, c, ...;, extern applies to a, b, c, ... regardless of how many variables are declared, even if zero variables are declared.
In C++, extern struct S; is invalid. For compatibility with C, most C++ compilers allow it as an extension, but GCC doesn't. You were right to just remove the extern keyword.
It's possible that code originally written extern struct S s; was split up into separate declarations for struct S and s, and extern was accidentally left in. Since the compiler didn't mind, the author didn't notice.

I installed and tested other most popular compilers. Aforementioned code:
does NOT compile with GCC-7.3.0 and produces following error:
main.cpp:1:1: error: a storage class can only be specified for objects and functions
extern struct Edge;
^~~~~~
compiles using clang-6.0 and produces following warning:
main.cpp:1:1: warning: 'extern' is not permitted on a declaration of a type
[-Wmissing-declarations]
extern struct Edge;
compiles using Visual Studio 2017 and produces following warning:
warning C4091: 'extern ': ignored on left of 'Edge' when no variable is declared

Related

Why do Clang and MSVC not like a member typedef declaration with a redundant set of parentheses?

Consider
using foo = int;
struct A {
typedef A (foo)();
};
GCC and ICC accept the snippet, while Clang and MSVC reject it. Clang's error message is
<source>:4:15: error: function cannot return function type 'void ()'
typedef A (foo)();
^
<source>:4:13: error: typedef name must be an identifier
typedef A (foo)();
^
2 errors generated.
And MSVC says
<source>(4,15): error C2091: function returns function
typedef A (foo)();
^
(live demo)
Why do Clang and MSVC produce this error? Which compilers are correct?
(I'm specifically looking for quotation from the standard or any defect report.)
Both Clang and MSVC are ignoring the typedef specifier and reading the declaration as that of a constructor (that is, A is the constructor name) accepting parameter types (foo) (that is, (int)) and "returning" a function type signified by the trailing parentheses ().
Yes, constructors don't have return types; but if they did have return types they would have return type A, so the additional () at the end makes these compilers think that you now have a constructor with return type the function type A().
This is supported by noting that the following "similar" declarations have similar error messages:
A (foo)();
typedef ~A(foo)();
Also, by adding static we can get an illuminating error message from MSVC:
A static (int)();
error C2574: '(__cdecl *A::A(int))(void)': cannot be declared static
For workarounds: under Clang (but not MSVC) you can move the typedef specifier to the right, or use an elaborated type specifier:
A typedef (foo)();
typedef struct A (foo)();
Under all compilers you can remove or add parentheses:
typedef A foo();
typedef A ((foo))();
And you can always update to a type alias:
using foo = A();
Clang is wrong: foo in the typedef declaration in A does not refer to the namespace-scope typedef-name foo
W.r.t. the standard rules, the enclosing namespace/scope alias declaration
using foo = int;
is a red herring; within the declarative scope of class A it will be shadowed by names declared in A
#include <type_traits>
using foo = int;
struct A {
using foo = char;
foo x;
};
static_assert(std::is_same_v<foo, int>,"");
static_assert(std::is_same_v<A::foo, char>,"");
static_assert(std::is_same_v<decltype(A::x), char>,"");
The key here being that typedef A (foo)(); declares the name foo within the declarative region of A, as per [dcl.spec]/3 [emphasis mine]:
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 defining-type-specifier other than a cv-qualifier in the decl-specifier-seq.
Specifically, this means that in the typedef declaration
typedef A (foo)();
even if there is an existing typedef-name foo, that foo is not considered in the typedef declaration, namely it is not considered as a type-name part of the decl-specifier-seq of typedef A (foo)(), as A has already been encountered previous to it, and A is a valid defining-type-specifier. Thus, the original example:
using foo = int;
struct A {
typedef A (foo)();
};
can be reduced to:
// (i)
struct A {
typedef A (foo)(); // #1
};
which declares the typedef name foo in A (A::foo), where the paranthese around the name are redundant, and the typedef declaration at #1 can likewise be written as
// (ii)
struct A {
typedef A foo(); // #1
};
and can likewise be introduced using an alias declaration ([dcl.typedef]/2):
// (iii)
struct A {
using foo = A();
};
(i), (ii) and (iii) are accepted by both GCC and Clang.
Finally, we may note that Clang accepts the following program:
using foo = int;
struct A {
typedef A foo();
using bar = A();
};
static_assert(std::is_same_v<A::foo, A::bar>,"");
and that the root issue of the example of the OP is arguably a Clang bug, where Clang fails to adhere to [dcl.spec]/3 and interprets the outer-scope typedef-name foo as part of the decl-specifier-seq of the inner-scope typedef declaration, only for the case where the latter has wrapped the shadowed name foo in parantheses.
You are changing the meaning of foo from int to A() when you redeclare it inside A. This violates basic.scope.class#2:
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.
Since this is IFNDR, all compilers are conforming.

Can a function typedef be associated with extern "C"

It is apparently legal to write:
extern "C" typedef int foo_func(int);
However, if I use this typedef to declare a function, like:
foo_func foo;
will foo() have C-linkage?
There is an example in the standard (and repeated on cppreference that covers this. The name foo will have C++ linkage, while its type is a C function.

erroneous explicit template specialization of variable template in gcc

// i.h
template<int> extern int const i;
// i.cpp
#include "i.h"
template<> extern int constexpr i<0> = 42;
// main.cpp
#include "i.h"
int main()
{
return i<0>;
}
In C++14/17 mode this returns 42 with clang, but is an error with gcc: "explicit template specialization cannot have a storage class".
Is this a bug in gcc?
There is a rather simple solution to this whole issue. Please additionally see this post on the ISO C++ Standard - Discussion forum and the reply from Richard Smith.
1.
extern must not be specified in an explicit specialization
So to answer the original question: no, it is not a bug in gcc, it is correct to report an error (as Massimiliano Janes already answered).
In contrast clang actually has a bug here (as Massimiliano Janes already guessed) because extern is accepted. Maybe clang accepts it silently because it is the same as that of the primary template.
2.
Theoretically (according to the standard) the solution is to drop extern because with templates linkage is by name and so the specialization 'inherits' the linkage of the primary template (again see Massimiliano Janes' answer)
But in practice it does not work because both compilers are incorrect here and the explicit specialization incorrectly has internal linkage instead of the linkage of the primary template which is external.
3.
In summary:
gcc never compiles which is correct in (1) but incorrect in (2).
clang compiles in (1) which is incorrect but does not compile in (2) which is also incorrect.
I'll file a bug report for clang. If someone is interested please feel free to file a bug for gcc. I won't do this because (unfortunately) I can't use gcc in my development environment Visual Studio.
The primary variable template must be declared extern since it is const and I don't want initializers in the header file (just like with ordinary "extern int const i;"). Instead I want specialization definitions in some source file.
the solution should be to drop the 'extern' in the specialization.
because
[declarations/specifiers-7.1.1]A storage-class-specifier shall not be specified in an explicit specialization
the rationale being that all specializations should have the same linkage ( see for example defect report 605). So, it seems clang's wrong here.
Anyway, given that compilers turn out behaving wildly on this, a workaround could be something like
// i.h
template<int I> struct i_impl{ static const int value; };
template<int I> int const i = i_impl<I>::value;
// i.cpp
#include <i.h>
template<> const int i_impl<0>::value = 42;
According to N4340:
A storage-class-specifier othe than thread_local shall not be
specified in an explicit specialization (14.7.3) or an explicit
instantiation (14.7.2) directive.
So, This happens with the extern specifier. Just remove any storage specifier on the specialization.In your code remove extern specifier. Such as :
template<>
int constexpr i<0> = 42;

Forward declaring an enum in C headers used in C++ code

You can't forward declare an enum in C++, but you can in C.
For a C code-base that uses some C++ code, is there a way to use a forward declared enum in C that doesn't cause errors when that header is used in C++ (within an extern "C" {..} block)?
Example:
extern "C" {
enum MyEnum;
}
int main() { return 0; }
GCC gives the error:
error: use of enum ‘MyEnum’ without previous declaration
enum MyEnum;
^~~~~~
Clang also fails with:
error: ISO C++ forbids forward references to 'enum' types
enum MyEnum;
To give some context, this is a mainly C code-base where a small C++ module happens to include a header for C code. I can do some hack to make C++ ignore the enum, but I would like to know if its possible for C++ to use C headers in this case.
Update: It's been noted the official C specification doesn't support this. However, it seems this is a de facto standard among some of the most widely used compilers: GCC, Clang, and Microsoft Visual C++.
You can forward-declare enum in C++ starting from C++11. It's called "opaque declaration" rather than "forward declaration" because technically it results in a bit different effect: the size of the enum is known after its opaque declaration, while with the forward-declared types that's not the case.
However, from the daily perspective it's the same idea: you can just declare your enum and use it further in the code. From cppreference:
enum-key attr(optional) nested-name-specifier(optional) identifier enum-base(optional) ;(2) (since C++11)
2) Opaque enum declaration: defines the enumeration type but not its enumerators: after this declaration, the type is a complete type and its size is known. Note: an explicit specialization declaration of a scoped enumeration member of a class template is the only case where nested-name-specifier appears before identifier (since C++14)
Forward declaration of enums in C just makes no sense. Enums in C only introduce integral enumeration constants. They don't introduce any types you would want to use. Each enum type is an unspecified integral type, compatible with all other integral types, so there isn't any type safety at all.
typedef enum e12 { ONE, TWO } e12;
typedef enum e34 { THREE, FOUR } e34;
int main () {
e12 one = ONE;
e34 three = THREE;
one = three;
three = ONE;
return one + three;
}
This C program compiles cleanly with GCC on -Wall -Wextra -Wpedantic (it won't work as a C++ program of course).
So a useful portable cross-language solution would be
#ifdef __cplusplus
enum MyEnum : int;
#else
typedef int MyEnum; // 'enum MyEnum' would give no improvement over this
#endif
This can be done in C++11, (see #vasiliy-galkin's answer), an example of how this can work for shared C/C++ headers.
C header forward declaring MyEnum.
#ifdef __cplusplus
extern "C" {
#endif
enum MyEnum
#ifdef __cplusplus
: int
#endif
;
#ifdef __cplusplus
}
#endif

How to declare a member as a pointer to an extern "C" function?

I'm looking to declare the type of an extern "C" function pointer. It is a member variable. The syntax in this question I cannot get to compile.
template<typename Sig> struct extern_c_fp {
extern "C" typedef typename std::add_pointer<Sig>::type func_ptr_type;
};
I have experimented with placing the extern "C" at both ends, and between typedef and typename and between type and func_ptr_type, but the compiler rejected all. Any suggestions?
extern "C" {
template<typename R, typename... Args>
using extern_c_fp = R(*)(Args...);
}
using my_function_ptr = extern_c_fp<void, int, double>;
// returns void, takes int and double
This doesn’t use the same interface as you use, but there may be a way to extract the return type and argument types of Sig.
This works in clang 3.1. Xeo pointed out it didn’t work in GCC. I’m not sure if this is a bug in either compiler, so be careful when using this.
You cannot declare a typedef like that (from 7.5p4):
A linkage-specification shall occur only in namespace scope (3.3).