7

此代码示例是否有效?

using ref = char&;

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

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

似乎:

  • clang 3.5 说是
  • 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};
                   ^
    

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)...};
}
4

1 回答 1

3

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

从 C++11(强调我的):

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

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

[...]

3 类似地,一个simple-type-specifiertypename-specifier后跟一个braced-init-list 使用指定的braced -init-list创建一个指定类型 direct-list-initialized (8.5.4) 的临时对象,并且它的value 是那个临时对象作为 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也是一个例外,其中功能转换不一定会产生纯右值。该标准未能为使用花括号初始化列表到引用类型的功能转换指定类似的异常。因此,在您的示例中,ref{x}构造了一个临时类型ref,直接列表初始化 from {x}。然后将该临时值视为纯右值,因为这就是标准所说的行为应该是的,并且该纯右值不能用于绑定到左值引用。

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

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

template <typename Tp, typename... Us>
Tp bar(Us&&... us) {
  return {std::forward<Us>(us)...};
}
于 2015-01-28T20:27:16.517 回答