2017-05-30 13 views
4

さまざまな型の引数を受け入れる汎用関数(またはメソッド)が必要です。提供された型に 'one'メソッドがある場合、関数はそれを使用する必要があります。 '2つの'メソッドがある場合、関数は代わりにそれを使用する必要があります。ここでC++テンプレートの特殊化 - 再定義の回避

は無効なコードです:

#include <iostream> 

template<typename Type> void func(Type t) 
{ 
    t.one(); 
} 

template<typename Type> void func(Type t) // redefinition! 
{ 
    t.two(); 
} 

class One 
{ 
    void one(void) const 
    { 
     std::cout << "one" << std::endl; 
    } 
}; 

class Two 
{ 
    void two(void) const 
    { 
     std::cout << "two" << std::endl; 
    } 
}; 

int main(int argc, char* argv[]) 
{ 
    func(One()); // should print "one" 
    func(Two()); // should print "two" 
    return 0; 
} 

はそれがSFINAEを使用して達成することは可能ですか? type_traitsを使用して達成することは可能ですか?


明確化:

これはSFINAEを使用可能である場合、私はもっと幸せになります。最良の場合のシナリオは、最初のテンプレートを使用します。失敗した場合は2番目のテンプレートを使用します。

メソッドの有無の確認は一例です。私が本当に望むのは、他のクラスとの互換性もチェックしています。

タスクは言い換えることができます。

  1. クラスが最初のインターフェイスをサポートしている場合、それを使用しています。
  2. 最初のインターフェイスに障害が発生した場合は、2番目のインターフェイスを使用します。
  3. 両方とも失敗する場合は、エラーを報告してください。
+1

のhttps://stackoverflow.com/questions/257288/is-it-possible-to-write-a-template-to-check-for-a-functions-existence – Rene

+0

可能な重複の可能性のある重複[関数の存在をチェックするテンプレートを書くことは可能ですか?](https://stackoverflow.com/questions/257288/is-it-possible-to-write-a-template-to-check-for-a -functions-existence) –

+0

@rene正確には重複していません。もし彼が機能の存在を確認する方法を知っていれば、ここでチェッカーの使い方を知ることはできません。明らかに、彼はその種のチェッカーの使い方を知らない。そうでなければ、彼は再定義について尋ねないだろう。 –

答えて

4

はい、可能です。 C++ 11以降では比較的簡単です。

#include <iostream> 
#include <type_traits> 

template<class, typename = void> 
struct func_dispatch_tag : 
    std::integral_constant<int, 0> {}; 

template<class C> 
struct func_dispatch_tag<C, 
    std::enable_if_t<std::is_same<decltype(&C::one), void (C::*)() const>::value> 
    > : std::integral_constant<int, 1> {}; 

template<class C> 
struct func_dispatch_tag<C, 
    std::enable_if_t<std::is_same<decltype(&C::two), void (C::*)() const>::value> 
    > : std::integral_constant<int, 2> {}; 

template<class C> 
void func(C const&, std::integral_constant<int, 0>) { 
    std::cout << "fallback!\n"; 
} 

template<class C> 
void func(C const &c, std::integral_constant<int, 1>) { 
    c.one(); 
} 

template<class C> 
void func(C const &c, std::integral_constant<int, 2>) { 
    c.two(); 
} 

template<class C> 
void func(C const &c) { 
    func(c, func_dispatch_tag<C>{}); 
} 

struct One 
{ 
    void one(void) const 
    { 
     std::cout << "one\n"; 
    } 
}; 

struct Two 
{ 
    void two(void) const 
    { 
     std::cout << "two\n"; 
    } 
}; 

struct Three {}; 

int main(int argc, char* argv[]) 
{ 
    func(One()); // should print "one" 
    func(Two()); // should print "two" 
    func(Three()); 
    return 0; 
} 

重要事項:

  1. 我々はfunc_dispatch_tagの2番目のパラメータにSFINAE。コンパイラは、テンプレート<C, void>をもたらすすべてのテンプレート特殊化を調べます。 SFが発生しない場合(すなわち、std::enable_if_tvoidの場合)、後者のいずれかが「より専門的」なので、それは選択される。

  2. 特徴の選択された特殊化は、タグディスパッチを行うタグを定義する。タグのディスパッチは、関数テンプレートの特殊化(部分的に特殊化できない)ではなく、関数のオーバーロードに依存します。

  3. フォールバック関数(私の場合のように)、またはstatic_assertを定義できます。定義できるタグの数はintの範囲によってのみ制限されるため、他のメンバに拡張することは別のfunc_dispatch_tag特殊化を追加することに過ぎません。

  4. メンバにアクセス可能である必要があります.SFが発生します。また、両方のメンバを持つクラスでは、あいまいさが発生します。それを念頭に置いてください。

+2

エレベーションには、積分定数ベースクラスを使用して1を加えます。実際にメソッドをチェックしないためのマイナス1(1と2の両方が存在すればあいまいさがあります) –

+0

@ JohannesSchaub-litb - 私は実際にパイプラインでそれを持っています。これを手に入れたいと熱望していただけです。 – StoryTeller

+0

@ JohannesSchaub-litbあなたは 'C :: one'と' C :: two'は単なるメソッドではなくどんなメンバーでもありえることを意味していますか? –

2

別の方法です。もう少しの定型文がありますが、func()という異なる実装を実際に表現すると、「合格したテストのリスト」はより表現力があると主張できます。

とにかく食べ物。

コードはC++ 11です。 C++ 14と17はより簡潔になります。

#include <iostream> 
#include <type_traits> 
#include <tuple> 

// boilerplate required prior to c++17 
namespace notstd { 
    using namespace std; 
    template<typename... Ts> struct make_void { typedef void type;}; 
    template<typename... Ts> using void_t = typename make_void<Ts...>::type; 
} 

// test for having member function one() 
template<class T, class Enable = notstd::void_t<>> struct has_one : std::false_type {}; 
template<class T> struct has_one<T, notstd::void_t<decltype(std::declval<T>().one())>> : std::true_type {}; 

//test for having member function two() 
template<class T, class Enable = notstd::void_t<>> struct has_two : std::false_type {}; 
template<class T> struct has_two<T, notstd::void_t<decltype(std::declval<T>().two())>> : std::true_type {}; 

// a type collection of tests that pass 
template<template <class...> class...Tests> struct passes_tests { 
}; 

// meta-function to append a type 
template<class Existing, template <class...> class Additional> struct append_pass; 

template< template <class...> class...Tests, template <class...> class Additional> 
struct append_pass<passes_tests<Tests...>, Additional> { 
    using type = passes_tests<Tests..., Additional>; 
}; 


// 
// meta-functions to compute a list of types of test that pass 
// 
namespace detail 
{ 
    template<class Previous, class T, template<class...> class Test, template<class...> class...Rest> 
    struct which_tests_pass_impl 
    { 
    using on_pass = typename append_pass<Previous, Test>::type; 
    using on_fail = Previous; 

    using this_term = typename std::conditional< Test<T>::value, on_pass, on_fail >::type; 
    using type = typename which_tests_pass_impl<this_term, T, Rest...>::type; 
    }; 

    template<class Previous, class T, template<class...> class Test> 
    struct which_tests_pass_impl<Previous, T, Test> 
    { 
    using on_pass = typename append_pass<Previous, Test>::type; 
    using on_fail = Previous; 

    using this_term = typename std::conditional< Test<T>::value, on_pass, on_fail >::type; 
    using type = this_term; 
    }; 

} 

template<class Type, template<class...> class...Tests> struct which_tests_pass 
{ 
    using type = typename detail::which_tests_pass_impl<passes_tests<>, Type, Tests...>::type; 
}; 


// 
// various implementations of func() 
// 
namespace detail 
{ 
    template<class T> 
    void func(T t, passes_tests<has_one>) 
    { 
    t.one(); 
    } 

    template<class T> 
    void func(T t, passes_tests<has_one, has_two>) 
    { 
    t.one(); 
    } 

    template<class T> 
    void func(T t, passes_tests<has_two>) 
    { 
    t.two(); 
    } 

    template<class T> 
    void func(T t, passes_tests<>) 
    { 
    // do nothing 
    } 
} 

template<class T> 
void func(T t) 
{ 
    detail::func(t, typename which_tests_pass<T, has_one, has_two>::type()); 
} 

// 
// some types 
// 
struct One 
{ 
    void one(void) const 
    { 
     std::cout << "one" << std::endl; 
    } 
}; 

struct Two 
{ 
    void two(void) const 
    { 
     std::cout << "two" << std::endl; 
    } 
}; 

// test 
int main(int argc, char* argv[]) 
{ 
    func(One()); // should print "one" 
    func(Two()); // should print "two" 
    return 0; 
} 
0

  • 以下のコードは、メンバ関数const性を正しく
  • を扱う機能のとらわれない型を返す
  • はそれもあり得る故障

に関する包括的なエラーを出力より短いC++ 14では、実装された関数の戻り値の型を指定する必要はありません。テンプレート化された変数宣言。 rvalueの過負荷を正しく処理するには、別のオーバーロードをas_memfunに提供する必要があります。

メンバ関数のテストだけでは不十分である場合は、最後のセクションでは、はるかに優れたカスタマイズオプションが用意されていますが、セットアップにも時間がかかります。

#include <utility> 
#include <functional> 
namespace detail { 
    template<typename T> struct _false : std::integral_constant<bool, false> { }; 
    template<typename T> struct HasNone { 
     static_assert(_false<T>::value, "No valid method found"); 
    }; 

    template<typename T, typename R> 
    constexpr auto as_memfun (R (T::* arg)()) 
     -> R (T::*)() 
     { return arg; } 
    template<typename T, typename R> 
    constexpr auto as_memfun (R (T::* arg)() const) 
     -> R (T::*)() const 
     { return arg; } 
    template<typename T> constexpr auto check_has_two(int) 
     -> decltype(as_memfun(&T::two)) 
     { return as_memfun(&T::two); } 
    template<typename T> constexpr auto check_has_two(...) 
     -> HasNone<T>; 

    template<typename T> constexpr auto check_has_one(int) 
     -> decltype(as_memfun(&T::one)) 
     { return as_memfun(&T::one); } 
    template<typename T> constexpr auto check_has_one(...) 
     -> decltype(check_has_two<T>(0)) 
     { return check_has_two<T>(0); } 

    template<typename T> 
    struct res { constexpr static auto detail = check_has_one<T>(0); }; 
} 

template<typename T> 
auto func(T t) -> decltype((t.*detail::res<T>::detail)()) { 
    return (t.*detail::res<T>::detail)(); 
} 


そして、ここでは、あなただけのメンバ関数の存在をテストするよりも、心の中でより広範な構造を持っているように見えるので、おそらくここで、

struct One { 
    void one(); 
}; 

struct Two { 
    void two(); 
}; 

struct TestBoth { 
    char one() const; 
    void two(); 
}; 

struct TestWilderStuff { 
    int one; 
    void two() const; 
}; 

int main() { 
    func(One{}); 
    func(Two{}); 
    func(TestBoth{}); 
    static_assert(decltype(func(TestBoth{})){} == 0, "Failed function selection"); 
    func(TestWilderStuff{}); 
} 

がしたいいくつかのテストですはるかに強力なメカニズムの始まりです。上記のソリューションの代わりにドロップインを使用することができますが、それははるかに長いですが、より多くのカスタマイズと、あらゆる段階であなたのタイプについて精巧なテストを行う可能性を提供します。

#include <utility> 
#include <functional> 
namespace detail { 
    template<typename T> struct _false : 
     std::integral_constant<bool, false> { }; 
    template<typename T> struct HasNone { 
     static_assert(_false<T>::value, "No valid method found"); 
    }; 

    // Generic meta templates used below 
    namespace Generics { 
     template<typename Getter, typename Else> 
     struct ChainGetter { 
      template<typename T> constexpr static auto get_(int) 
       -> decltype(Getter::template get<T>()) 
       { return Getter::template get<T>(); } 
      template<typename T> constexpr static auto get_(...) 
       -> decltype(Else::template get<T>()) 
       { return Else::template get<T>(); } 
      template<typename T> constexpr static auto get() 
       -> decltype(get_<T>(0)) 
       { return get_<T>(0); } 
     }; 

     template<typename Getter, typename Test> 
     struct TestGetter { 
      template<typename T, typename R> using _type = R; 
      template<typename T> constexpr static auto get_() 
       -> decltype(Getter::template get<T>()) 
       { return Getter::template get<T>(); } 
      template<typename T> constexpr static auto test() 
       -> decltype(Test::template test<T>(get_<T>())); 
      template<typename T> constexpr static auto get() 
       -> _type<decltype(test<T>()), 
         decltype(get_<T>()) 
         > 
       { return get_<T>(); } 
     }; 

     template<template<typename> class F> 
     struct FailGetter { 
      template<typename T> 
      constexpr static auto get() -> F<T>; 
     }; 
    } 

    // Test only exists for member function pointer arguments 
    struct IsMemberFunctionTest { 
     template<typename _, typename T, typename R> 
     constexpr static void test (R (T::* arg)()); 
     template<typename _, typename T, typename R> 
     constexpr static void test (R (T::* arg)() const); 
    }; 

    // Get member pointer to T::one 
    struct GetOne { 
     template<typename T> 
     constexpr static auto get() -> decltype(&T::one) { return &T::one; } 
    }; 

    // Get member pointer to T::two 
    struct GetTwo { 
     template<typename T> 
     constexpr static auto get() -> decltype(&T::two) { return &T::two; } 
    }; 

    using namespace Generics; 
    using getter_fail = FailGetter<HasNone>; 
    using get_two_tested = TestGetter<GetTwo, IsMemberFunctionTest>; 
    using getter_two = ChainGetter<get_two_tested, getter_fail>; 
    using get_one_tested = TestGetter<GetOne, IsMemberFunctionTest>; 
    using getter_one = ChainGetter<get_one_tested, getter_two>; 

    template<typename T> 
    struct result { constexpr static auto value = getter_one::template get<T>(); }; 
} 

template<typename T> 
auto func(T t) -> decltype((t.*detail::result<T>::value)()) { 
    return (t.*detail::result<T>::value)(); 
} 
関連する問題