【问题标题】:How to convert one type to another in C++17 using template meta programming?如何使用模板元编程在 C++17 中将一种类型转换为另一种类型?
【发布时间】:2019-02-01 04:07:31
【问题描述】:

我需要在 C++ 中实现一个类型转换器来将某种类型转换为另一种类型。例如,将int16_t 转换为float,将int64_t 转换为double。我已经使用模板专业化实现了一个:

template<class T>
struct TypeConverter
{

};
template<>
struct TypeConverter<int16_t>
{
       using type = float;
};

template<>
struct TypeConverter<int64_t>
{
       using type = double;
};

TEST(Exp, TypeConveter) {

    static_assert(std::is_same_v<TypeConverter<int16_t>::type, float>);
    static_assert(std::is_same_v<TypeConverter<int64_t>::type, double>);
}

有没有更好(更紧凑)的方法来使用 C++17 tmp 功能实现这一点?

【问题讨论】:

  • 您需要告诉我们您希望如何实现。事实上,我们只有一个定义了别名的结构。你有一个真正转换的方法吗?它的签名如何?您需要一些数据成员吗?或者它可以是免费功能吗?

标签: c++ c++17 template-meta-programming


【解决方案1】:

另一种简单的方式(兼容C++11):

template<class T> struct Type {};

float type_convert(Type<int16_t>);
double type_convert(Type<int64_t>);

int main() {
    static_assert(std::is_same_v<decltype(type_convert(Type<int16_t>{})), float>);
    static_assert(std::is_same_v<decltype(type_convert(Type<int64_t>{})), double>);
}

这种方法的好处是它使用参数相关名称查找 (ADL) 来查找相应的 type_convert 函数声明(不需要定义)。如果您需要处理用户定义类型 (UDT),很容易在声明它们的同一命名空间中为它们添加相应的 type_convert 重载(无需打开特征命名空间来定义特征类模板的另一个特化为您的 UDT)。例如:

namespace N { 
    struct MyType;
    long double type_convert(Type<MyType>);
} 

然后:

// type_convert is found by ADL.    
static_assert(std::is_same_v<decltype(type_convert(Type<N::MyType>{})), long double>);

【讨论】:

    【解决方案2】:

    CE C++17:https://gcc.godbolt.org/z/iY8Qoa

    CE C++11:https://gcc.godbolt.org/z/ApuxZj

    用户代码

    #include<TypeMap.h>
    #include<cstdlib>
    using TMap = TypeMap <
        std::pair<int16_t, float>,
        std::pair<int64_t, double>
    >;
    
    void foo() {
        static_assert(std::is_same_v<TMap::get<int16_t>, float>);
        static_assert(std::is_same_v<TMap::get<int64_t>, double>);
        static_assert(std::is_same_v<TMap::get<int>, NotFound>);
    }
    

    TypeMap.h

    #include<type_traits>
    #include<utility>
    
    struct NotFound;
    
    template<class... TPs>
    struct TypeMap {
        template<class T>
        using get = NotFound;
    };
    
    template<class TP1, class... TPs>
    struct TypeMap<TP1, TPs...> {
        template<class T>
        using get = std::conditional_t< std::is_same_v<T, typename TP1::first_type>
                , typename TP1::second_type
                , typename TypeMap<TPs...>::template get<T> >;
    };
    

    【讨论】:

    • "将代码移动到库" ...我认为这不是问题的重点。不管你把代码放在哪里,问题是它是否可以写得更紧凑。至少我是这么看待这个问题的。
    • @bolov 经过多次迭代,我想它现在已经足够紧凑了。
    【解决方案3】:

    我能想象到为类型转换器注册类型的最综合方式是std::tuple(或类似的东西)中的几个类型列表。

    例如:如果您想将std::int16_t 转换为floatstd::int32_t 转换为doublestd::int64_t 转换为long double,您可以using 定义这两个类型

    using list1 = std::tuple<std::int16_t, std::int32_t, std::int64_t>;
    using list2 = std::tuple<float, double, long double>;
    

    现在,给定以下结构和声明的函数

    template <typename, typename, typename>
    struct foo
     { using type = std::tuple<>; };
    
    template <typename T1, typename T2>
    struct foo<T1, T1, T2>
     { using type = std::tuple<T2>; };
    
    template <typename T, typename ... Ts1, typename ... Ts2>
    constexpr auto bar (std::tuple<Ts1...>, std::tuple<Ts2...>)
       -> decltype( std::tuple_cat(
             std::declval<typename foo<T, Ts1, Ts2>::type>()...) );
    

    TypeConverter 变成

    template <typename T>
    using TypeConverter
       = std::tuple_element_t<0u, decltype(bar<T>(std::declval<list1>(),
                                                  std::declval<list2>()))>;
    

    但我认为两个不同的std::tuples 中的几个列表是合成的,但难以理解和维护。

    所以我提出了一种基于单个类型对列表的合成较少(但更易于理解和可维护)的方法

    using list = std::tuple<std::pair<std::int16_t, float>,
                            std::pair<std::int32_t, double>,
                            std::pair<std::int64_t, long double>>;
    

    现在struct 并声明函数成为

    template <typename, typename>
    struct foo
     { using type = std::tuple<>; };
    
    template <typename T1, typename T2>
    struct foo<T1, std::pair<T1, T2>>
     { using type = std::tuple<T2>; };
    
    template <typename T, typename ... Ts>
    constexpr auto bar (std::tuple<Ts...>)
       -> decltype( std::tuple_cat(
             std::declval<typename foo<T, Ts>::type>()...) );
    

    还有TypeConverter

    template <typename T>
    using TypeConverter
       = std::tuple_element_t<0u, decltype(bar<T>(std::declval<list>()))>;
    

    以下是包含两种解决方案的完整编译 C++17 示例(您可以启用第一个或第二个更改 #if 0

    #include <tuple>
    #include <type_traits>
    
    #if 0
    template <typename, typename, typename>
    struct foo
     { using type = std::tuple<>; };
    
    template <typename T1, typename T2>
    struct foo<T1, T1, T2>
     { using type = std::tuple<T2>; };
    
    template <typename T, typename ... Ts1, typename ... Ts2>
    constexpr auto bar (std::tuple<Ts1...>, std::tuple<Ts2...>)
       -> decltype( std::tuple_cat(
             std::declval<typename foo<T, Ts1, Ts2>::type>()...) );
    
    using list1 = std::tuple<std::int16_t, std::int32_t, std::int64_t>;
    using list2 = std::tuple<float, double, long double>;
    
    template <typename T>
    using TypeConverter
       = std::tuple_element_t<0u, decltype(bar<T>(std::declval<list1>(),
                                                  std::declval<list2>()))>;
    #else
    
    template <typename, typename>
    struct foo
     { using type = std::tuple<>; };
    
    template <typename T1, typename T2>
    struct foo<T1, std::pair<T1, T2>>
     { using type = std::tuple<T2>; };
    
    template <typename T, typename ... Ts>
    constexpr auto bar (std::tuple<Ts...>)
       -> decltype( std::tuple_cat(
             std::declval<typename foo<T, Ts>::type>()...) );
    
    using list = std::tuple<std::pair<std::int16_t, float>,
                            std::pair<std::int32_t, double>,
                            std::pair<std::int64_t, long double>>;
    
    template <typename T>
    using TypeConverter
       = std::tuple_element_t<0u, decltype(bar<T>(std::declval<list>()))>;
    #endif
    
    int main ()
     {
       static_assert( std::is_same_v<float, TypeConverter<std::int16_t>> );
       static_assert( std::is_same_v<double, TypeConverter<std::int32_t>> );
       static_assert( std::is_same_v<long double, TypeConverter<std::int64_t>> );
     }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-12-02
      • 2012-03-28
      • 2023-02-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-14
      相关资源
      最近更新 更多