J'essaie de créer un exemple permettant de vérifier l'existence du operator==
(fonction membre ou non membre). Vérifier si une classe a un membre operator==
est facile, mais comment vérifier si elle a un operator==
non membre?
C'est ce que j'ai à trop loin:
#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;
}
Est-il possible d'écrire une fonction de test pour tester l'existence de operator==
non membre? Si oui comment?
j'ai vérifié des questions similaires, mais je n'ai pas trouvé de solution adéquate:
Est-il possible d’utiliser SFINAE/templates pour vérifier s’il existe un opérateur?
C'est ce que j'ai essayé:
template <typename C> static yes test( const C*,bool(*)(const C&,constC&) = &operator== );
mais la compilation échoue si l'opérateur non membre == est supprimé
Le tour suivant fonctionne. Et il peut être utilisé pour tous ces opérateurs:
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)) };
};
}
Usage:
CHECK::EqualExists<A>::value;
Le 2nd template typename Arg
est utile dans certains cas particuliers tels que A::operator==(short)
, où il n’est pas similaire à class
. Dans de tels cas, l'utilisation est:
CHECK::EqualExists<A, short>::value
// ^^^^^ argument of `operator==`
Démo .
Nous n'avons pas besoin d'utiliser sizeof
astuce lorsque nous avons 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 };
};
}
Jetez un coup d'œil à La bibliothèque de vérification du concept de Boost (BCCL) http://www.boost.org/doc/libs/1_46_1/libs/concept_check/concept_check.htm .
Cela vous permet d'écrire les exigences auxquelles une classe doit correspondre pour que le programme puisse être compilé. Vous êtes relativement libre avec ce que vous pouvez vérifier. Par exemple, vérifier la présence de operator==
d'une classe Foo écrira comme suit:
#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
}
Ce code se compile bien tant qu'une des deux implémentations de operator==
est disponible.
Suivant les conseils de @Matthieu M. et @Luc Touraille, j'ai mis à jour l'extrait de code afin de fournir un exemple d'utilisation de boost::EqualityComparable
. Veuillez noter à nouveau qu'EgalityComparable vous oblige à déclarer également operator!=
.
Il est également possible d'utiliser uniquement des traits de type c ++ 11 pour vérifier l'existence du membre:
#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 {};
Vous pouvez utiliser le trait comme suit:
bool test = has_operator_equal<MyClass>::value;
Le type résultant de has_operator_equal
sera soit std::true_type
OU std::false_type
(car il hérite d'un alias de std::is_same::type
), et définissent tous deux un membre value
statique qui est un booléen.
Si vous voulez pouvoir vérifier si votre classe définit operator==(someOtherType)
, vous pouvez définir le deuxième argument du modèle:
bool test = has_operator_equal<MyClass, long>::value;
où le paramètre de modèle MyClass
est toujours la classe que vous testez pour la présence de operator==
, et long
est le type que vous souhaitez pouvoir comparer, par exemple. pour vérifier que MyClass
a operator==(long)
.
si EqualTo
(comme c'était le cas dans le premier exemple) n'est pas spécifié, la valeur par défaut sera T
, ce qui donnera la définition normale de operator==(MyClass)
.
Note d'avertissement : Dans le cas de operator==(long)
, cette caractéristique sera vraie pour long
ou toute valeur implicitement convertible en long
, par ex. double
, int
, etc.
Vous pouvez également définir des contrôles pour d'autres opérateurs et fonctions, simplement en remplaçant le contenu de la variable decltype
. Pour vérifier !=
, remplacez simplement
static auto test(U*) -> decltype(std::declval<U>() == std::declval<V>());
avec
static auto test(U*) -> decltype(std::declval<U>() != std::declval<V>());
À partir de c ++ 14, les fonctions binaires standard effectuent la majeure partie du travail pour nous pour la majorité des opérateurs.
#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&>()()));
}
Plusieurs réponses ont déjà été apportées à cette question, mais il existe un moyen plus simple de vérifier l'existence de operator==
ou de toute autre opération (par exemple, tester une fonction membre avec un certain nom), en utilisant decltype
avec l'opérateur ,
:
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 {};
Vous pouvez utiliser cette même approche pour vérifier si un type T
a une fonction membre foo
qui est invocable avec une certaine liste d'arguments:
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 {};
Je pense que cela clarifie beaucoup l’intention du code. En plus de cela, il s’agit d’une solution C++ 11, elle ne dépend donc pas des nouvelles fonctionnalités C++ 14 ou C++ 17. Le résultat final est le même, bien sûr, mais cela est devenu mon idiome préféré pour tester ce genre de choses.
Edit: - Correction du cas insensé de l'opérateur de virgule surchargé, ça me manque toujours.
Je sais que cette question a été répondue depuis longtemps, mais j’ai pensé qu’il serait utile de noter pour ceux qui découvrent cette question à l’avenir que Boost vient d’ajouter une série de traits "has operator" à leur bibliothèque type_traits, et parmi eux se trouve has_equal_to. , qui fait ce que OP demandait.
Juste pour référence, je montre comment j'ai résolu mon problème, sans qu'il soit nécessaire de vérifier si le operator==
existe:
#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;
}
OMI, cela doit faire partie de la classe elle-même car elle traite des attributs privés de la classe. Les modèles sont interprétés à la compilation. Par défaut, il génère operator==
, constructeur, destructeur et constructeur de copie qui effectuent des comparaisons au niveau de la copie (copie peu profonde) ou au niveau du bit pour l'objet de même type. Les cas spéciaux (différents types) doivent être surchargés. Si vous utilisez la fonction d'opérateur global, vous devez déclarer la fonction en tant qu'ami pour accéder à la partie privée, sinon vous devez exposer les interfaces requises. Parfois, c'est vraiment moche, ce qui peut entraîner une exposition inutile d'une fonction.
Considérons une méta-fonction de la forme suivante, qui vérifie l'existence d'un opérateur d'égalité (i.e ==
) pour le type donné:
template<typename T>
struct equality { .... };
Cependant, cela pourrait ne pas suffire dans certains cas. Par exemple, supposons que votre classe X
définisse operator==
mais qu'elle ne renvoie pas bool
, mais plutôt Y
. Donc, dans ce cas, que devrait renvoyer equality<X>::value
? true
ou false
? Cela dépend du cas d'utilisation que nous ne connaissons pas encore, et il ne semble pas judicieux d'assumer quoi que ce soit et de le forcer de force. Cependant, en général, nous pouvons supposer que le type de retour doit être bool
, aussi exprimons ceci dans l'interface elle-même:
template<typename T, typename R = bool>
struct equality { .... };
La valeur par défaut de R
est bool
, ce qui indique qu'il s'agit du cas général. Dans les cas où le type de retour de operator==
est différent, par exemple, Y
, vous pouvez dire ceci:
equality<X, Y> //return type = Y
qui vérifie également le type de retour donné. Par défaut,
equality<X> //return type = bool
Voici une implémentation de cette méta-fonction:
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> {};
Tester:
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;
}
Sortie:
equality<A>::value = 0
equality<B>::value = 1
equality<C>::value = 0
equality<B,short>::value = 0
equality<C,short>::value = 1
J'espère que cela pourra aider.
c ++ 17 version légèrement modifiée de Richard Hodges 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>);