ryan0270
ryan0270

Reputation: 1187

Compiler is trying to initialize enum class from int when it shouln't

I have an enum class of types and want a "to_string" function for outputting the type name so I wrote that inside my own namespace. Problem is, other functions in that namespace trying to call to_string on, e.g., an int (really just an int, not intended to be part of the enum) are finding the custom to_string and giving errors about invalid initialization of the enum.

I know I could explicitly call std::to_string instead of to_string but I assume there's a better way. What am I doing wrong?

Here is example code:

#include <iostream>
#include <string>

namespace other {
    enum class Type {
        Type1,
        Type2
    };

    std::string to_string(const Type& type) {
        switch(type) {
            case Type::Type1:
                return "Type1";
                break;
            case Type::Type2:
                return "Type2";
                break;
            default:
            {}
        }

        return "Unknown";
    }

    void run() {
        using namespace std;
        cout << string("Type: ") + to_string(Type::Type1) << endl;
        cout << string("int: " )  + to_string(42) << endl;  // this one generates compile-time errors
    }
}

int main() {
    other::run();

    using namespace std;
    cout << string("int: " )  + to_string(42) << endl;  // This one is ok

    return 0;
}

Upvotes: 3

Views: 159

Answers (2)

Vaughn Cato
Vaughn Cato

Reputation: 64308

This is a tricky situation which involves some subtle rules of namespaces. Let's consider a simpler example:

namespace b {
  void f(int) { }
}

namespace a {
  using namespace b;

  void f(char) { }

  void g()
  {
    f(5); // calls f(char)
  }
}

The issue here is that even though we have using namespace b, the declarations inside b are treated as if they were declared in the common namespace (global) for the purposes of lookup:

(C++14 7.3.4/2)

A using-directive specifies that the names in the nominated namespace can be used in the scope in which the using-directive appears after the using-directive. During unqualified name lookup (3.4.1), the names appear as if they were declared in the nearest enclosing namespace which contains both the using-directive and the nominated namespace. [ Note: In this context, “contains” means “contains directly or indirectly”. — end note ]

Because of this, for the purposes of lookup, the names in namespace b are treated as if they were in the global namespace. That means f(char) inside namespace a will hide f(int) inside namespace b:

(C++14 3.3.10/4)

During the lookup of a name qualified by a namespace name, declarations that would otherwise be made visible by a using-directive can be hidden by declarations with the same name in the namespace containing the using-directive; see (3.4.3.2).

In your example, a call to to_string(42) in other::run() will find other::to_string, because std::to_string(int) is hidden.

Upvotes: 3

Vittorio Romeo
Vittorio Romeo

Reputation: 93274

You need to explicitly specify what function you want to bring into the "overload set" (see wandbox example):

void run() {
    using namespace std;
    using std::to_string;
    cout << string("Type: ") + to_string(Type::Type1) << endl;
    cout << string("int: " )  + to_string(42) << endl;  
}

The reason is that ADL ignores using directives. Refer to 3.4.2 [basic.lookup.argdep]:

When considering an associated namespace, the lookup is the same as the lookup performed when the associated namespace is used as a qualifier (3.4.3.2) except that: — Any using-directives in the associated namespace are ignored.

More detailed information available in this question.

Upvotes: 4

Related Questions