问题
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();
}
回答1:
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.
回答2:
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 { };
回答3:
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).
回答4:
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
回答5:
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:
namespace detail
{
template<typename T, REQUIRES(helper::has_to_string(T))>
std::string stringify(choice<0>, T&& t)
{
using std::to_string;
return to_string(std::forward<T>(t));
}
template<std::size_t N>
std::string stringify(choice<1>, char const(&arr)[N])
{
return std::string(arr, N);
}
template<typename T, REQUIRES(helper::has_output_operator(T))>
std::string stringify(choice<2>, T&& t)
{
std::ostringstream o;
o << std::forward<T>(t);
return std::move(o).str();
}
}
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:
template<typename T>
auto stringify(T&& t)
-> decltype( detail::stringify(choice<0>{}, std::forward<T>(t)) )
{
return detail::stringify(choice<0>{}, std::forward<T>(t));
}
How is this implemented? We steal a bit from Xeo @ Flaming Dangerzone and Paul @ void_t "can implement concepts"? (using simplified implementations):
constexpr static std::size_t choice_max = 10;
template<std::size_t N> struct choice : choice<N+1>
{
static_assert(N < choice_max, "");
};
template<> struct choice<choice_max> {};
#include <type_traits>
template<typename T, typename = void> struct models : std::false_type {};
template<typename MF, typename... Args>
struct models<MF(Args...),
decltype(MF{}.requires_(std::declval<Args>()...),
void())>
: std::true_type {};
#define REQUIRES(...) std::enable_if_t<models<__VA_ARGS__>::value>* = nullptr
The choice classes inherit from worse choices: choice<0>
inherits from choice<1>
. Therefore, for an argument of type choice<0>
, a function parameter of type choice<0>
is a better match than choice<1>
, which is a better match than choice<2>
and so on [over.ics.rank]p4.4
Note 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:
#include <string>
#include <sstream>
namespace helper
{
using std::to_string;
struct has_to_string
{
template<typename T>
auto requires_(T&& t) -> decltype( to_string(std::forward<T>(t)) );
};
struct has_output_operator
{
std::ostream& ostream();
template<typename T>
auto requires_(T&& t) -> decltype(ostream() << std::forward<T>(t));
};
}
Macros can be avoided by using an idea from R. Martinho Fernandes:
template<typename T>
using requires = std::enable_if_t<models<T>::value, int>;
// exemplary application:
template<typename T, requires<helper::has_to_string(T)> = 0>
std::string stringify(choice<0>, T&& t)
{
using std::to_string;
return to_string(std::forward<T>(t));
}
回答6:
Well, you can just skip all the metaprogramming magic and use the fit::conditional adaptor from the Fit library:
FIT_STATIC_LAMBDA_FUNCTION(stringify) = fit::conditional(
[](auto x) -> decltype(to_string(x))
{
return to_string(x);
},
[](auto x) -> decltype(static_cast<ostringstream&>(ostringstream() << x).str())
{
return static_cast<ostringstream&>(ostringstream() << x).str();
}
);
Or even more compact, if you don't mind macros:
FIT_STATIC_LAMBDA_FUNCTION(stringify) = fit::conditional(
[](auto x) FIT_RETURNS(to_string(x)),
[](auto x) FIT_RETURNS(static_cast<ostringstream&>(ostringstream() << x).str())
);
Note, I also constrained the second function as well, so if the type can't be called with to_string
nor streamed to ostringstream
then the function can't be called. This helps with better error messages and better composability with checking type requirements.
来源:https://stackoverflow.com/questions/30189926/metaprograming-failure-of-function-definition-defines-a-separate-function