我在 C++ 中有以下
Zip
类,它与 Python 的zip
. 一样工作
当我运行下面的代码时,我得到这个输出:
1 | 11
2 | 22
3 | 33
1 | 11 | 0 <--- problematic
2 | 22 | 6.91092e-317 <--- problematic
3 | 33 | 9
在第二种情况下,我另外创建了一个临时的
std::vector<int>{7, 8, 9}
,看起来这个向量的生命周期在Zip
构造函数退出后就结束了。这就是为什么我们看到错误的值 0
和 6.91092e-317
.
我想知道为什么会这样。因为在内部
Zip
将其元素存储为 const Ts&
。所以一个 const 引用。在父Zip
对象被破坏(=在for循环之后)之前,不应该延长临时向量的生命周期吗?有什么办法可以解决这个问题吗?
示例代码,https://godbolt.org/z/4n6j83fn5:
#include <tuple>
#include <vector>
#include <iostream>
template<class... Ts>
class Zip
{
public:
explicit Zip(const Ts&... objs)
: m_data(objs...) { }
struct ZipIterator
{
public:
explicit ZipIterator(const std::tuple<const Ts&...>& data, std::size_t idx)
: m_data(data), m_idx(idx) { }
ZipIterator& operator++()
{
++m_idx;
return *this;
}
bool operator!=(const ZipIterator& rhs) const
{
return m_idx != rhs.m_idx;
}
auto operator*() const
{
return std::apply([this](auto const&... obj) { return std::forward_as_tuple(obj.at(m_idx)...); }, m_data);
}
private:
const std::tuple<const Ts&...>& m_data;
std::size_t m_idx;
};
ZipIterator begin() const
{
return ZipIterator(m_data, 0);
}
ZipIterator end() const
{
return ZipIterator(m_data, std::get<0>(m_data).size());
}
private:
std::tuple<const Ts&...> m_data;
};
int main()
{
const std::vector<double> vec1{1, 2, 3};
const std::vector<double> vec2{11, 22, 33};
for (const auto& [v1, v2] : Zip(vec1, vec2))
{
std::cout << v1 << " | " << v2 << std::endl;
}
std::cout << std::endl;
for (const auto& [v1, v2, v3] : Zip(vec1, vec2, std::vector<double>{7, 8, 9}))
{
std::cout << v1 << " | " << v2 << " | " << v3 << std::endl;
}
return 0;
}
@BoP 完美地回答 你关于“为什么”的问题。此答案为您的问题提供了可能的解决方案。
修复涉及一个
MaybeOwning
类,如果它是从非临时构造的,则按引用存储,如果它是从临时构造的,则按值存储。为了让它工作,您需要将构造函数包装在工厂函数中 zip
以正确转发正确的类型。这就是为什么 Zip
的构造函数在这个实现中是私有的。
#include <tuple>
#include <vector>
#include <iostream>
template <typename E>
struct MaybeOwning
{};
template <typename E>
struct MaybeOwning<E&&>
{
MaybeOwning(E&& e) : _val(e) {}
E& get() { return _val; };
E const& get() const { return _val; };
E _val;
};
template <typename E>
struct MaybeOwning<E&>
{
MaybeOwning(E& e) : _val(e) {}
E& get() { return _val; };
E const& get() const { return _val; };
E& _val;
};
template<class... Ts>
class Zip
{
template <typename... Args>
friend auto zip(Args&&...);
private:
explicit Zip(Ts&&... objs)
: m_data(MaybeOwning<Ts>(std::forward<Ts>(objs))...) { }
public:
struct ZipIterator
{
public:
explicit ZipIterator(const std::tuple<MaybeOwning<Ts>...>& data, std::size_t idx)
: m_data(data), m_idx(idx) { }
ZipIterator& operator++()
{
++m_idx;
return *this;
}
bool operator!=(const ZipIterator& rhs) const
{
return m_idx != rhs.m_idx;
}
auto operator*() const
{
return std::apply([this](auto const&... obj) { return std::forward_as_tuple(obj.get().at(m_idx)...); }, m_data);
}
private:
const std::tuple<MaybeOwning<Ts>...>& m_data;
std::size_t m_idx;
};
ZipIterator begin() const
{
return ZipIterator(m_data, 0);
}
ZipIterator end() const
{
return ZipIterator(m_data, std::get<0>(m_data).get().size());
}
private:
std::tuple<MaybeOwning<Ts>...> m_data;
};
template <typename... Ts>
auto zip(Ts&&... ts){
return Zip<Ts&&...>(std::forward<Ts>(ts)...);
}
int main()
{
const std::vector<double> vec1{1, 2, 3};
const std::vector<double> vec2{11, 22, 33};
for (const auto& [v1, v2] : zip(vec1, vec2))
{
std::cout << v1 << " | " << v2 << std::endl;
}
std::cout << std::endl;
for (const auto& [v1, v2, v3] : zip(vec1, vec2, std::vector<double>{7, 8, 9}))
{
std::cout << v1 << " | " << v2 << " | " << v3 << std::endl;
}
return 0;
}
1 | 11
2 | 22
3 | 33
1 | 11 | 7
2 | 22 | 8
3 | 33 | 9
explicit Zip(const Ts&... objs)
: m_data(objs...) { }
生命周期延长仅适用于直接绑定,临时绑定到
objs
参数。
终身延长不转移给
m_data
会员
这是基于@joergbrech 的回答的补充。使用 class template argument deduction 而不是 wrapper function.
template<class... Ts>
class Zip
{
public:
template <typename... T>
explicit Zip(T&&... objs)
: m_data(MaybeOwning<T&&>(std::forward<T>(objs))...) {}
};
template <typename... Ts> Zip(Ts&&...) -> Zip<Ts&&...>;