1

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?
dyp
  • 38,334
  • 13
  • 112
  • 177
Haix64
  • 806
  • 1
  • 13
  • 21

1 Answers1

5

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.

dyp
  • 38,334
  • 13
  • 112
  • 177
  • :/ the static assert won't help much, as the return type cannot be determined in the `decltype` if the index is out of bounds. You might need to do a more complex thing to trigger a `static_assert` in the case of an out-of-bounds access. – dyp Dec 22 '13 at 12:16
  • Btw: all of the classes in the hierarchy will have a `get` member function, but they're hidden by the top class' `get`, which has the correct `sizeof...(tail)`. This `sizeof` is necessary as only the top class knows the true length of the tail, but the elements in the tail are assigned in reversed order to the class level in the hierarchy. – dyp Dec 22 '13 at 12:20
  • Note: You may use member instead of inheritance (This will also solve reverse order, and hidden method). – Jarod42 Dec 22 '13 at 12:22
  • You may replace the `static_assert` by SFINAE. – Jarod42 Dec 22 '13 at 12:25
  • @Jarod42 Ad `static_assert`: I'm not sure what you mean. The `static_assert` is there to provide a nicer error message, though it fails at doing that because the return type isn't valid in case of an out-of-bounds. Ad members instead of inheritance: What do you have in mind? Could you make an example? – dyp Dec 22 '13 at 13:19
  • @DyP So the reason why we have both `get` and `get_impl` is that C++ doesn't allow the specialization I had in mind, right? If it allowed, the code I posted would work, right? – Haix64 Dec 22 '13 at 13:23
  • @DyP and in fact my question is WHY such a specialization is not allowed? I can't yet put together the puzzle (pieces missing)... – Haix64 Dec 22 '13 at 13:42
  • @DyP: replace inheritance by `class record { T1 elem; record queue; }` – Jarod42 Dec 22 '13 at 13:57
  • @Dyp: SFINAE: something like `template ::type> auto get() const`.. – Jarod42 Dec 22 '13 at 13:59
  • @Jarod42 members vs inheritance: That would require another dispatcher, as you cannot use overloading then to select a `get_impl`. Something à la *if `index == sizeof...(tail)` call `this->get_impl` else call `queue.get`*. SFINAE: I don't think this helps with the error message (just tried with clang++). You could however provide an overload `get` template which triggers a `static_assert`. The current `get` is already subject to SFINAE because of the return type. – dyp Dec 22 '13 at 14:22
  • 1
    @Haix64 As I wrote in the answer, you need at one point a set of overloaded functions, because the return type is dependent on the index. That is, the function that is called from outside need to determine its return type based on the index, but the functions which returns the actual member has a fixed return type. It is also possible to call into a set of overloaded functions from the outside (like `r.get(int_const<5>{})`), but that would require restructuring the inheritance. I'll add an example to my answer. – dyp Dec 22 '13 at 14:58
  • @Haix64 See updated answer. *"and in fact my question is WHY such a specialization is not allowed?"* I don't know exactly, but AFAIK it has been asked + answered on SO already. I'll try to find it. – dyp Dec 22 '13 at 15:35
  • 1
    @Haix64 For example, there's [this similar question](http://stackoverflow.com/q/18569840/420683), however I didn't find a good explanation :/ (There's some rationale at the end of the top answer to that question.) You could ask this as a separate question (why isn't it allowed). Though it'll probably be closed as duplicate, you'll most likely get a (link to a) better answer. – dyp Dec 22 '13 at 15:47
  • @DyP I needed the impl guide too, and thanks for that. I found the answer to the WHY to some extent: `member specializtion could depend on the declaration of the non-specialized class-template, which IS NOT INHERITED to the the specializtions, thus member specialization --crafted for the generic class-template declaration -- would depend on an undetermined declaration/definition.` Such a problem seems to be detectable by the compiler, though I personally think the strategy has been to prevent compiler sophistication. More discussions pretty welcome. – Haix64 Dec 23 '13 at 08:38