c++ 如何在运行时获取std::variant中包含的对象?

b4lqfgs4  于 2023-03-14  发布在  其他
关注(0)|答案(2)|浏览(225)

我正在尝试实现一个模板化的请求-响应日志类。
这是一个用法示例:

struct RequestA {
    std::string data;
};

struct ResponseA {
    int code;
};

struct RequestB {
    int data;
};

struct ResponseB {
    double value;
};

int main(int argc, char* argv[])
{
    constexpr std::size_t maxEntries = 5;
    RequestJournal<maxEntries, std::pair<RequestA, ResponseA>, std::pair<RequestB, ResponseB>> requestJournal{ 1000 };

    auto requestA = std::make_shared<RequestA>(RequestA{ "RequestA data"});
    requestJournal.addRequest(0, requestA);

    auto requestB = std::make_shared<RequestB>(RequestB{ 10 });
    requestJournal.addRequest(1, requestB);
}

其思想是拥有一个不知道请求/响应类型的基础结构类,在创建时注册所有可能的请求-响应对,并能够为请求定义特定的槽。
以下是一个原型(基于此方法link):

inline constexpr std::size_t npos = -1;

template <typename T, typename... Ts>
struct index : std::integral_constant<std::size_t, npos> {};

template <typename T, typename... Ts>
inline constexpr std::size_t index_v = index<T, Ts...>::value;

template <typename T, typename... Ts>
struct index<T, T, Ts...> : std::integral_constant<std::size_t, 0> {};

template <typename T, typename Head, typename... Tail>
class index<T, Head, Tail...>
{
    static constexpr std::size_t tmp = index_v<T, Tail...>;

public:
    static constexpr std::size_t value = tmp == npos ? tmp : tmp + 1;
};

// Helper function that gets the variant type information for a specific type
template <typename T, typename... Ts>
std::pair<const std::type_info*, std::any> GetVariantTypeInfo(std::variant<Ts...>& variant)
{
    // Get the index of the specified type in the variant
    constexpr static auto indexOfType = index_v<T, Ts...>;

    // Check if the specified type is currently stored in the variant
    if (indexOfType == variant.index())
    {
        auto obj = std::get<indexOfType>(variant);

        // Get the type information and object for the specified type
        return std::make_pair(&typeid(obj), std::any(obj));
    }

    // Return a null pointer to indicate that the type information was not found
    return std::make_pair(nullptr, std::any());
}

// Helper function that calls GetVariantTypeInfo for each type in a parameter pack
template <typename... Ts>
const std::type_info* GetVariantTypeInfo(std::variant<Ts...>& variant)
{
    // Call GetVariantTypeInfo for each type in the parameter pack using fold expression
    const std::initializer_list<std::pair<const std::type_info*, std::any>> typeInfos = { GetVariantTypeInfo<Ts, Ts...>(variant)... };
    for (const auto& typeInfo : typeInfos)
    {
        if (typeInfo.first != nullptr)
        {
            const auto& typeIdx = *typeInfo.first;
            return typeInfo.first;
        }
    }
    return nullptr;
}

template <std::size_t maxEntries, typename... Pairs>
class RequestJournal {
public:
    using EntryIndex = std::size_t;

    using RequestTypesVariant = std::variant<std::shared_ptr<typename Pairs::first_type> ...>;
    using ResponseTypesVariant = std::variant<std::shared_ptr<typename Pairs::second_type> ...>;

    template <typename T>
    static constexpr bool request_is_in_pack = (std::is_same_v<T, typename Pairs::first_type> || ...);

    template <typename T>
    static constexpr bool response_is_in_pack = (std::is_same_v<T, typename Pairs::second_type> || ...);

    RequestJournal(int latencyMsec) {
        m_latency = std::chrono::milliseconds(latencyMsec);
    }

    template <typename T>
    std::enable_if_t<response_is_in_pack<T>, void> setResponse(EntryIndex index, std::shared_ptr<T> response) {

        const auto requestTypeInfo =
            GetVariantTypeInfo<std::shared_ptr<typename Pairs::first_type>...>
            (m_journal[index].requestContainer);
     
        // other code ...
    }

private:
    using Timestamp = std::chrono::time_point<std::chrono::steady_clock>;

    struct RequestEntry {
        RequestTypesVariant requestContainer;
        ResponseTypesVariant responseContainer;
    };

    RequestJournal() {}

    std::size_t i = 0;
    std::unordered_map<std::size_t, std::pair<std::type_index, std::type_index>> m_pairsMap{
        { i++, std::make_pair(std::type_index(typeid(typename Pairs::first_type)), std::type_index(typeid(typename Pairs::second_type)))}...
    };

    std::chrono::milliseconds m_latency;
    std::array<RequestEntry, maxEntries> m_journal;
};

问题是如何(以及是否可能)将变量本身(auto obj = std::get<indexOfType>(variant);)中的对象传播到setResponse函数。

8fq7wneg

8fq7wneg1#

您需要简化代码并使用std::visit。下面是一个使用std::variant调度响应的简单版本:https://godbolt.org/z/jTYj8o13e

#include <variant>
#include <string>
#include <vector>
#include <iostream>

struct request_a {};
struct request_b{};

struct response_a{
    std::string value;
};
struct response_b{
    int value;
};

response_a respond(request_a) {
    std::cout << "responding to request_a\n";

    return {"oceanic"};
} 

response_b respond(request_b) {
    std::cout << "responding to request_b\n";

    return {815};
} 

int main() {

    std::vector<std::variant<request_a, request_b>> requests{request_a(), request_b()};

    for (const auto &req : requests)
    {
        std::visit([](const auto &req){
            std::cout << respond(req).value << '\n';
        }, req);
    }

    return 0;
}

输出:

responding to request_a
oceanic
responding to request_b
815
nnt7mjpx

nnt7mjpx2#

好吧,我没有找到在getResponse或其他函数中访问对象本身的方法(std::get〈〉(variant)),但我意识到,实际上我不需要访问它。
我通过将对象进一步传播为std::(更新GetVariantTypeInfo)使其工作,在那里我可以将其转换为模板返回类型,这就是我在返回响应时实际需要的。

// Helper function that calls GetVariantTypeInfoPointer for each type in a parameter pack
template <typename... Ts>
std::pair<const std::type_info*, std::any> GetVariantTypeInfo(std::variant<Ts...>& variant)
{
    // Call GetVariantTypeInfo for each type in the parameter pack using fold expression
    const std::initializer_list<std::pair<const std::type_info*, std::any>> typeInfos = { GetVariantTypeInfo<Ts, Ts...>(variant)... };
    for (const auto& typeInfo : typeInfos)
    {
        if (typeInfo.first != nullptr)
        {
            return typeInfo;
        }
    }

    return std::make_pair(nullptr, std::any());
}
template <typename T>
    std::enable_if_t<response_is_in_pack<T>, std::optional<std::shared_ptr<T>>>
        getResponse(ChannelIndex channel, const std::type_info& requestType) {

        //some code ...

        const auto responseTypeInfo =
            GetVariantTypeInfo<std::shared_ptr<typename Pairs::second_type>...>
            (m_journal[channel].responseContainer);

        return std::any_cast<std::shared_ptr<T>>(responseTypeInfo.second);
    }

我很好奇如何用std::visit实现它,所以很高兴看到这个例子。谢谢大家的帮助。
使用的简化示例:

struct RequestA {
    std::string data;
};

struct ResponseA {
    int code;
};

struct RequestB {
    int data;
};

struct ResponseB {
    double value;
};

int main(int argc, char* argv[])
{
    constexpr std::size_t channelsNum = 6;
    RequestJournal<channelsNum, std::pair<RequestA, ResponseA>, std::pair<RequestB, ResponseB>> requestJournal{ 1000000 };

    // Start the first thread which generates requests
    std::thread requestThread([&requestJournal, channelsNum]() {
        for (int i = 0; i < channelsNum; i++) {
            if (i % 2 == 0) {
                auto request = std::make_shared<RequestA>(RequestA{ "RequestA data #" + std::to_string(i) });
                requestJournal.addRequest(i, request);
            }
            else {
                auto request = std::make_shared<RequestB>(RequestB{ i });
                requestJournal.addRequest(i, request);
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(10)); // sleep for 10 milliseconds
        }

        while (requestJournal.getPendingRequestsNum(typeid(RequestA)) != 0) {
            // Check the responses
            for (int i = 0; i < channelsNum; i++) {

                auto responseA = requestJournal.getResponse<ResponseA>(i, typeid(RequestA));
                if (responseA.has_value() == true) {
                    std::cout << "ResponseA received: " << responseA->get()->code << std::endl;
                }
            }
        }

        while (requestJournal.getPendingRequestsNum(typeid(RequestB)) != 0) {
            // Check the responses
            for (int i = 0; i < channelsNum; i++) {

                auto responseB = requestJournal.getResponse<ResponseB>(i, typeid(RequestB));
                if (responseB.has_value()) {
                    std::cout << "ResponseB received: " << responseB->get()->value << std::endl;
                }
            }
        }
        });

    // Start the second thread which sets responses
    std::thread responseThread([&requestJournal, channelsNum]() {
        for (int i = 0; i < channelsNum; i++) {
            std::this_thread::sleep_for(std::chrono::milliseconds(20)); // sleep for 20 milliseconds
            if (i % 2 == 0) {
                auto response = std::make_shared<ResponseA>(ResponseA{ 200 });
                requestJournal.setResponse(i, response);
            }
            else {
                auto response = std::make_shared<ResponseB>(ResponseB{ 3.14 });
                requestJournal.setResponse(i, response);
            }
        }
        });

    // Wait for the threads to finish
    requestThread.join();
    responseThread.join();
}

相关问题