繁体   English   中英

有没有更好的替代 std::unordered_map<std::string, std::any> 有一个 map 字符串到任何类型的值?</std::string,>

[英]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} })

其中value1value2可能不是同一类型(可能是一个 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM