(Oktalist gave an excellent answer below, check it out and the comments below it to demonstrate everything we discussed, I added a complete, compilation solution at the bottom of my question that demonstrates everything that was discussed.)
I have a set of global namespace methods and template methods, such as:
namespace PrettyPrint { String to_string(bool val); String to_string(char val); String to_string(int val); String to_string(uint val);
The type "String" is not a C ++ string; it is a special class derived from the IBM ICU library, but not very relevant for this question. The point is, I have a bunch of global namespace methods called to_string, as well as some template functions that override them. So far, so good, everything works fine. But now I have a different header, where I have something like the following:
namespace User { struct Service { int code; String name; }
So, now I defined some other type somewhere else, and I also defined another to_string override in my PrettyPrint namespace to indicate how to convert my new type to String. Throw both headers into a file, something like this:
#include <the to_string and templates header> #include <the User::Service header> main() { User::Service s = {1, U("foo")}; User::Service *p = &s; PrettyPrint::to_string(s); PrettyPrint::to_string(p); }
(Yes, the to_string methods should actually return a value somewhere, not a period.) The fact is that the second call gives a compiler error (gcc, btw) saying that the templated to_string method does not have a corresponding function to call to_string ( const User :: Service &) ', which, of course, exactly matches my method, which is defined and enabled. If I cancel the #include order, it works fine.
So, I assume that the template only considers the methods defined before it. Is there any fix for this? Considering the size of my project and the number of complex # include, just saying: “always make sure they come in the right order” is not an acceptable solution and makes the code too fragile. The original to_string defintions is one of those files that will usually be included in many places, so make sure that any other random type definitions that include the over_string override do not work first.
Another solution that I use in some places is that I defined the Printable interface in the base file:
namespace PrettyPrint { class Printable { public: virtual String pretty_print_to_string() const = 0; } String to_string(const Printable& obj) { return obj.pretty_print_to_string(); } }
This definition applies to template methods in the same file. So this is great for classes where I can just add to this interface and implement it. Apart from the big solutions, I’ll just try to always use this, but there are places where it’s not convenient, and I just wanted to understand if there is a way to get the method of overloading the solution regardless of ordering #include to work.
Anyway, what do you all think of these options? Is there an approach that I did not think about that could work?
Oktalist inspired solution
This code actually compiles, so you can copy it and play with it, I think that it captured all the relevant use cases along with what works and what doesn't and why.
#include <iostream> using namespace std; namespace PrettyPrint { void sample(int val) { cout << "PrettyPrint::sample(int)\n"; } void sample(bool val) { cout << "PrettyPrint::sample(bool)\n"; } template<typename T> void sample(T* val) { cout << "PrettyPrint::sample(pointer); -> "; sample(*val); } } namespace User { struct Foo { int i; bool b; }; void sample(const Foo& val) { //below doesn't work un-qualified, tries to convert the int (val.i) into a Foo to make a recursive call. //meaning, it matches the User namespace version first //sample(val.i); doesn't work, tries to call User::sample(const Foo&) cout << "User::sample(const Foo&); -> {\n"; cout << '\t'; PrettyPrint::sample(val.i); //now it works cout << '\t'; PrettyPrint::sample(val.b); cout << "}\n"; } } namespace Other { void test(User::Foo* fubar) { cout << "In Other::test(User::Foo*):\n"; //PrettyPrint::sample(*fubar); //doesn't work, can't find sample(const User::Foo&) in PrettyPrint PrettyPrint::sample(fubar); //works, by argument-dependent lookup (ADL) from the template call sample(*fubar); //works, finds the method by ADL //sample(fubar); //doesn't work, only sees User::sample() and can't instantiate a Foo& from a Foo* } void test2(User::Foo* happy) { using PrettyPrint::sample; //now both work! this is the way to do it. cout << "In Other::test2(User::Foo*):\n"; sample(*happy); sample(happy); } } int main() { int i=0, *p = &i; bool b=false; User::Foo f = {1, true}, *pf = &f; //sample(i); <-- doesn't work, PrettyPrint namespace is not visible here, nor is User for that matter. PrettyPrint::sample(i); //now it works //PrettyPrint::sample(f); //doesn't work, forces search in PrettyPrint only, doesn't see User override. using namespace PrettyPrint; // now they all work. sample(p); sample(b); sample(f); sample(pf); Other::test(pf); Other::test2(pf); return 0; }
The result is the following:
PrettyPrint::sample(int) PrettyPrint::sample(pointer); -> PrettyPrint::sample(int) PrettyPrint::sample(bool) User::sample(const Foo&); -> { PrettyPrint::sample(int) PrettyPrint::sample(bool) } PrettyPrint::sample(pointer); -> User::sample(const Foo&); -> { PrettyPrint::sample(int) PrettyPrint::sample(bool) } In Other::test(User::Foo*): PrettyPrint::sample(pointer); -> User::sample(const Foo&); -> { PrettyPrint::sample(int) PrettyPrint::sample(bool) } User::sample(const Foo&); -> { PrettyPrint::sample(int) PrettyPrint::sample(bool) } In Other::test2(User::Foo*): User::sample(const Foo&); -> { PrettyPrint::sample(int) PrettyPrint::sample(bool) } PrettyPrint::sample(pointer); -> User::sample(const Foo&); -> { PrettyPrint::sample(int) PrettyPrint::sample(bool) }