【问题标题】:std::tuple, get item by inherited typestd::tuple,通过继承类型获取项目
【发布时间】:2015-11-30 13:33:29
【问题描述】:

在 c++11 中,我有非常简洁且有效的代码,用于按类型提取 std::tuple 项目(据我所知,这个功能甚至 placed 到 c++14 stl)

现在我面临着通过继承的类规范选择项目的任务

 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

以下代码是我的不成功尝试:

    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);
    }

【问题讨论】:

  • 拥有1 的用例是什么?您是否预计要从中选择的元组中有多个派生类?
  • @AndyG 只是元组的一个例子,它在 A/B 之外还有另一个值。 (2) 我保证不使用一个界面的 2 降序

标签: c++ c++11 tuples template-meta-programming


【解决方案1】:
#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

【讨论】:

  • 为什么要添加手动重载?不可以用&amp;&amp;和std::forward吗?
  • @GuyGreer 我想保持干净,但是很好,我已将其替换为转发参考版本
  • 对不起,我不是故意强迫你的手,保持手干净对我来说似乎是一个很好的理由。
  • 简化:它可以只返回decltype(auto)。另外,我认为我们希望 is_base_of 为 std::is_base_of_v&lt;std::decay_t&lt;Base&gt;, std::decay_t&lt;Head&gt;&gt;,因此如果元组也包含引用,它也可以工作。
【解决方案2】:

首先,一些元编程样板。

void_t 是 C++14:

namespace details {
  template<class...>struct voider{using type=void;};
}

template<class...Ts>
using void_t=typename details::voider<Ts...>::type;

这将对列表的每个元素运行测试,并返回第一个通过的测试:

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...>
{};

现在我们编写is_derived_from,它会产生一个测试,如果某些东西是从一个基础派生的:

template<class Base>
struct is_derived_from {
  template<class Derived>
  using test = std::is_base_of<Base,Derived>;
};

组合以上两个,我们得到列表中的第一个类型,它是从某个基派生的:

template<class Base, class List>
using get_first_derived =
  get_first_that_passes_t<
    is_derived_from<Base>::template test,
    List
  >;

这让我们可以编写一个简单的get_from_base&lt;T&gt;(tuple),它在派生自T 的tuple 中获取第一个类型,然后对其调用std::get&lt;T&gt;:

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)); }

将其翻译成 C++11 留作练习。 (删除_ts 可能就足够了)。

Live example.

请注意,正如所写,它将找到派生自Base 的第一个类型。然后它将返回对该元素的引用,但前提是列表中不再有 该类型的实例。

要匹配get&lt;Type&gt;,您需要确认尾部没有派生自Base 的其他类型。

如果你想要get_first_that_derives_from,你必须得到索引而不是类型。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-02
    • 2012-07-22
    相关资源
    最近更新 更多