显式构造函数无法用于std::initializer_list/std::unordered_multimap的解决方案咨询
问题
我想创建一个字符串容器类,在构造阶段检查内部字符串是否有效。为了避免隐式类型转换,我希望把构造函数声明为explicit,但当这个类用于std::initializer_list(进而用于std::unordered_multimap)时,explicit构造函数会导致编译错误。有没有办法既能保留explicit构造函数,又能正常在std::unordered_multimap中使用这个类?
最小复现代码
#include <utility> #include <string> #include <stdexcept> #include <unordered_map> class constrained_string_container_t{ public: // variant 1: works //constrained_string_container_t(const std::string& inst) : str{inst} {validate();} // variant 2: doesn't work explicit constrained_string_container_t(const std::string& inst) : str{inst} {validate();} explicit constrained_string_container_t(std::string&& inst) : str{std::move(inst)} {validate();} // variant 3: works //template<typename STR_T> //constrained_string_container_t(STR_T&& inst) : str{std::forward<STR_T>(inst)} {validate();} // variant 4: doesn't work //template<typename STR_T> //explicit constrained_string_container_t(STR_T&& inst) : str{std::forward<STR_T>(inst)} {validate();} void validate() {if (str == "abc") {throw std::runtime_error{"abc not allowed"};}} private: std::string str; }; using pair_t = std::pair<std::string, constrained_string_container_t>; using init_pair_list_t = std::initializer_list<std::pair<std::string, constrained_string_container_t>>; using map_t = std::unordered_multimap<std::string, constrained_string_container_t>; int main() { using namespace std::string_literals; //suceeds for all variants pair_t p0{std::string{"test"}, std::string{"test2"}}; //fails to compile for variant 2 and variant 4 init_pair_list_t p1{{std::string{"test"}, std::string{"test2"}}}; //fails to compile for variant 2 and variant 4 init_pair_list_t p2{{"test"s, "test2"s}}; //fails to compile for variant 2 and variant 4 map_t m{{"test"s, "test2"s}}; }
编译错误信息
explicit_shit.cpp: In function ‘int main()’: explicit_shit.cpp:40:72: error: could not convert ‘{{std::__cxx11::basic_string<char>(((const char*)"test"), std::allocator<char>()), std::__cxx11::basic_string<char>(((const char*)"test2"), std::allocator<char>())}}’ from ‘<brace-enclosed initializer list>’ to ‘init_pair_list_t’ {aka ‘std::initializer_list<std::pair<std::__cxx11::basic_string<char>, constrained_string_container_t> >’} 40 | init_pair_list_t p1{{std::string{"test"}, std::string{"test2"}}}; | ^ | | | <brace-enclosed initializer list> explicit_shit.cpp:43:48: error: could not convert ‘{{std::literals::string_literals::operator""s(const char*, std::size_t)(4), std::literals::string_literals::operator""s(const char*, std::size_t)(5)}}’ from ‘<brace-enclosed initializer list>’ to ‘init_pair_list_t’ {aka ‘std::initializer_list<std::pair<std::__cxx11::basic_string<char>, constrained_string_container_t> >’} 43 | init_pair_list_t p2{{"test"s, "test2"s}}; | ^ | | | <brace-enclosed initializer list> explicit_shit.cpp:46:36: error: no matching function for call to ‘std::unordered_multimap<std::__cxx11::basic_string<char>, constrained_string_container_t>::unordered_multimap(<brace-enclosed initializer list>)’ 46 | map_t m{{"test"s, "test2"s}}; | ^
解决方案
问题根源是:用初始化列表构造std::pair(进而构造std::initializer_list和unordered_multimap)时,需要从std::string隐式转换为constrained_string_container_t,但explicit构造函数禁止了这种隐式转换。以下是可行的解决方式:
方法1:显式构造容器实例
在初始化pair或map时,手动调用constrained_string_container_t的构造函数,彻底避免依赖隐式转换:
int main() { using namespace std::string_literals; // 显式构造容器 pair_t p0{std::string{"test"}, constrained_string_container_t{"test2"}}; // 显式构造每个pair中的容器 init_pair_list_t p1{{std::string{"test"}, constrained_string_container_t{"test2"}}}; init_pair_list_t p2{{"test"s, constrained_string_container_t{"test2"s}}}; map_t m{{"test"s, constrained_string_container_t{"test2"s}}}; }
这种方法直接保留了explicit构造函数的安全性,同时解决编译问题,是最直观的方案。
方法2:添加const char*类型的explicit构造函数
如果希望直接用字符串字面量初始化,可以额外添加一个接收const char*的explicit构造函数,扩展显式构造的支持范围:
class constrained_string_container_t{ public: explicit constrained_string_container_t(const std::string& inst) : str{inst} {validate();} explicit constrained_string_container_t(std::string&& inst) : str{std::move(inst)} {validate();} explicit constrained_string_container_t(const char* inst) : str{inst} {validate();} // 新增构造函数 void validate() {if (str == "abc") {throw std::runtime_error{"abc not allowed"};}} private: std::string str; };
初始化时可以简化为:
map_t m{{"test"s, constrained_string_container_t{"test2"}}};
核心原理总结
explicit构造函数的设计目的就是阻止隐式类型转换,所以任何解决方案都必须显式触发构造函数调用,不能再依赖编译器自动完成std::string到容器类的隐式转换。
内容的提问来源于stack exchange,提问作者byteunit
相关产品推荐
相关产品推荐

