How can I make my templated function see other global methods defined later?

(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); // ETC... template <typename T> String to_string(const T* val) { if (! val) return U("NULL"); return String().copy_formatted("(%p)-> %S", (const void*)val, to_string(*val).uchars()); } // ... and more templates to help with containers and such } 

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; } //... } namespace PrettyPrint { String to_string(const User::Service& val) { return val.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) } 
+6
source share
3 answers

During the first phase of a two-phase search, when a template is defined, an unqualified search searches for dependent and independent names in the immediate spanning namespace of the template and finds only the to_string overloads that appear before the template definition.

During the second phase of a two-phase search, when a template is created, an argument-dependent search searches for dependent names in namespaces associated with any class types passed as arguments to the named functions. But since your overload of to_string(const User::Service&) is in the PrettyPrint namespace, it will not be found dependent on the search argument.

Move the to_string(const User::Service&) transfer to the to_string(const User::Service&) namespace to use an argument-dependent search that will find any overloads declared at the time the template was instantiated, including any declared after the template was defined.

See also http://clang.llvm.org/compatibility.html#dep_lookup

+4
source

What the compiler does when creating the template instance significantly expands it (more or less like a macro) and compiles the result on the fly. To do this, any functions (or other things) mentioned should be visible at this point. Therefore, make sure that all used ads are mentioned in advance (in the same header file, most likely).

0
source

I do not quite understand what is happening here, but it seems that if you want to use later specific methods, then they should be specialized specialists. The compiler will no longer see function overrides in the program.

First, we reproduce the problem. I do this with this program in gcc-4.8.2:

 // INCLUDE FILE 1 template <class T> void to_string (T const * a) { to_string (*a); } // INCLUDE FILE 1 END // INCLUDE FILE 2 // How do we make this program work when this is necessarily declared after to_string(T const * A)? void to_string(int const & a) { return; } // INCLUDE FILE 2 END int main (void) { int i = 5; int * p = &i; to_string(i); to_string(p); to_string(&i); return 0; } 

To make it work, we need to do this ...

 // INCLUDE FILE 1 // The pointer version needs something to call... template <class T> void to_string (T const & a); // This can't be T const * a. Try it. So weird... template <class T> void to_string (T * a) { foo (*a); } // INCLUDE FILE 1 END // INCLUDE FILE 2 // This has to specialize template<> void to_string. If we just override, we get a linking error. template<> void to_string <int> (int const & a) { return; } // INCLUDE FILE 2 END int main (void) { int i = 5; int * p = &i; to_string(i); to_string(p); to_string(&i); return 0; } 
0
source

All Articles