In the code below, why do I get compilation error for n = 8
whereas all other cases are OK? My intent was to report some error and return empty pointer without cluttering the code with unnecessary braces. I will use nullptr for this purpose but I'm curious why {}
fails to compile with comma operator whereas it works alone.
You can play with this code here. I used C++20 setting.
#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 output:
<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 output:
<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?
^
{}
is not an expression. return {...};
is its own special syntax (a form of list initialization) that figures out the return type from the function's signature, as if by Ret{...}
where Ret
is the return type:
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
}
There is no special syntax in return a, b
. It's just the expression a, b
being returned using the normal return
syntax. The comma operator requires both a
and b
to be expressions, but since {}
is not an expression, expressions like oops(""), {}
is invalid syntax.
As an aside, there are other places where {...}
can be used that make it look like it's an expression when it's actually not, e.g. function calls:
void f(std::string);
f({5, 'a'})
Again, though this looks like {5, 'a'}
is an expression with type std::string
(as is the intention), it is not. The {5, 'a'}
is part of the function call itself, and its type is decided by overload resolution.
As to the compiler errors, both of them are being confused by the fact that oops(""), {}
is invalid syntax as an expression. GCC appears to have read oops(""),
, and is thus expecting an expression to follow. But expressions cannot start with {
, so it reads that character and immediately barfs, complaining that it was expecting an expression to begin. Clang does the same thing. I think that Clang then continues, in its quest to be "helpful", to pretend that you just didn't write the , {};
part (that is, as if you wrote return oops("");
), and issues an error for that, too. This behavior is e.g. what allows Clang to provide useful type errors even if you misspell something; that is, if you were to run Clang on
void supercalifragilisticexpialidocious(std::string);
int main() { supercalifragilisticexpialidociuos(7); }
it would first issue an error for the bad spelling (-ous -> -uos
), suggest the correct spelling, and then it would issue an error for the type mismatch between the int
7
and the argument type std::string
as if you had already fixed the spelling. Similarly, here, it's issuing one error for the bad syntax and then a second error as if you had gotten rid of the bad syntax. However, it's not very helpful in this case since the "fix" it imagines you using is not the actual fix.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With