Reputation: 4849
Consider this code:
#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()
}
So A::foo()
is outputted because A
is the last type in the chain that has a value
member. I want to now define
template <std::size_t N, typename T> void foo (const T&)
so that N = 0 will do the same thing, but N = 1 will have the second last type in the chain call its foo()
function, N = 2 will have the third last type in the chain call its foo()
function, etc... Thus in the example above,
foo<1>(d);
will output B::foo()
and foo<2>(d);
will output C::foo()
. But I can't think of how to implement this. Can anyone help here?
Update: Thanks to Ryan Haining's discussion, I've come up with the following to compute the depth during compile time:
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> {};
Now we only need to use it properly.
Upvotes: 1
Views: 139
Reputation: 275750
The plan is to do everything with functions instead of with types.
First, a wrapper for integral constant to reduce typing:
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 = {};
Now, some simple operations on it:
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 {}; };
Calculate depth as an index:
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));
}
Invoke at a specific depth:
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>);
}
Use them:
template <typename T>
void foo (const T& t) {
auto idx = fooIndex(t, 0);
return fooValue( t, sub( idx, index<1> ) );
}
Upvotes: 1
Reputation: 4849
This is thanks to the discussion with 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.
}
Upvotes: 1