Variadic рекурсивный шаблон mem fun специализация

Это код, который я хочу написать:

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 с -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::tuple, get() предназначен для обеспечения доступа к элементу.

Пожалуйста, помогите мне заставить этот скелет работать; Что мне нужно, так это понимание, необходимое для правильной реализации такой конструкции; 2 конкретных вопроса, хотя:

  1. Как правильно специализироваться? get<0>()?
  2. Что будет typename... tail быть, когда компилятор достигнет корня иерархии?

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<T1, tail...> не является явно специализированным; следовательно, вы не можете явно специализироваться get,

Есть две другие проблемы:

  1. Тип возврата get должен зависеть от индекса.
  2. Ваша запись будет рекурсивно получена из себя. Каждый деривация удалит один элемент из tail, так tail заканчивает тем, что был пуст. Затем, record<tail...> не удастся, как первый параметр шаблона T1 не может быть установлен, когда tail пустой. Поэтому нужно специализироваться 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, Это также дает хорошую возможность разместить static_assert в первичном шаблоне get функция-член.

Другие вопросы по тегам