【问题标题】:When using variadric template, how to get nth arguments type?使用可变参数模板时,如何获取第 n 个参数类型?
【发布时间】:2021-12-03 10:03:04
【问题描述】:

我想要一个包含n 值的类,就像std::tuple。不过,我不能完全使用元组,因为在获取值时有额外的逻辑——它们是按需提供的。

请以我写的这个类为例:

// somewhere else
template<typename TVal>
TVal valueGetter() { ... };

template<typename ...TColValue>
class ResultRow
{
public:
  template<unsigned int TIndex>
  get_nth_from_variadric<TIndex, TColValue> GetValue() const
  {
    return valueGetter<get_nth_from_variadric<TIndex, TColValue> >();
  }
  

};

我希望它的工作方式是用户只需调用int myVal = GetValue&lt;1&gt;,给定类模板参数ResultRow&lt;bool, int&gt;。为此,我需要能够将模板参数的索引转换为类型。

我该怎么做?

【问题讨论】:

  • 看看this great answer。它使用实际上非常快的多重继承。
  • 我不能 100% 确定我是否理解正确,但也许您可以改为使用 std::tuple 的值获取器函子来实现实际的按需获取值 工作吗?
  • @TedLyngmo 我不完全确定那会是什么 - 要填充元组中的函子类型,我需要知道它们应该返回的类型,然后我又回到了我开始的地方。
  • @TomášZato-ReinstateMonica 嗯,好的,是的。
  • “虽然我不能完全使用元组”。不明白原因,std::get&lt;I&gt;(tuple)是C++11,std::get&lt;T&gt;(tuple)是C++14。而且似乎我并不孤单,因为 3/4 的答案使用std::tuple

标签: c++ templates c++17 variadic-templates


【解决方案1】:

您可以借助递归继承类型特征从参数包中获取类型。

template<unsigned int TIndex, typename ...TColValue>
struct get_nth_from_variadric_type;

template<unsigned int TIndex, typename Head, typename... Tail >
struct get_nth_from_variadric_type<TIndex, Head, Tail...>
    : get_nth_from_variadric_type<TIndex-1, Tail...> { };

template<typename Head, typename... Tail>
struct get_nth_from_variadric_type<0, Head, Tail...> {
   using type = Head;
};

template<unsigned int TIndex, typename ...TColValue>
using get_nth_from_variadric = typename get_nth_from_variadric_type<TIndex, TColValue...>::type;

然后像这样使用它

template<typename ...TColValue>
class ResultRow
{
public:
  template<unsigned int TIndex>
  get_nth_from_variadric<TIndex, TColValue...> GetValue() const
  {
    return valueGetter<get_nth_from_variadric<TIndex, TColValue...> >();
  }
};

【讨论】:

    【解决方案2】:

    一种方法是将可变参数转发到一个元组中,然后使用std::get,例如:

    #include <iostream>
    #include <tuple>
    
    template<size_t N, typename... Args>
    auto f(Args&&... args)
    {
      return std::get<N>(std::tuple{std::forward<Args>(args)...});
    }
    
    int main(void)
    {
      std::cout << f<0>("Hello", "world") << ' ' << f<1>("Hello", "world") << f<0>('!') << '\n';
    }
    

    【讨论】:

      【解决方案3】:

      我认为我们应该包装 stl 的 tuple_element,但你可以:

      template <std::size_t, class>
      struct nth_of_pack;
      
      template <std::size_t N, template <class...> class Pack, class ... Ts>
      struct nth_of_pack <N, Pack<Ts...>>
          : std::tuple_element<N, std::tuple<Ts...>> {};
      
      template <std::size_t N, class Pack>
      using nth_of_pack_t = typename nth_of_pack<N, Pack>::type;
      

      Demo


      更好的解决方案:

      tuple_element 似乎总是使用非常慢的递归继承来实现(我已经在 MSVC、gcc、clang 上测试过它们的许多版本——我仍然不知道他们为什么使用递归!)。它也不适用于tuple 以外的任何东西,这很不幸。因此,我们将为任何带有类型参数的类(我称之为“包”)创建一些通用的东西。

      下面我们将 Juliusgreat answer 用于这个特定问题。有关标准继承、多继承和tuple_element 的性能讨论,请参阅他们的答案。这里我们使用多继承:

      #include <utility>
      
      template <class T>
      struct tag
      {
          using type = T;
      };
      
      template <class T>
      using result_t = typename T::type;
      
      ////////////////////////////////////////////////////////////////////////////////
      
      template<std::size_t, std::size_t, class>
      struct type_if_equal {};
      
      template<std::size_t n, class T>
      struct type_if_equal<n, n, T> : tag<T> {};
      
      ////////////////////////////////////////////////////////////////////////////////
      
      template<std::size_t n, class Is, class... Ts>
      struct select_nth_implementation;
      
      template<std::size_t n, std::size_t... is, class... Ts>
      struct select_nth_implementation<n, std::index_sequence<is...>, Ts...>
          : type_if_equal<n, is, Ts>... {};
      
      template<std::size_t n, class... Ts>
      struct select_nth : select_nth_implementation<
          n, std::index_sequence_for<Ts...>, Ts...> {};
      
      template<std::size_t n, class... Ts>
      using select_nth_t = result_t<select_nth<n, Ts...>>;
      
      ////////////////////////////////////////////////////////////////////////////////
      
      template <std::size_t, class>
      struct nth_of_pack;
      
      template <std::size_t N, template <class...> class Pack, class ... Ts>
      struct nth_of_pack <N, Pack<Ts...>> : select_nth<N, Ts...> {};
      
      template <std::size_t N, class Pack>
      using nth_of_pack_t = result_t<nth_of_pack<N, Pack>>;
      

      然后我们可以这样使用:

      #include <type_traits>
      
      template <class...>
      class foo;
      
      int main () {
          using my_tuple = foo<int, bool, char, double>;
          using second_type = nth_of_pack_t<2, my_tuple>;
          static_assert(std::is_same_v<second_type, char>);
      }
      

      Demo

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-08-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-02-22
        • 2016-01-22
        相关资源
        最近更新 更多