ryan0270 ryan0270 - 1 month ago 4
C++ Question

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;
}

Answer

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.