SFINAE递归使用

时间:2016-03-28 00:41:29

标签: templates c++11 sfinae

考虑以下代码:

#include <iostream>

struct A {
    void foo() const {std::cout << "A::foo()\n";}
};

struct B {
    A value;
    void foo() const {std::cout << "B::foo()\n";}
};

struct C {
    B value;
    void foo() const {std::cout << "C::foo()\n";}
};

struct D {
    C value;
};

struct Stop {
    Stop(...) {}
};

template <typename T>
void fooValue (const T& t, Stop) {
    t.foo();
}

template <typename T, typename = decltype(T::value)>
void fooValue (const T& t, int) {
    fooValue(t.value, 0);
}

template <typename T>
void foo (const T& t) {
    fooValue(t, 0);
}

int main() {
    const D d;
    foo(d);  // A::foo()
}

因此输出A::foo()是因为A是链中具有value成员的最后一个类型。我现在要定义

template <std::size_t N, typename T> void foo (const T&)

这样N = 0会做同样的事情,但是N = 1将在链调用其foo()函数中具有第二个最后一个类型,N = 2将在链调用中具有第三个最后一个类型foo()函数等...因此在上面的例子中, foo<1>(d);将输出B::foo()foo<2>(d);将输出C::foo()。但我无法想到如何实现这一点。任何人都可以帮忙吗?

更新:感谢Ryan Haining的讨论,我提出了以下内容来计算编译时的深度:

template <typename T>
using void_t = void;

template <typename T, std::size_t N, typename = void>
struct ChainLength : std::integral_constant<std::size_t, N> {};

template <typename T, std::size_t N>
struct ChainLength<T, N, void_t<decltype(T::value)>> : ChainLength<decltype(T::value), N+1> {};

现在我们只需要正确使用它。

2 个答案:

答案 0 :(得分:1)

这要归功于与Ryan Haining的讨论:

#include <iostream>
#include <type_traits>

template <typename T>
using void_t = void;

template <typename T, std::size_t N, typename = void>
struct ChainLength : std::integral_constant<std::size_t, N> {};

template <typename T, std::size_t N>
struct ChainLength<T, N,
    void_t<decltype(std::declval<decltype(T::value)>().foo())>> :
    ChainLength<decltype(T::value), N+1> {};  // Here we check not only that T::data exists, but that decltype(T::data) has a foo() member function too.

template <std::size_t Count, typename T>
struct FooData {
    static void execute (const T& t) {
        FooData<Count-1, decltype(T::value)>::execute(t.value);
    }
};

template <typename T>
struct FooData<0, T> {
    static void execute (const T& t) { t.foo(); }
};

template <std::size_t N, std::size_t Length, typename T>
void fooHelper (const T& t, std::enable_if_t<(N < Length)>* = nullptr) {
    FooData<Length-N, T>::execute(t);
}

template <std::size_t N, std::size_t Length, typename T>
void fooHelper (const T&, std::enable_if_t<(N >= Length)>* = nullptr) {
    std::cout << "N value too big.\n";
}

template <std::size_t N, typename T>
void foo (const T& t) {
    fooHelper<N, ChainLength<T,0>::value>(t);
}

// Testing
struct A {
    void foo() const {std::cout << "A::foo()\n";}
};

struct B {
    A value;
    void foo() const {std::cout << "B::foo()\n";}
};

struct C {
    B value;
    void foo() const {std::cout << "C::foo()\n";}
};

struct D {
    C value;
};

int main() {
    const D d;
    foo<0>(d);  // A::foo()  (this is outputted because A is the last type in the chain that has a 'value' member)
    foo<1>(d);  // B::foo()  (this is outputted because B is the second last type in the chain that has a 'value' member)
    foo<2>(d);  // C::foo()  (this is outputted because C is the third last type in the chain that has a 'value' member)
    foo<3>(d);  // N value too big.
}

答案 1 :(得分:1)

计划是用功能而不是用类型来做所有事情。

首先,使用整数常量的包装来减少输入:

template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index = {};

现在,对它进行一些简单的操作:

template<std::size_t A, std::size_t B>
constexpr index_t<A+B> add(index_t<A>, index_t<B>){ return {}; }

template<std::size_t A, std::size_t B>
constexpr index_t<A-B> sub(index_t<A>, index_t<B>){ return {}; }

template<std::size_t I>
constexpr index_t<I+1> next( index_t<I> ) { return {}; };

将深度计算为指数:

template<class T>
constexpr index_t<0> fooIndex(const T& t, Stop) {
  return {};
}

template<class T, class = decltype(T::value)>
constexpr auto fooIndex(const T& t, int) {
  return next(fooIndex(t.value, 0));
}

在特定深度调用:

template<class T>
void fooValue(const T& t, index_t<0>) {
  t.foo();
}
template<class T, std::size_t I>
void fooValue(const T& t, index_t<I>) {
  fooValue(t.value, index<I-1>);
}

使用它们:

template <typename T>
void foo (const T& t) {
  auto idx = fooIndex(t, 0);
  return fooValue( t, sub( idx, index<1> ) ); 
}