【问题标题】:Variadic CRTP template class with a field using constexpr basing on the parameter classes list基于参数类列表的具有使用 constexpr 的字段的可变 CRTP 模板类
【发布时间】:2018-09-10 12:16:55
【问题描述】:

我已经(在 c++11 中)编写了一个可变参数模板 constexpr 函数,它计算参数类型的最大 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
}

问题是如何实现方法的声明并同时拥有适当大小的数组字段。由于 cmets 中所述的原因,(1)和(2)不起作用。

【问题讨论】:

  • 顺便说一句,这里没有 CRTP。

标签: c++ c++11 variadic-templates


【解决方案1】:

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

【讨论】:

  • “计算机科学中的任何问题都可以通过另一个间接级别来解决。” en.wikipedia.org/wiki/…
  • @AndriyTylychko 是的,这正是我想要参考的;感谢您的链接。
【解决方案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&lt;pack, pack&gt; 做显而易见的事情。

现在你需要apply&lt;template, pack&gt;,然后写left_halfright_half

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      一种方法是利用函数式编程语言用户熟知的一个老技巧:递归部分结果,例如累积的最大值作为第一个模板参数。然而,为了维护Myclass&lt;Ts...&gt; 签名,我们需要添加一个间接级别,正如 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 倍。)

      【讨论】:

        猜你喜欢
        • 2021-03-30
        • 1970-01-01
        • 2011-12-12
        • 2021-08-09
        • 1970-01-01
        • 2011-06-12
        • 1970-01-01
        • 2017-10-18
        • 1970-01-01
        相关资源
        最近更新 更多