C++ 在结构上应用std算法?
我们有,我们有。如果我们将两者结合起来,我们可能有一种方法可以在结构上应用std算法。解决方案是否已经存在?我要找的是:C++ 在结构上应用std算法?,c++,multithreading,struct,boost,tuples,C++,Multithreading,Struct,Boost,Tuples,我们有,我们有。如果我们将两者结合起来,我们可能有一种方法可以在结构上应用std算法。解决方案是否已经存在?我要找的是: S a, b; auto const ra(to_range(a)), rb(to_range(b)); std::transform(ra.begin(), ra.end(), rb.begin(), [](auto&& a)noexcept{return a;}); 这将允许我们使用较新的功能来处理无序结构或并行结构。您已经可以使用pfr::struct
S a, b;
auto const ra(to_range(a)), rb(to_range(b));
std::transform(ra.begin(), ra.end(), rb.begin(), [](auto&& a)noexcept{return a;});
这将允许我们使用较新的
功能来处理无序结构或并行结构。您已经可以使用pfr::structure\u tie
拥有现有功能,这将产生一个引用元组
这将允许我们使用较新的特性来处理无序或并行的结构
只有当每个元素的处理具有相当大的执行成本时,这才有意义。在这种情况下,你可能已经可以定制了
boost::pfr::for_each_field(var, [](auto& field) { field += 1; });
调用为了说明我在中尝试提出的观点,让我们编写一些类似于您的转换的内容: 直接实施 我跳过了迭代器和标准库的概念,因为它充满了整个“迭代器值类型必须固定”和其他负担 相反,让我们“按功能”来做 让我们演示一下,突出显示混合类型成员、非对称类型以及混合长度结构:
struct S1 { int a; double b; long c; float d; };
struct S2 { double a; double b; double c; double d; };
struct S3 { double a; double b; };
测试用例:
int main() {
auto n_ary = [](auto&... fields) {
puts(__PRETTY_FUNCTION__);
return (... = fields);
};
S1 a;
S2 b;
S3 c;
// all directions
transform(binary, a, b);
transform(binary, b, a);
// mixed sizes
transform(binary, b, c);
transform(binary, c, a);
// why settle for binary?
transform(n_ary, a, b);
transform(n_ary, a, b, c);
transform(n_ary, c, b, a);
}
查看它
分解已经表明,一切都在内联和优化。字面上只剩下puts
调用:
main:
sub rsp, 8
mov edi, OFFSET FLAT:.LC0
call puts
mov edi, OFFSET FLAT:.LC1
call puts
mov edi, OFFSET FLAT:.LC2
call puts
...
...
xor eax, eax
add rsp, 8
ret
给出输出
main()::<lambda(const auto:12&, auto:13&)> [with auto:12 = int; auto:13 = double]
main()::<lambda(const auto:12&, auto:13&)> [with auto:12 = double; auto:13 = double]
main()::<lambda(const auto:12&, auto:13&)> [with auto:12 = long int; auto:13 = double]
main()::<lambda(const auto:12&, auto:13&)> [with auto:12 = float; auto:13 = double]
main()::<lambda(const auto:12&, auto:13&)> [with auto:12 = double; auto:13 = int]
main()::<lambda(const auto:12&, auto:13&)> [with auto:12 = double; auto:13 = double]
main()::<lambda(const auto:12&, auto:13&)> [with auto:12 = double; auto:13 = long int]
main()::<lambda(const auto:12&, auto:13&)> [with auto:12 = double; auto:13 = float]
main()::<lambda(const auto:12&, auto:13&)> [with auto:12 = double; auto:13 = double]
main()::<lambda(const auto:12&, auto:13&)> [with auto:12 = double; auto:13 = double]
main()::<lambda(const auto:12&, auto:13&)> [with auto:12 = double; auto:13 = int]
main()::<lambda(const auto:12&, auto:13&)> [with auto:12 = double; auto:13 = double]
main()::<lambda(auto:14& ...)> [with auto:14 = {int, double}]
main()::<lambda(auto:14& ...)> [with auto:14 = {double, double}]
main()::<lambda(auto:14& ...)> [with auto:14 = {long int, double}]
main()::<lambda(auto:14& ...)> [with auto:14 = {float, double}]
main()::<lambda(auto:14& ...)> [with auto:14 = {int, double, double}]
main()::<lambda(auto:14& ...)> [with auto:14 = {double, double, double}]
main()::<lambda(auto:14& ...)> [with auto:14 = {double, double, int}]
main()::<lambda(auto:14& ...)> [with auto:14 = {double, double, double}]
所以这两个操作都是右折叠乘法赋值。给出了一些明确选择的初始值设定项
S1 a {1,2,3,4};
S2 b {2,3,4,5};
S3 c {3,4};
我们希望能够看到通过数据结构的数据流。因此,让我们有选择地对其进行一些调试跟踪:
#define DEMO(expr) \
void(expr); \
if constexpr (output_enabled) { \
std::cout << "After " << std::left << std::setw(26) << #expr; \
std::cout << " a:" << pfr::io(a) << "\tb:" << pfr::io(b) \
<< "\tc:" << pfr::io(c) << "\n"; \
}
DEMO("initialization");
// all directions
DEMO(nway_visit(binary, a, b));
DEMO(nway_visit(binary, b, a));
// mixed sizes
DEMO(nway_visit(binary, b, c));
DEMO(nway_visit(binary, c, a));
// why settle for binary?
DEMO(nway_visit(n_ary, a, b));
DEMO(nway_visit(n_ary, a, b, c));
DEMO(nway_visit(n_ary, c, b, a));
return long(c.a + c.b) % 37; // prevent whole program optimization...
演示在启用输出的情况下,在禁用输出的情况下显示拆解:
main:
mov eax, 13
ret
哇!
神圣的烟。这就是优化。整个程序是静态计算的,只返回退出代码13。让我们看看这是否是正确的退出代码:
已启用输出:
因此,返回值应该是,袖珍计算器确认为13
从这里开始:并行任务等。
您最初的动机包括免费从标准库获得并行执行选项。如你所知,这很有用
但是,几乎没有什么可以阻止您从该算法中获得这种行为:
boost::asio::thread_pool ctx; // or, e.g. system_executor
auto run_task = [&](auto&... fields) {
boost::asio::post(ctx, [=] { long_running_task(fields...); });
};
希望这是一个很好的启示。谢谢你让我看PFR。这很好。看起来像是用PFR函数(在该元组迭代器中)简单地替换标准元组函数就可以了。但是请注意,
a
将是一个std::variant
。是的,我希望有人能做出更实质性的东西:)不错,但元组迭代器只是一个玩具实现。我把一些东西放在一起,它为我的目的工作。看起来它假设所有成员都是(可转换为)第一个元素的类型?你没有提到这样一个大大简化的要求。也许我误读了不,你没有,我只是得出了和你一样的结论,任何选择都太慢了。一种变体会让事情变得缓慢。如果你有一个很好的简化假设的解决方案,我很有兴趣看到它。“一个变体会把事情拖慢到爬行。”-需要证据。我说:当然不是。给定静态类型,编译器可以100%内联和解约,因为它分配内存,这对性能是致命的。这里有一个黑客版本,只是为了表明std::variant
不需要产生分配开销:(不要使用此代码,对于泛型使用它是不一致的;特别是我“模拟的”*=
将结果赋回最左边的操作数的行为)。优化程度较低,但看不到堆。(boost::variant也是如此,尽管我不得不对三元调用进行注释,因为boost::apply\u visitor
不支持这一点)这确实是一个令人大开眼界的答案。就std::execution::par
而言,我同意你的怀疑,但std::execution::unseq
有时会产生令人瞠目结舌的SIMD代码。
#define DEMO(expr) \
void(expr); \
if constexpr (output_enabled) { \
std::cout << "After " << std::left << std::setw(26) << #expr; \
std::cout << " a:" << pfr::io(a) << "\tb:" << pfr::io(b) \
<< "\tc:" << pfr::io(c) << "\n"; \
}
DEMO("initialization");
// all directions
DEMO(nway_visit(binary, a, b));
DEMO(nway_visit(binary, b, a));
// mixed sizes
DEMO(nway_visit(binary, b, c));
DEMO(nway_visit(binary, c, a));
// why settle for binary?
DEMO(nway_visit(n_ary, a, b));
DEMO(nway_visit(n_ary, a, b, c));
DEMO(nway_visit(n_ary, c, b, a));
return long(c.a + c.b) % 37; // prevent whole program optimization...
return long(c.a + c.b) % 37; // prevent whole program optimization...
main:
mov eax, 13
ret
After "initialization" a:{1, 2, 3, 4} b:{2, 3, 4, 5} c:{3, 4}
After nway_visit(binary, a, b) a:{2, 6, 12, 20} b:{2, 3, 4, 5} c:{3, 4}
After nway_visit(binary, b, a) a:{2, 6, 12, 20} b:{4, 18, 48, 100} c:{3, 4}
After nway_visit(binary, b, c) a:{2, 6, 12, 20} b:{12, 72, 48, 100} c:{3, 4}
After nway_visit(binary, c, a) a:{2, 6, 12, 20} b:{12, 72, 48, 100} c:{6, 24}
After nway_visit(n_ary, a, b) a:{24, 432, 576, 2000} b:{12, 72, 48, 100} c:{6, 24}
After nway_visit(n_ary, a, b, c) a:{1728, 746496, 576, 2000} b:{12, 72, 48, 100} c:{6, 24}
After nway_visit(n_ary, c, b, a) a:{1728, 746496, 576, 2000} b:{12, 72, 48, 100} c:{124416, 1289945088}
boost::asio::thread_pool ctx; // or, e.g. system_executor
auto run_task = [&](auto&... fields) {
boost::asio::post(ctx, [=] { long_running_task(fields...); });
};