2013-07-29 82 views
3

我想写一个模板类,它可能会或可能不会定义一个特定的成员函数,具体取决于它的模板参数类型。此外,此成员函数的返回类型取决于模板参数成员的返回类型(如果已定义)。SFINAE选择性地包括成员

下面是我的代码小例子

#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 ms1.someFunc(41)。我还希望如果不小心试图拨打 ms2它将无法编译。

不幸的是,我试过的每个选择都失败了。第一和第二,我想我明白他们为什么不行。

我读here SFINAE只适用于模板函数,所以我试着给出一个虚拟模板参数来计算返回类型,但是这也以相同的方式失败。

我很清楚这里没有理解的东西,我错过了什么?是否有可能实现我想要做的事情?

谢谢。

P.s.我使用的是G ++ 4.7.3

P.p.s我也曾尝试std::enable_if但得到大致相同的结果与我的foo_int_return_type结构。

+1

该函数本身需要将相关参数作为模板参数,否则在调用点没有替换,并且您没有获得SFINAE。一个流行的解决方法是像'template ' – Xeo

+0

它可行!我不确定为什么,但如果您将此作为答案并解释,我会很乐意接受。 – Dan

+0

对于SFINAE,编译器(或用户)需要推导(提供)编译器稍后将尝试的类型** S ** ubstitute和** F ** ail –

回答

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并且采用int类型的单个参数 。它没有。它仅仅是一种确定 U::foo(ArgType)是否存在的SFINAE方式,其中ArgType0是 可以隐式转换的任何东西。因此ArgType可以是任何指针或算术 类型,而不仅仅是int

2)您可能没有考虑到std::declval<U>().foo(0)会满意 如果或任两者U::foo(ArgType)U::foo(ArgType) const存在。您 可能会在意您是否拨打constU的非const成员函数,并且您肯定会关心您调用的两个成员函数中的哪一个。如果 with_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,请将const限定符添加到mystruct::someMethod。 如果您不在意或只希望拨打T::foo(ArgType),请保留 原样。

1)是有点难以解决,因为你必须制作一个SNIFAE探头 T::foo这是唯一满意的,如果它有正确的签名,那 签名要么是const合格与否。假设你想要 int T::foo(int) const。在这种情况下,更换模板 has_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适用 从my other answer和 你可以阅读它的工作原理。)

你从SFINAE精度中获得的后一种方法的价格是 。该方法要求您尝试取T::foo, 的地址以查看它是否存在。但C++不会给你超载的成员函数的地址,所以如果T::foo被重载,这种方法将失败。

此处的代码将编译(或适当地static_assert)与 GCC> = 4.7.2 clang> = 3.2。