SFINAE选择性地加入成员

时间:2013-07-29 15:54:08

标签: c++ templates c++11 sfinae decltype

我正在尝试编写一个模板类,它可能会也可能不会定义特定的成员函数,具体取决于其模板参数类型。此成员函数的返回类型取决于模板参数成员的返回类型(如果已定义)。

以下是我的代码的最小示例

#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)。如果一个人意外地试图在someFunc上调用ms2,那么我也会发现它无法编译。

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

我读到here SFINAE仅适用于模板函数,所以我尝试给出一个虚拟模板参数来计算返回类型,但这也失败了。

我显然不明白这里有什么,我错过了什么?是否有可能实现我想要做的事情?

感谢。

P.S。我正在使用g ++ 4.7.3

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

1 个答案:

答案 0 :(得分: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。它没有。它只是一种确定是否是SFINAE的方式 U::foo(ArgType)存在ArgType,其中0ArgType 隐含可兑换。因此int可以是任何指针或算术 类型,而不仅仅是std::declval<U>().foo(0)

2)您可能没有考虑过U::foo(ArgType)会满意 如果存在U::foo(ArgType) const const或两者。您 您可能会关注是否要调用const或非U成员函数 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会== T::foo(ArgType) const

2)容易处理。如果您希望确保只能打电话 const然后将mystruct::someMethod限定符添加到T::foo(ArgType)。 如果你不在乎或只想打电话给T::foo,那就留下一些东西 他们是。

1)有点难以解决,因为你必须制作一个SNIFAE探测器 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? */ }; with:

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

(模板T::foo已经过调整 来自my other answer和 你可以在那里阅读它的工作原理。)

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

这里的代码将编译(或适当地{{1}}) GCC&gt; = 4.7.2 clang&gt; = 3.2。