Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variadic CRTP template class with a field using constexpr basing on the parameter classes list

I've written (in c++11) a variadic template constexpr function which calculates max sizeof of the parameter types, e.g.:

maxSizeof<int, char, MyType>()

That works correctly. Then I would like to have a variadic template class with a field which is an array of size equal to the maxSizeof(). That should also work correctly:

template <typename... TypesT>
    class Myclass {
        uint8_t field[maxSizeOf<TypesT...>()]
    }

But I also need Myclass to declare methods for each of the parameter types. I've used CRTP for that in following way:

template <typename... TypesT>
class Myclass;

template <>
class Myclass {
    uint8_t field[maxSizeOf<TypesT...>()] // (1) Couldn't do it here as there are no `TypesT`
}

template <typename FirstT, typename... OtherT>
class Myclass<FirstT, OtherT...> : public Myclass<OtherT...> {
    public:
        virtual void doSomething(FirstT object) = 0;
    private:
        uint8_t field[maxSizeOf<FirstT, OtherT...>()] // (2) Shouldn't do it here as it will create field for all of the "middle" classes
}

The question is how implement the declarations of the methods and in the same time have the array field with proper size. (1) and (2) doesn't work for the reasons stated in comments.

like image 695
damgad Avatar asked Sep 10 '18 12:09

damgad


2 Answers

Like most SW engineering problems, this can be solved by adding more layers of indirection[1]:

template <typename... TypesT>
class MyclassFunctions;

template <>
class MyclassFunctions
{};

template <typename FirstT, typename... OtherT>
class MyclassFunctions<FirstT, OtherT> : public MyClassFunctions<OtherT...>
{
public:
  virtual void doSomething(FirstT object) = 0;
};

template <typename... TypesT>
class Myclass : public MyclassFunctions<TypesT...>
{
  uint8_t field[maxSizeOf<TypesT...>()]
};
like image 73
Angew is no longer proud of SO Avatar answered Oct 16 '22 05:10

Angew is no longer proud of SO


There are two problems here. The first is getting it to compile. The second is to be able to call doSomething.

The easy solution to the first problem is:

template <class T>
class MyClassFunction {
public:
  virtual void doSomething(T object) = 0;
};


template <typename... TypesT>
class MyClass : public MyClassFunction<TypesT>...
{
  uint8_t field[maxSizeOf<TypesT...>()];
};

this has the disadvantage that calls to doSomething can be hard to do:

prog.cpp:33:59: error: request for member ‘doSomething’ is ambiguous
    using foo = decltype( ((MyClass<int, char>*)nullptr)->doSomething(7) );
                                                          ^~~~~~~~~~~

we need some usings to fix this. In c++17 we could just do:

template <typename... TypesT>
class MyClass : public MyClassFunction<TypesT>...
{
  uint8_t field[maxSizeOf<TypesT...>()];
public:
  using MyClassFunction<TypesT>::doSomething...;
};

but that isn't available in c++11.

To fix this we have to do a tree-based inheritance. The easiest tree-based is linear:

template<class...Ts>
struct MyClassFunctions {};

template<class T0, class T1, class...Ts>
struct MyClassFunctions<T0, T1, Ts...>:
  MyClassFunction<T0>, MyClassFunctions<T1, Ts...>
{
  using MyClassFunction<T0>::doSomething;
  using MyClassFunctions<T1, Ts...>::doSomething;
};
template<class T0>
struct MyClassFunctions:MyClassFunction<T0> {};

template <typename... TypesT>
class MyClass : public MyClassFunctions<TypesT...>
{
  uint8_t field[maxSizeOf<TypesT...>()];
};

Live example.

This has the disadvantage of creating O(n^2) total type name length, which can cause problems for long lists of types. At medium length you get memory bloat and compile time slowdown, at long length you get compilers crashing.

To get around that you can build a binary tree of inheritance. The trick is to be able to split a ... pack in half using log-depth template recursion. Once you have that, the code becomes:

template<class T0, class T1, class...Ts>
struct MyClassFunctions<T0, T1, Ts...>:
  left_half< MyClassFunctions, T0, T1, Ts... >,
  right_half< MyClassFunctions, T0, T1, Ts... >
{
  using left_half< MyClassFunctions, T0, T1, Ts... >::doSomething;
  using right_half< MyClassFunctions, T0, T1, Ts... >::doSomething;
};

however this effort is only worthwhile if you have more than a few dozen types being passed in.

left/right half look like this:

template<template<class...>class Z, class...Ts>
using left_half = /* todo */;
template<template<class...>class Z, class...Ts>
using right_half = /* todo */;

with some crazy metaprogramming in todo.

You can use the indexes trick and the machinery of std::tuple to split those lists (in c++11 log-depth index generation takes a bit of effort). Or you can do an exponential split on the type list.

Write

template<class...Ts>
struct pack {};
template<std::size_t N, class Pack>
struct split/* {
  using lhs = // part before N
  using rhs = // part after N
};

that splits a type list with the first N being on the left. It can be written recursively:

template<std::size_t N, class...Ts>
struct split<N, pack<Ts...>> {
private:
  using half_split = split<N/2, pack<Ts...>>;
  using second_half_split = split<N-N/2, typename half_split::rhs>;
public:
  using lhs = concat< typename half_split::lhs, typename second_half_split::lhs >;
  using rhs = typename second_half_split::rhs;
};
template<class...Ts>
struct split<0, pack<Ts...>> {
  using lhs=pack<>;
  using rhs=pack<Ts...>;
};
template<class T0, class...Ts>
struct split<1, pack<T0, Ts...>> {
  using lhs=pack<T0>;
  using rhs=pack<Ts...>;
};

this requires concat<pack, pack> to do the obvious thing.

Now you need apply<template, pack> and then write left_half and right_half.

like image 29
Yakk - Adam Nevraumont Avatar answered Oct 16 '22 07:10

Yakk - Adam Nevraumont