【问题标题】:Expand parameter pack into tuple with tuple_cat使用 tuple_cat 将参数包扩展为元组
【发布时间】:2021-07-04 13:09:12
【问题描述】:

天箭链接:https://godbolt.org/z/18nseEn4G

我有一个包含各种类型向量(强制转换为 void*)的 std::map 和一个 T& get<T> 方法,它为我提供了对映射中一个向量中的元素的引用。

class Container {
public:
    Container() {
        auto v1 = new std::vector<int>({1, 2, 3, 4, 5});
        auto v2 = new std::vector<char>({'a','b','c','d','e'});
        auto v3 = new std::vector<double>({1.12, 2.34, 3.134, 4.51, 5.101});

        items.insert({
            std::type_index(typeid(std::vector<int>)),
            reinterpret_cast<void*>(v1)
        });
        items.insert({
            std::type_index(typeid(std::vector<char>)),
            reinterpret_cast<void*>(v2)
        });
        items.insert({
            std::type_index(typeid(std::vector<double>)),
            reinterpret_cast<void*>(v3)
        });
    }

    template<typename T>
    T& get(int index) {
        auto idx = std::type_index(typeid(std::vector<T>));
        auto ptr = items.at(idx);
        auto vec = reinterpret_cast<std::vector<T>*>(ptr);
        return (*vec)[index];
    }

private:
    std::map<std::type_index, void*> items {};
};

我希望能够使用结构化绑定来取回对 3 个元素的引用,这些元素都位于相同的索引但在不同的向量中,但我不确定如何创建一个对 T&amp; get&lt;T&gt; 方法进行多次调用的元组。 像这样的东西;

auto [a, b, c] = myContainer.get_all<int, char, double>(1); // get a reference to an int, a char, and a double from myContainer at index 1. 

我目前正在尝试对参数包中的每个参数重复调用T&amp; get&lt;T&gt;,但我无法找出正确的语法。

template<typename... Ts>
auto get_all(int index) {
    return std::tuple_cat<Ts...>(
        std::make_tuple<Ts>(get<Ts>(index)...)
    );

我怎样才能做到这一点? 这是我当前尝试的链接: https://godbolt.org/z/18nseEn4G

或者,有没有“更好的方法”来实现这一点?

【问题讨论】:

  • 使用void*typeid 通常是不好的迹象。特别是没有RAII。这可能会使用类型擦除以更安全的方式实现。
  • 当然,我可以在 void* 上使用 std::any,但我只是在 Godbolt 上做一个快速而肮脏的例子,作为以后更具体实施的基础。同样的原因我没有删除任何这些分配或将它们放入智能指针中,我只是想要一个快速而肮脏的基线。
  • 我在答案中提供了一个示例类型擦除实现

标签: c++ variadic-templates parameter-pack


【解决方案1】:

我建议使用类型擦除。这是一个例子:

#include <vector>
#include <typeindex>
#include <memory>
#include <any>
#include <unordered_map>
#include <iostream>
#include <experimental/propagate_const>

// If no library implementation is availble, one may be copied from libstdc++
template<class T>
using propagate_const = std::experimental::propagate_const<T>;

class Container
{
public:
    Container() {
        std::unique_ptr<Eraser> v1{ static_cast<Eraser*>(new ErasedVector<int>(1, 2, 3, 4, 5)) };
        std::unique_ptr<Eraser> v2{ static_cast<Eraser*>(new ErasedVector<char>('a','b','c','d','e')) };
        std::unique_ptr<Eraser> v3{ static_cast<Eraser*>(new ErasedVector<double>(1.12, 2.34, 3.134, 4.51, 5.101)) };

        items[std::type_index(typeid(int))] = std::move(v1);
        items[std::type_index(typeid(char))] = std::move(v2);
        items[std::type_index(typeid(double))] = std::move(v3);
    }

    template<typename... Ts>
    std::tuple<Ts&...> get(size_t index)
    {
        return {
            std::any_cast<std::reference_wrapper<Ts>>((*items.find(std::type_index{typeid(Ts)})->second)[index]).get()...
        };
    }
    template<typename... Ts, typename = std::enable_if_t<(std::is_const_v<Ts> && ...)>>
    std::tuple<Ts&...> get(size_t index) const
    {
        return {
            std::any_cast<std::reference_wrapper<Ts>>((*items.find(std::type_index{typeid(Ts)})->second)[index]).get()...
        };
    }
private:
    class Eraser
    {
    public:
        virtual std::any operator[](size_t index) = 0;
        virtual std::any operator[](size_t index) const = 0;
        virtual ~Eraser() = default;
    };
    template <typename T>
    class ErasedVector : public Eraser
    {
    public:
        template <typename... Args>
        ErasedVector(Args&&... args) :
            data{ std::forward<Args>(args)... }
        {
        }

        virtual std::any operator[](size_t index) override final
        {
            return std::reference_wrapper{ data[index] };
        };
        virtual std::any operator[](size_t index) const override final
        {
            return std::reference_wrapper{ data[index] };
        }
    private:
        std::vector<T> data;
    };

    std::unordered_map<std::type_index, propagate_const<std::unique_ptr<Eraser>>> items;
};

在这个例子中可以正常工作:

int main()
{
    Container co;
    auto [i0_0, c0_0, d0_0] = co.get<int, char, double>(0);
    std::cout << i0_0 << ' ' << c0_0 << ' ' << d0_0 << '\n';
    i0_0 = 3; // is a reference
    d0_0 = 42; // is a reference
    auto [i0_1, d0_1] = static_cast<const Container&>(co).get<const int, const double>(0); // works on const Container
    std::cout << i0_1 << ' ' << d0_1; // original values modified
    // i0_1 = 0xDEADBEEF; can be const too
}

然后输出:

1 a 1.12
3 42

Demo

【讨论】:

    【解决方案2】:

    简单地说:

    template<typename... Ts>
    auto get_all(int index) {
        return std::tuple<Ts&...>(get<Ts>(index)...);
    }
    

    Demo

    【讨论】:

      【解决方案3】:

      您可以使用std::tie 获取来自get&lt;Ts&gt;(index) 的所有“返回”,然后将它们打包到引用元组中。看起来像

      template<typename... Ts>
      auto get_all(int index) {
          return std::tie(get<Ts>(index)...);
      }
      

      【讨论】:

        猜你喜欢
        • 2023-03-09
        • 2010-12-31
        • 2012-11-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多