GCC中的赋值求值顺序与C++17

yhxst69z  于 2023-08-06  发布在  其他
关注(0)|答案(1)|浏览(98)

C++17在赋值表达式中定义了求值顺序:右手侧评估及其副作用被排序在左手侧评估及其副作用之前。但是,下面的代码显示,GCC中的“聚合赋值”违反了这一点:

#include <iostream>
#include <type_traits>

struct Trivial {
  int x;
};

struct NonTrivial {
  constexpr NonTrivial(int x = 0) : x{x} { }

  int x;
};

constexpr int foo() {
  Trivial a[1]{};
  int i = 0;
  a[i++] = {+i};
  return a->x;
}

int postincrement(int& i) {
  std::cout << "postincrement\n";
  return i++;
}

int load(int& i) {
  std::cout << "load\n";
  return i;
}

int main() {
  std::cout << "--aggregate assignment--\n";
  std::cout << std::integral_constant<int, foo()>() << '\n';
  std::cout << foo() << '\n';
  {
    Trivial a[1]{};
    int i = 0;
    a[i++] = Trivial{i * i};
    std::cout << a->x << '\n';
  }

  std::cout << "\n--is not even indeterminately sequenced--\n";
  {
    Trivial a[1]{};
    int i = 0;
    (std::cout << "LHS\n", a[postincrement(i)])
        = (std::cout << "RHS\n", Trivial{load(i)});
    std::cout << a->x << '\n';
  }

  std::cout << "\n--copy assignment OK--\n";
  // {
  //   NonTrivial a[1]{};
  //   int i = 0;
  //   a[i++] = NonTrivial{i};
  //   std::cout << a->x << '\n';
  // }
  {
    NonTrivial a[1]{};
    int i = 0;
    a[i++] = NonTrivial{*&i};
    std::cout << a->x << '\n';
  }

  std::cout << "\n--scalar assignment OK--\n";
  // {
  //   Trivial a[1]{};
  //   int i = 0;
  //   a[i++].x = +i;
  //   std::cout << a->x << '\n';
  // }
  {
    Trivial a[1]{};
    int i = 0;
    a[i++].x = +*&i;
    std::cout << a->x << '\n';
  }
}

字符串
第一个打印的值是0,正如预期的那样。第二个是0与O2,但1与O0。第三个值为1,第四个示例还显示右侧和左侧求值是交错的(输出RHS LHS postincrement load 1),并且代码不会导致警告。第五个到第八个,不再使用“聚合赋值”,为0,注解掉的会导致警告,其他的不会。Clang将所有输出评估为0,并且不显示警告。据我从程序集判断,Msvc没有显示任何警告,并且只对第一个明显为常量求值的输出表示不同意。godbolt链接与这些和其他几个案件。
我见过几个类似的SO问题和GCC bug报告,但它们要么是多年前的问题,要么是在程序行为正确的情况下发出过多的警告。我试着检查C++17标准中的相关段落,但没有发现任何与赋值表达式求值顺序有关的“聚合赋值”异常。
在提交bug报告之前,我想确保我没有遗漏任何东西。GCC(和msvc)的行为真的不正确吗?

v7pvogib

v7pvogib1#

该标准仅规定,在对左操作数求值之前,对右操作数求值。
然而,在右手操作数{i, 0}{i}中,唯一相关的求值是i,而没有任何左值到右值的转换,即在该评估中从不读取i的值。
i的值只在以后调用operator=(X&&)XTrivialNonTrivial)时读取,该赋值解析到operator=(X&&),以便从{i, 0}构造临时对象来绑定X&&参数。
该标准仅对“operand”的求值进行了说明。我不清楚这是否包括临时构造(或为此应用于此类操作数的任何转换)。

相关问题