Pergunta

This is the code I wanna write:

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

and the compiler output I get: (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>();
                ^

Ok, what I mean to achieve is clear: Something like std::tuple. get() is meant to provide element access.

Please help me get this skeleton working; What I'm after is the understanding required to implement such a construct correctly; 2 specific questions though:

  1. What is the right way to specialize get<0>()?
  2. What will typename... tail be, when the compiler reaches the root of hierarchy?
Foi útil?

Solução

If you want to explicitly specialize a function (member function, member function template, ..), then you must do that at namespace scope:

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

But this isn't allowed: You may not explicitly specialize a member of a not explicitly specialized class template. Here, record<T1, tail...> is not explicitly specialized; hence you may not explicitly specialize get.

There are two other problems:

  1. The return type of get must be dependent on the index.
  2. Your record will recursively derive from itself. Each derivation will remove one element from tail, so tail ends up being empty. Then, record<tail...> will fail, as the first template parameter T1 cannot be set when tail is empty. Therefore, you need to specialize record as well.

One way to get it working is to use overloading:

#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';
}

Here's an example using a different inheritance technique:

#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';
}

The usage of record_impl allows to get rid of the additional get_impl. It also provides a good opportunity to place a static_assert in the primary template's get member function.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top