繁体   English   中英

如何将 std::tuple 转换为 std::any?

[英]How to cast std::tuple to std::any?

这是我正在尝试的:

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));

错误是:

错误 C2440: '': 无法从 'std::tuple<const char (&)[78],_Ty &&>' 转换为 'std::any'

可以追溯到这里:

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

Font c'tor 在哪里:

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

我的问题:

  1. 我可以将任意 args 转换为std::any吗?
  2. 如果没有,我如何使用任意参数作为 map 中的键?

完整代码如下:

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> ...

Godbolt 示例


我正在尝试做的事情:

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

当然,我可以手动完成这一切,但我不想跟踪我在哪里使用 24px FontA 和 32px FontB 并手动 pipe 我的游戏周围的那些对象。 我只想要可以将所有内容转储到的通用缓存。 然后,如果我以前使用过该特定资产,那太好了,它会被重新使用,如果没有,它可以制作一个新的。 如果我后来决定废弃该级别或资产或您拥有的东西,我只需删除 get<> 并且它就消失了,我不必回溯并找到我通过管道传输它的每个地方。

Remember that type erasure facilities like std::any and std::function only exposes the interface they promise and nothing more: std::any encapsulates copy-ability but nothing more, thus you cannot compare equality / hash a std::any object . 使用额外的std::function来存储operator<很麻烦(这实际上是为每种类型使用两个 vtable),最好使用手动类型擦除。

此外,根据您的要求,您必须对特殊情况const char*const char(&)[N]参数进行特殊处理,因为您希望它们存储为std::string以及它们的比较运算符。 这也解决了您的“使用std::any中的引用成员存储std::tuple ”问题。

您的godbolt链接中的代码在某些地方不正确,尤其是您将arguments传递给T的构造函数以构造std::any (即缺少前面的std::in_place_type<T> )。

为方便起见,以下实现使用 C++20,但可以通过一些修改使其在旧标准下工作。

#include <iostream>
#include <unordered_map>
#include <any>
#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 std::any_cast<const T&>(iter->second);
        std::any 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 std::any_cast<const T&>(iter->second);
    }

private:
    // Type erasure
    struct KeyModel
    {
        virtual ~KeyModel() noexcept = default;
        virtual std::size_t hash() const = 0;
        virtual bool equal(const KeyModel& other) const = 0;
        virtual std::any 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_;
        }

        std::any construct() const override
        {
            return std::apply([](const Args&... args)
            {
                return std::any(std::in_place_type<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, std::any, 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;
}

如评论中所述,问题不是std::tuple模板。 它特别是您的代码中的这一部分:

const T &get(Args &&... args) {
    auto fwd_args = std::forward_as_tuple(std::forward<Args>(args)...);

这显然是一个引用元组。 您甚至不能在std::any中放置一个引用,更不用说它们的元组了。 如果您可以使用副本,只需将参考资料放在此处即可。 C++ 中没有规定必须将模板参数包作为引用元组转发。

至于 Q2,“如果没有,我如何使用任意 args 作为 map 中的键?” - 你不能。 Map 密钥必须具有部分排序。 如果有人通过NaN ,即使是float fontSize也已经有点问题了。

忽略您提供的代码,并阅读“我正在尝试做什么”。
这就是我所拥有的:

#include <map>
#include <any>
#include <string_view>

class LevelCache {
  std::map<const std::string_view, std::any> self_;

public:
  template <class T> auto get(std::string_view key) const -> T const& {
    // If it's not there what on earth do I return? Throw. You handle it.
    return std::any_cast<T const&>(self_.at(key));
  }

  auto operator [](std::string_view key) -> std::any& {
    return self_[key]; // not const. Used for insert
  }
};

#include <iostream>
auto main() -> int {
  auto lvl = LevelCache();
  lvl["Some resource"] = 1;
  lvl["a string"] = "some string"; // Not sure if UB, or not. 

  std::cout << "lvl[\"Some resource\"] -> " << lvl.get<int>("Some resource") << '\n'
            << "lvl[\"a string\"] -> " << lvl.get<char const*>("a string");
}

我想不出比我头上更好的东西了。 这是否解决了(部分)您面临的问题? 因为我很难理解确切的问题。
编译器资源管理器

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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