In this answer I define a template based on the type's is_arithmetic
property:
template<typename T> enable_if_t<is_arithmetic<T>::value, string> stringify(T t){
return to_string(t);
}
template<typename T> enable_if_t<!is_arithmetic<T>::value, string> stringify(T t){
return static_cast<ostringstream&>(ostringstream() << t).str();
}
dyp suggests that rather than the is_arithmetic
property of the type, that whether to_string
is defined for the type be the template selection criteria. This is clearly desirable, but I don't know a way to say:
If
std::to_string
is not defined then use theostringstream
overload.
Declaring the to_string
criteria is simple:
template<typename T> decltype(to_string(T{})) stringify(T t){
return to_string(t);
}
It's the opposite of that criteria that I can't figure out how to construct. This obviously doesn't work, but hopefully it conveys what I'm trying to construct:
template<typename T> enable_if_t<!decltype(to_string(T{})::value, string> (T t){
return static_cast<ostringstream&>(ostringstream() << t).str();
}
Using Walter Brown's void_t
:
template <typename...>
using void_t = void;
It's very easy to make such a type trait:
template<typename T, typename = void>
struct has_to_string
: std::false_type { };
template<typename T>
struct has_to_string<T,
void_t<decltype(std::to_string(std::declval<T>()))>
>
: std::true_type { };
First, I think SFINAE should usually be hidden from interfaces. It makes the interface messy. Put the SFINAE away from the surface, and use tag dispatching to pick an overload.
Second, I even hide SFINAE from the traits class. Writing "can I do X" code is common enough in my experience that I don't want to have to write messy SFINAE code to do it. So instead I write a generic can_apply
trait, and have a trait that SFINAE fails if passed the wrong types using decltype
.
We then feed the SFIANE failing decltype
trait to can_apply
, and get out a true/false type depending on if the application fails.
This reduces the work per "can I do X" trait to a minimal amount, and places the somewhat tricky and fragile SFINAE code away from day-to-day work.
I use C++1z's void_t
. Implementing it yourself is easy (at the bottom of this answer).
A metafunction similar to can_apply
is being proposed for standardization in C++1z, but it isn't as stable as void_t
is, so I'm not using it.
First, a details
namespace to hide the implementation of can_apply
from being found by accident:
namespace details {
template<template<class...>class Z, class, class...>
struct can_apply:std::false_type{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:
std::true_type{};
}
We can then write can_apply
in terms of details::can_apply
, and it has a nicer interface (it doesn't require the extra void
being passed):
template<template<class...>class Z, class...Ts>
using can_apply=details::can_apply<Z, void, Ts...>;
The above is generic helper metaprogramming code. Once we have it in place, we can write a can_to_string
traits class very cleanly:
template<class T>
using to_string_t = decltype( std::to_string( std::declval<T>() ) );
template<class T>
using can_to_string = can_apply< to_string_t, T >;
and we have a trait can_to_string<T>
that is true iff we can to_string
a T
.
The work require to write a new trait like that is now 2-4 lines of simple code -- just make a decltype
using
alias, and then do a can_apply
test on it.
Once we have that, we use tag dispatching to the proper implementation:
template<typename T>
std::string stringify(T t, std::true_type /*can to string*/){
return std::to_string(t);
}
template<typename T>
std::string stringify(T t, std::false_type /*cannot to string*/){
return static_cast<ostringstream&>(ostringstream() << t).str();
}
template<typename T>
std::string stringify(T t){
return stringify(t, can_to_string<T>{});
}
All of the ugly code is hiding in the details
namespace.
If you need a void_t
, use this:
template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;
which works in most major C++11 compilers.
Note that the simpler template<class...>using void_t=void;
fails to work in some older C++11 compilers (there was an ambiguity in the standard).
Freshly voted into the library fundamentals TS at last week's committee meeting:
template<class T>
using to_string_t = decltype(std::to_string(std::declval<T>()));
template<class T>
using has_to_string = std::experimental::is_detected<to_string_t, T>;
Then tag dispatch and/or SFINAE on has_to_string
to your heart's content.
You can consult the current working draft of the TS on how is_detected
and friends can be implemented. It's rather similar to can_apply
in @Yakk's answer.
You could write a helper trait for this using expression SFINAE:
namespace detail
{
//base case, to_string is invalid
template <typename T>
auto has_to_string_helper (...) //... to disambiguate call
-> false_type;
//true case, to_string valid for T
template <typename T>
auto has_to_string_helper (int) //int to disambiguate call
-> decltype(std::to_string(std::declval<T>()), true_type{});
}
//alias to make it nice to use
template <typename T>
using has_to_string = decltype(detail::has_to_string_helper<T>(0));
Then use std::enable_if_t<has_to_string<T>::value>
Demo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With