返回统一初始化引用有效吗?

Is returning uniform initialized reference valid?

此代码示例有效吗?

using ref = char&;

ref foo(ref x) {
  return ref{x};
}

int main() {
  char a;
  foo(a);
  return 0;
}

似乎:

http://coliru.stacked-crooked.com/a/cb6604b81083393f

那么哪个编译器是对的呢?还是未指定?

这很容易克服 gcc 构建错误:

  1. 使用圆括号代替大括号

    ref foo(ref x) {
      return ref(x);
    }
    
  2. 通过命名返回值

    ref foo(ref x) {
      ref ret{x};
      return ret;
    }
    

选项 1. 破坏统一初始化,选项 2. 添加无用的代码行。

这里已经提出了类似的问题: Why can't I initialize a reference in an initializer list with uniform initialization?

但提到的 pr50025 已在 gcc 4.9 中修复。

我知道上面的代码示例很没用, 但我故意过度简化它以指出这个问题。 在现实生活中,代码问题可以隐藏在通用函数中,例如:

#include <utility>
template <typename Tp, typename... Us>
Tp bar(Us&&... us) {
  return Tp{std::forward<Us>(us)...};
}

这似乎是标准中的一个遗漏,GCC 正在执行标准所要求的内容,而 clang 正在执行可能的意图。

来自 C++11(强调我的):

5.2.3 Explicit type conversion (functional notation) [expr.type.conv]

1 A simple-type-specifier (7.1.6.2) or typename-specifier (14.6) followed by a parenthesized expression-list constructs a value of the specified type given the expression list. If the expression list is a single expression, the type conversion expression is equivalent (in definedness, and if defined in meaning) to the corresponding cast expression (5.4). [...]

[...]

3 Similarly, a simple-type-specifier or typename-specifier followed by a braced-init-list creates a temporary object of the specified type direct-list-initialized (8.5.4) with the specified braced-init-list, and its value is that temporary object as a prvalue.

对于 braced-init-list 的情况,标准没有指定它像 C 风格的转换一样工作。它不会:

typedef char *cp;
int main() {
  int i;
  (cp(&i)); // okay: C-style casts can be used as reinterpret_cast
  (cp{&i}); // error: no implicit conversion from int * to char *
}

不幸的是,T(expr) 等价于 (T)expr 也是一个例外,其中函数转换 不一定 产生纯右值。该标准未能为使用 braced-init-list 到引用类型的功能转换指定类似的异常。因此,在您的示例中,ref{x} 构造了一个 ref 类型的临时对象,直接列表从 {x} 初始化。然后将该临时值视为纯右值,因为这就是标准所说的行为应该是什么,并且该纯右值不能用于绑定到左值引用。

我强烈怀疑如果将此提交给 ISO C++ 委员会,标准将更改为要求 clang 的行为,但根据标准的当前措辞,我认为 GCC 是正确的,至少对于你的具体例子。

您可以省略 ref (Tp) 来避免问题,而不是添加变量或切换到括号:

template <typename Tp, typename... Us>
Tp bar(Us&&... us) {
  return {std::forward<Us>(us)...};
}