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();
}
Well, you can just skip all the metaprogramming magic and use the
fit::conditional
adaptor from the Fit library:Or even more compact, if you don't mind macros:
Note, I also constrained the second function as well, so if the type can't be called with
to_string
nor streamed toostringstream
then the function can't be called. This helps with better error messages and better composability with checking type requirements.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 usingdecltype
.We then feed the SFIANE failing
decltype
trait tocan_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 asvoid_t
is, so I'm not using it.First, a
details
namespace to hide the implementation ofcan_apply
from being found by accident:We can then write
can_apply
in terms ofdetails::can_apply
, and it has a nicer interface (it doesn't require the extravoid
being passed):The above is generic helper metaprogramming code. Once we have it in place, we can write a
can_to_string
traits class very cleanly:and we have a trait
can_to_string<T>
that is true iff we canto_string
aT
.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 acan_apply
test on it.Once we have that, we use tag dispatching to the proper implementation:
All of the ugly code is hiding in the
details
namespace.If you need a
void_t
, use this: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:
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 tocan_apply
in @Yakk's answer.Using Walter Brown's
void_t
:It's very easy to make such a type trait:
You could write a helper trait for this using expression SFINAE:
Then use
std::enable_if_t<has_to_string<T>::value>
Demo
I think there are two problems: 1) Find all viable algorithms for a given type. 2) Select the best one.
We can, for example, manually specify an order for a set of overloaded algorithms:
The first function parameter specifies the order between those algorithms ("first choice", "second choice", ..). In order to select an algorithm, we simply dispatch to the best viable match:
How is this implemented? We steal a bit from Xeo @ Flaming Dangerzone and Paul @
void_t
"can implement concepts"? (using simplified implementations):The choice classes inherit from worse choices:
choice<0>
inherits fromchoice<1>
. Therefore, for an argument of typechoice<0>
, a function parameter of typechoice<0>
is a better match thanchoice<1>
, which is a better match thanchoice<2>
and so on [over.ics.rank]p4.4Note that the more specialized tie breaker applies only if neither of two functions is better. Due to the total order of
choice
s, we'll never get into that situation. This prevents calls from being ambiguous, even if multiple algorithms are viable.We define our type traits:
Macros can be avoided by using an idea from R. Martinho Fernandes: