簡體   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