当返回语句时,逗号运算符、大括号初始化列表和 std::unique_ptr 组合在一起

When return statement, comma operator, braced-init-list and std::unique_ptr come together

本文关键字:std 列表 unique 在一起 组合 ptr 初始化 语句 返回 运算符      更新时间:2023-10-16

在下面的代码中,为什么我得到n = 8的编译错误,而所有其他情况都可以?我的目的是报告一些错误并返回空指针,而不会用不必要的大括号弄乱代码。我将为此目的使用 nullptr,但我很好奇为什么{}无法使用逗号运算符进行编译,而它只能单独工作。

您可以在此处使用此代码。我使用了C++20设置。

#include <cstdint>
#include <memory>
void oops(const char*) {
}
std::unique_ptr<uint8_t[]> please_do(int n) {
if (n == 1)
return std::unique_ptr<uint8_t[]>(); // compiles ok
if (n == 2)
return oops(""), std::unique_ptr<uint8_t[]>(); // compiles ok
if (n == 3)
return std::make_unique<uint8_t[]>(n); // compiles ok
if (n == 4)
return oops(""), std::make_unique<uint8_t[]>(n); // compiles ok
if (n == 5)
return nullptr; // compiles ok
if (n == 6)
return oops(""), nullptr; // compiles ok
if (n == 7)
return {}; // compiles ok
if (n == 8)
return oops(""), {}; // why compilation error?
return nullptr; // compiles ok
}
int main() {
please_do(42);
return 0;
}

GCC 9.2 输出:

<source>: In function 'std::unique_ptr<unsigned char []> please_do(int)':
<source>:26:26: error: expected primary-expression before '{' token
26 |         return oops(""), {}; // why compilation error?
|                          ^
<source>:26:25: error: expected ';' before '{' token
26 |         return oops(""), {}; // why compilation error?
|                         ^~
|                         ;

Clang 9.0.0 输出:

<source>:26:16: error: no viable conversion from returned value of type 'void' to function return type 'std::unique_ptr<uint8_t []>'
return oops(""), {}; // why compilation error?
^~~~~~~~
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/bits/unique_ptr.h:528:7: note: candidate constructor not viable: cannot convert argument of incomplete type 'void' to 'std::unique_ptr<unsigned char [], std::default_delete<unsigned char []> > &&' for 1st argument
unique_ptr(unique_ptr&& __u) noexcept
^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/bits/unique_ptr.h:533:12: note: candidate constructor template not viable: cannot convert argument of incomplete type 'void' to 'std::nullptr_t' (aka 'nullptr_t') for 1st argument
constexpr unique_ptr(nullptr_t) noexcept
^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/bits/unique_ptr.h:681:7: note: candidate constructor not viable: cannot convert argument of incomplete type 'void' to 'const std::unique_ptr<unsigned char [], std::default_delete<unsigned char []> > &' for 1st argument
unique_ptr(const unique_ptr&) = delete;
^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/bits/unique_ptr.h:542:2: note: candidate template ignored: could not match 'unique_ptr<type-parameter-0-0, type-parameter-0-1>' against 'void'
unique_ptr(unique_ptr<_Up, _Ep>&& __u) noexcept
^
<source>:26:24: error: expected expression
return oops(""), {}; // why compilation error?
^
{}

不是表达式。return {...};是它自己的特殊语法(列表初始化的一种形式(,它从函数的签名中找出返回类型,就像通过Ret{...}Ret是返回类型一样:

Ret f() {
return {...}; // special syntax for return; {args...} need not be a valid expression
}
// same as
Ret f() {
return Ret{...}; // generic return <expression> syntax; Ret{args...} must be a valid expression
}

return a, b中没有特殊的语法。它只是使用正常的return语法返回a, b表达式。逗号运算符要求ab都是表达式,但由于{}不是表达式,因此像oops(""), {}这样的表达式是无效语法。

顺便说一句,还有其他地方可以使用{...},使其看起来像一个表达式,而实际上并非如此,例如函数调用:

void f(std::string);
f({5, 'a'})

同样,尽管这看起来像{5, 'a'}是类型为std::string的表达式(这是意图(,但事实并非如此。{5, 'a'}是函数调用本身的一部分,其类型由重载解析决定。

至于编译器错误,它们都因oops(""), {}作为表达式的无效语法而混淆。GCC似乎已经读取了oops(""),,因此期待一个表达式跟随。但是表达式不能以{开头,所以它会读到那个字符并立即吠叫,抱怨它期待一个表达式开始。叮当也做同样的事情。我认为 Clang 然后继续,为了"有帮助",假装你只是没有写, {};部分(也就是说,好像你写了return oops("");(,并为此发出错误。例如,这种行为允许 Clang 提供有用的类型错误,即使您拼错了某些内容;也就是说,如果您要运行 Clang

void supercalifragilisticexpialidocious(std::string);
int main() { supercalifragilisticexpialidociuos(7); }

它将首先为错误的拼写(-ous -> -uos(发出错误,建议正确的拼写,然后它会为int7和参数类型之间的类型不匹配发出错误std::string就像您已经修复了拼写一样。同样,在这里,它为错误的语法发出一个错误,然后发出第二个错误,就好像你已经摆脱了错误的语法一样。但是,在这种情况下,它不是很有帮助,因为它想象您使用的"修复"不是实际的修复。

{}没有类型,所以逗号运算符oops(""), {}是错误的。

return {};对副本初始化有特殊处理。