考虑以下代码:
#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> {};
现在我们只需要正确使用它。
答案 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> ) );
}