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

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

Related

extern struct forward declaration

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

Visual studio 2010 unscoped opaque-enum-declarartion with omitted base

In the following code there are no errors or warnings at compile/link-time
enum E;
enum E;
int _tmain(int argc, _TCHAR* argv[]){ }
Is it VS2010 bug?
Your code compiles with VC++ 2010 and later by virtue of MS language extensions
that you can disable with the compiler flag /Za.
At first sight,
enum E;
enum E;
looks just like two forward declarations of enum E. In that light, the duplication does not appear
relevant. N forward declarations of something are no more or less legal than one, so the effect of
enabling the MS extensions (/Ze) would appear to include enabling forward enum declarations,
which are illegal per C++03 or C++11.
But that appearance is misleading, as the following program shows:
#include <iostream>
enum E;
//enum E;
enum E e;
int main()
{
std::cout << (e == 0) << std::endl;
return e;
}
This also compiles clean with VC++ 2010 or later, proving that
the compiler parses enum E; not as forward declaration of E but as a definition.
The language extension here is that enum E;, on first sight, is equated with enum E {}.
If we uncomment //enum E; in the program, it still compiles clean, showing that on second sight
and subsequently, enum E; is not again parsed as a definition, just as a redeclaration of E.
VC++ 2013 supports the C++11 concepts of scoped and based enums. With extensions disabled,
it will compile and reject declarations as indicated by this piece of code:
enum UnscopedBasedEnum : int; // :)
enum UnscopedBasedEnum : int; // :)
UnscopedBasedEnum ube; // :)
enum struct ScopedBaselessEnum; // :)
enum struct ScopedBaselessEnum; // :)
ScopedBaselessEnum sbe;
//enum UnscopedBaseLessEnum; // :(
all of which is C++11 compliant.
The declarations of ube and sbe prove, however, that none of the
declarations here is a forward declaration, if foward declaration has the customary meaning
of declaration of an incomplete type. If UnscopedBasedEnum|ScopedBaselessEnum
was an incomplete type in these declarations then the declaration of ube|sbe would
would declare an object of incomplete type, and would not compile. Per C++11, the declarations
of UnscopedBasedEnum and ScopedBaselessEnum are opaque enum declarations, meaning
that no enumerator-list is present or implied. But they nevertheless declare complete
types: p 7.2.3
An opaque-enum-declaration is either a redeclaration of an enumeration in the current scope or a declaration
of a new enumeration. [ Note: An enumeration declared by an opaque-enum-declaration has fixed underlying
type and is a complete type. The list of enumerators can be provided in a later redeclaration with an enum-specifier.
—end note ]
What you are doing here is enum forward declaration. Some compilers (like VS) provide language extensions which enable this behaviour for pre-C++11 standard. You can verify this by disabling the language extensions in your project settings which will result in a compiler error. G++ does not have such extensions but it should compile your code with -std=c++11 after adding size specifiers or when using enum class instead.
Valid in Visual Studio with compiler extensions enabled
enum E;
enum E;
Valid in all compilers supporting C++11
enum class E;
enum class E;
// or
enum E : short;
enum E : short;

Defining a top-level no-op in C++?

Is the following legal according to the C++ standard? (If the answer differs from standard to standard, I would like to know that, too.)
#define VERY_OLD_COMPILER 1
#ifdef VERY_OLD_COMPILER
#define USING_NAMESPACE_STD enum { }
#else
#define USING_NAMESPACE_STD using namespace std
#endif
USING_NAMESPACE_STD;
int main(int argc, char *argv[]) {
// etc.
The goal is to define a macro that I can invoke at the top-level and follow with a semicolon, such that it has no effect. I am pretty sure stray semicolons at the top-level are not allowed (GCC complains about them, anyway), so simply defining an empty macro does not work.
Declaring an empty anonymous struct does not work because it needs a name, and I do not want to pollute the namespace.
Does an anonymous empty enum declaration (enum { }) do the trick? It works on all of the compilers I have tried, but of course that is not the same thing as being permitted by the spec.
Any other ideas/comments welcome. Well, anything other than "throw out that compiler". Believe me, I would love to.
Looking at the latest public C++0x draft, it seems semicolons at top-level are allowed and ignored.
The grammar treats a translation-unit as a sequence of declarations, and amongst the various kinds of declarations there is an empty-declaration that is just a simple semicolon.
Pragmatic solution: considering that your VERY_OLD_COMPILER constant suggests that the whole thing is to be a part of a workaround for an older compiler, I'd just pick a solution that works with this compiler, be it standardised or not.
#define USING_NAMESPACE_STD static int dummy##__LINE__
An empty enum is in fact illegal according to C++03:
7/3 Declarations:
In a simple-declaration, the optional init-declarator-list can be omitted only when declaring a class (clause 9) or enumeration (7.2), that is, when the decl-specifier-seq contains either a class-specifier, an elaboratedtype-specifier with a class-key (9.1), 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 eing 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]
So I would agree with MSN's answer to declare a dummy enum, struct forward declaration, or typedef declaration with a name that is obviously not going to conflict with anything (throw a GUID in there for good measure). The nice thing about these things is that you can have the declaration show up more than once, and as long as it's the same as before there's no problem.
Comeau says no:
> Comeau C/C++ 4.3.10.1 (Oct 6 2008 11:28:09) for
> ONLINE_EVALUATION_BETA2 Copyright 1988-2008 Comeau Computing. All
> rights reserved. MODE:strict errors C++ C++0x_extensions
>
> "ComeauTest.c", line 1: error: declaration does not declare anything
> enum { }; ^
>
> 1 error detected in the compilation of "ComeauTest.c".
You can use
#define USING_NAMESPACE_STD struct very_long_name_that_i_hope_doesnt_collide_because_if_it_does_oh_noes
Sorry, I forgot you needed to do it at the top level.
How about
extern int _;
? I don't know what undesirable side effects this would have, though I can't think of any.
#define VERY_OLD_COMPILER 1
#ifdef VERY_OLD_COMPILER
#define USING_NAMESPACE_STD typedef unsigned long uint32
#else
#define USING_NAMESPACE_STD using namespace std
#endif
USING_NAMESPACE_STD;
Edit:
This should work also:
#define VERY_OLD_COMPILER 1
#ifdef VERY_OLD_COMPILER
#define USING_NAMESPACE_STD double fabs(double)
#else
#define USING_NAMESPACE_STD using namespace std
#endif
USING_NAMESPACE_STD;
#define VERY_OLD_COMPILER 1
#ifdef VERY_OLD_COMPILER
#define USING_NAMESPACE_STD ;
#else
#define USING_NAMESPACE_STD using namespace std
#endif
USING_NAMESPACE_STD;
int main(int argc, char *argv[]) {
// etc.

extern enum in c++

I have an enum I have declared in some .h file:
typedef enum {
NONE,
ONE,
TWO,
THREE
} MYENUM;
in a seperate .cpp I cannot do this:
extern enum MYENUM; //works
extern MYENUM TWO; //makes sence, TWO is not an INSTANCE of MYENUM...
how would one do so without including the whole header where the enum is declared?
You can't use an incomplete type. You can only pass around pointers to it. This is because until the type is completed, the compiler doesn't know how big it is. OTOH a pointer is the size of a data pointer, no matter what type it's pointing to. One of the things you can't do with an incomplete type is declare variables of that type.
extern in a variable declaration means that the compiler will emit a reference to an identifier provided in another compilation unit (to be resolved by the linker), instead of allocating storage. extern does not modify the type, even if it appears next to the type name in C++ grammar.
What you can do is take advantage of the fact that enum members are integral constant values, and convert just fine to the primitive integral types.
So you can do this:
A.cpp
enum MYENUM { ONE=1, TWO, THREE };
int var = TWO;
B.cpp
extern int var;
But the types must match. You couldn't say MYENUM var = TWO; and also extern int var;. That would violate the one-definition-rule (violation might or might not be detected by the linker).
As an aside, this is incorrect:
typedef enum {
NONE,
ONE,
TWO,
THREE
} MYENUM;
enum MYENUM TWO;
MYENUM is NOT an enum identifier. It is a typedef, and cannot be qualified with the enum keyword later.
You cannot use enum values, if they are not visible. If the header is too large to include, why not just put the enum in its own header, and include only that?

Is it legal C++ to use a typedef in a method declaration but the canonical type in the method definition?

The GNU C++ (g++ -pedantic -Wall) accepts this:
typedef int MyInt;
class Test
{
public:
MyInt foo();
void bar(MyInt baz);
};
int Test::foo()
{
return 10;
}
void Test::bar(int baz)
{
}
int main(void)
{
Test t;
t.bar(t.foo());
return 0;
}
Is it legal C++? Are other compilers likely to accept it?
Yes it is legal:
7.1.3 The typedef specifier
A name declared with the typedef
specifier becomes a typedef-name.
Within the scope of its declaration, a
typedef-name is syntactically
equivalent to a keyword and names the
type associated with the identifier in
the way described in clause 8. A
typedef-name is thus a synonym for
another type. A typedef-name does not
introduce a new type the way a class
declaration (9.1) or enum declaration
does.
Regardless of whether it is legal it's not the best of practices. Typedefs exist so you can change the base type and have it reflected all over your code and if you ever do so you'll find suddenly your program doesn't compile anymore.
Yes, it is legal.
It is questionable, since it's not obvious anymore how declaration and definition match, but if you have a good reason, you can do it.