Reputation: 3705
I want to create a function template that is specialized for types that may have std::to_string
applied to them, another for classes for which I have defined a mylib::to_string
, and let any types for which it may not fall through to other implementations or specializations.
Example:
// I only want this to be found if std::to_string exists for ItemType
template<typename ItemType>
void func(ItemType &i)
{
std::cout << std::to_string(i);
}
// I only want this to be found if mylib::to_string exists for ItemType
template<typename ItemType>
void func(ItemType &i)
{
std::cout << mylib::to_string(i);
}
// And finally, I'd like to be able to fall through to anything that has a << defined for streams
template<>
void func<MyClass>(MyClass &i)
{
std::cout << MySpecialConverterFunc(i);
}
I always have problems with std::enable_if
syntax. How would you add it to the first template?
Upvotes: 4
Views: 951
Reputation: 10750
You can avoid having to use SFINAE by taking advantage of ADL (Argument Dependent Lookup). What ADL states is that:
If a function and a type are both declared in the same namespace, you don't have to specify the namespace when invoking that function.
For example:
namespace foo
{
class MyClass {};
std::string to_string(MyClass const& x) {
return "[Element of MyClass]";
}
}
int main() {
foo::MyClass x;
std::cout << to_string(x); // This is legal because x is in namespace foo
}
If you write to_string
functions in your namespace for your types, then you can write func
once in terms of that:
template<class T>
void func(T const& t) {
using std::to_string;
// This calls std::to_string if std::to_string is defined;
// Otherwise it uses ADL to find the correct to_string function
std::cout << to_string(t);
}
In this example, func
can be called with foo::MyClass
because there's a to_string
function that accepts MyClass
in namespace foo
:
int main() {
func(10); // Prints 10;
func(3.1415); // Prints 3.1415
foo::MyClass x;
func(x); // Prints [Element of MyClass]
}
In this case, I'd recommend creating an additional namespace and sticking the to_string
functions there, and then adding that as a using
statement:
namespace textConvert {
// Because we use std::to_string here, it automatically gets included
// When we use textConvert::to_string
using std::to_string;
// Convert a vector
template<class T>
std::string to_string(std::vector<T> const& vect) {
using std::to_string;
std::string str = "[";
for(auto& elem : vect) {
str += to_string(elem);
str += ',';
}
str.back() = ']';
return str;
}
}
Then, we can update func
to include textConvert::to_string
, and because textConvert
uses std::to_string
in addition to the various custom conversion functions, both get included!
template<class T>
void func(T const& t) {
// This *also* automatically includes std::to_string
// Because we put using std::to_string inside textConvert
using textConvert::to_string;
std::cout << to_string(t);
}
Upvotes: 2
Reputation: 170203
For SFINAE you need not to specialize, but to overload. Which is saner than dealing with specializations anyway (those have quirks). SFINAE is a mechanism to guide overload resolution, not for choosing explicit specializations of function templates.
As for the check itself, you don't need enable_if
really. Just a little bit of expression SFINAE will do:
template<typename ItemType>
auto func(ItemType &i) -> decltype(std::to_string(i), void())
{
std::cout << std::to_string(i);
}
void func(MyClass &i)
{
std::cout << MySpecialConverterFunc(i);
}
In the above code, decltype
is applied to a comma expression. The left hand side is std::to_string(i)
, if it isn't well-formed, the whole expression isn't well formed, and we get a substitution error, making the compiler discard that overload (the "not an error" part). If it's well-formed, the right hand side is a void()
literal, so decltype
resolves to void
as you intended.
When substitution fails, all we are left with is the second overload, so it's picked. It would have been picked for this case anyway, because non-template overloads are always favored in overload resolution if the signature is the same. But adding a check to better guide the compiler in other cases is not a bad idea in and of itself.
Upvotes: 5