[英]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來解決這個問題。 在c ++ 17中,我們可以這樣做:
template <typename... TypesT>
class MyClass : public MyClassFunction<TypesT>...
{
uint8_t field[maxSizeOf<TypesT...>()];
public:
using MyClassFunction<TypesT>::doSomething...;
};
但這在c ++ 11中不可用。
要解決這個問題,我們必須進行基於樹的繼承。 最簡單的基於樹的是線性的:
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
來拆分這些列表(在c ++ 11中, 日志深度索引生成需要花費一些精力)。 或者您可以在類型列表上進行指數分割。
寫
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_half
和right_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.