Как проверить, существует ли оператор ==?
Я пытаюсь создать пример, который проверил бы существование operator==
(член или не-функция). Чтобы проверить, есть ли у класса член operator==
это легко, но как проверить, есть ли у него не член operator==
?
Это то, что я должен далеко:
#include <iostream>
struct A
{
int a;
#if 0
bool operator==( const A& rhs ) const
{
return ( a==rhs.a);
}
#endif
};
#if 1
bool operator==( const A &l,const A &r )
{
return ( l.a==r.a);
}
#endif
template < typename T >
struct opEqualExists
{
struct yes{ char a[1]; };
struct no { char a[2]; };
template <typename C> static yes test( typeof(&C::operator==) );
//template <typename C> static yes test( ???? );
template <typename C> static no test(...);
enum { value = (sizeof(test<T>(0)) == sizeof(yes)) };
};
int main()
{
std::cout<<(int)opEqualExists<A>::value<<std::endl;
}
Можно ли написать тестовую функцию для проверки существования не члена operator==
? Если да, то как?
Кстати, я проверил подобные вопросы, но не нашел правильного решения:
Можно ли использовать SFINAE/ шаблоны, чтобы проверить, существует ли оператор?
Вот что я попробовал:
template <typename C> static yes test( const C*,bool(*)(const C&,constC&) = &operator== );
но компиляция завершится неудачно, если удаленный оператор == удален
14 ответов
C++03
Следующий трюк работает. И это может быть использовано для всех таких операторов:
namespace CHECK
{
class No { bool b[2]; };
template<typename T, typename Arg> No operator== (const T&, const Arg&);
bool Check (...);
No& Check (const No&);
template <typename T, typename Arg = T>
struct EqualExists
{
enum { value = (sizeof(Check(*(T*)(0) == *(Arg*)(0))) != sizeof(No)) };
};
}
Использование:
CHECK::EqualExists<A>::value;
2-й template typename Arg
полезно для некоторых особых случаев, таких как A::operator==(short)
где это не похоже на class
сам. В таких случаях используется:
CHECK::EqualExists<A, short>::value
// ^^^^^ argument of `operator==`
C++11
Нам не нужно использовать sizeof
трюк, когда у нас есть decltype
namespace CHECK
{
struct No {};
template<typename T, typename Arg> No operator== (const T&, const Arg&);
template<typename T, typename Arg = T>
struct EqualExists
{
enum { value = !std::is_same<decltype(*(T*)(0) == *(Arg*)(0)), No>::value };
};
}
Взгляните на библиотеку Boost's Concept Check Library (BCCL) http://www.boost.org/doc/libs/1_46_1/libs/concept_check/concept_check.htm.
Это позволяет вам написать требования, которым должен соответствовать класс, чтобы программа могла компилироваться. Вы относительно свободны с тем, что вы можете проверить. Например, проверка наличия operator==
класса Foo написал бы следующее:
#include <boost/concept_check.hpp>
template <class T>
struct opEqualExists;
class Foo {
public:
bool operator==(const Foo& f) {
return true;
}
bool operator!=(const Foo& f) {
return !(*this == f);
}
// friend bool operator==(const Foo&, const Foo&);
// friend bool operator!=(const Foo&, const Foo&);
};
template <class T>
struct opEqualExists {
T a;
T b;
// concept requirements
BOOST_CONCEPT_USAGE(opEqualExists) {
a == b;
}
};
/*
bool operator==(const Foo& a, const Foo& b) {
return true; // or whatever
}
*/
/*
bool operator!=(const Foo& a, const Foo& b) {
return ! (a == b); // or whatever
}
*/
int main() {
// no need to declare foo for interface to be checked
// declare that class Foo models the opEqualExists concept
// BOOST_CONCEPT_ASSERT((opEqualExists<Foo>));
BOOST_CONCEPT_ASSERT((boost::EqualityComparable<Foo>)); // need operator!= too
}
Этот код прекрасно компилируется, пока одна из двух реализаций operator==
доступен.
Следуя советам @Matthieu M. и @Luc Touraille, я обновил фрагмент кода, чтобы предоставить пример boost::EqualityComparable
использование. Еще раз, обратите внимание, что EqualityComparable заставляет вас объявить operator!=
тоже.
Также можно использовать только черты типа C++11 для проверки существования члена:
#include <type_traits>
#include <utility>
template<class T, class EqualTo>
struct has_operator_equal_impl
{
template<class U, class V>
static auto test(U*) -> decltype(std::declval<U>() == std::declval<V>());
template<typename, typename>
static auto test(...) -> std::false_type;
using type = typename std::is_same<bool, decltype(test<T, EqualTo>(0))>::type;
};
template<class T, class EqualTo = T>
struct has_operator_equal : has_operator_equal_impl<T, EqualTo>::type {};
Вы можете использовать эту черту следующим образом:
bool test = has_operator_equal<MyClass>::value;
Полученный тип has_operator_equal
либо будет std::true_type
или жеstd::false_type
(потому что он наследует от псевдонима std::is_same::type
), и оба определяют статический value
член, который является логическим.
Если вы хотите иметь возможность проверить, определяет ли ваш класс operator==(someOtherType)
Вы можете установить второй аргумент шаблона:
bool test = has_operator_equal<MyClass, long>::value;
где параметр шаблона MyClass
все еще класс, который вы проверяете на наличие operator==
, а также long
это тип, который вы хотите сравнить, например, чтобы проверить, MyClass
имеет operator==(long)
,
если EqualTo
(как это было в первом примере) не указано, по умолчанию T
, привести к нормальному определению operator==(MyClass)
,
Примечание осторожности: эта черта в случае operator==(long)
будет верно для long
или любое значение, неявно преобразуемое в long
например, double
, int
, так далее.
Вы также можете определить проверки для других операторов и функций, просто заменив то, что внутри decltype
, Проверить на !=
, просто замени
static auto test(U*) -> decltype(std::declval<U>() == std::declval<V>());
с
static auto test(U*) -> decltype(std::declval<U>() != std::declval<V>());
C++20
Я предполагаю, что вы хотите проверить, есть ли у пользователя тип шаблона оператора равенства или нет, если это так, здесь вам помогут концепции.
#include<concepts>
struct S{
int x;
};
template<class T>
requires std::EqualityComparable<T>
void do_magic(T a, T b){
return a == b;
}
int main(){
// do_magic(S{}, S{}); Compile time error
do_magic(56, 46); // Okay int has == and !=
return 0;
}
Если вы передаете любой тип, который не имеет ==
а также !=
перегрузить компилятор только с ошибками с сообщением:
EqualityComparable
концепция не удовлетворена типом
Вы также можете использовать std::EqualityComparableWith<T, U>
Концепция может проверить эти перегрузки между двумя различными типами.
Есть много других концепций, которые были добавлены в стандарты, такие как Incrementable
и т.д.. Посмотрите здесь
Начиная с C++14, стандартные бинарные функции выполняют большую часть работы за нас для большинства операторов.
#include <utility>
#include <iostream>
#include <string>
#include <algorithm>
#include <cassert>
template<class X, class Y, class Op>
struct op_valid_impl
{
template<class U, class L, class R>
static auto test(int) -> decltype(std::declval<U>()(std::declval<L>(), std::declval<R>()),
void(), std::true_type());
template<class U, class L, class R>
static auto test(...) -> std::false_type;
using type = decltype(test<Op, X, Y>(0));
};
template<class X, class Y, class Op> using op_valid = typename op_valid_impl<X, Y, Op>::type;
namespace notstd {
struct left_shift {
template <class L, class R>
constexpr auto operator()(L&& l, R&& r) const
noexcept(noexcept(std::forward<L>(l) << std::forward<R>(r)))
-> decltype(std::forward<L>(l) << std::forward<R>(r))
{
return std::forward<L>(l) << std::forward<R>(r);
}
};
struct right_shift {
template <class L, class R>
constexpr auto operator()(L&& l, R&& r) const
noexcept(noexcept(std::forward<L>(l) >> std::forward<R>(r)))
-> decltype(std::forward<L>(l) >> std::forward<R>(r))
{
return std::forward<L>(l) >> std::forward<R>(r);
}
};
}
template<class X, class Y> using has_equality = op_valid<X, Y, std::equal_to<>>;
template<class X, class Y> using has_inequality = op_valid<X, Y, std::not_equal_to<>>;
template<class X, class Y> using has_less_than = op_valid<X, Y, std::less<>>;
template<class X, class Y> using has_less_equal = op_valid<X, Y, std::less_equal<>>;
template<class X, class Y> using has_greater_than = op_valid<X, Y, std::greater<>>;
template<class X, class Y> using has_greater_equal = op_valid<X, Y, std::greater_equal<>>;
template<class X, class Y> using has_bit_xor = op_valid<X, Y, std::bit_xor<>>;
template<class X, class Y> using has_bit_or = op_valid<X, Y, std::bit_or<>>;
template<class X, class Y> using has_left_shift = op_valid<X, Y, notstd::left_shift>;
template<class X, class Y> using has_right_shift = op_valid<X, Y, notstd::right_shift>;
int main()
{
assert(( has_equality<int, int>() ));
assert((not has_equality<std::string&, int const&>()()));
assert((has_equality<std::string&, std::string const&>()()));
assert(( has_inequality<int, int>() ));
assert(( has_less_than<int, int>() ));
assert(( has_greater_than<int, int>() ));
assert(( has_left_shift<std::ostream&, int>() ));
assert(( has_left_shift<std::ostream&, int&>() ));
assert(( has_left_shift<std::ostream&, int const&>() ));
assert((not has_right_shift<std::istream&, int>()()));
assert((has_right_shift<std::istream&, int&>()()));
assert((not has_right_shift<std::istream&, int const&>()()));
}
Я знаю, что на этот вопрос уже давно дан ответ, но я подумал, что для любого, кто найдет этот вопрос в будущем, возможно, стоит отметить, что Boost просто добавил набор признаков "оператор оператора" в свою библиотеку type_traits, и среди них есть has_equal_to, который делает то, что просил OP.
На этот вопрос уже отвечали несколько раз, но есть более простой способ проверить наличие operator==
или в основном любой другой операции (например, тестирование функции-члена с определенным именем), используя decltype
вместе с ,
оператор:
namespace detail
{
template<typename L, typename R>
struct has_operator_equals_impl
{
template<typename T = L, typename U = R> // template parameters here to enable SFINAE
static auto test(T &&t, U &&u) -> decltype(t == u, void(), std::true_type{});
static auto test(...) -> std::false_type;
using type = decltype(test(std::declval<L>(), std::declval<R>()));
};
} // namespace detail
template<typename L, typename R = L>
struct has_operator_equals : detail::has_operator_equals_impl<L, R>::type {};
Вы можете использовать этот же подход, чтобы проверить, является ли тип T
имеет функцию-член foo
который вызывается с определенным списком аргументов:
namespace detail
{
template<typename T, typename ...Args>
struct has_member_foo_impl
{
template<typename T_ = T>
static auto test(T_ &&t, Args &&...args) -> decltype(t.foo(std::forward<Args>(args)...), void(), std::true_type{});
static auto test(...) -> std::false_type;
using type = decltype(test(std::declval<T>(), std::declval<Args>()...));
};
} // namespace detail
template<typename T, typename ...Args>
struct has_member_foo : detail::has_member_foo_impl<T, Args...>::type {};
Я думаю, что это делает смысл кода намного понятнее. Кроме того, это решение C++11, поэтому оно не зависит от каких-либо новых функций C++14 или C++17. Конечный результат, конечно, тот же, но это стало моей любимой идиомой для тестирования подобных вещей.
Редактировать: Исправлен безумный случай перегруженного оператора запятой, я всегда скучаю по этому.
C++17 слегка модифицированная версия Godbolt Ричарда Ходжеса
#include <functional>
#include <type_traits>
template<class T, class R, class ... Args>
std::is_convertible<std::invoke_result_t<T, Args...>, R> is_invokable_test(int);
template<class T, class R, class ... Args>
std::false_type is_invokable_test(...);
template<class T, class R, class ... Args>
using is_invokable = decltype(is_invokable_test<T, R, Args...>(0));
template<class T, class R, class ... Args>
constexpr auto is_invokable_v = is_invokable<T, R, Args...>::value;
template<class L, class R = L>
using has_equality = is_invokable<std::equal_to<>, bool, L, R>;
template<class L, class R = L>
constexpr auto has_equality_v = has_equality<L, R>::value;
struct L{};
int operator ==(int, L&&);
static_assert(has_equality_v<int>);
static_assert(!has_equality_v<L>);
static_assert(!has_equality_v<L, int>);
static_assert(has_equality_v<int, L>);
Давайте рассмотрим мета-функцию следующего вида, которая проверяет существование оператора равенства (т.е. ==
) для данного типа:
template<typename T>
struct equality { .... };
Тем не менее, это может быть недостаточно для некоторых случаев. Например, скажем, ваш класс X
действительно определяет operator==
но это не возвращает bool
вместо этого он возвращает Y
, Так что в этом случае, что должно equality<X>::value
вернуть? true
или же false
? Что ж, это зависит от конкретного варианта использования, о котором мы сейчас не знаем, и, кажется, не стоит предполагать что-либо и навязывать это пользователям. Однако в целом можно предположить, что тип возвращаемого значения должен быть bool
Итак, давайте выразим это в самом интерфейсе:
template<typename T, typename R = bool>
struct equality { .... };
Значение по умолчанию для R
является bool
что указывает на общий случай. В случаях, когда тип возврата operator==
отличается, скажем Y
Тогда вы можете сказать это:
equality<X, Y> //return type = Y
который также проверяет данный тип возврата. По умолчанию,
equality<X> //return type = bool
Вот одна реализация этой мета-функции:
namespace details
{
template <typename T, typename R, typename = R>
struct equality : std::false_type {};
template <typename T, typename R>
struct equality<T,R,decltype(std::declval<T>()==std::declval<T>())>
: std::true_type {};
}
template<typename T, typename R = bool>
struct equality : details::equality<T, R> {};
Тестовое задание:
struct A {};
struct B { bool operator == (B const &); };
struct C { short operator == (C const &); };
int main()
{
std::cout<< "equality<A>::value = " << equality<A>::value << std::endl;
std::cout<< "equality<B>::value = " << equality<B>::value << std::endl;
std::cout<< "equality<C>::value = " << equality<C>::value << std::endl;
std::cout<< "equality<B,short>::value = " << equality<B,short>::value << std::endl;
std::cout<< "equality<C,short>::value = " << equality<C,short>::value << std::endl;
}
Выход:
equality<A>::value = 0
equality<B>::value = 1
equality<C>::value = 0
equality<B,short>::value = 0
equality<C,short>::value = 1
Надеюсь, это поможет.
Мы можем использовать std::equal_to<Type>
(или любые другие перегруженные элементы структуры), чтобы получить более общее решение.
struct No {};
template<class T, class Operator>
struct ExistsOperator
{
enum { value = !std::is_same<decltype(std::declval<Operator>()(std::declval<T>(), std::declval<T>())), No>::value };
};
Применение:
using Type = int;
constexpr bool hasEqual = ExistsOperator<Type, std::equal_to<Type>>::value;
Это должно работать на С++11
template <class Void, template<class...> class Type, class... Args>
struct validator
{
using value_t = std::false_type;
};
template <template<class...> class Type, class... Args>
struct validator< std::void_t<Type<Args...>>, Type, Args... >
{
using value_t = std::true_type;
};
template <template<class...> class Type, class... Args>
using is_valid = typename validator<void, Type, Args...>::value_t;
template<typename... T>
using has_equal_t = decltype((std::declval<T&>().operator ==(std::declval<T&>()), ...));
template<typename... T>
using has_gequal_t = decltype((operator ==(std::declval<T&>(),std::declval<T&>()), ...));
struct EQ
{
bool operator==(const EQ&) const;
};
struct GEQ
{
};
bool operator==(const GEQ&, const GEQ&);
struct NOEQ
{
};
static_assert(is_valid<has_equal_t,EQ>::value || is_valid<has_gequal_t,EQ>::value, "should have equal operator");
static_assert(is_valid<has_equal_t,GEQ>::value || is_valid<has_gequal_t,GEQ>::value, "should have equal operator");
// static_assert(is_valid<has_equal_t,NOEQ>::value || is_valid<has_gequal_t,NOEQ>::value, "should have equal operator"); // ERROR:
Помимо ответа @coder3101,concepts
может помочь вам реализовать любые тесты на существование функций, которые вы хотите. Например,std::equality_comparable
реализован с помощью 4 простых тестов, проверяющих следующие сценарии:
За A
а также B
переменных, убедитесь, что следующие выражения действительны:
A == B, returns bool
A != B, returns bool
B == A, returns bool
B != A, returns bool
Если какой-либо из них недопустим во время компиляции, программа не будет компилироваться. Реализация этого теста (упрощенная от стандарта):
template <typename T> concept equality_comparable
= requires(T t, T u) {
{ t == u } -> std::convertible_to<bool>;
{ t != u } -> std::convertible_to<bool>;
{ u == t } -> std::convertible_to<bool>;
{ u != t } -> std::convertible_to<bool>;
};
Как видите, вы можете настроить эту концепцию и создать свою собственную концепцию, соответствующую вашим условиям. Например, если вы хотите заставить только существованиеoperator==
, вы можете сделать что-то вроде этого:
template <typename T> concept my_equality_comparable
= requires(T t, T u) {
{ t == u } -> std::convertible_to<bool>;
{ u == t } -> std::convertible_to<bool>;
};
ИМО, это должно быть частью самого класса, так как он имеет дело с закрытыми атрибутами класса. Шаблоны интерпретируются во время компиляции. По умолчанию он генерирует operator==
, конструктор, деструктор и конструктор копирования, которые делают побитовое копирование (поверхностное копирование) или побитовое сравнение для объекта того же типа. Особые случаи (разных типов) должны быть перегружены. Если вы используете глобальную операторную функцию, вы должны будете объявить эту функцию как друга, чтобы получить доступ к приватной части, иначе вам нужно будет предоставить необходимые интерфейсы. Иногда это действительно ужасно, что может привести к ненужному раскрытию функции.
Просто для справки, я выкладываю, как я решил свою проблему, без необходимости проверять, operator==
существует:
#include <iostream>
#include <cstring>
struct A
{
int a;
char b;
#if 0
bool operator==( const A& r ) const
{
std::cout<<"calling member function"<<std::endl;
return ( ( a==r.a ) && ( b==r.b ) );
}
#endif
};
#if 1
bool operator==( const A &l,const A &r )
{
std::cout<<"calling NON-member function"<<std::endl;
return ( ( l.a==r.a ) &&( l.b==r.b ) );
}
#endif
namespace details
{
struct anyType
{
template < class S >
anyType( const S &s ) :
p(&s),
sz(sizeof(s))
{
}
const void *p;
int sz;
};
bool operator==( const anyType &l, const anyType &r )
{
std::cout<<"anyType::operator=="<<std::endl;
return ( 0 == std::memcmp( l.p, r.p, l.sz ) );
}
} // namespace details
int main()
{
A a1;
a1.a=3;a1.b=0x12;
A a2;
a2.a=3;a2.b=0x12;
using details::operator==;
std::cout<< std::boolalpha << "numbers are equals : " << ( a1 == a2 ) <<std::endl;
}