返回统一初始化参考有效吗?

Kar*_*iak 7 gcc reference clang uniform-initialization c++11

此代码示例是否有效?

using ref = char&;

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

int main() {
  char a;
  foo(a);
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

似乎:

  • clang 3.5说YES
  • gcc 4.9说没有

    main.cpp: In function 'char& foo(ref)':
    main.cpp:4:15: error: invalid cast of an rvalue expression of type 'char' to type 'ref {aka char&}'
       return ref{x};
                   ^
    
    Run Code Online (Sandbox Code Playgroud)

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

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

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

  1. 使用括号而不是括号

    ref foo(ref x) {
      return ref(x);
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 通过命名返回值

    ref foo(ref x) {
      ref ret{x};
      return ret;
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

类似的问题在这里已经解决了: 为什么我不能在初始化列表中使用统一初始化初始化引用?

但是提到的pr50025在gcc 4.9中是固定的.

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

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

小智 3

这似乎是标准中的一个遗漏,其中 GCC 正在准确地实现标准所要求的内容,而 clang 正在实现可能的预期目的。

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

5.2.3 显式类型转换(函数表示法)[expr.type.conv]

1简单类型说明符(7.1.6.2) 或类型名称说明符(14.6) 后跟带括号的表达式列表,在给定表达式列表的情况下构造指定类型的值。如果表达式列表是单个表达式,则类型转换表达式与相应的强制转换表达式 (5.4) 等效(在定义方面,并且如果在含义上定义)。[...]

[...]

3 类似地,简单类型说明符类型名说明符后跟花括号初始化列表会使用指定的花括号初始化列表创建指定类型 direct-list-initialized (8.5.4) 的临时对象,及其value 是作为纯右值的临时对象

对于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 *
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,T(expr)等价于(T)expr也是一种例外,其中函数强制转换不一定会产生纯右值。该标准未能为使用花括号初始化列表到引用类型的函数强制转换指定类似的异常。因此,在您的示例中,构造了一个类型为、 direct-list-initialized 的ref{x}临时对象。然后,该临时值被视为纯右值,因为这就是标准规定的行为应该是什么,并且该纯右值不能用于绑定到左值引用。ref{x}

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

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

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