web-dev-qa-db-ja.com

operator ==が存在するかどうかを確認する方法は?

私は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== );

しかし、メンバー以外のoperator ==が削除されると、コンパイルは失敗します

51
BЈовић

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 };
  };  
}
_

デモ

38
iammilind

Boostのコンセプトチェックライブラリ(BCCL) http://www.boost.org/doc/libs/1_46_1/libs /concept_check/concept_check.htm

これにより、プログラムをコンパイルするためにクラスが一致する必要がある要件を記述できます。チェックできるものは比較的自由です。たとえば、クラスFooのoperator==の存在を確認するには、次のように記述します。

#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==の2つの実装のいずれかが利用可能である限り、正常にコンパイルされます。

@ Matthieu M.と@Luc Tourailleのアドバイスに従って、boost::EqualityComparableの使用例を提供するようにコードスニペットを更新しました。繰り返しますが、EqualityComparableではoperator!=も宣言する必要があることに注意してください。

16
jopasserat

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)を定義しているかどうかをテストしたい場合は、2番目のテンプレート引数を設定できます。

_bool test = has_operator_equal<MyClass, long>::value;
_

ここで、テンプレートパラメータMyClassは、_operator==_の存在をテストするクラスであり、longは、比較できるようにするタイプです。 MyClassoperator==(long)があることをテストします。

(最初の例のように)EqualToが指定されていない場合、デフォルトでTになり、operator==(MyClass)の通常の定義になります。

注意事項operator==(long)の場合のこの特性は、longにも当てはまります、または任意の値を暗黙的にlongに変換可能、たとえばdoubleintなど.


decltypeの内部を置き換えるだけで、他の演算子と関数のチェックを定義することもできます。 _!=_を確認するには、単に

_static auto test(U*) -> decltype(std::declval<U>() == std::declval<V>());
_

_static auto test(U*) -> decltype(std::declval<U>() != std::declval<V>());
_
10

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&>()()));
}
4
Richard Hodges

この質問はすでに何度か回答されていますが、decltypeを一緒に使用して、operator==または基本的に他の操作(たとえば、特定の名前のメンバー関数のテスト)の存在を確認する簡単な方法があります,演算子を使用:

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の機能に依存しません。もちろん、最終結果は同じですが、これは、これらの種類のものをテストするための私の好ましいイディオムになっています。

編集:オーバーロードされたカンマ演算子の異常なケースを修正しました。

2
monkey0506

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コンセプトはtypeによって満たされていません


std::EqualityComparableWith<T, U>コンセプトを使用して、2つの異なるタイプ間のオーバーロードをチェックすることもできます。

Incrementableなどの標準に追加されたコンセプトは他にもたくさんあります。ぜひご覧ください here

1
coder3101

私はこの質問に長い間回答してきたことを知っていますが、Boostがtype_traitsライブラリに "has operator"トレイトの束を追加したばかりで、その中に has_equal_to 、OPが要求していたことを実行します。

1
crudcore

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>);
1
Anton Dyachenko

参考までに、私が問題を解決した方法を投稿しています。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;
}
0
BЈовић

IMO、これはクラスのプライベート属性を扱うため、クラス自体の一部である必要があります。テンプレートはコンパイル時に解釈されます。デフォルトではoperator==、コンストラクタ、デストラクタ、コピーコンストラクタ。同じタイプのオブジェクトに対してビット単位のコピー(浅いコピー)またはビット単位の比較を行います。特殊なケース(異なるタイプ)はオーバーロードする必要があります。グローバル演算子関数を使用する場合、プライベート部分にアクセスするには、関数をフレンドとして宣言する必要があります。そうでない場合、必要なインターフェースを公開する必要があります。時々これは本当に醜いものであり、機能の不必要な露出を引き起こすかもしれません。

0
sarat

次の形式のメタ関数について考えてみましょう。これは、指定された型の等価演算子(つまり_==_)の存在をチェックします。

_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
_

このメタ関数の実装の1つを次に示します。

_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
_

オンラインデモ

お役に立てば幸いです。

0
Nawaz