Генерация функций преобразования типов с использованием шаблонов и частичной специализации - PullRequest
0 голосов
/ 26 марта 2019

Я хочу генерировать безопасные для типов преобразования между двумя наборами типов, используя шаблоны. Базовый корпус выглядит так:

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);
}

Вышесказанное работает для основных типов. Теперь я хочу распространить это на указатели и константные типы. Это работает, когда я определяю следующие частичные специализации:

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-квалифицированных типов и т. Д .?

1 Ответ

1 голос
/ 26 марта 2019

Будет много шаблонов.

Я бы фактически предложил использовать систему, отличную от специализации шаблонов.

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>, которая соответствует типу, который мы хотим.

Далее мы разбиваем входящий тип, перераспределяем только тега_t в кортеже, затем суммируем кортеж с помощью выражения свертки и std::apply.

Но я сумасшедший.

Единственное преимущество этого состоит в том, что вы можете (A) повторно использовать код декомпозиции / перекомпоновки и (B) можно распределять сопоставления типов по пространствам имен типов, с которыми вы работаете, как будет выглядеть функция map в tag_t вверх имя функции в пространстве имен тегового типа.

Живой пример .

Затем мы можем использовать этот (довольно сложный) механизм для решения вашей проблемы.

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);
}  

Живой пример .

Опять же, единственным и единственным преимуществом является то, что весь этот беспорядок с разрывом const, функций, массивов, бла-бла-бла, делается один раз в этой системе. И вы можете использовать его где-то еще (в этом случае я использовал его дважды).

Естественно, становится намного хуже, когда мы расширяем его на функции-члены (которые сами по себе требуют около 100 специализаций для охвата множества случаев), предполагая, что мы тоже хотим переназначить их.

...