簡體   English   中英

Variadic CRTP模板類,其中一個字段使用constexpr,基於參數類列表

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

我寫了(在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)因評論中說明的原因不起作用。

像大多數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...>()]
};

這里有兩個問題。 首先是讓它編譯。 第二個是能夠調用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) ); ^~~~~~~~~~~ 

我們需要一些using s來解決這個問題。 我們可以這樣做:

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

實例

這具有創建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來拆分這些列表(在 日志深度索引生成需要花費一些精力)。 或者您可以在類型列表上進行指數分割。

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

總結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)];
};

一種方法是利用函數式編程語言的用戶熟知的舊技巧:對部分結果進行遞歸,例如累積的最大值作為第一模板參數。 然而,為了維護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倍。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM