When writing templated libraries, sometimes it is desirable to implement behavior later than the definition of a function template. For example, I'm thinking of a log
function in a logger library that is implemented as
template< typename T >
void log(const T& t) {
std::cout << "[log] " << t << std::endl;
}
Later if I want to use log
on my own type, I would implement std::ostream& operator<<(std::ostream&, CustomType)
and hope that log
function works on CustomType
automatically.
However, I'm very unsure whether this pattern is conformant to the standard. To see how compilers treat it, I wrote the following minimal example.
#include<iostream>
// In some library...
void foo(double) { std::cout << "double" << std::endl; }
template< typename T>
void doFoo(T x) {
foo(x);
}
// In some codes using the library...
struct MyClass {};
template< typename T > struct MyClassT {};
namespace my { struct Class {}; }
void foo(MyClass) { std::cout << "MyClass" << std::endl; }
void foo(MyClassT<int>) { std::cout << "MyClassT<int>" << std::endl; }
void foo(my::Class) { std::cout << "my::Class" << std::endl; }
void foo(int) { std::cout << "int" << std::endl; }
int main() {
doFoo(1.0); // okay, prints "double".
doFoo(MyClass{}); // okay, prints "MyClass".
doFoo(MyClassT<int>{}); // okay, prints "MyClassT<int>".
doFoo(42); // not okay, prints "double". int seems to have been converted to double.
// doFoo(my::Class{}); // compile error, cannot convert my::Class to int.
return 0;
}
where I hope to inject to doFoo
function template by overloading the foo
function. The results seem very inconsistent, because it works for custom (templated) types, but not for custom types in namespaces or built-in types. The results are the same for compilers MSVC (bundled with Visual Studio 16.10.1), as well as gcc 9.3.0.
I'm now very confused about what should be the correct behavior. I guess it has something to do with the location of instantiation. My questions are:
- Are the codes above legal? Or are they ill-formed?
- If the codes are legal, what causes the inconsistent behaviors for different overloads?
- If the codes are illegal, what would be a good alternative to injecting library templates? (I'm thinking of passing functions/functors explicitly to my
doFoo
function, like what<algorithm>
is doing.)