Обнаружение поддержки оператора с помощью decltype/SFINAE
A (несколько) устаревшая статья описывает способы использования decltype
вместе с SFINAE для определения того, поддерживает ли тип определенные операторы, такие как ==
или <
.
Здесь примерный код для определения того, поддерживает ли класс оператор <
:
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;
}
Это выводит true
, так как, конечно, std::string
поддерживает оператор <
. Однако, если я попытаюсь использовать его с классом, который не поддерживает оператор <
, я получаю ошибку компилятора:
error: no match for ‘operator<’ in ‘* t < * t’
Итак, SFINAE здесь не работает. Я попробовал это на GCC 4.4 и GCC 4.6, и оба показали то же поведение. Итак, можно ли использовать SFINAE таким образом, чтобы определить, поддерживает ли тип определенные выражения?
Ответы
Ответ 1
Вам нужно сделать вашу функцию less_than_test шаблоном, так как SFINAE означает, что ошибка подстановки не является ошибкой, и нет никакой функции шаблона, которая может вызывать сбои в вашем коде.
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;
}
Ответ 2
В С++ 11 самое короткое наиболее общее решение, которое я нашел, было следующим:
#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'
}
Работает с g++ 4.8.1
и clang++ 3.3
Более общее решение для произвольных операторов (ОБНОВЛЕНИЕ 2014)
Существует более общее решение, в котором используется тот факт, что все встроенные операторы также доступны (и могут быть специализированы) через обертки операторов STD, такие как std::less
(двоичный) или std::negate
(унарный).
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>()...)){};
Это можно использовать довольно обычным образом, особенно в С++ 14, где вывод типа задерживается на вызов оболочки оператора ( "прозрачные операторы" ).
Для двоичных операторов его можно использовать как:
#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'
}
Для унарных операторов:
#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'
}
(С стандартной библиотекой С++ 11 немного сложнее, потому что нет никакого сбоя при instatiating decltype(std::less<random_type>()(...))
, даже если для random_type
нет операции, можно реализовать вручную прозрачные операторы в С++ 11, которые являются стандартными в С++ 14)
Синтаксис довольно плавный. Я надеюсь, что что-то подобное принято в стандарте.
Два расширения:
1) Он работает для обнаружения приложений с необработанной функцией:
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) Он может дополнительно определить, является ли результат конвертируемым/сопоставимым с определенным типом, в этом случае поддерживается double < double
, но возвращается ложь compile-time, потому что результат не является указанным.
std::cout << supports<std::equal_to<>(std::result_of<std::less<>(double, double)>::type, random_type)>::value << std::endl; // '0'
Примечание. Я просто попытался скомпилировать код с С++ 14 в http://melpon.org/wandbox/, и он не работал, Я думаю, что существует проблема с прозрачными операторами (например, std::less<>
) в этой реализации (clang++ 3.5 С++ 14), поскольку, когда я реализую свой собственный less<>
с автоматическим выводом, он работает хорошо.
Ответ 3
Это С++ 0x, нам больше не нужны трюки на основе sizeof
...; -]
#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
> { };
}
(Это основано на ответе iammilind, но не требует, чтобы возвращаемый тип T
operator<
был другого размера, чем long long
, и не требует, чтобы T
был конструктивным по умолчанию.)
Ответ 4
Ниже простого кода выполняется ваше требование (если вы не хотите компилировать ошибку):
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));
};
}
Использование:
supports::less_than<std::string>::value ====> true; // ok
supports::less_than<Other>::value ====> false; // ok: no error
[Примечание. Если вы хотите скомпилировать ошибку для классов, не имеющих operator <
, чем это очень легко создать с очень небольшим количеством строк кода.]
Ответ 5
@xDD действительно корректен, хотя его пример немного ошибочен.
Это компилируется на 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;
}
И результаты:
true
false
Смотрите здесь в действии.
Дело в том, что SFINAE применяется только к шаблонам функций.