问题
I have a dozen functions or so that take two parameters: a generic, and a specific type. E.g.:
template <class A, class B>
void foo(A& a, B& b)
{
cout << "generic fallback" << endl;
}
template <class A>
void foo(A& a, int &i)
{
cout << "generic int" << endl;
}
template <class A>
void foo(A& a, string& s)
{
cout << "generic str" << endl;
}
I want to create an overload which will get called whenever A
is an instance of a particular struct[1]. The best I came up with so far was:
struct mine
{
int is_special;
};
template <class A, class B>
auto foo(A& a, B& b) -> decltype(A::is_special, void())
{
cout << "specialized fallback" << endl;
}
The result I want is:
int x;
string y;
float z;
string generic;
mine special;
foo(generic, x); // generic int
foo(generic, y); // generic string
foo(generic, z); // generic fallback
foo(special, x); // specialized fallback
foo(special, y); // specialized fallback
foo(special, z); // specialized fallback
The above code doesn't work, however, because for the specialized cases, there is an ambiguous overload. Is there any easy way to cause those functions to only get created if A::is_special
is not a valid type? Ideally I would annotate each function with something like:
template <class A, class B>
auto foo(A& a, B& b) -> decltype(doesnt_work(A::is_special), void())
// ...
I ask also in the more general case: given any "positive" SFINAE test which results in a function or class getting created as a result of the test, is there any way to negate that test specifically for use in other cases? In essence, the equivalent of if ... else if
with SFINAE.
I did get this case to work, but I had to rename all foo
to foo_imp
, add a long
parameter to the generic ones, an int
parameter to the specialized one, and then define a foo
that called them (ideone code here). This seems less-than-ideal in that it's not as straightforward, although either way I have to modify all the existing foo
s anyway.
[1] Note that I can't use the type's name because it is a nested template and would thus result in a non-deducible context.
回答1:
In essence, the equivalent of
if ... else if
with SFINAE.
You can manually control overload resolution with an extra parameter:
template<int I> struct rank : rank<I-1> { static_assert(I > 0, ""); };
template<> struct rank<0> {};
template <class A, class B>
auto foo(A& a, B& b, rank<10>) -> /*some SFINAE */
{
}
template <class A, class B>
auto foo(A& a, B& b, rank<9>) -> /* some other SFINAE */
{
}
// ...
template <class A, class B>
void foo(A& a, B& b, rank<0>)
{
// fallback
}
template <class A, class B>
void foo(A& a, B& b) { return foo(a, b, rank<20>()); }
The viable overload with the highest "rank" will be selected by overload resolution.
You can't directly negate an ad hoc SFINAE constraint ("this expression used in the signature must be well-formed"). You'll need to write an actual trait to detect it, and then negate the result. Simplest way to do it is probably by using std::experimental::is_detected_v, recently voted into v2 of the library fundamentals TS:
template<class T>
using is_special_t = decltype(T::is_special);
template <class A, class B>
auto foo(A& a, B& b) -> std::enable_if_t<!std::experimental::is_detected_v<is_special_t, A>>
{
cout << "generic fallback" << endl;
}
template <class A, class B>
auto foo(A& a, B& b) -> std::enable_if_t<std::experimental::is_detected_v<is_special_t, A>>
{
cout << "specialized fallback" << endl;
}
回答2:
First, a template to check whether a given type is "special":
template<typename ...> using void_t = void;
template< typename, typename = void >
struct is_special : public std::false_type {};
template< typename T >
struct is_special<T, void_t<decltype(T::is_special)> > : public std::true_type {};
Instead of the void_t
, you could also use your decltype(T::is_special, void() )
construct.
Next, you can plug in some SFINAE expressions which enable/disable the relevant functions depending on the passed types. The following code particularly disables any generic callback for your special type.
template <class A, class B, typename = std::enable_if_t<!is_special<A>::value> >
void foo(A& a, B& b)
{
cout << "generic fallback" << endl;
}
template <class A, typename = std::enable_if_t<!is_special<A>::value>>
void foo(A& a, int &i)
{
cout << "generic int" << endl;
}
template <class A, typename = std::enable_if_t<!is_special<A>::value>>
void foo(A& a, string& s)
{
cout << "generic str" << endl;
}
//this SFINAE check is probably redundant
template <class A, class B, typename = std::enable_if_t<is_special<A>::value> >
auto foo(A& a, B& b)
{
cout << "specialized fallback" << endl;
}
DEMO
Note that the last check for is_special
in the specialized fallback is probably redundant.
回答3:
template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;
std::void_t
is not in C++11. The above will work in every C++11 compiler I've tried it on. (a one-step alias breaks some compilers due to a C++11 language defect)
namespace details {
template<template<class...>class Z, class, class...Ts>
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 {};
}
template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z, void, Ts...>;
this takes a template Z
and a pack of parameters Ts...
, and returns true_type
iff Z<Ts...>
is a valid expression.
template<class T>
using is_special_t = decltype( T::is_special );
takes a T
, and returns its is_special
type.
template<class T>
using has_special = can_apply< is_special_t, T >;
solves your problem.
This is similar in function to std::experimental::is_detected, but is an independent implementation.
来源:https://stackoverflow.com/questions/30766965/is-there-a-generic-way-to-negate-a-decltype-condition-with-sfinae