简体   繁体   English

Variadic CRTP模板类,其中一个字段使用constexpr,基于参数类列表

[英]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, eg: 我写了(在c ++ 11中)一个可变参数模板constexpr函数,它计算参数类型的max sizeof,例如:

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(). 然后我想有一个可变参数模板类,其中一个字段是一个大小等于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. 但我还需要Myclass为每个参数类型声明方法。 I've used CRTP for that in following way: 我通过以下方式使用CRTP:

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. (1)和(2)因评论中说明的原因不起作用。

Like most SW engineering problems, this can be solved by adding more layers of indirection [1] : 像大多数SW工程问题一样,这可以通过添加更多层间接来解决[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...>()]
};

There are two problems here. 这里有两个问题。 The first is getting it to compile. 首先是让它编译。 The second is to be able to call doSomething . 第二个是能够调用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: 这样做的缺点是调用doSomething很难做到:

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

we need some using s to fix this. 我们需要一些using s来解决这个问题。 In 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 . 但这在不可用。

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. 这具有创建O(n ^ 2)总类型名称长度的缺点,这可能导致长类型列表的问题。 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. 在todo中有一些疯狂的元编程。

You can use the indexes trick and the machinery of std::tuple to split those lists (in log-depth index generation takes a bit of effort). 您可以使用索引技巧和std::tuple来拆分这些列表(在 日志深度索引生成需要花费一些精力)。 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. 分割类型列表,左边是第一个N 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. 这需要concat<pack, pack>来做明显的事情。

Now you need apply<template, pack> and then write left_half and right_half . 现在您需要apply<template, pack>然后编写left_halfright_half

To sum up Angew answer, and the accessiblity problem pointed out by Yakk, you can reapply the Angew's engineering principle: 总结Angew的答案,以及Yakk指出的可访问性问题,你可以重新申请Angew的工程原理:

template <typename... TypesT>
class MyclassFunctions;

template <>
class MyclassFunctions<>
{};

template <typename FirstT>
class MyclassFunctions<FirstT>
{
public:
  virtual void doSomething(FirstT object){};
};

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

template <typename... TypesT>
class Myclass : public MyclassFunctions<TypesT...>
{
  int field[sizeof...(TypesT)];
};

One way to do it is to exploit an old trick well known to users of functional programming languages: Recursing on a partial result, eg the accumulated maximum as first template parameter. 一种方法是利用函数式编程语言的用户熟知的旧技巧:对部分结果进行递归,例如累积的最大值作为第一模板参数。 To maintain the Myclass<Ts...> signature, however, we then need to add a level of indirection, as Angew suggested: 然而,为了维护Myclass<Ts...>签名,我们需要添加一个间接级别,正如Angew建议的那样:

template<std::size_t curMax, typename... Ts>
struct MyclassImpl
{
    std::uint8_t field[curMax];
};

template<std::size_t curMax, typename T1, typename... Ts  >
struct MyclassImpl<curMax, T1, Ts...> : MyclassImpl<(curMax > sizeof(T1) ? curMax : sizeof(T1)), Ts...>
{
    virtual void doSomething(T1) = 0;
};

template<typename... Ts>
using Myclass = MyclassImpl<0u, Ts...>;

Let's add a primitive test case (you can also make use of maxSizeof here): 让我们添加一个原始测试用例(你也可以在这里使用maxSizeof ):

struct TestStruct{ char c[100]; };
using TestMC = Myclass<int, double, TestStruct, short>;
static_assert( sizeof(TestMC::field) == sizeof(TestStruct), "Field size incorrect" );

While this is not better than Angew's version (other than not requiring maxSizeof ), it's still a good opportunity to point out this useful recursion pattern. 虽然这并不比Angew的版本好(除了不需要maxSizeof ),但它仍然是指出这种有用的递归模式的好机会。 Mostly but not only in pure functional programming languages like Haskell, it is very useful for enabling tail-call optimization, eg: 主要但不仅仅是在像Haskell这样的纯函数式编程语言中,它对于启用尾调用优化非常有用,例如:

-- slowMax "tail call unfriendly"
-- (optional) signature
slowMax :: Ord a => [a] -> a
slowMax [x] = x
slowMax (x:xs) = let m = slowMax xs in if x > m then x else m

-- fastMax "tail call friendly"
fastMax :: Ord a => [a] -> a
fastMax (x:xs) = fastMaxImpl x xs where
   fastMaxImpl m [] = m
   fastMaxImpl m (x:xs) = fastMaxImpl (if x > m then x else m) xs

(In a simple test I made with -O2 , fastMax was 3.5x faster.) (在我用-O2做的简单测试中, fastMax快了3.5倍。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM