std::tuple, get item by inherited type
Asked Answered
C

2

9

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);
    }
Candleberry answered 30/11, 2015 at 13:33 Comment(2)
What's the use case for having the 1? Do you anticipate having more than one derived class in the tuple that you wish to select from?Rosiarosicrucian
@Rosiarosicrucian just an example of tuple that have another value beside A/B. (2) I promise don't use 2 descending of one interfaceCandleberry
B
7
#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

Bully answered 30/11, 2015 at 15:42 Comment(4)
Why add a manual overload? Isn't it possible to use && and std::forward?Sermon
@GuyGreer I wanted to keep it clean, but fine, I've replaced it with a forwarding-reference versionBully
Sorry, I didn't mean to force your hand, and keeping it clean seems like a good reason to me.Sermon
Simplification: It can just return decltype(auto). Also, I think we want the is_base_of to be std::is_base_of_v<std::decay_t<Base>, std::decay_t<Head>> so it works if the tuple contains references too.Dinkins
R
2

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 _ts 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.

Rudiger answered 30/11, 2015 at 15:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.