元编程技巧:如何简化两个元函数的实现

时间:2016-08-25 11:21:06

标签: c++ metaprogramming c++14 template-specialization typetraits

我正在编写一些程序,通过代码生成自动调用一些API 在某些情况下,我需要从类型Source转换为类型Target,但这些类型用指针,const等装饰。所以我需要做的是删除所有装饰,如指针,常量,数组等,获取普通类型以将其映射到另一种类型,然后将装饰应用回新类型。

实现有很多模板专业化。代码后的问题。我不能使用constexpr元编程,因为我需要使用 VS2013

template <class T>
struct TypeIs {
    using type = T;
};

template <class T>
struct GetPlainType : TypeIs<typename std::decay<T>::type> {};

template <class T>
struct GetPlainType<T&> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T const &> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T &&> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T const &&> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T*> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T const *> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T const> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T[]> : TypeIs<typename GetPlainType<T>::type> {};


template <class T>
struct GetPlainType<T const[]> : TypeIs<typename GetPlainType<T>::type> {};

template <class T, std::size_t I>
struct GetPlainType<T[I]> : TypeIs<typename GetPlainType<T>::type> {};

template <class T, std::size_t I>
struct GetPlainType<T const [I]> : TypeIs<typename GetPlainType<T>::type> {};


template <class T>
using GetPlainType_t = typename GetPlainType<T>::type;


template <class Decorated, class Plain>
struct CopyDecorations : TypeIs<Plain> {};


template <class T, class Plain>
struct CopyDecorations<T const, Plain> :
    TypeIs<typename CopyDecorations<T, Plain const>::type> {};


template <class T, class Plain>
struct CopyDecorations<T *, Plain> :
    TypeIs<typename CopyDecorations<T, Plain *>::type> {};

template <class T, class Plain>
struct CopyDecorations<T const *, Plain> :
    TypeIs<typename CopyDecorations<T, Plain const *>::type> {};


template <class T, class Plain>
struct CopyDecorations<T &, Plain> :
    TypeIs<typename CopyDecorations<T, Plain &>::type> {};

template <class T, class Plain>
struct CopyDecorations<T const &, Plain> :
    TypeIs<typename CopyDecorations<T, Plain const &>::type> {};

template <class T, class Plain>
struct CopyDecorations<T  &&, Plain> :
    TypeIs<typename CopyDecorations<T, Plain &&>::type> {};


template <class T, class Plain>
struct CopyDecorations<T  const &&, Plain> :
    TypeIs<typename CopyDecorations<T, Plain const &&>::type> {};


template <class T, class Plain>
struct CopyDecorations<T[], Plain> :
     TypeIs<typename CopyDecorations<T, Plain[]>::type> {};

template <class T, class Plain>
struct CopyDecorations<T const [], Plain> :
TypeIs<typename CopyDecorations<T, Plain const []>::type> {};


template <class T, class Plain, std::size_t I>
struct CopyDecorations<T [I], Plain> :
     TypeIs<typename CopyDecorations<T, Plain[I]>::type> {};

template <class T, class Plain, std::size_t I>
struct CopyDecorations<T const [I], Plain> :
     TypeIs<typename CopyDecorations<T, Plain const [I]>::type> {};


template <class Decorated, class Plain>
using CopyDecorations_t = typename CopyDecorations<Decorated, Plain>::type;


int main()
{
    static_assert(std::is_same<GetPlainType_t<int>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int const>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int *>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int **>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int * &>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int ** &>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int const * []>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int const **[][3][5]>, int>{}, "");


    static_assert(std::is_same<CopyDecorations_t<int, double>, double>{}, "");
    static_assert(std::is_same<CopyDecorations_t<int const, double>, double const>{}, "");
    static_assert(std::is_same<CopyDecorations_t<int *, double>, double *>{}, "");
    static_assert(std::is_same<CopyDecorations_t<int **, double>, double **>{}, "");
    static_assert(std::is_same<CopyDecorations_t<int[], double>, double[]>{}, "");
    static_assert(std::is_same<CopyDecorations_t<int[3], double>, double[3]>{}, "");


    //******************THE TESTS BELOW DO NOT WORK
    //static_assert(std::is_same<CopyDecorations_t<int[][3], double>, double[][3]>{}, "");
    //static_assert(std::is_same<CopyDecorations_t<int * &, double>, double * &>{}, "");
    // static_assert
    //     (
    //std::is_same<CopyDecorations_t<int const * [], double>,
    //      double const * []>{}, "");
    // static_assert
    //     (std::is_same<CopyDecorations_t<int const **[][3][5], double>,
    //      double const **[][3][5]>{}, "");
}

问题:

  1. 我可以简化实施吗?
  2. 失败的测试(请参阅main函数),我该如何解决?
  3. 在哪些情况下(忽略volatile和指向成员的指针,指向函数和函数的指针)。你认为我的实施会失败吗?

4 个答案:

答案 0 :(得分:5)

我发现这个问题是关于SO的C ++元编程最有趣的问题之一 我很高兴找到一个合适的解决方案。谢谢。 : - )

它遵循一个极小的工作实例 它不是完整的,但它提供了一种可能的方法来实现这一点 函数f(好的,你可以在代码中选择一个更好的名字)接受两个模板参数: clean 的类型和装饰的
它返回一个模板类型(types),引入两个使用声明basicdecorated,第一个模板参数清除为basic,第二个模板参数设为{ {1}}。
它一次完成(清理和装饰)。您仍然可以只使用第一个参数,在这种情况下,decorated默认为装饰decorated类型。

以下是完整代码:

char

由于#include<type_traits> #include<cstddef> static constexpr std::size_t N = 42; template<std::size_t N> struct choice: choice<N-1> {}; template<> struct choice<0> {}; template<typename T, typename U> struct types { using basic = T; using decorated = U; }; template<typename T, typename U> constexpr auto f(choice<0>) { return types<T, U>{}; } template<typename T, typename U, typename = std::enable_if_t<std::is_pointer<T>::value>> constexpr auto f(choice<1>) { auto t = f<std::remove_pointer_t<T>, U>(choice<N>{}); using B = typename decltype(t)::basic; using D = typename decltype(t)::decorated; return types<B, std::add_pointer_t<D>>{}; } template<typename T, typename U, typename = std::enable_if_t<std::is_lvalue_reference<T>::value>> constexpr auto f(choice<2>) { auto t = f<std::remove_reference_t<T>, U>(choice<N>{}); using B = typename decltype(t)::basic; using D = typename decltype(t)::decorated; return types<B, std::add_lvalue_reference_t<D>>{}; } template<typename T, typename U, typename = std::enable_if_t<std::is_rvalue_reference<T>::value>> constexpr auto f(choice<3>) { auto t = f<std::remove_reference_t<T>, U>(choice<N>{}); using B = typename decltype(t)::basic; using D = typename decltype(t)::decorated; return types<B, std::add_rvalue_reference_t<D>>{}; } template<typename T, typename U, typename = std::enable_if_t<std::is_array<T>::value>> constexpr auto f(choice<4>) { auto t = f<std::remove_extent_t<T>, U>(choice<N>{}); using B = typename decltype(t)::basic; using D = typename decltype(t)::decorated; return types<B, std::conditional_t<(0==std::extent<T>::value), D[], D[std::extent<T>::value]>>{}; } template<typename T, typename U, typename = std::enable_if_t<std::is_const<T>::value>> constexpr auto f(choice<5>) { auto t = f<std::remove_const_t<T>, U>(choice<N>{}); using B = typename decltype(t)::basic; using D = typename decltype(t)::decorated; return types<B, std::add_const_t<D>>{}; } template<typename T, typename U, typename = std::enable_if_t<std::is_volatile<T>::value>> constexpr auto f(choice<6>) { auto t = f<std::remove_volatile_t<T>, U>(choice<N>{}); using B = typename decltype(t)::basic; using D = typename decltype(t)::decorated; return types<B, std::add_volatile_t<D>>{}; } template<typename T, typename U = char> constexpr auto f() { return f<T, U>(choice<N>{}); } int main() { // something complex to show that it seems to work static_assert(std::is_same< decltype(f<const int ** const &&, char>()), types<int, const char ** const &&> >::value, "!"); // some of the OP's examples (the most interesting) static_assert(std::is_same<decltype(f<int, int>()), types<int, int>>::value, "!"); static_assert(std::is_same<decltype(f<int const, int>()), types<int, int const>>::value, "!"); static_assert(std::is_same<decltype(f<int *, int>()), types<int, int *>>::value, "!"); static_assert(std::is_same<decltype(f<int **, double>()), types<int, double **>>::value, "!"); static_assert(std::is_same<decltype(f<int *&, int>()), types<int, int *&>>::value, "!"); static_assert(std::is_same<decltype(f<int **&, float>()), types<int, float **&>>::value, "!"); static_assert(std::is_same<decltype(f<int [3], char>()), types<int, char [3]>>::value, "!"); static_assert(std::is_same<decltype(f<int [], int>()), types<int, int []>>::value, "!"); static_assert(std::is_same<decltype(f<int [][3], double>()), types<int, double [][3]>>::value, "!"); static_assert(std::is_same<decltype(f<int const **[][3][5], int>()), types<int, int const **[][3][5]>>::value, "!"); // of course, you don't need to provide the second type if you don't need it // in this case, types::decorated is defaulted to a decorated char type f<int const **[][3][5]>(); } 的原因,它在没有constexpr的情况下不会编译,你可以自由地删除它们并在运行时使用该函数。

实际上,它可以转换为无定义解决方案,为声明提供正确的返回类型并使用一堆static_assert s,但我怀疑它会远远不可读。

修改

如OP所述,他不想(或至少,他不能使用)decltype。 它遵循一个略有不同的解决方案,仍然基于前一个解决方案 基本思想是使用constexpr作为f的未评估操作数 这是完整的代码:

decltype

答案 1 :(得分:3)

因此,您可以使用模式匹配的函数执行此操作并执行转录的一个步骤,如下所示:

template<class In, class Out>
struct types {
  using type=types;
  using in=In;
  using out=Out;
};

// transcribe cv:
template<class In, class Out>
types<In, const Out> transcribe( types<const In, Out> ) { return {}; }
template<class In, class Out>
types<In, volatile Out> transcribe( types<volatile In, Out> ) { return {}; }
template<class In, class Out>
types<In, const volatile Out> transcribe( types<const volatile In, Out> ) { return {}; }

// references and pointers:
template<class In, class Out>
types<In, Out*> transcribe( types<In*, Out> ) { return {}; }
template<class In, class Out>
types<In, Out&> transcribe( types<In&, Out> ) { return {}; }
template<class In, class Out>
types<In, Out&&> transcribe( types<In&&, Out> ) { return {}; }

// arrays
template<class In, class Out>
types<In, Out[]> transcribe( types<In[], Out> ) { return {}; }
template<class In, class Out, std::size_t N>
types<In, Out[N]> transcribe( types<In[N], Out> ) { return {}; }

// return type of a function
template<class In, class...In_Args, class Out>
types<In, Out(In_Args...)> transcribe( types<In(In_Args...), Out> ) { return {}; }
// return type of a function
template<class In, class...In_Args, class Out>
types<In, Out(*)(In_Args...)> transcribe( types<In(*)(In_Args...), Out> ) { return {}; }

// default case
template<class X>
X transcribe( X ) { return {}; }

过载规则做正确的事。唯一令人讨厌的事情是const volatile案例。

有了上述内容后,我们可以将其转化为特征:

template<class In, class Out>
struct transcribe_one:
  decltype(transcribe( types<In,Out>{} ))
{};

容易。请注意,永远不需要调用transcribe

别名使这更容易使用:

template<class T>
using strip_one=typename transcribe_one<T, int>::in;
template<class T>
using can_strip=std::integral_constant<bool, !std::is_same<T, strip_one<T>>{}>;
template<class T, class U>
using typescribe_one=typename transcribe_one<T, U>::out;

并且还表示“有什么东西要剥离吗?”。

这只会从左到右移动一种类型的装饰。要全部移动它们,我们只需这样做:

template<class In, class Out, class=void>
struct transcribe_all:types<In, Out> {};

template<class T>
using strip=typename transcribe_all<T, int>::in;
template<class T, class U>
using typescribe=typename transcribe_all<T, U>::out;

template<class In, class Out>
struct transcribe_all<In, Out, std::enable_if_t<
  can_strip<In>{}
>> :
types<
  strip< strip_one< In > >, // must strip on strip_one, trust me
  typescribe_one<
    In,
    typescribe< strip_one<In>, Out >
  >
>
{};

当你无法剥离时,什么都不做。

当你可以剥离时,它会从In类型中删除一个,将剩余部分转录到Out上,然后对其结果进行一步转录。

这会给你两个别名:

template<class T>
using strip=// ...
template<class T, class U>
using typescribe=// ...

第一个采用类型T并将其剥离为底部的“原始”类型。

第二个采用类型T并将其所有装饰移至U

template<template<class...>class M, class U>
using under_map = typescribe< U, M<strip<U>> >;

剥离类型装饰,应用M,然后重新应用它们。

请注意,最“用户友好”的工具是strip<T>typescribe<In, Out>。第一个删除所有装饰器并获取“底层”类型。第二个将装饰器从一种类型复制到另一种类型。使用transcribe_alltranscribe_onestrip_one等可能会导致混淆,它们是实施细节。

只需将您的类型地图写为template,然后将其传递给under_map

live example

唯一使用的C ++ 14功能是std::enable_if_t<?>,可以在C ++ 11上用typename std::enable_if<?>::type替换。

MSVC可能会遇到decltype内容的问题,因为它对SFINAE中的decltype的支持非常糟糕。

答案 2 :(得分:1)

我喜欢使用标签进行类型元编程。

enum type_quals {
  none_qualified = 0,
  const_qualified = 1,
  volatile_qualified = 2,
  lreference_qualified = 4,
  rreference_qualified = 8,
  pointer_qualified = 16,
  all_qualified = 31,
};

template<type_quals qs, class inside=void>
struct tq_t {
  constexpr tq() {};
  constexpr explicit operator bool() const { return qs!=none_qualified; }
};

template<type_quals q>
tq_t<q> tq{};

template<type_quals a, type_quals b>
constexpr
tq_t< type_quals(unsigned(a)|unsigned(b)) >
operator|( tq_t<a>, tq_t<b> ) { return {}; }

template<type_quals a, type_quals b>
constexpr
tq_t< type_quals(a&b) >
operator&( tq_t<a>, ta_t<b> ) { return {}; }

template<class T>
struct tag_t {
  constexpr tag_t() {};
  using type=T;
};

template<class T>
tag_t<T> tag{};

template<class T>
constexpr
tag_t<const T>
operator+( tag_t<T>, tq_t<const_qualified> ) { return {}; }

template<class T>
constexpr
tag_t<volatile T>
operator+( tag_t<T>, tq_t<volatile_qualified> ) { return {}; }

template<class T>
constexpr
tag_t<T&>
operator+( tag_t<T>, tq_t<lreference_qualified> ) { return {}; }

template<class T>
constexpr
tag_t<T&&>
operator+( tag_t<T>, tq_t<rreference_qualified> ) { return {}; }

template<class T>
constexpr
tag_t<T>
operator+( tag_t<T>, tq_t<none_qualified> ) { return {}; }

template<class T, type_quals qs>
constexpr
auto
operator+( tag_t<T> t, tq_t<qs> q ) {
  return t
    +(q&tq<const_qualified>)
    +(q&tq<volatile_qualified>)
    +(q&tq<lreference_qualified>)
    +(q&tq<rreference_qualified>)
  ;
}

template<class T, type_quals qs>
constexpr
auto
operator+( tq_t<qs> q, tag_t<T> t ) {
  return t+q;
}

现在,如果您有tq和标签,则可以添加将类型添加回标签。

指针需要嵌套,因此是不同类型的tq,但模式有点类似。上面的内容还不支持嵌套。

现在,您需要使用代码检测tq中的tag并删除它们。也许添加一个tq&tag运算符,返回标记上的tq。然后添加tag-tq以从tq中删除匹配的tag

您的代码最终看起来像:

auto decorations = tag<T>&tq<all_qualified>;
auto raw = tag<T>-decorations;
// use raw::type here for the undecorated type
// produce type R
auto redecorated = tag<R>+decorations;
return redecorated;

现在,所有这一切确实是将烦人的类型的东西移出业务逻辑的界限。但它以一种漂亮的方式这样做。

答案 3 :(得分:-2)

这是我的解决方案。它适用于我的需求(没有波动)。

template <class T>
struct TypeIs {
    using type = T;
};

template <class T>
struct GetPlainType : TypeIs<typename std::decay<T>::type> {};

template <class T>
struct GetPlainType<T const> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T&> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T const &> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T &&> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T const &&> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T*> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T const *> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T[]> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T const[]> : TypeIs<typename GetPlainType<T>::type> {};

template <class T, std::size_t I>
struct GetPlainType<T[I]> : TypeIs<typename GetPlainType<T>::type> {};

template <class T, std::size_t I>
struct GetPlainType<T const [I]> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
using GetPlainType_t = typename GetPlainType<T>::type;

namespace detail {

//Qualifiers
struct ConstQual {};

//Category
struct ValueCat {};

template <std::size_t I = 0>
struct ArrayCat  : std::integral_constant<std::size_t, I> {};

struct PointerCat {};
struct LValueReferenceCat {};
struct RValueReferenceCat {};


template <class Cat, class...Quals>
struct Decoration {
    using Category = Cat;
    using Qualifiers = std::tuple<Quals...>;
};

template <class Cat, class...Quals>
using DecorationCategory_t = typename Decoration<Cat, Quals...>::type;


template <class T>
struct SaveDecorations : TypeIs<brigand::list<Decoration<ValueCat>>> {};

template <class T>
struct SaveDecorations<T const> : TypeIs<brigand::list<Decoration<ValueCat, ConstQual>>> {};


template <class T>
struct SaveDecorations<T *> :
        TypeIs<
    brigand::append<
    typename SaveDecorations<T>::type,
        brigand::list<Decoration<PointerCat>>>> {};


template <class T>
struct SaveDecorations<T * const> :
        TypeIs<
    brigand::append<
    typename SaveDecorations<T>::type,
        brigand::list<Decoration<PointerCat, ConstQual>>>> {};

template <class T>
struct SaveDecorations<T &> :
        TypeIs<
    brigand::append<
    typename SaveDecorations<T>::type,
        brigand::list<Decoration<LValueReferenceCat>>>> {};

template <class T>
struct SaveDecorations<T &&> :
        TypeIs<
    brigand::append<
    typename SaveDecorations<T>::type,
        brigand::list<Decoration<RValueReferenceCat>>>> {};


template <class T>
struct SaveDecorations<T []> :
        TypeIs<
    brigand::append<
    typename SaveDecorations<T>::type,
        brigand::list<Decoration<ArrayCat<>>>
                                 >> {};

template <class T>
struct SaveDecorations<T const []> :
        TypeIs<
    brigand::append<
    typename SaveDecorations<T const>::type,
        brigand::list<Decoration<ArrayCat<>>>
                                 >> {};

template <class T, std::size_t N>
struct SaveDecorations<T [N]> :
        TypeIs<
    brigand::append<
    typename SaveDecorations<T>::type,
        brigand::list<Decoration<ArrayCat<N>>>
        >> {};


template <class T, std::size_t N>
struct SaveDecorations<T const [N]> :
        TypeIs<
    brigand::append<
    typename SaveDecorations<T const>::type,
        brigand::list<Decoration<ArrayCat<N>>>
        >> {};


template <class State, class Elem>
struct AddDecoration : TypeIs<State> {};


template <class State>
struct AddDecoration<State, Decoration<ValueCat, ConstQual>> : TypeIs<State const> {};

template <class State>
struct AddDecoration<State, Decoration<PointerCat>> : TypeIs<State *> {};

template <class State>
struct AddDecoration<State, Decoration<PointerCat, ConstQual>> :
        TypeIs<State * const> {};

template <class State>
struct AddDecoration<State, Decoration<LValueReferenceCat>> : TypeIs<State &> {};

template <class State>
struct AddDecoration<State, Decoration<RValueReferenceCat>> : TypeIs<State &&> {};

template <class State>
struct AddDecoration<State, Decoration<ArrayCat<>>> : TypeIs<State[]> {};

template <class State, std::size_t I>
struct AddDecoration<State, Decoration<ArrayCat<I>>> : TypeIs<State[I]> {};


template <class T, class DecorationsList>
struct ApplyDecorations :
        TypeIs<brigand::fold<DecorationsList, T,
                             AddDecoration<brigand::_state,
                                           brigand::_element>>> {};
}


template <class T>
using SaveDecorations_t = typename detail::SaveDecorations<T>::type;

template <class T, class TList>
using ApplyDecorations_t = typename detail::ApplyDecorations<T, TList>::type;


int main()
{
    static_assert(std::is_same<GetPlainType_t<int>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int const>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int *>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int **>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int * &>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int ** &>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int const * []>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int const **[][3][5]>, int>{}, "");

    using Decorations = SaveDecorations_t<int>;
    using shouldBeFloat = ApplyDecorations_t<float, Decorations>;
    static_assert(std::is_same<float, shouldBeFloat>{}, "");

    using Decorations2 = SaveDecorations_t<int const>;
    using shouldBeConst = ApplyDecorations_t<float, Decorations2>;
    static_assert(std::is_same<shouldBeConst, float const>{}, "");

    using Decorations3 = SaveDecorations_t<int const *>;
    using shouldPointerToConst = ApplyDecorations_t<float, Decorations3>;
    static_assert(std::is_same<shouldPointerToConst, float const *>{}, "");

    using Decorations4 = SaveDecorations_t<int const * const>;
    using shouldConstPointerToConst = ApplyDecorations_t<float, Decorations4>;
    static_assert(std::is_same<shouldConstPointerToConst, float const * const>{}, "");


    using Decorations5 = SaveDecorations_t<int const * const &>;
    using shouldBeLValRefToConstPointerToConst = ApplyDecorations_t<float, Decorations5>;
    static_assert(std::is_same<shouldBeLValRefToConstPointerToConst, float const * const &>{}, "");

    using Decorations6 = SaveDecorations_t<int * const ** const &>;
    using Res = ApplyDecorations_t<float, Decorations6>;
    static_assert(std::is_same<Res, float * const ** const &>{}, "");

    using Decorations7 = SaveDecorations_t<int * const>;
    using Res2 = ApplyDecorations_t<float, Decorations7>;
    static_assert(std::is_same<Res2, float * const>{}, "");

    //Arrays tests
    using Decorations8 = SaveDecorations_t<int const * const * const []>;
    using Res3 = ApplyDecorations_t<float, Decorations8>;
    static_assert(std::is_same<Res3, float const * const * const []>{}, "");


    using Decorations9 = SaveDecorations_t<int const * const * [3]>;
    using Res4 = ApplyDecorations_t<float, Decorations9>;
    static_assert(std::is_same<Res4, float const * const * [3]>{}, "");

    //Multidimensional arrays
    using Decorations10 = SaveDecorations_t<int const * const * [3][5]>;
    using Res5 = ApplyDecorations_t<float, Decorations10>;
    static_assert(std::is_same<Res5, float const * const * [3][5]>{}, "");

    using Decorations11 = SaveDecorations_t<int const * const * [][3][5]>;
    using Res6 = ApplyDecorations_t<float, Decorations11>;
    static_assert(std::is_same<Res6, float const * const * [][3][5]>{}, "");
}