Oli*_*ock 6 c++ strict-aliasing undefined-behavior stdarray
我可以使用 astd::array<int, N>来为 a 的部分别名int[]而不调用 UB 吗?
https://en.cppreference.com/w/cpp/container/array “此容器是一个聚合类型,其语义与将 C 样式数组 T[N] 作为其唯一非静态数据成员的结构体具有相同的语义。 ”
动机:copy下面的函数不受我的控制,需要通过引用进行单个分配。只有 a struct { int[N]; }like astd::array<int, N>才能进行那种“多对象赋值”?
这是UB吗?
还有别的办法吗?
#include <iostream>
#include <array>
template <std::size_t N>
void print(int (&arr)[N], std::size_t number_rows, std::size_t number_cols) {
assert(number_rows * number_cols == N);
for (std::size_t r = 0; r != number_rows; ++r) {
for (std::size_t c = 0; c != number_cols; ++c) {
std::cout << arr[r * number_cols + c] << ' ';
}
std::cout << '\n';
}
std::cout << '\n';
}
void copy(std::array<int, 4>& a, std::array<int, 4>& b) {
b = a;
}
int main() {
int vals[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
print(vals, 4, 4);
auto s1 = reinterpret_cast<std::array<int, 4>*>(&vals[0]);
auto s2 = reinterpret_cast<std::array<int, 4>*>(&vals[4]);
copy(*s2, *s1);
print(vals, 4, 4);
}
Run Code Online (Sandbox Code Playgroud)
输出
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
5 6 7 8
5 6 7 8
9 10 11 12
13 14 15 16
Run Code Online (Sandbox Code Playgroud)
感谢所有的评论/答案。根据大众的要求,我发布了更广泛的问题以获取更多背景信息。
我将分两个级别来完成。
这是我想做的下一层:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
5 6 7 8
5 6 7 8
9 10 11 12
13 14 15 16
Run Code Online (Sandbox Code Playgroud)
但ranges::remove抱怨说这ranges::view::chunk是“不可改变的”。这已在这里得到确认:
https ://github.com/ericniebler/range-v3/issues/1760
所以我的下一个尝试是编写一个“分块范围”,我可以将其传递给 range::remove。我通过多种方式做到了这一点。有几个“有效”但基于 UB,包括使用 std::array<int,4> 作为“块代理”的这种方式(以及上面的 OP):
#include "range/v3/algorithm/remove.hpp"
#include "range/v3/view/chunk.hpp"
#include <vector>
int main() {
std::vector<int> v{
1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12,
13, 14, 15, 16
};
auto chunked = ranges::views::chunk(v, 4);
auto it = ranges::remove(chunked, 9, [](const auto& e) { return e[0]; }); // <== compile error
// expected result (xx = unspecified)
// std::vector<int> v{
// 1, 2, 3, 4,
// 5, 6, 7, 8,
// 13, 14, 15, 16,
// xx, xx, xx, xx
// };
// and it pointing at chunked[3] (ie the row of xx)
}
Run Code Online (Sandbox Code Playgroud)
输出(根据需要但基于 UB)
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
1 2 3 4
5 6 7 8
13 14 15 16
13 14 15 16
Run Code Online (Sandbox Code Playgroud)
之所以非常热衷于使用范围,是因为我想要的算法还有另一层,因为实际上有一个一维并行向量,我将其与分块向量压缩在一起,然后删除条件基于一维向量。
请注意,这里两个向量都相当大(~100-500k 项),所以我想避免制作副本。这就是为什么我不使用|组合和惰性ranges::views::filter,而是使用渴望 ranges::remove来修改原始容器(两者都需要修改)。
下面的代码“对我有用”,但包含根据OP的UB:
#include "range/v3/algorithm/remove.hpp"
#include "range/v3/view/chunk.hpp"
#include "range/v3/view/zip.hpp"
#include <iostream>
#include <iterator>
#include <vector>
class Integers {
public:
struct Iterator {
using chunk = std::array<int, 4>;
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = chunk;
using pointer = value_type*;
using reference = value_type&;
template <class ForwardIt, class UnaryPredicate, class ChunkedForwardIt>
ForwardIt remove_if_par(ForwardIt first, ForwardIt last, UnaryPredicate p,
ChunkedForwardIt chunked_first, std::ptrdiff_t chunk_size) {
auto first_orig = first;
first = std::find_if(first, last, p);
// advance chunked_first in lockstep. TODO this is linear compelxity unless random_access_iter
std::advance(chunked_first, std::distance(first_orig, first) * chunk_size);
if (first != last) {
ForwardIt i = first;
ChunkedForwardIt chunk_i = chunked_first;
while (++i != last) {
std::advance(chunk_i, chunk_size);
if (!p(*i)) {
*first++ = std::move(*i);
// move chunk
auto loop_chunk_i = chunk_i;
for (std::ptrdiff_t ci = 0; ci != chunk_size; ++ci)
*chunked_first++ = std::move(*loop_chunk_i++);
}
}
}
return first;
}
Iterator();
Iterator(int* ptr) : current_row_(reinterpret_cast<chunk*>(ptr)) {} // <== UB here
reference operator*() const { return *current_row_; }
pointer operator->() { return current_row_; }
Iterator& operator++() {
++current_row_;
return *this;
}
Iterator operator++(int) {
Iterator tmp = *this;
++(*this);
return tmp;
}
friend bool operator==(const Iterator& a, const Iterator& b) {
return a.current_row_ == b.current_row_;
}
friend bool operator!=(const Iterator& a, const Iterator& b) {
return a.current_row_ != b.current_row_;
}
private:
chunk* current_row_;
};
Iterator begin() { return Iterator(&data_[0]); }
Iterator end() { return Iterator(&data_[16]); }
int data_[16];
};
template <std::size_t N>
void print(int (&arr)[N], std::size_t number_rows, std::size_t number_cols) {
assert(number_rows * number_cols == N);
for (std::size_t r = 0; r != number_rows; ++r) {
for (std::size_t c = 0; c != number_cols; ++c) {
std::cout << arr[r * number_cols + c] << ' ';
}
std::cout << '\n';
}
std::cout << '\n';
}
int main() {
Integers chunked{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
print(chunked.data_, 4, 4);
auto it = ranges::remove(chunked, 9, [](const auto& e) { return e[0]; });
print(chunked.data_, 4, 4);
Run Code Online (Sandbox Code Playgroud)
输出(根据需要但基于 UB):
1 2 3 4 | 10
5 6 7 8 | 20
9 10 11 12 | 30
13 14 15 16 | 40
1 2 3 4 | 10
5 6 7 8 | 20
13 14 15 16 | 40
Run Code Online (Sandbox Code Playgroud)
我当前最好的替代方案是使用处理“chunk 4”逻辑的混乱原始循环来手动编写“zip”和“remove”代码。下面是其中的一个版本,它基本上是实现的修改版本std::remove:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
1 2 3 4
5 6 7 8
13 14 15 16
13 14 15 16
Run Code Online (Sandbox Code Playgroud)
输出(根据需要且无 UB)
10 | 1 2 3 4
20 | 5 6 7 8
30 | 9 10 11 12
40 | 13 14 15 16
50 | 17 18 19 20
60 | 21 22 23 24
70 | 25 26 27 28
10 | 1 2 3 4
30 | 9 10 11 12
50 | 17 18 19 20
70 | 25 26 27 28
50 | 17 18 19 20
60 | 21 22 23 24
70 | 25 26 27 28
10 | 1 2 3 4
30 | 9 10 11 12
50 | 17 18 19 20
70 | 25 26 27 28
Run Code Online (Sandbox Code Playgroud)
我还没有检查过,但我怀疑为此“原始迭代器循环”版本生成的汇编代码至少与任何基于替代方案的版本一样好range;可能更好。
正如您所注意到的,chunked_view是不可变的。您可以做的是adjacent<N> | stride(n)创建一个可变的分块视图(假设N在编译时已知):
std::vector data {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
std::vector b{10, 20, 30, 40};
constexpr std::size_t chunk_size = 4;
auto chunked_view = data
| std::views::adjacent<chunk_size>
| std::views::stride(chunk_size);
auto zipped_view = std::views::zip(chunked_view, b);
auto removed_range = std::ranges::remove(zipped_view, 30, [](auto pair){ return std::get<1>(pair); });
data.resize(data.size() - removed_range.size() * chunk_size);
b.resize(b.size() - removed_range.size());
Run Code Online (Sandbox Code Playgroud)
现在data将是1,2,3,4,5,6,7,8,13,14,15,16。
需要注意的一件事是adjacent创建所有元素的引用元组,因此您无法迭代元组。但是,您可以使用第一个元素的地址 和 来创建span或,因为基础数据是连续的:views::countedchunk_size
for (auto& [tuple, key] : zipped_view) {
for (auto i : std::span(&std::get<0>(tuple), chunk_size)) std::cout << i << ' ';
std::cout << " | " << key << '\n';
}
Run Code Online (Sandbox Code Playgroud)
如果需要,您还可以删除所有临时视图,因为从删除函数中您需要的只是删除了多少个元素:
std::vector data {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
std::vector b{10, 20, 30, 40};
constexpr std::size_t chunk_size = 4;
auto removal_size =
std::ranges::remove(
std::views::zip(
data | std::views::adjacent<chunk_size> | std::views::stride(chunk_size)
, b)
, 30, [](auto pair){ return std::get<1>(pair); }
).size();
data.resize(data.size() - removal_size * chunk_size);
b.resize(b.size() - removal_size);
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
293 次 |
| 最近记录: |