【问题标题】:Calling an argument-permutation-invariant function f(i++, i++)调用参数置换不变函数 f(i++, i++)
【发布时间】:2019-02-25 02:06:47
【问题描述】:

假设我有一个函数 foo(x, y, z) 是不变的 w.r.t。其论点的所有排列。我还有一个迭代器it,这样迭代器itit + 1it + 2 可以被取消引用。

可以写吗

... = foo(*it++, *it++, *it++);  // (1)

而不是

... = foo(*it, *(it + 1), *(it + 2));  // (2)

?

据我了解,由于(引用 cppreference.com 并考虑到 it 可以是原始指针),从 C++17 开始技术上是正确的

15) 在函数调用中,每个参数初始化的值计算和副作用相对于任何其他参数的值计算和副作用是不确定的。

未定义函数参数的求值顺序,但对于foo(),顺序无关紧要。

但这是一种可接受的编码风格吗?一方面,(1) 非常对称,暗示foo 具有这样的不变性,而(2) 看起来有些丑陋。另一方面,(1) 立即对其正确性提出质疑——阅读代码的人应检查 foo 的描述或定义以验证调用的正确性。

如果foo()的body很小,从函数定义看不变性很明显,你会接受(1)吗?

(可能这个问题是基于意见的。但我忍不住问了。)

【问题讨论】:

  • "但这是一种可接受的编码风格吗?"没有。
  • 要详细说明@Barry 的评论,即使它是合法且有效的,它提出如此复杂的问题这一事实很好地表明它不应该出现在生产代码中。您希望您的代码显而易见且易于理解,这显然不是,因此这不是“可接受的编码风格”。
  • 这不仅仅是因为它向阅读它的人提出了问题。这行代码在传递给 C++14 编译器时会中断,这也是一个大问题。如果代码库的其余部分不依赖于 C++17 特性,那么很容易犯这样的错误。
  • @TimRandall 在 Q 的正文中提到了 据我了解,从 C++17 开始技术上是正确的。此外,如果 Q 被标记为 C++17,则意味着它正在谈论 C++17。这就是标签系统的用途。我们不想在标题中添加标签

标签: c++ c++17


【解决方案1】:

你在 C++17 中是正确的

foo(*it++, *it++, *it++);

不是未定义的行为。正如您的报价所述,以及[expr.call]/8

中所述

后缀表达式在表达式列表中的每个表达式和任何默认参数之前排序。参数的初始化,包括每个相关的值计算和副作用,相对于任何其他参数的初始化顺序是不确定的。

每个增量都是排序的,因此您不会有多个未排序的写入。在 C++17 中,函数参数的求值顺序仍未指定,这意味着您只是有未指定的行为(您无法知道将哪个元素传递给每个参数)。

只要你的函数不关心这个,那么你就可以了。如果参数的顺序很重要,那么您将不得不使用您的第二个版本。


都说我更喜欢使用

foo(*it, *(it + 1), *(it + 2));

即使顺序无关紧要。它使代码向后兼容,恕我直言,它更容易推理。我宁愿在 for 循环的增量部分看到 it += 3,然后在函数调用中看到多个增量,并且在循环的增量部分没有增量。

【讨论】:

  • 第一个变体可以与基本的输入迭代器一起使用,但不是这样。
  • 您能否引用标准中声明每个参数评估的副作用在任何其他参数评估开始之前完成的部分? (好吧,如果没有,我只是想看看)
  • @Yakk-AdamNevraumont 我在标准中找到了相关部分。
  • @NathanOliver 看,我阅读了一些导致改变的论文和评论,所以我知道他们意味着要说什么。我只是说我不确定他们说得有多好。 ;)
  • @Deduplicator: "第一个变体可以与基本的输入迭代器一起使用,而替代方案则不然。" 不,它不能。输入迭代器返回的reference 在与其关联的迭代器增加后不能使用。或者更确切地说,InputIterator 不要求返回的reference 在递增迭代器后仍然有效。 iostream 迭代器一直这样做。现在,Forward/BidirectionalIterators确实有这个要求,而且它们不能添加整数。
猜你喜欢
  • 1970-01-01
  • 2019-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多