[英]Is there a better alternative to std::unordered_map<std::string, std::any> to have a map of strings to any type of value?
我正在编写输出到控制台的日志 function。 我最终要使用的语法如下所示:
Log(Info,
"Message",
{ {"Key1", value1},
{"Key2", value2} })
其中value1
和value2
可能不是同一类型(可能是一个 std::string 的名称和一个 int 他们的年龄)。 有没有更好的方法使用std::unordered_map<std::string, std::any>
存储键值对并使用std::any_cast
在输出到控制台之前推断类型?
如果可能的类型数量有限,请使用std::variant
而不是std::any
,例如
std::unordered_map<std::string, std::variant<int, std::string, etc>>
您可以通过std::variant::index()
或std::holds_alternative()
查询std::variant
当前持有的类型,然后通过std::get()
检索值(使用index()
例如,使用get()
在switch()
块中会很有用)。 或者,如果它与给定类型匹配,您可以使用std::get_if()
来获取指向数据的指针。
或者,您可以使用std::visit()
,这在日志记录用例中很有用,因为您可以根据需要提供单独的访问者来记录每种可能的类型。
是的,有更好的方法:
#include <iostream>
#include <string_view>
#include <sstream>
struct LogPair{
template<typename T>
LogPair(std::string_view key, T&& value){
std::stringstream buffer;
//Feel free to change
buffer<<key <<": "<<std::forward<T>(value);
msg = buffer.str();
}
std::string msg;
};
enum class LogLevel{
Info = 0,
};
void log(LogLevel level, std::string_view msg, std::initializer_list<LogPair> args){
(void)level;//TODO
std::cout<<msg<<'\n';
for(const LogPair& s: args)
std::cout<<'\t'<<s.msg<<'\n';
}
struct Test{//Complex type with custom print
float val;
friend std::ostream& operator<<(std::ostream& os, const Test& t){
os<<t.val;
return os;
}
};
用法:
int main(){
int value1 = 10;
Test value2{1.4};
log(LogLevel::Info,"Message", { {"Key1", value1},{"Key2", value2} });
}
Output:
Message
Key1: 10
Key2: 1.4
完美的解决方案类似于@Miles Budnek 发布的解决方案:
template <typename... Params>
void Log(LogLevel level, std::string_view message, const Params&... params);
或成对:
template <typename... Params>
void Log(LogLevel level, std::string_view message, const std::pair<std::string_view,Params>&... params)
但是这两种情况都不会编译:
Log(level, "Msg", {"Key1",value1},{"Key2", value2})
这是必需的:(@Miles 使用了一个元组)
Log(level, "Msg", std::pair{"Key1",10},std::pair{"Key2", 1.2})
原因是即使我们可以看到Params
应该被推导为int,double
,编译器却没有,因为大括号语法是不可推导的。 推导指南在这里没有帮助(即使我们将其重新制作为 Log 构造函数),因为实际上没有办法匹配大括号语法,因为它甚至没有类型。 我不相信有可变参数模板的解决方案。
但是,您提供的语法可以导致解决方案。 唯一的希望是将大括号初始值设定项语法转换为std::initializer_list<T>
,但是 T 是什么? 它应该看起来像std::pair<std::string_view,?>
但每一对都是不同的。 在这里,您可以使用std::any
并且它会起作用,有一种更简单的方法。
使用模板构造函数创建非模板class LogPair
,该构造函数可以接受匹配任何对的大括号初始值设定项语法!
template<typename T> LogPair(std::string_view key, T&& value);
差不多了,现在 log 可以接受std::initializer_list<LogPair>
因为它正确匹配语法并且不正确的使用会导致编译时错误。
现在,很诱人的是在构造函数中进行打印,但未指定这些变量的创建顺序,并且它也会在消息打印之前。 因此,解决方案是将其保存到稍后打印的字符串中。
请注意,如果不重用或重新实现std::any
,就无法将值保存在LogPair
中。 但这不应该是必需的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.