Forward declaring an enum in C++

2019-01-02 14:41发布

I'm trying to do something like the following:

enum E;

void Foo(E e);

enum E {A, B, C};

which the compiler rejects. I've had a quick look on Google and the consensus seems to be "you can't do it", but I can't understand why. Can anyone explain?

Clarification 2: I'm doing this as I have private methods in a class that take said enum, and I do not want the enum's values exposed - so, for example, I do not want anyone to know that E is defined as

enum E {
    FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X
}

as project X is not something I want my users to know about.

So, I wanted to forward declare the enum so I could put the private methods in the header file, declare the enum internally in the cpp, and distribute the built library file and header to people.

As for the compiler - it's GCC.

标签: c++ enums
18条回答
闭嘴吧你
2楼-- · 2019-01-02 15:06

In my projects, I adopted the Namespace-Bound Enumeration technique to deal with enums from legacy and 3rd-party components. Here is an example:

forward.h:

namespace type
{
    class legacy_type;
    typedef const legacy_type& type;
}

enum.h:

// May be defined here or pulled in via #include.
namespace legacy
{
    enum evil { x , y, z };
}


namespace type
{
    using legacy::evil;

    class legacy_type
    {
    public:
        legacy_type(evil e)
            : e_(e)
        {}

        operator evil() const
        {
            return e_;
        }

    private:
        evil e_;
    };
}

foo.h:

#include "forward.h"

class foo
{
public:
    void f(type::type t);
};

foo.cc:

#include "foo.h"

#include <iostream>
#include "enum.h"

void foo::f(type::type t)
{
    switch (t)
    {
        case legacy::x:
            std::cout << "x" << std::endl;
            break;
        case legacy::y:
            std::cout << "y" << std::endl;
            break;
        case legacy::z:
            std::cout << "z" << std::endl;
            break;
        default:
            std::cout << "default" << std::endl;
    }
}

main.cc:

#include "foo.h"
#include "enum.h"

int main()
{
    foo fu;
    fu.f(legacy::x);

    return 0;
}

Note that the foo.h header does not have to know anything about legacy::evil. Only the files that use the legacy type legacy::evil (here: main.cc) need to include enum.h.

查看更多
公子世无双
3楼-- · 2019-01-02 15:08

Seems it can not be forward-declared in GCC!

Interesting discussion here

查看更多
初与友歌
4楼-- · 2019-01-02 15:08

You can wrap the enum in a struct, adding in some constructors and type conversions, and forward declare the struct instead.

#define ENUM_CLASS(NAME, TYPE, VALUES...) \
struct NAME { \
    enum e { VALUES }; \
    explicit NAME(TYPE v) : val(v) {} \
    NAME(e v) : val(v) {} \
    operator e() const { return e(val); } \
    private:\
        TYPE val; \
}

This appears to work: http://ideone.com/TYtP2

查看更多
美炸的是我
5楼-- · 2019-01-02 15:10

I'm adding an up-to-date answer here, given recent developments.

You can forward-declare an enum in C++11, so long as you declare its storage type at the same time. The syntax looks like this:

enum E : short;
void foo(E e);

....

enum E : short
{
    VALUE_1,
    VALUE_2,
    ....
}

In fact, if the function never refers to the values of the enumeration, you don't need the complete declaration at all at that point.

This is supported by G++ 4.6 and onwards (-std=c++0x or -std=c++11 in more recent versions). Visual C++ 2013 supports this; in earlier versions it has some sort of non-standard support that I haven't figured out yet - I found some suggestion that a simple forward declaration is legal, but YMMV.

查看更多
与风俱净
6楼-- · 2019-01-02 15:10

Just noting that the reason actually is that the size of the enum is not yet known after forward declaration. Well, you use forward declaration of a struct to be able to pass a pointer around or refer to an object from a place that's refered to in the forward declared struct definition itself too.

Forward declaring an enum would not be too useful, because one would wish to be able to pass around the enum by-value. You couldn't even have a pointer to it, because i recently got told some platforms use pointers of different size for char than for int or long. So it all depends on the content of the enum.

The current C++ Standard explicitly disallows doing something like

enum X;

(in 7.1.5.3/1). But the next C++ Standard due to next year allows the following, which convinced me the problem actually has to do with the underlying type:

enum X : int;

It's known as a "opaque" enum declaration. You can even use X by value in the following code. And its enumerators can later be defined in a later redeclaration of the enumeration. See 7.2 in the current working draft.

查看更多
与风俱净
7楼-- · 2019-01-02 15:12

For VC, here's the test about forward declaration and specifying underlying type:

  1. the following code is compiled ok.
    typedef int myint;
    enum T ;
    void foo(T * tp )
    {
        * tp = (T)0x12345678;
    }
    enum T : char
    {
        A
    };

But got the warning for /W4(/W3 not incur this warning)

warning C4480: nonstandard extension used: specifying underlying type for enum 'T'

  1. VC(Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 15.00.30729.01 for 80x86) looks buggy in the above case:

    • when seeing enum T; VC assumes the enum type T uses default 4 bytes int as underlying type, so the generated assembly code is:
    ?foo@@YAXPAW4T@@@Z PROC                 ; foo
    ; File e:\work\c_cpp\cpp_snippet.cpp
    ; Line 13
        push    ebp
        mov ebp, esp
    ; Line 14
        mov eax, DWORD PTR _tp$[ebp]
        mov DWORD PTR [eax], 305419896      ; 12345678H
    ; Line 15
        pop ebp
        ret 0
    ?foo@@YAXPAW4T@@@Z ENDP                 ; foo

The above assembly code is extracted from /Fatest.asm directly, not my personal guess. Do you see the mov DWORD PTR[eax], 305419896 ; 12345678H line?

the following code snippet proves it:

    int main(int argc, char *argv)
    {
        union {
            char ca[4];
            T t;
        }a;
        a.ca[0] = a.ca[1] = a.[ca[2] = a.ca[3] = 1;
        foo( &a.t) ;
        printf("%#x, %#x, %#x, %#x\n",  a.ca[0], a.ca[1], a.ca[2], a.ca[3] );
        return 0;
    }

the result is: 0x78, 0x56, 0x34, 0x12

  • after remove the forward declaration of enum T and move the definition of function foo after the enum T's definition: the result is OK:

the above key instruction becomes:

mov BYTE PTR [eax], 120 ; 00000078H

the final result is: 0x78, 0x1, 0x1, 0x1

Note the value is not being overwritten

So using of the forward-declaration of enum in VC is considered harmful.

BTW, to not surprise, the syntax for declaration of the underlying type is same as its in C#. In pratice I found it's worth to save 3 bytes by specifying the underlying type as char when talk to the embedded system, which is memory limited.

查看更多
登录 后发表回答