In c++11 I have very neat and working code for extracting std::tuple
item by type (As I know this feature even placed to c++14 stl)
Now I'm facing with the task to select item by the inherited class specification
struct A
{
int a;
};
struct B : public A
{
int b;
};
...
auto tval = std::make_tuple(1, B());
//now I would like to reference items as following:
tuple_ref_by_inheritance<A>(tval).a = 5; //Access to B instance by parent A
Following code is my unsuccessful try:
template< class T, class Tuple >
struct tuple_ref_index;
// recursive case
template<class T, class Head, class... Tail >
struct tuple_ref_index<T, std::tuple<Head, Tail...> >
{
enum { value = tuple_ref_index<T, std::tuple<Tail...>>::value + 1 };
};
template<class T, class Head, class... Tail >
struct tuple_ref_index<T, std::tuple<Head, Tail...> >
{
const static typename std::enable_if<
std::is_same<T, Head>::value>::type* _= nullptr;
enum { value = 0 };
};
template <class T, class Tuple>
inline T& tuple_ref_by_inheritance(Tuple& tuple)
{
return std::get< tuple_ref_index<T, Tuple>::value >(tuple);
}
#include <type_traits>
#include <utility>
#include <cstddef>
#include <tuple>
template <typename Base, typename Tuple, std::size_t I = 0>
struct tuple_ref_index;
template <typename Base, typename Head, typename... Tail, std::size_t I>
struct tuple_ref_index<Base, std::tuple<Head, Tail...>, I>
: std::conditional<std::is_base_of<Base, Head>::value
, std::integral_constant<std::size_t, I>
, tuple_ref_index<Base, std::tuple<Tail...>, I+1>
>::type
{
};
template <typename Base, typename Tuple>
auto tuple_ref_by_inheritance(Tuple&& tuple)
-> decltype(std::get<tuple_ref_index<Base, typename std::decay<Tuple>::type>::value>(std::forward<Tuple>(tuple)))
{
return std::get<tuple_ref_index<Base, typename std::decay<Tuple>::type>::value>(std::forward<Tuple>(tuple));
}
DEMO
First, some metaprogramming boilerplate.
void_t
is C++14:
namespace details {
template<class...>struct voider{using type=void;};
}
template<class...Ts>
using void_t=typename details::voider<Ts...>::type;
This runs a test on each element of a list, and returns the first test that passes:
template<template<class...>class Test, class List>
struct get_first_that_passes;
template<template<class...>class Test, class List>
using get_first_that_passes_t=
typename get_first_that_passes<Test,List>::type;
namespace details {
template<template<class...>class, class, class...>
struct get_first_pass {};
template<template<class...>class Test, class T0, class...Ts>
struct get_first_pass<Test, std::enable_if_t< !Test<T0>::value >, T0, Ts...> :
get_first_pass<Test, void, Ts...>
{};
template<template<class...>class Test, class T0, class...Ts>
struct get_first_pass<Test, std::enable_if_t< Test<T0>::value >, T0, Ts...> {
using type=T0;
};
}
template<template<class...>class Test, template<class...>class List, class...Ts>
struct get_first_that_passes<Test, List<Ts...>>:
details::get_first_pass<Test, void, Ts...>
{};
Now we write is_derived_from
, which produces a test if something is derived from a base:
template<class Base>
struct is_derived_from {
template<class Derived>
using test = std::is_base_of<Base,Derived>;
};
Composing the above two, we get the first type in a list that is derived from some base:
template<class Base, class List>
using get_first_derived =
get_first_that_passes_t<
is_derived_from<Base>::template test,
List
>;
which lets us write a simple get_from_base<T>(tuple)
, which gets the first type in a tuple
that derives from T
, then calls std::get<T>
on it:
template<class Base, class Tuple>
auto get_from_base( Tuple&& tuple )
->decltype(std::get< get_first_derived<Base, std::decay_t<Tuple>> >(std::forward<Tuple>(tuple)))
{ return std::get< get_first_derived<Base, std::decay_t<Tuple>> >(std::forward<Tuple>(tuple)); }
translating this to C++11 is left as an exercise. (remove the _t
s is probably enough).
Live example.
Note that as written, it will find the first type derived from Base
. Then it will return a reference to that element, but only if there are no more instances of that type in the list.
To match get<Type>
, you'd want to confirm the tail has no other types derived from Base
.
If you want get_first_that_derives_from
, you'll have to get the index instead of the type.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With