使用模板和部分特化生成类型转换函数

问题描述 投票:0回答:1

我想使用模板在两组类型之间生成类型安全的转换。基本案例如下:

template <typename T> struct ATraits {};
template <typename T> struct BTraits {};
template <> struct ATraits<BFoo> { using AType = AFoo; };
template <> struct ATraits<BBar> { using AType = ABar; };
template <> struct BTraits<AFoo> { using BType = BFoo; };
template <> struct BTraits<ABar> { using BType = BBar; };

template <typename TB>
auto AFromB(TB x) -> typename ATraits<TB>::AType {
    return static_cast<typename ATraits<TB>::AType>(x);
}
template <typename TA>
auto BFromA(TA x) -> typename BTraits<TA>::BType {
    return static_cast<typename BTraits<TA>::BType>(x);
}

以上适用于基本类型。现在我想将它扩展为指针和const限定类型。它在我定义以下部分特化时有效:

template <typename T> struct ATraits<T*> {
    using AType = typename ATraits<T>::AType*;
}
template <typename T> struct BTraits<T*> {
    using BType = typename BTraits<T>::BType*;
}
template <typename T> struct ATraits<const T> {
    using AType = const typename ATraits<T>::AType;
}
template <typename T> struct BTraits<const T> {
    using BType = const typename BTraits<T>::BType;
}

然而,这似乎是很多样板。是否有更简洁的方法(可能涉及类型特征)来为指针,引用,cv限定类型等定义此类型映射?

c++ template-meta-programming typetraits partial-specialization
1个回答
1
投票

会有很多样板。

我实际上建议使用与模板专业化不同的系统。

template<class T>struct tag_t{using type=T;};
template<class T>constexpr tag_t<T> tag{};
template<class Tag>using type_t=typename Tag::type;
struct const_t {}; constexpr const_t const_v{};
struct volatile_t {}; constexpr volatile_t volatile_v{};
struct ptr_t {}; constexpr ptr_t ptr_v{};
struct lref_t {}; constexpr lref_t lref_v{};
struct rref_t {}; constexpr rref_t rref_v{};
struct retval_t{}; constexpr retval_t retval_v{}; 
struct func_t{}; constexpr func_t func_v{};
template<class Sig>
struct func_builder_t{}; template<class Sig> constexpr func_builder_t<Sig> func_builder_v{};

现在是代数:

template<class T>
constexpr tag_t<T&> operator+( tag_t<T>,lref_t ) { return {}; }
template<class T>
constexpr tag_t<T&&> operator+( tag_t<T>,rref_t ) { return {}; }
template<class T>
constexpr tag_t<T*> operator+( tag_t<T>,ptr_t ) { return {}; }
template<class T>
constexpr tag_t<T const> operator+( tag_t<T>,const_t ) { return {}; }
template<class T>
constexpr tag_t<T volatile> operator+( tag_t<T>,volatile_t ) { return {}; }
template<class T>
constexpr func_builder_t<T()> operator+(tag_t<T>,retval_t){ return {}; }

template<class R, class...Ts, class T0, class T1>
constexpr func_builder_t<R(T1,Ts...,T0)> operator+(func_builder_t<R(T0,Ts...)>,tag_t<T1>){ return {}; }
template<class R, class T0>
constexpr func_builder_t<R(T0)> operator+(func_builder_t<R()>,tag_t<T0>){ return {}; }
template<class R, class...Ts, class T0>
constexpr tag_t<R(Ts...,T0)> operator+(func_builder_t<R(T0,Ts...)>,func_t){ return {}; }

template<class R, class...Ts, class T0, class Rhs>
constexpr auto operator+(func_builder_t<R(T0,Ts...)>,Rhs rhs){
    return func_builder_v<R(Ts...)>+(tag<T0>+rhs);
}

接下来我们可以分解一些东西:

template<class T>
constexpr std::tuple<tag_t<T>> decompose( tag_t<T> ) { return {}; }

template<class T>
constexpr auto decompose( tag_t<T*> ) {
  return std::tuple_cat( decompose(tag<T>), std::make_tuple( ptr_v ) );
}

template<class T>
constexpr auto decompose( tag_t<T&> ) {
  return std::tuple_cat( decompose(tag<T>), std::make_tuple( lref_v ) );
}

template<class T>
constexpr auto decompose( tag_t<T&&> ) {
  return std::tuple_cat( decompose(tag<T>), std::make_tuple( rref_v ) );
}

template<class T>
constexpr auto decompose( tag_t<T const> ) {
  return std::tuple_cat( decompose(tag<T>), std::make_tuple( const_v ) );
}

template<class T>
constexpr auto decompose( tag_t<T volatile> ) {
  return std::tuple_cat( decompose(tag<T>), std::make_tuple( volatile_v ) );
}


template<class T>
constexpr auto decompose( tag_t<T const volatile> ) {
  return std::tuple_cat( decompose(tag<T>), std::make_tuple( const_v, volatile_v ) );
}

template<class R, class...Args>
constexpr auto decompose( tag_t<R(Args...)> ) {
    constexpr auto args = std::tuple_cat( decompose(tag<Args>)... );
    return std::tuple_cat( decompose(tag<R>), std::make_tuple(retval_v), args, std::make_tuple(func_v) );
}

template<class...Ts>
constexpr auto compose( std::tuple<Ts...> ) {
    return (... + Ts{});
}

现在我们可以采取一种类型:

struct X;

tag<X * const volatile *>

并做

auto decomp0 = decompose(tag<X * const volatile *>);

其中decomp是类型的

std::tuple< tag_t<X>, ptr_t, const_t, volatile_t, ptr_t > tup0 = decomp0;

auto decomp1 = decompose(tag<int(double, char)>);

std::tuple< tag_t<int>, retval_t, tag_t<double>, tag_t<char>, func_t > tup1 = decomp1;

tag_t<int(double, char)> tag_test = compose( decomp1 );

std::tuple< tag_t<int>, retval_t, tag_t<int>, func_t, ptr_t > tup_test_2 = decompose( tag<int(*)(int)> );

tag_t<int(*)(int)> tag_test_3 = compose( tup_test_2 );

我们可以更进一步,包括支持功能签名,大小和未分级,数组等。

然后我们在tag_t<T>上编写一个映射到我们想要的类型的函数。

接下来,我们分解传入类型,仅重新映射元组中的tag_t,然后使用折叠表达式和std::apply对元组求和。

但我疯了。

这样做的唯一好处是你可以(A)重用分解/重组代码,并且(B)可以将类型映射分发到你正在使用的类型的命名空间,因为tag_t上的map函数将查找函数标记类型的命名空间中的名称。

Live example

然后我们可以使用这个(相当复杂的)机器来解决您的问题。

template<class T>
constexpr auto ATypeFromB( tag_t<T> ) {
  return tag< typename ATraits<T>::AType >;
}
template<class T>
constexpr auto BTypeFromA( tag_t<T> ) {
  return tag< typename BTraits<T>::BType >;
}

template<class F, class T>
constexpr auto map_tags_only( F&& f, tag_t<T> t ) {
  return f(t);
}
template<class F, class O>
constexpr auto map_tags_only( F&& f, O o ) {
  return o;
}

template <typename TB>
auto AFromB(TB x) {
  auto decomp = decompose( tag<TB> );
  auto mapped = std::apply( [](auto...elements) {
    return std::make_tuple(
      map_tags_only( [](auto x){return ATypeFromB(x);}, elements )...
    );
  }, decomp );
  auto comp = compose(mapped);
  using R = typename decltype(comp)::type;
  return static_cast<R>(x);
}  
template <typename TA>
auto BFromA(TA x) {
  auto decomp = decompose( tag<TA> );
  auto mapped = std::apply( [](auto...elements) {
    return std::make_tuple(
      map_tags_only( [](auto x){return BTypeFromA(x);}, elements )...
    );
  }, decomp );
  auto comp = compose(mapped);
  using R = typename decltype(comp)::type;
  return static_cast<R>(x);
}  

Live example

再一次,唯一的优点是所有混乱的撕裂const,函数,数组,等等等等,在这个系统中完成一次。你可以在其他地方重复使用它(在这种情况下我使用了两次)。

当我们将它扩展到成员函数(它本身需要100个特化来覆盖一堆案例)时,它自然会变得更糟,假设我们也想重新映射它们。

© www.soinside.com 2019 - 2024. All rights reserved.