c++ - 如何将 std::tuple 转换为 std::any?

标签 c++ c++20

这是我正在尝试的:

auto fwd_args = std::forward_as_tuple(std::forward<Args>(args)...);
auto key = std::make_pair(std::type_index(typeid(T)), std::any(fwd_args));

错误是:

error C2440: '': cannot convert from 'std::tuple<const char (&)[78],_Ty &&>' to 'std::any'

追溯到这里:

factory->get<Font>(R"(C:\Fonts\myfont.ttf)", 24)

哪里Font c'tor 是:

explicit Font(const std::string& filename, float fontSize=32) {

我的问题:

  1. 我可以将任意参数转换为 std::any 吗? ?
  2. 如果不是,我如何使用任意参数作为映射中的键?

完整代码如下:

class SingletonFactory {
  public:
    template<typename T, typename... Args>
    const T &get(Args &&... args) {
        auto fwd_args = std::forward_as_tuple(std::forward<Args>(args)...);
        auto key = std::make_pair(std::type_index(typeid(T)), std::any(fwd_args));

        auto it = _cache.find(key);

        if(it != _cache.end()) {
            return std::any_cast<T>(it->second);
        }

        return std::any_cast<T>(_cache.emplace(std::piecewise_construct, std::forward_as_tuple(key), fwd_args).first);
    }

  private:
    std::map<std::pair<std::type_index, std::any>, std::any> _cache{};
};

而不是 std::pair<std::type_index, std::any>我们可以尝试使用实现所有必要方法的结构...

Example on Godbolt


我正在尝试做的事情:

我正在尝试为我的游戏构建一些 Assets /资源的缓存。例如如果我想在两个不同的地方使用相同的字体,我不想加载它两次(这涉及从磁盘读取它并将其转换为纹理并将其上传到 GPU)。因为资源有句柄,所以确定性地调用它们的析构函数很重要(例如,当卸载关卡时,我将销毁工厂)。

我当然可以手动完成这一切,但我不想跟踪我使用 24px FontA 和 32px FontB 的位置并手动将这些对象传送到我的游戏中。我只想要可以将所有内容转储到其中的通用缓存。然后,如果我以前使用过该特定 Assets ,很好,它会被重新使用,如果没有,它可以制作一个新的。如果我后来决定废弃那个关卡或 Assets 或你拥有的东西,我只需删除 get<> 然后它就消失了,我不必回溯并找到我通过管道传递它的每个地方。

最佳答案

记住像 std::any 这样的类型删除工具和 std::function只暴露接口(interface) 他们只 promise :std::any封装了复制能力,仅此而已,因此你不能 比较相等性/散列 std::any目的。使用额外的 std::function只是为了存储 operator<很麻烦(这实际上是为每种类型使用两个 vtable),你最好 使用手卷式删除。

此外,根据您的要求,您必须特殊情况 const char*const char(&)[N] 参数,因为您希望将它们存储为 std::string , 以及它们的比较运算符。这 还解决了“在 std::tuple 中存储 std::any 和引用成员”的问题。 (有关更多讨论,请参阅编辑说明 #2。)

您的 Godbolt 链接中的代码在某些地方不正确,尤其是您传递的代码 T 的构造函数构造一个 std::any 的参数(缺少前面的 std::in_place_type<T>, 那就是)。

为方便起见,以下实现使用 C++20,但它可以在 有一些修改的旧标准。<​​/p>

编辑 #1:修复了未初始化的初始哈希值,这对我来说真的是一个菜鸟错误。

编辑 #2:是的,特殊情况的技巧 const char*不是很好,它可以防止采取 const char* 的 c'tors从工作。您可以将其重写为“仅衰减每个参数并且不对 const char*const char(&)[N] 采取任何特殊操作”,这对所有 c'tor 都有效。但这也只有在您传入字符串文字时才有效,否则您的 HashMap 中可能会存储一个悬空指针。如果您通过 std::string 指定您真正想要传递引用的每个位置,也许这​​种方法是可行的。 (例如,通过使用像 "hello"s 这样的 UDL 或显式构建一个 std::string )。
AFAIK 你不能得到 c'tors 的参数类型,因为 C++ 明确不允许获取 c'tors 的地址,如果你不能形成指向成员函数的指针,你就不能在它上面做模板技巧。此外,重载决议可能是实现这一目标的另一个障碍。

编辑 #3:我没有注意到会有不可复制的对象要缓存。在这种情况下,std::any没有用,因为它只能存储可复制的对象。使用类似类型的删除技术也可以存储不可复制的对象。我的实现只使用 std::unique_ptr存储删除的键和值,强制将它们存储在堆上。这个简单的方法甚至支持不可复制的 不可移动的类型。如果需要 SBO,则必须使用更复杂的方法来存储类型删除的对象。

#include <iostream>
#include <unordered_map>
#include <type_traits>

// Algorithm taken from boost
template <typename T>
void hash_combine(std::size_t& seed, const T& value)
{
    static constexpr std::size_t golden_ratio = []
    {
        if constexpr (sizeof(std::size_t) == 4)
            return 0x9e3779b9u;
        else if constexpr (sizeof(std::size_t) == 8)
            return 0x9e3779b97f4a7c15ull;
    }();
    seed ^= std::hash<T>{}(value) + golden_ratio +
        std::rotl(seed, 6) + std::rotr(seed, 2);
}

class Factory
{
public:
    template <typename T, typename... Args>
    const T& get(Args&&... args)
    {
        Key key = construct_key<T, Args...>(static_cast<Args&&>(args)...);
        if (const auto iter = cache_.find(key); iter != cache_.end())
            return static_cast<ValueImpl<T>&>(*iter->second).value;
        Value value = key->construct();
        const auto [iter, emplaced] = cache_.emplace(
            std::piecewise_construct,
            // Move the key, or it would be forwarded as an lvalue reference in the tuple
            std::forward_as_tuple(std::move(key)),
            // Also the value, remember that this tuple constructs a std::any, not a T
            std::forward_as_tuple(std::move(value))
        );
        return static_cast<ValueImpl<T>&>(*iter->second).value;
    }

private:
    struct ValueModel
    {
        virtual ~ValueModel() noexcept = default;
    };

    template <typename T>
    struct ValueImpl final : ValueModel
    {
        T value;

        template <typename... Args>
        explicit ValueImpl(Args&&... args): value(static_cast<Args&&>(args)...) {}
    };
    
    using Value = std::unique_ptr<ValueModel>;

    struct KeyModel
    {
        virtual ~KeyModel() noexcept = default;
        virtual std::size_t hash() const = 0;
        virtual bool equal(const KeyModel& other) const = 0;
        virtual Value construct() const = 0;
    };

    template <typename T, typename... Args>
    class KeyImpl final : public KeyModel
    {
    public:
        template <typename... Ts>
        explicit KeyImpl(Ts&&... args): args_(static_cast<Ts&&>(args)...) {}

        // Use hash_combine to get a hash
        std::size_t hash() const override
        {
            std::size_t seed{};
            std::apply([&](auto&&... args)
            {
                (hash_combine(seed, args), ...);
            }, args_);
            return seed;
        }

        bool equal(const KeyModel& other) const override
        {
            const auto* ptr = dynamic_cast<const KeyImpl*>(&other);
            if (!ptr) return false; // object types or parameter types don't match
            return args_ == ptr->args_;
        }

        Value construct() const override
        {
            return std::apply([](const Args&... args)
            {
                return std::make_unique<ValueImpl<T>>(args...);
            }, args_);
        }

    private:
        std::tuple<Args...> args_;
    };

    using Key = std::unique_ptr<KeyModel>;
    using Hasher = decltype([](const Key& key) { return key->hash(); });
    using KeyEqual = decltype([](const Key& lhs, const Key& rhs) { return lhs->equal(*rhs); });

    std::unordered_map<Key, Value, Hasher, KeyEqual> cache_;

    template <typename T, typename... Args>
    static Key construct_key(Args&&... args)
    {
        constexpr auto decay_or_string = []<typename U>(U&& arg)
        {
            // convert to std::string if U decays to const char*
            if constexpr (std::is_same_v<std::decay_t<U>, const char*>)
                return std::string(arg);
                // Or just decay the parameter otherwise
            else
                return std::decay_t<U>(arg);
        };
        using KeyImplType = KeyImpl<T, decltype(decay_or_string(static_cast<Args&&>(args)))...>;
        return std::make_unique<KeyImplType>(decay_or_string(static_cast<Args&&>(args))...);
    }
};

struct IntRes
{
    int id;
    explicit IntRes(const int id): id(id) {}
};

struct StringRes
{
    std::string id;
    explicit StringRes(std::string id): id(std::move(id)) {}
};

int main()
{
    Factory factory;
    std::cout << factory.get<IntRes>(42).id << std::endl;
    std::cout << factory.get<StringRes>("hello").id << std::endl;
}

关于c++ - 如何将 std::tuple 转换为 std::any?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70239155/

相关文章:

c++ - 如何给 Rand() 条件

c++ - 在 Qt 5.2.1 上编写的 SSL 服务器的 SSL 握手失败

c++ - 我可以 co_await 一个 io_context 在协程中由另一个在 Asio 中执行的操作吗?

c++ - 来自 CWnd 的 ReleaseDC 覆盖来自 winuser 的 ReleaseDC

c++ - Boost spirit解析字符串以前缀开头

c++ - 以紧凑的方式引用嵌套类

c++ - 范围修剪 View 实现不适用于反向 View

c++ - 如何在 if-else 语句中使用 C++20 的可能/不太可能属性

C++20 "transparently replaceable"关系

c++ - 究竟标准的哪些部分需要涉及运算符 < 的重大更改?