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

Ger*_*ago 9 c++ metaprogramming type-traits template-specialization c++14

我正在编写一些程序来通过代码生成自动调用一些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]>{}, "");
}
Run Code Online (Sandbox Code Playgroud)

问题:

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

sky*_*ack 5

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


它遵循一个最小的工作示例.
它并不完整,但它提供了一种可能的方法来实现它.
该函数f(好的,您可以在代码中选择更好的名称)接受两个模板参数:要清理的类型和要装饰的类型.
它返回一个模板类型(types),它引入了两个使用声明,basic并且decorated第一个模板参数被清理为basic,第二个模板参数被装饰为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]>();
}
Run Code Online (Sandbox Code Playgroud)

constexpr由于s的原因,它不能在没有s的情况下编译static_assert,你可以自由地删除它们并在运行时使用该函数.

实际上,它可能转向一个无定义的解决方案,为声明提供正确的返回类型并使用一堆decltypes,但我怀疑它远不是可读的.

编辑

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

#include<type_traits>
#include<cstddef>

static const 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>
auto f(choice<0>) { return types<T, U>{}; }

template<typename T, typename U,
    typename = std::enable_if_t<std::is_pointer<T>::value>>
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>>
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>>
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>>
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>>
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>>
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>
auto f() {
    return f<T, U>(choice<N>{});
}

template<typename T, typename U = char>
using my_type = decltype(f<T, U>());

template<typename T, typename U = char>
using my_type_basic_t = typename decltype(f<T, U>())::basic;

template<typename T, typename U = char>
using my_type_decorated_t = typename decltype(f<T, U>())::decorated;

int main() {
    int i = 42;
    my_type_decorated_t<char *, int> ptr = &i;
    // of course, it can still be used in a constant expression if needed
    // constexpr my_type_decorated_t<char *, int> ptr = nullptr;

}
Run Code Online (Sandbox Code Playgroud)


Yak*_*ont 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 {}; }
Run Code Online (Sandbox Code Playgroud)

超载规则做正确的事。唯一令人讨厌的就是这种情况const volatile

一旦我们有了上述内容,我们就可以将其变成一个特质:

template<class In, class Out>
struct transcribe_one:
  decltype(transcribe( types<In,Out>{} ))
{};
Run Code Online (Sandbox Code Playgroud)

容易地。请注意,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;
Run Code Online (Sandbox Code Playgroud)

并表示“有什么要脱的吗?”。

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

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 >
  >
>
{};
Run Code Online (Sandbox Code Playgroud)

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

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

这给你两个别名:

template<class T>
using strip=// ...
template<class T, class U>
using typescribe=// ...
Run Code Online (Sandbox Code Playgroud)

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

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

template<template<class...>class M, class U>
using under_map = typescribe< U, M<strip<U>> >;
Run Code Online (Sandbox Code Playgroud)

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

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

只需将类型映射写为 atemplate并将其传递给under_map

活生生的例子

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

MSVC 可能对这些decltype东西有问题,因为它在 SFINAE 中对 decltype 的支持很糟糕。