C++中返回std::string时模板函数为何报错?枚举类映射场景
问题描述
现有如下枚举类、字符串映射表,以及对应枚举值的类型包装模板:
enum class FRUIT { APPLE, ORANGE, MELON }; std::map<FRUIT, std::string> m { {FRUIT::APPLE, "3"}, {FRUIT::ORANGE, "SWEET"}, {FRUIT::MELON, "24.5"} };
#include <string> template<FRUIT f> struct Type { using type=int; }; template<> struct Type<FRUIT::ORANGE> { using type=std::string; }; template<> struct Type<FRUIT::MELON> { using type=double; };
尝试通过以下模板函数获取对应枚举值的强类型数据:
#include <type_traits> #include <string> template<FRUIT f> typename Type<f>::type getValue() { if (std::is_same<typename Type<f>::type, int>::value) return std::stoi( m[f] ); else if (std::is_same<typename Type<f>::type, double>::value) return std::stod( m[f] ); else if (std::is_same<typename Type<f>::type, std::string>::value) return m[f]; }
调用getValue<FRUIT::APPLE>()时,预期会进入第一个分支返回int类型,但编译器报错:
Cannot convert ‘std::map<...>::mapped_type’ {aka ‘std::__cxx11::basic_string’} to ‘Type::type’ {aka ‘int’}
错误指向最后一行的return m[f];,但移除字符串相关的最后一个分支后,代码运行正常。
问题原因
普通的if-else是运行时条件判断,编译器在实例化模板函数时,会检查所有分支的代码合法性,不会因为某个分支的条件在编译期可知为false就跳过检查。
以getValue<FRUIT::APPLE>()为例,虽然最后一个分支的std::is_same条件编译期就能确定是false,但编译器仍然会验证return m[f];是否能转换成返回类型int——显然std::string无法直接转成int,因此触发类型转换错误。
解决方案
方案1:使用C++17 constexpr if(推荐)
constexpr if是编译期条件判断,编译器会只实例化符合条件的分支,其他分支会被完全忽略:
#include <type_traits> #include <string> template<FRUIT f> typename Type<f>::type getValue() { if constexpr (std::is_same_v<typename Type<f>::type, int>) return std::stoi( m[f] ); else if constexpr (std::is_same_v<typename Type<f>::type, double>) return std::stod( m[f] ); else if constexpr (std::is_same_v<typename Type<f>::type, std::string>) return m[f]; }
方案2:模板特化
直接为每个枚举值特化getValue函数,完全避免分支判断:
#include <string> template<FRUIT f> typename Type<f>::type getValue(); template<> int getValue<FRUIT::APPLE>() { return std::stoi(m[FRUIT::APPLE]); } template<> std::string getValue<FRUIT::ORANGE>() { return m[FRUIT::ORANGE]; } template<> double getValue<FRUIT::MELON>() { return std::stod(m[FRUIT::MELON]); }
方案3:标签分发(C++11及以上可用)
利用重载决议实现编译期分支:
#include <type_traits> #include <string> // 标签定义 struct IntTag {}; struct DoubleTag {}; struct StringTag {}; // 根据Type<f>::type获取对应标签 template<FRUIT f> struct GetTag { using type = typename std::conditional_t< std::is_same<typename Type<f>::type, int>::value, IntTag, typename std::conditional_t< std::is_same<typename Type<f>::type, double>::value, DoubleTag, StringTag > >; }; // 重载不同标签的实现函数 template<FRUIT f> typename Type<f>::type getValueImpl(IntTag) { return std::stoi(m[f]); } template<FRUIT f> typename Type<f>::type getValueImpl(DoubleTag) { return std::stod(m[f]); } template<FRUIT f> typename Type<f>::type getValueImpl(StringTag) { return m[f]; } // 对外接口 template<FRUIT f> typename Type<f>::type getValue() { return getValueImpl<f>(typename GetTag<f>::type{}); }
内容的提问来源于stack exchange,提问作者MinLinC
相关产品推荐
相关产品推荐

