Why can't I make a external type appear like an inner type in c++?

Viewed 76

I want to maintain an existing API that heavily uses an inner enum but I need to separate the enum out into its own type:

This is the current API:

class Outer {
public:
    enum Inner {
        FIELD1
    };
};

In other words, users currently expect to set Inners by using Outer::Inner. I would like to preserve the name if possible when I make the new outside type:

enum Inner {
    FIELD1
};

class Outer {
public:
    typedef Inner Inner;
}

However - it gives me a compiler error:

./Playground/file0.cpp:23:19: error: declaration of 'typedef enum Inner Outer::Inner' changes meaning of 'Inner' [-fpermissive]
   23 |     typedef Inner Inner;
      |                   ^~~~~
./Playground/file0.cpp:17:6: note: 'Inner' declared here as 'enum Inner'
   17 | enum Inner {
      |      ^~~~~

If I just change the name of the enum it all works fine.

#include <cassert>

enum Enum {
    FIELD1
};

class Outer {
public:
    typedef Enum Inner;
};

int main() {

    Enum field = Enum::FIELD1;
    Outer::Inner field_alt = Outer::Inner::FIELD1;
    assert(field == field_alt);
    return 0;
}
3 Answers

Your typedef for Inner needs to refer to the Inner enum in the outer scope, so it should look like this:

typedef ::Inner Inner;

I generally prefer using statements though, which looks like this:

using Inner = ::Inner;

Here's a demo

This is not allowed because of the rule [basic.scope.class]/3:

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.

When you do typedef Inner Inner;, you're violating this rule. The first time Inner appears on that line, it is a use of the name, and is found to refer to the declaration ::Inner. But once the class is completely defined, Inner now refers to the typedef declaration. So the program is ill-formed NDR (GCC is nice enough to give you a diagnostic).

You need to change it to:

typedef ::Inner Inner;

Now, the unqualified name Inner is not being used anymore; instead you are using the qualified name ::Inner, and its meaning stays the same after the typedef, so there should be no issue.

The solutions provided answered my original question, but unfortunately didn't solve my troubles. I hadn't realized at the time that the API in fact took advantage of inner enums.

enum Inner {
    FIELD1,
    FIELD2
};

class Base {
public:
    virtual void use(Inner val) = 0;
};

class Outer : public Base {
public:
    virtual void use(Inner val) override;
};

/*
 * Some API that can't change
 */
void someAPI(Outer &inst) {
    inst.use(Outer::FIELD1);
}

In other words, the API I needed to maintain was not Outer::Inner::FIELD1 but Outer::FIELD1.

As @Brian pointed out, using a typedef to 'hoist' the Inner enum into Outer could not work in current c++.

Eventually, I found I could put Inner into Base, and the API could be maintained:

class Base {
public:
    enum Inner {
        FIELD1,
        FIELD2
    };

    virtual void use(Inner val) = 0;
};

class Outer : public Base {
public:
    virtual void use(Inner val) override;
};

/*
 * Some API that can't change
 */
void someAPI(Outer &inst) {
    inst.use(Outer::FIELD1);
}
Related