3

テンプレート パラメーターの型に応じて、特定のメンバー関数を定義する場合と定義しない場合があるテンプレート クラスを作成しようとしています。さらに、このメンバー関数の戻り値の型は、テンプレート パラメーターのメンバーの戻り値の型に依存します (定義されている場合)。

以下は私のコードの最小限の例です

#include <iostream>
#include <type_traits>

template <typename T>
struct has_foo_int {
private:
    template <typename U>
    static decltype(std::declval<U>().foo(0), void(), std::true_type()) test(int);
    template <typename>
    static std::false_type test(...);
public:
    typedef decltype(test<T>(0)) test_type;
    enum { value = test_type::value };
};

template <typename T, bool HasFooInt>
struct foo_int_return_type;

template<typename T>
struct foo_int_return_type<T,false> {};

template<typename T>
struct foo_int_return_type<T,true> {
    using type = decltype(std::declval<T>().foo(0));
};

template<typename T>
struct mystruct
{
    T val;

    //auto someMethod(int i) -> decltype(std::declval<T>().foo(0)) // error: request for member ‘foo’ in ‘std::declval<double>()’, which is of non-class type ‘double’
    //auto someMethod(int i) -> typename foo_int_return_type<T,has_foo_int<T>::value>::type // error: no type named ‘type’ in ‘struct foo_int_return_type<double, false>’
    template<typename R=typename foo_int_return_type<T,has_foo_int<T>::value>::type> R someMethod(int i) // error: no type named ‘type’ in ‘struct foo_int_return_type<double, false>’
    {
        return val.foo(i);
    }
};

struct with_foo_int {
    int foo(int i){
        return i+1;
    }
};

using namespace std;

int main(void)
{
    mystruct<with_foo_int> ms1;
    cout << ms1.someMethod(41) << endl;

    mystruct<double> ms2;

    return 0;
}

私がしたいことは、コードが正常にコンパイルされ、42 for が出力されることms1.someFunc(41)です。someFuncまた、誤ってms2それを呼び出そうとすると、コンパイルに失敗することも予想されます。

残念ながら、私が試した代替案はすべて失敗しました。1 つ目と 2 つ目は、なぜ機能しないのか理解できたと思います。

SFINAE はテンプレート関数に対してのみ機能することをここで読んだので、ダミーのテンプレート パラメーターを指定して戻り値の型を計算しようとしましたが、これも同じように失敗します。

私は明らかにここで何かを理解していません。何が欠けていますか? 私がやろうとしていることを達成することは可能ですか?

ありがとう。

PS私はg ++ 4.7.3を使用しています

Pps 私も試しましたが、構造体std::enable_ifとほぼ同じ結果が得られましたfoo_int_return_type

4

1 に答える 1

2

これは、あなたが試みていることを行うための簡潔で整然とした文書化された方法であり、その後に対処された可能性のあるバグがいくつかあります。

#include <type_traits>

/*
    Template `has_mf_foo_accepts_int_returns_int<T>`
    has a static boolean public member `value` that == true
    if and only if `T` is a class type that has a public
    member function or member function overload 
    `int T::foo(ArgType) [const]`  where `ArgType`
    is a type to which `int` is implicitly convertible.
*/
template <typename T>
struct has_mf_foo_accepts_int_returns_int {

    /* SFINAE success:
        We know now here `int *` is convertible to
        "pointer to return-type of T::foo(0)" 
    */
    template<typename A>
    static constexpr bool test(
        decltype(std::declval<A>().foo(0)) *prt) {
        /* Yes, but is the return-type of `T::foo(0)`
            actually *the same* as `int`?...
        */
        return std::is_same<int *,decltype(prt)>::value;
    }

    // SFINAE failure :(
    template <typename A>
    static constexpr bool test(...) {
        return false; 
    }

    /* SFINAE probe.
        Can we convert `(int *)nullptr to 
        "pointer to the return type of T::foo(0)"?
    */
    static const bool value = test<T>(static_cast<int *>(nullptr)); 
};

template<typename T>
struct mystruct
{
    using has_good_foo = has_mf_foo_accepts_int_returns_int<T>;

    T val;

    /*  SFINAE:
        `template<typename R> R someMethod(R)` will be this if and only
        if `R` == `int` and `has_good_foo` == true.         
    */
    template<typename R = int>
    typename std::enable_if<
        (has_good_foo::value && std::is_same<R,int>::value),R
    >::type 
    someMethod(R i) {
        return val.foo(i);
    }

    /*  SFINAE:
        `template<typename R> R someMethod(R)` will be this if and only
        if `R` != `int` or `has_good_foo` != true.      
    */
    template<typename R = int>
    typename std::enable_if<
        !(has_good_foo::value && std::is_same<R,int>::value),R
    >::type
    someMethod(R i) {
        static_assert(has_good_foo::value && std::is_same<R,int>::value,
            "mystruct<T> does not implement someMethod(R)");
        return i;
    }
};

// Testing...

#include <iostream>

struct with_foo_int
{
    int foo(int i) {
        return i + 1;
    }
};

using namespace std;

int main(void)
{
    mystruct<with_foo_int> ms1;
    cout << ms1.someMethod(41) << endl;

    mystruct<double> ms2;
    cout << ms2.someMethod(41) << endl; // static_assert failure

    return 0;
}

このソリューションは、投稿されたように、あなた自身の試みで考えられるいくつかの抜け穴を忠実に再現します:-

1)評価std::declval<U>().foo(0)は、存在するかどうかを判断する SFINAE の方法でありU::foo、 type の単一の引数を取ると信じているように見えますint。そうではありません。これは、暗黙的に変換可能なものがU::foo(ArgType)どこに存在するかを判断する SFINAE の方法にすぎません 。したがって、 だけでなく、任意のポインターまたは算術型にすることができます。ArgType0ArgTypeint

2)のいずれかまたは両方が存在するstd::declval<U>().foo(0)場合に が満たされるとは考えていないかもしれません。で a を呼び出すか非メンバー関数 を呼び出すかは気にするかもしれません。次のように定義されている場合 :U::foo(ArgType) U::foo(ArgType) constconstconstUwith_foo_int

struct with_foo_int
{
    int foo(int i) const {
        return i + 1;
    }
    int foo(int i) {
        return i + 2;
    }   
};

次に、指定されたソリューションは非constオーバーロードを 呼び出し、 ms1.someMethod(41)== になり43ます。

2) 扱いやすい。呼び出しのみを実行できるようにする場合は 、修飾子を にT::foo(ArgType) const追加します。気にしない、または電話したいだけの場合は、そのままにしておいてください。constmystruct::someMethodT::foo(ArgType)

1) は、正しい署名T::fooがある場合にのみ満たされるSNIFAE プローブを作成する必要があり 、その署名が修飾されているかどうかのいずれかになるため、解決するのが少し難しくなります。あなたがしたいと仮定しましょう 。その場合、テンプレート を次のものに置き換えます。constint T::foo(int) consthas_mf_foo_accepts_int_returns_int

/*  Template `has_mf_foo_arg_int_returns_int<T>
    has a static boolean public member `value` that == true
    if and only if `T` is a class type that has an un-overloaded
    a public member `int T::foo(int) const`.
*/ 
template< typename T>
struct has_mf_foo_arg_int_returns_int
{
    /* SFINAE foo-has-correct-sig :) */
    template<typename A>
    static std::true_type test(int (A::*)(int) const) {
        return std::true_type();
    }

    /* SFINAE foo-exists :) */
    template <typename A> 
    static decltype(test(&A::foo)) 
    test(decltype(&A::foo),void *) {
        /* foo exists. What about sig? */
        typedef decltype(test(&A::foo)) return_type; 
        return return_type();
    }

    /* SFINAE game over :( */
    template<typename A>
    static std::false_type test(...) {
        return std::false_type(); 
    }

    /* This will be either `std::true_type` or `std::false_type` */
    typedef decltype(test<T>(0,0)) type;

    static const bool value = type::value; /* Which is it? */
};

そしてテンプレートでmystruct置き換えます:

using has_good_foo = has_mf_foo_accepts_int_returns_int<T>;

と:

using has_good_foo = has_mf_foo_arg_int_returns_int<T>;

(テンプレートは私の他の回答has_mf_foo_arg_int_returns_intから適応されており、そこでどのように機能するかを読むことができます。)

後者のアプローチから SFINAE 精度で得られるものには代償が伴います。このアプローチでは、 のアドレスを取得して、T::foo存在するかどうかを確認する必要があります。ただし、C++ はオーバーロードされたメンバー関数のアドレスを提供しないため、オーバーロードされている場合、このアプローチは失敗しT::fooます。

ここのコードはstatic_assert、GCC >= 4.7.2 clang >= 3.2 でコンパイル (または適切に) されます。

于 2013-07-30T09:11:15.620 に答える