19

I came across void(); being used as a 'do-nothing' in the 'else' branch of a ternary operator, as a shorthand for a null pointer check

if(var){
   var->member();
}

as

var ? var->member() : void();

but I can't seem to find any reference to the void keyword being used in this way, is this a function or functor call on the void keyword itself? or is it casting nothing to the type of void? or is this just the c++ syntax of something like pass?

Edit: The return type of member() is void in this situation.

Jay
  • 2,553
  • 3
  • 17
  • 37
  • 20
    You will do yourself a favor if you completely forget that you saw this code, and promise that you'll never write something this ugly, yourself. – Sam Varshavchik Apr 11 '20 at 21:06
  • 2
    The real question you should ask is what imbecile wrote the alternative, when the former is as expressive and yet clear in intent as you will *ever* need to be, and the latter seems purposeful in denying both of those attributes to the reader of the code. – WhozCraig Apr 11 '20 at 21:09
  • 2
    @SamVarshavchik: Some languages (like C#) have a null-conditional or null-coalescing (or Elvis) operator. For people coming from such a language, a one-liner for the same in c++ could make sense. – André Apr 12 '20 at 09:38
  • 2
    Why would someone write this instead of the `if (var) var->member();` one-liner? – user7761803 Apr 12 '20 at 12:02
  • 1
    @JosephSible-ReinstateMonica It turned out that I must have remembered wrong. As @T.C. pointed out correctly in my (now deleted) answer, `void` was not allowed as a type in `constexpr` functions in C++11, making my answer non-sense. – walnut Apr 12 '20 at 18:11
  • @André you're absolutely correct, I came from c# and was looking for something similar to ```var?.member()``` which I naively assumed was shorthand for ```var ? var.member()```, akin to the ```+=``` shorthand, but specifically for null pointer checks. Seems I was wrong – Jay Apr 12 '20 at 18:30

4 Answers4

11

You are just "constructing" a prvalue (not a variable, for the reason suggested in the comments) of type void, just as int() would default-construct an int.

As others said in the comments, the second alternative is pejorative. The ternary operator is, well, ternary because it has the if, the then, and the else parts. If you don't need an else, why would you write one and leave it empty?

That alternative is even uglier and more cryptic than this,

if(var){
   var->member();
} else {}

which may just look stupid.

Enlico
  • 23,259
  • 6
  • 48
  • 102
  • 2
    @WhozCraig Curiously, **[expr.type.conv]** has this to say: "If the type is `cv void` and the initializer is `()`, the expression is a prvalue of the specified type that performs no initialization." Which suggests that it's possible to have a prvalue of type `void` (not an lvalue though, which is what "variable" kinda implies). – Igor Tandetnik Apr 11 '20 at 21:13
8

Assuming var->member() has type void,

var ? var->member() : void();

has type void and either evaluates var->member() or evaluates void() if var is null.

Now, void() is an expression; according to [expr.type.conv]/2, it just does nothing:

if the type is cv void and the initializer is () or {} (after pack expansion, if any), the expression is a prvalue of the specified type that performs no initialization.

YSC
  • 38,212
  • 9
  • 96
  • 149
  • 2
    And (alas) `void()` isn't a value or a dummy non-value. Can be used as a `void Foo() { return void(); }` (why would you do so? Templates where T is `void` and you need to return `T()` and you don't have to do SFINAE shenanigans just to handle the T = `void` edge case). But can't be used calling `Foo(void());` because that'd be too obvious. C++ doesn't have a an actual _bottom type_. It is barely has a _unit type_ (i.e., `std::monostate` ... but it also has 0-tuple `std::tuple<>`, and any degenerate struct `struct Unit_t {};`). Or whatever the right terminology is for "degenerate" struct. – Eljay Apr 11 '20 at 23:33
  • @Eljay and I'd love to be able to write `return f();` in a function with the same return type as `f` regardless of that to be `void` or not. – YSC Apr 22 '20 at 07:05
  • `void foo() { }` should be able to be used with `void bar() { return foo(); }` or `auto baz() { return foo(); }` or `void quux(bool pred) { return pred ? foo() : void(); }`. – Eljay Apr 22 '20 at 11:03
7

void is a type, but if it's followed by () it does initialization of a prvalue of type void.

The reason for doing such a thing, is that the return type of var->member() is void, and the second and third operands of ?: operator must be the same. Note: They don't have to be exactly the same; there are conversion rules that say when the the types can be different.

You have shown one way to get a void prvalue for one of the operands, but there are a number of ways to achieve the same effect,

var ? var->member() : throw 42;

a throw expression has the void type, so this compiles. It won't do nothing if var is nullptr of course, since it throws.

This statement will compile and do nothing,

var ? var->member() : []{}();

where the second operand is an anonymous function returning void.

and this one,

var ? var->member() : decltype(var->member())(); 

which in my opinion says most clearly, "I'm trying to get the same type in both operands".

That being said, I don't see why one would ever write this code. If there's no meaningful else branch, then there is already the if construct in the language, and the conditional ?: operator is the wrong tool for the job.

Edit: @walnut's (now deleted) answer actually shows a use-case: in c++11, but pre-c++14, the ?: operator is the only way to express conditional branches in constexpr functions.

Donald Duck
  • 8,409
  • 22
  • 75
  • 99
cigien
  • 57,834
  • 11
  • 73
  • 112
  • "the second and third operands of `?:` operator must be the same" - not really. A `throw` expression can pair with any other expression, a _cv_ `void` type must pair with another _cv_ `void` type, or else when the types are different it must be possible to implicitly convert from one type to the other. – aschepler Apr 11 '20 at 22:28
  • @aschepler thanks, I added a caveat, and a link to the rules. – cigien Apr 11 '20 at 22:46
1

I've only seen such code written by people who were 'old motorists', or so to say. A void() call basically does nothing, and ternary operators require you to put something in the else branch, so people do this sometimes. if (stuff) { stuff->member(); } would also be a one-liner.

  • 1
    You don't even need `{` and `}`, if it's only one statement. – Enlico Apr 11 '20 at 22:41
  • This does not answer the question. The OP understands that this is used as a no-op here and wants to understand it syntactically. – Carsten S Apr 12 '20 at 08:33
  • I think OP's question was: "or is this just the c++ syntax of something like pass?" And my answer states (or was meant to state anyways) that "ternary operators require you to put _something_ in the else branch". Maybe I should make that clearer. – Dávid Kiss Apr 12 '20 at 12:25
  • `stuff ? stuff->member() : throw my_null_error("stuff");` works too, as a special case of the ternary in that the two branches don't have the same type. – Eljay Apr 22 '20 at 11:09