C++20中让AttributeMap支持可转换为string的临时可迭代输入
问题描述
正在学习C++20的concepts与ranges特性,希望为AttributeMap类实现setAttribute方法,使其能够接收任何元素可转换为string的可迭代输入。该类内部持有map<string, vector<string>>类型的成员变量。
使用的concept定义如下:
template<typename input_data_t, typename range_element_t> concept is_range_with_elements_of_type = std::ranges::input_range<input_data_t> && std::convertible_to<std::ranges::range_value_t<input_data_t>, range_element_t>;
当前方法可正常接收vector<string>、array<string>、vector<const char*>类型输入,但无法支持initializer_list<const char*>或static constexpr array<string_view>类型的临时输入,猜测问题与右值类型有关,需修改约束条件以兼容这类输入。
精简后的测试代码:
#include <ranges> #include <concepts> #include <string> #include <vector> #include <map> #include <string_view> template<typename input_data_t, typename range_element_t> concept is_range_with_elements_of_type = std::ranges::input_range<input_data_t> && std::convertible_to<std::ranges::range_value_t<input_data_t>, range_element_t>; class AttributeMap { public: using attribute_name_t = std::string; using attribute_element_t = std::string; using attribute_data_t = std::vector<attribute_element_t>; template<typename input_data_t> requires is_range_with_elements_of_type<input_data_t, attribute_element_t> bool setAttribute(const attribute_name_t&, const input_data_t&) { if constexpr (std::convertible_to<input_data_t, attribute_element_t>) { //currently not implemented return false; } else if constexpr (is_range_with_elements_of_type<input_data_t, attribute_element_t>) { //currently not implemented return false; } return false; } private: std::map<attribute_name_t, attribute_data_t> m_data; }; #include <array> int main() { AttributeMap dut; dut.setAttribute("Friends", { "Chery", "Jack", "Nguyen" }); //error dut.setAttribute(std::string("other Friends"), std::array<std::string, 4>{ "Milo of Croton", "Cleopatra", "300", "..." }); dut.setAttribute(std::string("even more Friends"), std::vector<const char*>{ "Karl", "Gilgamesh" }); { const std::string attribute{ "books" }; const std::vector<std::string> attributeData{ "Book1", "Book2" }; dut.setAttribute(attribute, attributeData); } { const std::string attribute{ "Cloths" }; const std::array<std::string, 5> attributeData{ "Shirt", "Pullover", "Jeans", "Cap", "Sneaker" }; dut.setAttribute(attribute, attributeData); } { const std::string attribute{ "Comments" }; static constexpr std::array<std::string_view, 3> attributeData{ "great", "rofl u said lol", " . " }; dut.setAttribute(attribute, attributeData); //error } }
使用MSVC 14.36.32502编译时,两处标注//error的代码报以下错误:
Error C2672 'AttributeMap::setAttribute': no matching overloaded function found
解决方案
修改点说明
- 调整concept约束逻辑:改用requires表达式直接检查元素转换可行性,替代
std::convertible_to,兼容更多场景(如const元素引用、string_view)。 - 增加initializer_list重载:解决braced-init-list无法被模板推导为input_range的问题,转发到通用可迭代版本处理。
- 补充单个元素重载(可选):支持直接传递单个string或const char*,提升API易用性。
修改后的完整代码
#include <ranges> #include <concepts> #include <string> #include <vector> #include <map> #include <string_view> #include <array> template<typename input_data_t, typename range_element_t> concept is_range_with_elements_of_type = std::ranges::input_range<input_data_t> && requires(std::ranges::range_reference_t<input_data_t> elem) { static_cast<range_element_t>(elem); }; class AttributeMap { public: using attribute_name_t = std::string; using attribute_element_t = std::string; using attribute_data_t = std::vector<attribute_element_t>; // 处理单个string元素 bool setAttribute(const attribute_name_t& name, const attribute_element_t& value) { m_data[name] = {value}; return true; } // 处理单个const char*元素 bool setAttribute(const attribute_name_t& name, const char* value) { return setAttribute(name, attribute_element_t(value)); } // 通用可迭代输入版本 template<typename input_data_t> requires is_range_with_elements_of_type<input_data_t, attribute_element_t> bool setAttribute(const attribute_name_t& name, const input_data_t& data) { attribute_data_t vec; // 预分配空间提升性能 if constexpr (std::ranges::sized_range<input_data_t>) { vec.reserve(std::ranges::size(data)); } for (const auto& elem : data) { vec.emplace_back(elem); } m_data[name] = std::move(vec); return true; } // 处理initializer_list的重载 template<typename U> requires std::convertible_to<U, attribute_element_t> bool setAttribute(const attribute_name_t& name, std::initializer_list<U> data) { return setAttribute(name, static_cast<const std::initializer_list<U>&>(data)); } private: std::map<attribute_name_t, attribute_data_t> m_data; }; int main() { AttributeMap dut; dut.setAttribute("Friends", { "Chery", "Jack", "Nguyen" }); // 正常编译 dut.setAttribute(std::string("other Friends"), std::array<std::string, 4>{ "Milo of Croton", "Cleopatra", "300", "..." }); dut.setAttribute(std::string("even more Friends"), std::vector<const char*>{ "Karl", "Gilgamesh" }); { const std::string attribute{ "books" }; const std::vector<std::string> attributeData{ "Book1", "Book2" }; dut.setAttribute(attribute, attributeData); } { const std::string attribute{ "Cloths" }; const std::array<std::string, 5> attributeData{ "Shirt", "Pullover", "Jeans", "Cap", "Sneaker" }; dut.setAttribute(attribute, attributeData); } { const std::string attribute{ "Comments" }; static constexpr std::array<std::string_view, 3> attributeData{ "great", "rofl u said lol", " . " }; dut.setAttribute(attribute, attributeData); // 正常编译 } // 测试单个元素输入 dut.setAttribute("SingleValue", "HelloWorld"); dut.setAttribute("SingleCStr", "HelloCStr"); }
问题根源解析
- initializer_list无法推导:braced-init-list本身不是具体类型,编译器无法将其直接推导为模板参数
input_data_t,因此需要专门的重载匹配std::initializer_list。 - constexpr array的兼容性:原concept使用
std::convertible_to结合range_value_t的方式,在MSVC旧版本中可能存在判定偏差,改用requires表达式直接检查元素转换,避免了这类兼容性问题。
内容的提问来源于stack exchange,提问作者Tobxon
相关产品推荐
相关产品推荐

