2

I'm creating a small metaprogramming oriented module in one of my libraries, that uses a List<Ts...> class for compile-type type list manipulation.

I specialize List with an empty parameter pack to specialize certain metafunctions or typedefs. However, a lot of typedefs or metafunctions have the same implementation in both List<> and List<Ts...>.

Example:

template<typename...> struct List;

template<> struct List<>
{
    using Type = List<>;        // Redundant
    using Tuple = std::tuple<>; // Redundant
    // ...other redundant typedefs...

    template<typename TList> using Append = TList; // Non-redundant
    // ...other non-redundant typedefs...
};

template<typename... Ts> struct List
{
    using Type = List<Ts...>;        // Redundant
    using Tuple = std::tuple<Ts...>; // Redundant
    // ...other redundant typedefs...

    template<typename TList> 
    using Append = AppendImpl<Type, TList>; // Non-redundant
    // ...other non-redundant typedefs...
};

As you can see, some typedefs are redundant between List<> and List<Ts...>.

What I would like to do is similar to this:

template<typename...> struct List;

template<typename... Ts> struct ListBase
{
    using Type = List<Ts...>;
    using Tuple = std::tuple<Ts...>;
};

template<> struct List<> : public ListBase<>
{
    template<typename TList> using Append = TList;
};

template<typename... Ts> struct List : public ListBase<Ts...>
{
    template<typename TList> 
    using Append = AppendImpl<Type, TList>;
};

// The lines below should be valid code:
using X0 = List<>::Type;
using X1 = List<int, char, int>::Type;
using X2 = List<>::Tuple;
using X3 = List<char, char>::Tuple;
using X4 = List<>::Append<List<int, float>>;
using X5 = List<int>::Append<List<float>>;

Unfortunately, typedefs do not propagate from the base class to the derived one. As sbabbi said in the comments, you have to resort to using the full qualified name to refer to a typedef in the base class, which is not acceptable for the List<...> class I'm designing.

Is there any way I can avoid this repetition without resorting to macros?

Community
  • 1
  • 1
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • `Unfortunately, typedefs do not propagate from the base class to the derived one.` Technically they do, you just have to use the full qualified name to refer to a typedef in the base class if the name is dependant. – sbabbi Feb 27 '15 at 15:29
  • Where is the redundancy? You are using different types... – DanDan Mar 03 '15 at 17:02
  • Your definition of `template<> struct List<>` is actually fine: the problem is name lookup in a *dependent* base class. `ListBase<>` is not dependent, hence there's no problem for the explicit (full) specialization `List<>`. – dyp Mar 03 '15 at 17:08
  • @sbabbi I'm not sure what "full qualified name" is supposed to mean; it is enough to use the injected-class-name to make the name dependent. E.g. `typename List::template AppendImpl` within the primary template `List`. – dyp Mar 03 '15 at 17:13
  • @DanDan: The implementation of `Type` and `Tuple` doesn't need to be written twice, as a variadic template implementation with an empty variadic list would still return the correct answer (`List<>` and `std::tuple<>`). `Append`, however, has a different behavior depending on whether the list is empty or not. – Vittorio Romeo Mar 03 '15 at 17:26
  • N.B. you don't _have_ to use fully-qualified names, you can also add typedefs or using declarations to declare each name in the derived class. But you'd need to repeat all those typedefs in the primary template and the specialization. – Jonathan Wakely Mar 03 '15 at 17:43

3 Answers3

2

How about doing it the other way around, keep all the common stuff in the List primary template and then use a separate template, with a specialization for an empty parameter pack, for the parts that differ such as Append?

template<typename... Ts> struct ListAppend
{
    template<typename TList> 
    using Append = AppendImpl<Ts..., TList>;
};

template<> struct ListAppend<>
{
    template<typename TList> 
    using Append = TList;
};

template<typename... Ts> struct List 
{
    using Type = List<Ts...>;
    using Tuple = std::tuple<Ts...>;

    template<typename TList> 
    using Append = typename ListAppend<Ts...>::Append<TList>;
};

Now you only need to use a specialization for the parts that aren't the same, and don't repeat anything.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
2

You can avoid a lot of redundant code by adding a simple using statement to your List class template: using typename ListBase<Ts...>::Type;
You still have to do this for every type you've declared in your ListBase however you can freely use the type names in all subsequent template specializations. Here's a code example to clarify what I mean:

#include <tuple>

using namespace std;
template<typename... Ts> struct ListBase
{
    using Type = ListBase<Ts...>;
    using Tuple = tuple<Ts...> ;

};
template<typename... Ts> struct List : public ListBase<Ts...>
{
    using typename ListBase<Ts...>::Type;
    template<typename TList>
    using Append = List<Type, TList>;
    //more stuff using Type
};
template <> struct List<> : public ListBase<>
{
    template<typename TList>
    using Append = TList; 
    using something = Type;
};

int main() {

using X0 = List<>::Type;
using X1 = List<int, char, int>::Type;
using X2 = List<>::Tuple;
using X3 = List<char, char>::Tuple;
using X4 = List<>::Append<List<int, float>>;
using X5 = List<int>::Append<List<float>>;
return 0;
}  

I hope this is a viable solution to your problem.
Regards, Marcel Meißner

1

I think you are doing to much (and wrong).

Your code

template<typename... Ts> struct List
{
  using Type = List<Ts...>;        // Redundant
  using Tuple = std::tuple<Ts...>; // Redundant
};

works well for empty paramter packs. But there are some improvements:

  • using Type=List is sufficient in templates. You don't need to repeat all parameters
  • you can implement functionality outside of your class:

    template<typename List>
    struct to_tuple_impl;
    template<typename... Ts>
    struct to_tuple_impl<List<Ts...>> {
       using Type = std::tuple<Ts...>;
    };
    template<typename List>
    using to_tuple = typename to_tuple_impl<List>::Type;
    

Other functionality can be implemented in a simular way:

  • declaration of template metafunction
  • partial specialization for your sequence with variadic arguments and logic
  • a template alias for convenience use

A running example can be found here.

Jan Herrmann
  • 2,717
  • 17
  • 21