代码之家  ›  专栏  ›  技术社区  ›  mpen

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

  •  0
  • mpen  · 技术社区  · 2 年前

    以下是我正在尝试的:

    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”转换<常量字符(&)[78],_Ty&&>'到“性病::任何”

    可以追溯到这里:

    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


    我想做的是:

    我正试图为我的游戏建立一些资产/资源的缓存。e、 g.如果我想在两个不同的地方使用相同的字体,我不想加载两次(这涉及从磁盘读取字体,并将其转换为纹理,然后上传到GPU)。因为资源有句柄,所以它们的析构函数必须被确定地调用(例如,当卸载一级时,工厂将被摧毁)。

    当然,我可以手动完成这一切,但我不想跟踪我在哪里使用24px FontA和32px FontB,并手动在游戏中传输这些对象。我只想要一个通用的缓存,我可以把所有东西都倒进里面。那么如果我以前使用过这个特定的资产,很好,它会被重复使用,如果没有,它可以制造一个新的。如果我后来决定放弃该级别或资产或您拥有的东西,我只需删除get<>它已经消失了,我不需要回溯,找到我用管道输送的每一个地方。

    0 回复  |  直到 2 年前
        1
  •  3
  •   Chlorie    2 年前

    还记得那种类型的擦除设备吗 std::any std::function 仅公开接口 他们只承诺: 有吗 封装了复制能力,但仅此而已,因此您无法 比较等式/哈希a 有吗 对象使用额外的 std::函数 只是为了存储 operator< 如果很麻烦(基本上每种类型都使用两个vtable),那么你就更好了 使用手卷式擦除。

    此外,根据您的需求推断,您 不得不 特例 const char* const char(&)[N] 参数,因为您希望将它们存储为 std::string ,以及它们的比较运算符。这 还解决了“存储 std::tuple 参考成员 有吗 “问题。(有关更多讨论,请参见编辑注释#2。)

    在某些地方,你的godbolt链接中的代码是不正确的,尤其是当你通过时 T的构造函数构造 有吗 (错过了 std::in_place_type<T> 在前线, 也就是说)。

    为了方便起见,下面的实现使用C++20,但它可以在 经过一些修改的旧标准。

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

    编辑#2 :是的,特例的诀窍 常量字符* 不太好,而且它阻止了 常量字符* 不工作。你可以把它改写成“只需衰减每个参数,不需要对每个参数采取任何特殊措施” 常量字符* 常量字符(&)[N] ,这将适用于所有的c'Tor。但这也只适用于传入字符串文本的情况,否则可能会在哈希映射中存储一个悬空指针。如果您指定了真正希望通过其传递引用的每个位置,这种方法可能是可行的 std::string (例如,通过使用UDL-like "hello"s 或者显式地构造 std::string ).
    AFEK不能获得Ccor的参数类型,因为C++明确禁止使用Ccor的地址,如果不能形成指向成员函数的指针,就不能在其上做模板技巧。此外,超负荷解决可能是实现这一目标的另一个障碍。

    编辑#3 :我没有注意到会有不可复制的对象要缓存。在这种情况下, 有吗 没有用,因为它只能存储可复制的对象。使用类似类型的擦除技术,也可以存储不可复制的对象。我的实现只是使用 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;
    }
    
        2
  •  1
  •   MSalters    2 年前

    正如评论中指出的,问题不在于 std::tuple 样板具体来说,代码中的这一部分:

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

    这显然是一组引用。你甚至不能把 a中的引用 std::any 更不用说一个元组了。如果你能接受副本,就把参考资料放在这里。C++中没有规则,模板参数包必须被转发为引用的元组。

    至于问题2,“如果不是,我如何在地图中使用任意参数作为键?”-不能。地图键必须有偏序。甚至 float fontSize 已经有点问题了,如果有人通过 NaN .

        3
  •  0
  •   viraltaco_    2 年前

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

    #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");
    }
    

    我想不出比这更好的办法了。这能解决你面临的问题吗?因为我很难理解确切的担忧。
    Compiler Explorer