Detect operator support with decltype/SFINAE
A (somewhat) outdated article explores ways to use decltype
along with SFINAE to detect if a type supports certain operators, such as ==
or <
.
Here's example code to detect if a class supports the <
operator:
template <class T>
struct supports_less_than
{
static auto less_than_test(const T* t) -> decltype(*t < *t, char(0))
{ }
static std::array<char, 2> less_than_test(...) { }
static const bool value = (sizeof(less_than_test((T*)0)) == 1);
};
int main()
{
std::cout << std::boolalpha <开发者_如何转开发;< supports_less_than<std::string>::value << endl;
}
This outputs true
, since of course std::string
supports the <
operator. However, if I try to use it with a class that doesn't support the <
operator, I get a compiler error:
error: no match for ‘operator<’ in ‘* t < * t’
So SFINAE is not working here. I tried this on GCC 4.4 and GCC 4.6, and both exhibited the same behavior. So, is it possible to use SFINAE in this manner to detect whether a type supports certain expressions?
In C++11 the shortest most general solution I found was this one:
#include <type_traits>
template<class T, class = decltype(std::declval<T>() < std::declval<T>() )>
std::true_type supports_less_than_test(const T&);
std::false_type supports_less_than_test(...);
template<class T> using supports_less_than = decltype(supports_less_than_test(std::declval<T>()));
#include<iostream>
struct random_type{};
int main(){
std::cout << supports_less_than<double>::value << std::endl; // prints '1'
std::cout << supports_less_than<int>::value << std::endl; // prints '1'
std::cout << supports_less_than<random_type>::value << std::endl; // prints '0'
}
Works with g++ 4.8.1
and clang++ 3.3
A more general solution for arbitrary operators (UPDATE 2014)
There is a more general solution that exploits the fact that all built-in operators are also accessible (and posibly specialized) through STD operator wrappers, such as std::less
(binary) or std::negate
(unary).
template<class F, class... T, typename = decltype(std::declval<F>()(std::declval<T>()...))>
std::true_type supports_test(const F&, const T&...);
std::false_type supports_test(...);
template<class> struct supports;
template<class F, class... T> struct supports<F(T...)>
: decltype(supports_test(std::declval<F>(), std::declval<T>()...)){};
This can be used in a quite general way, especially in C++14, where type deduction is delayed to the operator wrapper call ("transparent operators").
For binary operators it can be used as:
#include<iostream>
struct random_type{};
int main(){
std::cout << supports<std::less<>(double, double)>::value << std::endl; // '1'
std::cout << supports<std::less<>(int, int)>::value << std::endl; // '1'
std::cout << supports<std::less<>(random_type, random_type)>::value << std::endl; // '0'
}
For unary operators:
#include<iostream>
struct random_type{};
int main(){
std::cout << supports<std::negate<>(double)>::value << std::endl; // '1'
std::cout << supports<std::negate<>(int)>::value << std::endl; // '1'
std::cout << supports<std::negate<>(random_type)>::value << std::endl; // '0'
}
(With the C++11 standard library is a bit more complicated because there is no failure on instatiating decltype(std::less<random_type>()(...))
even if there is no operation defined for random_type
, one can implement manually transparent operators in C++11, that are standard in C++14)
The syntax is quite smooth. I hope something like this is adopted in the standard.
Two extensions:
1) It works to detect raw-function applications:
struct random_type{};
random_type fun(random_type x){return x;}
int main(){
std::cout << supports<decltype(&fun)(double)>::value << std::endl; // '0'
std::cout << supports<decltype(&fun)(int)>::value << std::endl; // '0'
std::cout << supports<decltype(&fun)(random_type)>::value << std::endl; // '1'
}
2) It can additionally detect if the result is convertible/comparable to a certain type, in this case double < double
is supported but a compile-time false will be returned because the result is not the specified one.
std::cout << supports<std::equal_to<>(std::result_of<std::less<>(double, double)>::type, random_type)>::value << std::endl; // '0'
Note: I just tried to compile the code with C++14 in http://melpon.org/wandbox/ and it didn't work. I think there is a problem with transparent operators (like std::less<>
) in that implementation (clang++ 3.5 c++14), since when I implement my own less<>
with automatic deduction it works well.
You need to make your less_than_test function a template, since SFINAE stands for Substitution Failure Is Not An Error and there's no template function that can fail selection in your code.
template <class T>
struct supports_less_than
{
template <class U>
static auto less_than_test(const U* u) -> decltype(*u < *u, char(0))
{ }
static std::array<char, 2> less_than_test(...) { }
static const bool value = (sizeof(less_than_test((T*)0)) == 1);
};
int main()
{
std::cout << std::boolalpha << supports_less_than<std::string>::value << endl;
}
This is C++0x, we don't need sizeof
-based tricks any more... ;-]
#include <type_traits>
#include <utility>
namespace supports
{
namespace details
{
struct return_t { };
}
template<typename T>
details::return_t operator <(T const&, T const&);
template<typename T>
struct less_than : std::integral_constant<
bool,
!std::is_same<
decltype(std::declval<T const&>() < std::declval<T const&>()),
details::return_t
>::value
> { };
}
(This is based on iammilind's answer, but doesn't require that T
's operator<
return-type be a different size than long long
and doesn't require that T
be default-constructable.)
Below simple code satisfies your requirement (if you don't want compile error):
namespace supports {
template<typename T> // used if T doesn't have "operator <" associated
const long long operator < (const T&, const T&);
template <class T>
struct less_than {
T t;
static const bool value = (sizeof(t < t) != sizeof(long long));
};
}
Usage:
supports::less_than<std::string>::value ====> true; // ok
supports::less_than<Other>::value ====> false; // ok: no error
[Note: If you want compile error for classes not having operator <
than it's very easy to generate with very few lines of code.]
@xDD is indeed correct, though his example is slightly erroneous.
This compiles on ideone:
#include <array>
#include <iostream>
struct Support {}; bool operator<(Support,Support) { return false; }
struct DoesNotSupport{};
template <class T>
struct supports_less_than
{
template <typename U>
static auto less_than_test(const U* u) -> decltype(*u < *u, char(0))
{ }
static std::array<char, 2> less_than_test(...) { }
static const bool value = (sizeof(less_than_test((T*)0)) == 1);
};
int main()
{
std::cout << std::boolalpha << supports_less_than<Support>::value << std::endl;
std::cout << std::boolalpha <<
supports_less_than<DoesNotSupport>::value << std::endl;
}
And results in:
true
false
See it here in action.
The point is that SFINAE only applies to template functions.
精彩评论