What does the void() in decltype(void()) mean exactly?
Using a hyperlinked C++ grammar, the parsing of decltype(void())
is:
decltype( expression )
decltype( assignment-expression )
decltype( conditional-expression )
... lots of steps involving order of operations go here ...
decltype( postfix-expression )
decltype( simple-type-specifier ( expression-listopt ) )
decltype( void() )
So void()
is a kind of expression
here, in particular a postfix-expression
.
Specifically, quoting section 5.2.3 [expr.type.conf] paragraph 2 of the 2011 ISO C++ standard:
The expression
T()
, whereT
is a simple-type-specifier or
typename-specifier for a non-array complete object type or the (possibly cv-qualified)void
type, creates a prvalue of the
specified type, which is value-initialized (8.5; no initialization is
done for thevoid()
case).
So void()
is an expression of type void
, just as int()
is an expression of type int
(with value 0
). Clearly a void expression has no value, but here it's the operand of decltype
, so it's not evaluated. decltype
refers only to its operand's type, not its value.
decltype(void())
is simply a verbose way of referring to the type void
.
Differences between decltype(void()) and decltype(void{})
void()
is interpreted as type-id when used with sizeof
.void()
is interpreted as an expression when used with decltype
.
I don't think void{}
is valid in any context. It is neither a valid type-id nor a valid expression.
What does the 'void()' in 'auto f(params) - decltype(..., void())' do?
Since it is an expression that comma is simply the comma operator (meaning the type is the type of the rhs side: void
), not another argument.
That code is using SFINAE - it's enabled if t.reserve(n)
exists but it wants to keep the return type as void
.
Expression void() and its validity with ternary conditional
What exactly does void() evaluate, if it does that at all, to?
Here, void()
is in a context where the grammar expects an expression rather than a type. As such, it has the same meaning as in the linked question What does the void() in decltype(void()) mean exactly? i.e. it is a prvalue expression of type void
.
Why is it not possible to simply omit one side of the ternary conditional?
The grammar of the language does not allow that.
How is void() useful?
The statement
void();
creates a void value and then discards it. (You can't actually do much with a void value other than discard it or return it.)
The standard† says in 5.2.3 [expr.type.conv
The expression T(), where T is a simple-type-specifier or typename-specifier for a non-array complete object
type or the (possibly cv-qualified) void type, creates a prvalue of the specified type, whose value is that
produced by value-initializing (8.5) an object of type T; no initialization is done for the void() case
Note that it explictaly calls out that void()
is legal.
† My link is to N4296 which was the last public committee draft before C++14, however the various versions of the standard do not vary here.
Edit
Is it useful? Explicitly like this? No. I can't see a use for it. It is however, useful in template functions which sometimes do something like:
template <typename T>
T foo() {
if (prepare_for_for()) {
return do_foo();
} else {
return T();
}
}
And this will work, even for T
== void
.
Is sizeof(void()) a legal expression?
void()
is a function type (it's a function which takes no arguments and returns nothing), so it's not a valid type in sizeof()
.
How to handle void decltype();
Unfortunately, I think you're stuck here with having to SFINAE on the return type. Start with a type trait (this is way cleaner than your decltype
expression)
template <typename MF, typename... Args>
using Res = typename std::result_of<MF(C, Args...)>::type;
And then just switch:
template <typename MF, typename... Args>
typename std::enable_if<
std::is_same<Res<MF, Args...>, void>::value
>::type safeOperation(string key, MF mfp, Args... args)
{
/* void case */
}
template <typename MF, typename... Args>
typename std::enable_if<
!std::is_same<Res<MF, Args...>, void>::value,
Res<MF, Args...>
>::type safeOperation(string key, MF mfp, Args... args)
{
/* non-void case */
}
Or you could tag dispatch on is_void
:
template <typename MF, typename... Args>
Res<MF, Args...> safeOperation(string key, MF mfp, Args... args)
{
return safeOperation(std::is_void<Res<MF, Args...>>{},
key, mfp, args...);
}
with:
template <typename MF, typename... Args>
void safeOperation(std::true_type /* void */,
string key, MF mfp, Args... args)
{ .. }
template <typename MF, typename... Args>
Res<MF, Args...> safeOperation(std::false_type /* non-void */,
string key, MF mfp, Args... args)
{ .. }
Related Topics
Why, Really, Deleting an Incomplete Type Is Undefined Behaviour
Create a Reverse Linkedlist in C++ from a Given Linkedlist
Member Fields, Order of Construction
Detecting Simulated Keyboard/Mouse Input
Why Is Locking a Std::Mutex Twice 'Undefined Behaviour'
Is There a Functional Difference Between "2.00" and "2.00F"
Where Would You Use a Friend Function VS. a Static Member Function
Does 'Auto' Type Assignments of a Pointer in C++11 Require '*'
Difference Between 'Strcpy' and 'Strcpy_S'
How to Pass Command Line Arguments to a C Program
How to Use Boost Preprocessor to Generate Accessors
Cpu Dispatcher for Visual Studio for Avx and Sse
Understanding Stack Frame of Function Call in C/C++
How to Make Cout Behave as in Binary Mode
Why Does Nvcc Fails to Compile a Cuda File with Boost::Spirit