What Does the Void() in Decltype(Void()) Mean Exactly

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(), 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, which is value-initialized (8.5; no initialization is
done for the void() 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



Leave a reply



Submit