【问题标题】:Variadic recursive template mem fun specialization可变递归模板 mem fun 特化
【发布时间】:2013-12-22 11:35:03
【问题描述】:

这是我要写的代码:

template <typename T1, typename ... tail>
  class record : public record<tail...>
{
  using baseT = record<tail...>;

  T1 elem;

public:
  record(T1 first, tail... rest)
    : elem(first), baseT(rest...)
  {}

  template <int index>
  inline constexpr T1& get()
  {
        // line 83:
    return baseT::get<index-1>();
  }
  // line 85:
  template <>
  inline constexpr T1& get<0>()
  {
    return elem;
  }
};

我得到的编译器输出:(GCC 4.8.2 with -std=c++11

record.hpp:85:15: error: explicit specialization in non-namespace scope 'class base::record<T1, tail>'
 template <>
       ^
record.hpp:86:33: error: template-id 'get<0>' in declaration of primary template
 inline constexpr T1& get<0>()
                 ^
record.hpp: In member function 'constexpr T1& base::record<T1, tail>::get() const':
record.hpp:83:32: error: expected primary-expression before ')' token
   return baseT::get<index-1>();
                ^

好的,我的意思很明确:类似于std::tupleget() 旨在提供元素访问。

请帮我让这个骨架工作;我所追求的是正确实施这种结构所需的理解; 2个具体问题:

  1. 专业化get&lt;0&gt;()的正确方法是什么?
  2. 当编译器到达层次结构的根时,typename... tail 会是什么?

【问题讨论】:

    标签: c++ c++11 variadic-templates specialization


    【解决方案1】:

    如果你想显式特化一个函数(成员函数、成员函数模板、..),那么你必须在命名空间范围内这样做:

    template <typename T1, typename ... tail>
      class record : public record<tail...>
    {
      using baseT = record<tail...>;
    
      T1 elem;
    
    public:
      record(T1 first, tail... rest) // you should use perfect forwarding here
        : elem(first), baseT(rest...)
      {}
    
      template <int index>
      inline constexpr T1& get() // the `inline` is redundant here
      {
            // line 83:
        return baseT::get<index-1>();
      }
    };
    
    template<typename T1, typename ... tail>
    template<>
    inline constexpr T1& record<T1, tail...>::template get<0>()
    {  return elem;  }
    

    但是这是不允许的:你不能显式地特化一个没有显式特化的类模板的成员。在这里,record&lt;T1, tail...&gt; 没有明确专门化;因此您可能没有明确专门化get

    还有两个问题:

    1. get 的返回类型必须依赖于索引。
    2. 您的记录将递归地从其自身派生。每个派生将从tail 中删除一个元素,因此tail 最终为空。那么record&lt;tail...&gt;就会失败,因为tail为空时,不能设置第一个模板参数T1。因此,您还需要专门化 record

    让它工作的一种方法是使用重载:

    #include <type_traits>
    #include <utility>
    
    template<int N>
    using int_const = std::integral_constant<int, N>;
    
    template <typename T1, typename ... tail>
      class record : public record<tail...>
    {
      using baseT = record<tail...>;
    
      T1 elem;
    
    protected:
      using baseT::get_impl;  // "unhide" the base class overloads
    
      constexpr T1 const& get_impl(int_const<sizeof...(tail)>) const
      {
          return elem;
      }
    
    public:
      template<typename T1_, typename ... tail_>
      record(T1_&& first, tail_&&... rest)
        : baseT(std::forward<tail_>(rest)...), elem(std::forward<T1_>(first))
      {}
    
      template <int index>
      constexpr auto get() const
      -> decltype( this->get_impl( int_const<sizeof...(tail) - index>{} ) )
      {
        static_assert(1+sizeof...(tail) > index, "out of bounds");
        return this->get_impl( int_const<sizeof...(tail) - index>{} );
      }
    };
    
    template <typename T1>
    class record<T1>
    {
      T1 elem;
    
    protected:
      constexpr T1 const& get_impl(int_const<0>) const
      {
          return elem;
      }
    
    public:
      template<typename T1_>
      record(T1_&& first)
        : elem(first)
      {}
    
      template <int index>
      constexpr auto get() const
      -> decltype( get_impl( int_const<index>{} ) )
      {
        static_assert(0 == index, "out of bounds");
        return this->get_impl( int_const<index>{} );
      }
    };
    
    
    #include <iostream>
    
    int main()
    {
        record<int, double, char, bool> r{42, 1.2, 'c', false};
        std::cout << r.get<1>() << '\n';
        std::cout << r.get<0>() << '\n';
    }
    

    这是一个使用不同继承技术的示例:

    #include <type_traits>
    #include <utility>
    
    template<int N>
    using int_const = std::integral_constant<int, N>;
    
    template<int N, class... Ts>
    struct record_impl
    {
        struct out_of_bounds {};
    
        template<int I>
        constexpr out_of_bounds get(int_const<I>) const
        {
            static_assert(I < N, "out of bounds");
            return {};
        }
    };
    
    template<int N, class T, class... Ts>
    struct record_impl<N, T, Ts...> : record_impl<N+1, Ts...>
    {
        using base = record_impl<N+1, Ts...>;
    
        T mem;
    
        template<class Arg, class... Args>
        record_impl(Arg&& arg, Args&&... args)
        : base(std::forward<Args>(args)...), mem(std::forward<Arg>(arg))
        {}
    
        using base::get;
        constexpr T const& get(int_const<N>) const
        {  return mem;  }
    };
    
    template<class... Ts>
    using record = record_impl<0, Ts...>;
    
    
    #include <iostream>
    
    int main()
    {
        record<int, double, char, bool> r{42, 1.2, 'c', false};
        std::cout << r.get(int_const<0>{}) << '\n';
        std::cout << r.get(int_const<3>{}) << '\n';
    }
    

    record_impl 的使用允许摆脱额外的get_impl。它还提供了一个在主模板的get 成员函数中放置static_assert 的好机会。

    【讨论】:

    • :/ 静态断言不会有太大帮助,因为如果索引超出范围,则无法在 decltype 中确定返回类型。在越界访问的情况下,您可能需要执行更复杂的操作来触发static_assert
    • 顺便说一句:层次结构中的所有类都有一个get 成员函数,但它们被顶级类'get 隐藏,它具有正确的sizeof...(tail)。这个sizeof 是必要的,因为只有顶级类知道尾部的真实长度,但尾部中的元素以相反的顺序分配给层次结构中的类级别。
    • 注意:你可以使用成员而不是继承(这也将解决逆序和隐藏方法)。
    • @Haix64 正如我在答案中所写,您有时需要一组重载函数,因为返回类型取决于索引。也就是说,从外部调用的函数需要根据索引确定其返回类型,而返回实际成员的函数具有固定的返回类型。也可以从外部调用一组重载函数(如r.get(int_const&lt;5&gt;{})),但这需要重组继承。我将在我的答案中添加一个示例。
    • @Haix64 例如,有this similar question,但是我没有找到很好的解释:/(该问题的最佳答案末尾有一些基本原理。)您可以这样问一个单独的问题(为什么不允许)。虽然它可能会因为重复而被关闭,但您很可能会得到一个更好的答案(链接到一个)。
    猜你喜欢
    • 1970-01-01
    • 2017-02-23
    • 1970-01-01
    • 2017-10-15
    • 2015-05-20
    • 1970-01-01
    • 1970-01-01
    • 2012-04-29
    • 1970-01-01
    相关资源
    最近更新 更多