使用sfinae来检测可变参数模板的基类是否具有特定方法

时间:2016-03-23 15:03:47

标签: c++ c++14 variadic-templates sfinae

我正在尝试"迭代"在可变参数派生类的所有基类上调用一个名为" stream"的方法。如果它存在。

要检查方法是否存在,我使用sfinae,它可以工作(参见注释掉的代码)。但是,当我将它与可变参数模板和#34;迭代"如果不起作用,你的错误有点看起来像sfinae部分突然工作不像预期的那样,当进入可变量魔法时。

帮助表示感谢。我使用gcc 5.3.0。

#include <type_traits>
#include <iostream>

namespace detail{
  template<class> struct sfinae_true : std::true_type{};

  template<class T, class A0, class A1> static auto test_stream( int) -> sfinae_true<decltype(
          std::declval<T>().stream(std::declval<A0>(), std::declval<A1>()))>;
  template<class, class A0, class A1> static auto test_stream(long) -> std::false_type;
}

template<class T, class A0, class A1> struct has_stream : decltype(detail::test_stream<T, A0, A1>(0)){};

struct X{ void stream(int, bool){} };
struct A{ void stream(int, bool){} };
struct Y{};

template <typename ... T> class Z : public T ... {
    public:
    void ff() {
        std::initializer_list<bool> {
            ( has_stream<T,int,bool>() ? (T::stream(0, 0) , true) : false) ...
        };
    }
};

int main(){

    Z<X,A> a;
    Z<X,A,Y> b;

/* this works as expected.
    // this runs
    if (has_stream<X, int, bool>()) {
        std::cout << "has int, bool" << std::endl;
    }
    // and this doesn't
    if (has_stream<Y, int, long>()) {
        std::cout << "has int long" << std::endl;
    }
*/

    a.ff(); // no error
    b.ff(); // error

}


$ g++ --std=c++14 -O0 2.cpp                                                                                                                                                     
2.cpp: In instantiation of ‘void Z<T>::ff() [with T = X, A, Y]’:
2.cpp:41:10:   required from here
2.cpp:22:52: error: ‘stream’ is not a member of ‘Y’
             ( has_stream<T,int,bool>() ? (T::stream(0, 0) , true) : false) ...
                                                    ^
2.cpp:21:9: error: no matching function for call to ‘std::initializer_list<bool>::initializer_list(<brace-enclosed initializer list>)’
         std::initializer_list<bool> {
         ^

3 个答案:

答案 0 :(得分:4)

使用由您的类型特征组成的标签调度:

void ff()
{
    std::initializer_list<int> {
        (call<T>(has_stream<T,int,bool>{}), 0)...
    };
}   

template <typename U>
void call(std::true_type)
{
    U::stream(0, 0);
}

template <typename U>
void call(std::false_type) {}

或使用表达式SFINAE:

void ff()
{
    std::initializer_list<int> {
        (call<T>(0), 0)...
    };
}   

template <typename U>
auto call(int) -> decltype(U::stream(0, 0), void())
{
    U::stream(0, 0);
}

template <typename U>
void call(char) {}

答案 1 :(得分:3)

两个分支都应在ff内有效。 您必须添加特定方法来解决这个问题:

template <typename Base, typename T>
std::enable_if_t<has_stream<Base,int,bool>::value, bool>
call_stream_if_any(T& obj)
{
    obj.Base::stream(0, 0);
    return true;
}

template <typename Base, typename T>
std::enable_if_t<!has_stream<Base,int,bool>::value, bool>
call_stream_if_any(T& obj)
{
    return false;
}

然后

template <typename ... T> class Z : public T ... {
    public:
    void ff() {
        static_cast<void>(std::initializer_list<bool> {
            ( call_stream_if_any<T>(*this)) ...
        });
    }
};

Demo

答案 2 :(得分:0)

以下是带有一些私有帮助器结构的Z类:

#include <iostream>
#include <type_traits>

namespace detail {
    template <typename> struct TrueIfIsAType : std::true_type {};

    template <typename T, typename... Args>
    auto test (int) -> TrueIfIsAType< decltype(std::declval<T>().stream(std::declval<Args>()...)) >;

    template <typename, typename...>
    auto test (bool) -> std::false_type;
}

template <typename T, typename... Args>
struct HasStreamMember : decltype(detail::test<T, Args...>(0)) {};

template <typename... Ts>
struct Z : public Ts... {
    void foo() {
        const std::initializer_list<bool> a = {(HasStreamMember<Ts, int, bool>::value ? true : false)...};
        const int b[] = {(Check<Ts>::execute(this, 0, 0), 0)...};  // The fix
        std::cout << a.size() << '\n';  // Do whatever with 'a'.
        static_cast<void>(b);
    }
private:
    template <typename T, typename = void>
    struct Check {
        static void execute (Z*, int, bool) {}  // Do nothing.
    };

    template <typename T>
    struct Check<T, std::enable_if_t<HasStreamMember<T, int, bool>::value>> {
        static void execute (Z* z, int a, bool b) {
            z->T::stream(a, b);
        }       
    };
};

// Test
struct Thing { void stream(int, bool) {std::cout << "Thing stream\n";} };
struct Object { void stream(int, bool) {std::cout << "Object stream\n";} };
struct Widget {};

int main(){
    std::cout << std::boolalpha << HasStreamMember<Thing, int, bool>() << '\n';  // true
    std::cout << HasStreamMember<Object, int, bool>() << '\n';  // true
    std::cout << HasStreamMember<Object, Widget>() << '\n';  // false

    Z<Thing, Object> a;
    Z<Thing, Object, Widget> b;

    a.foo();  // Thing stream  Object stream  2
    b.foo();  // Thing stream  Object stream  3
}