3

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;
}
ryan0270
  • 1,135
  • 11
  • 33
  • Just my view on things...if you can be more specific, do it. For example in your `run()`, I would type `other::to_string` and `std::to_string` so that the next poor guy that edits it knows exactly what I intended. Good question, I never considered namespace hiding/overloading before. – Matt Nov 08 '16 at 15:34

2 Answers2

4

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.

Community
  • 1
  • 1
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • Why do I need to do that? Shouldn't the compiler detect that conversion from int to Type is not valid and then move on to look for another signature that is valid (and ending up at std::to_string)? – ryan0270 Nov 08 '16 at 14:47
  • Bringing in `std::to_string` explicitly makes this method a better match in the name lookup. Without this, you pay (roughly speaking) for each step in the namespace hierarchy. With the import, it is available at no cost. – m8mble Nov 08 '16 at 14:49
  • Sorry for not providing an explanation, I'm trying to find a good source/standard part to quote. [Take a look here meanwhile](http://stackoverflow.com/questions/27544893/why-doesnt-a-using-directive-affect-adl) – Vittorio Romeo Nov 08 '16 at 14:50
3

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.

Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
  • So functions in "lower" namespaces will shadow functions higher up if just the name is the same? Even though the signature is different? Is there a practical reason why the compiler doesn't see an error trying to match signatures and then continue on to look further up for a valid match? – ryan0270 Nov 08 '16 at 15:26
  • @ryan0270: Yes, it was considered too confusing to have overloading work across scopes. More detail here: http://stackoverflow.com/a/1629074/951890 – Vaughn Cato Nov 08 '16 at 15:29