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

问题描述 投票:5回答:4

我写了(在c ++ 11中)一个可变参数模板constexpr函数,它计算参数类型的max sizeof,例如:

maxSizeof<int, char, MyType>()

这工作正常。然后我想有一个可变参数模板类,其中一个字段是一个大小等于maxSizeof()的数组。这也应该正常工作:

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

但我还需要Myclass为每个参数类型声明方法。我通过以下方式使用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
}

问题是如何实现方法的声明,同时具有适当大小的数组字段。 (1)和(2)因评论中说明的原因不起作用。

c++ c++11 variadic-templates
4个回答
6
投票

像大多数软件工程问题一样,这可以通过添加更多层的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...>()]
};

2
投票

这里有两个问题。首先是让它编译。第二是能够打电话给doSomething

第一个问题的简单解决方案是:

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...>()];
};

这样做的缺点是调用doSomething很难做到:

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

我们需要一些usings来解决这个问题。在,我们可以这样做:

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

但这在中不可用。

要解决这个问题,我们必须进行基于树的继承。最简单的基于树的是线性的:

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

这具有创建O(n ^ 2)总类型名称长度的缺点,这可能导致长类型列表的问题。在中等长度,你得到内存膨胀和编译时间减慢,长时间你会让编译器崩溃。

为了解决这个问题,你可以构建一个继承的二叉树。诀窍是能够使用对数深度模板递归将...包分成两半。完成后,代码变为:

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

但是,如果你传递了几十种类型,这种努力是值得的。

左/右半看起来像这样:

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

在todo中有一些疯狂的元编程。

您可以使用索引技巧和std::tuple的机制来拆分这些列表(在 log-depth index generation需要一些努力)。或者您可以在类型列表上进行指数分割。

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

分裂一个类型列表,第一个N在左边。它可以递归写入:

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

这需要concat<pack, pack>做明显的事情。

现在你需要apply<template, pack>然后写left_halfright_half


0
投票

总结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)];
};

0
投票

一种方法是利用函数式编程语言用户熟知的旧技巧:对部分结果进行递归,例如:累积的最大值作为第一个模板参数。然而,为了保持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...>;

让我们添加一个原始测试用例(你也可以在这里使用maxSizeof):

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

虽然这并不比Angew的版本好(除了不需要maxSizeof),但它仍然是指出这种有用的递归模式的好机会。大多数但不仅仅是在像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

(在我使用-O2进行的简单测试中,fastMax的速度提高了3.5倍。)

© www.soinside.com 2019 - 2024. All rights reserved.