Why is it sometimes necessary to cast to (void)?

Hi,

I have this code from a book, but do not understand why there are so many casts to (void):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
constexpr auto call_cart = [=](auto f, auto x, auto ...rest) constexpr
{
        (void)initializer_list<int>{
	       (((x < rest)
		? (void)f(x, rest)
		: (void)0), 0)...
	};
};

constexpr auto cartesian = [=](auto...xs) constexpr
{
	return[=](auto f) constexpr {
		(void)initializer_list<int>{
			((void)call_cart(f, xs, xs...), 0)...
		};
	};
};



Thanks for your input!!

Juan Dent
It is optional syntax. It's just used to say "I know this function can return something, but I have no interest in the result of this function or value".
https://stackoverflow.com/questions/689677/why-cast-unused-return-values-to-void

There was another forum post that asked this same question a while back, but I can't find it.
the cast on zero seems kind of nuts. Does that serve some purpose??
the cast on zero seems kind of nuts. Does that serve some purpose?

The void cast on 0 is probably so it matches the void on the other possible value of the conditional operator; I think both values are supposed to be of the same type, although void is a strange type.

To OP:
Overall I feel the void casts are pointless and unnecessary here.
Where did you get this code?
Last edited on
@OP: Please do not write your ternary functions like in this example.
@OP: Please do not write your ternary functions like in this example.


Why not? I mean if its harmless, it looks kind of cool, you know?

Because once you stare into the void, you can't ever unsee it.

If there's a good reason, its always helpful to share.
Reading has more to do with pattern recognition then it does straight up see what's there
and translate. Introducing weird patterns like this slows people down without actually making it any easier to read. If this were a common pattern, people would get used to it. But I don't think I have ever seen a ternary written like this before.
In case it isn't clear, I agree with the others that this should be avoided. Ha, haha... Apparently it used to be helpful with old static analysis tools. But, I too, have never actually seen it used in code, modern or old, and agree it would add more confusion without any benefit.
Stare into the void? Indeed :+)

With regard to splitting the ternary like that: On one hand splitting things where there are sub-expressions is sometimes helpful ; on the other hand it does seem confusing, for me because of the mismatch in parentheses on line 6. I know it's because they match the whole ternary from line 4, but it makes it harder to read. Maybe these opening and closing parentheses could be on a line of their own?

@JUAN DENT
What's more confusing is the cast to void of a std:: (?) initializer_list ; and the comma operator with a 0 on line 6.

So the cast to void is a discarded value expression, relying on side effects. But it seems to apply to all of the lambda, what are the side effects?

So what does this code do? Does it call function f when x is less than rest (a parameter pack, ergo a fold expression) but casts it to void, (so what are the side effects of function f ? ) ; else set values to 0.

Also the constexpr at the end of lines 1 and 10, I have never seen that before ?

It would be great to see some input / output for this code.

Was this code from the Template Meta Programming book by Vandervoode & Josuttis ?
What's more confusing is the cast to void of a std:: (?) initializer_list ; and the comma operator with a 0 on line 6.

The purpose of the std::initializer_lists are to sequence the evaluations of the sub-expressions that result from those ellipses. For instance, It makes sure that call_cart() is evaluated in-order WRT the arguments to cartesian(). This is important when arguments have side effects.

Since it's hard to explain without code, here's an example of a similar trick (with int[] instead of initializer_list) being used in context:
http://www.cplusplus.com/forum/general/207656/

casting the initializer_list to void avoids compiler warnings.

The constexpr qualifies the lambda - it's a new C++17 feature. Now lambdas can appear in constant expressions (finally.)
Furthermore, we have fold-expressions, which make this trick obsolete:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

// wrong: no capture-default allowed for lambdas at namespace scope
constexpr auto call_cart = [/* = */](auto f, auto x, auto... rest) constexpr {
  (((x < rest) ? (void)f(x, rest) : (void)0), ...);
};

constexpr auto cartesian = [/* = */](auto... xs) constexpr {
  return [=](auto f) constexpr { (call_cart(f, xs, xs...), ...); };
};

int main() {
  auto f = [](auto x, auto y) { std::cout << x << ", " << y << '\n'; };
  cartesian(1, 2, 3, 4)(f);
}


Demo:
http://coliru.stacked-crooked.com/a/a775ce08d4afecde
Last edited on
Cheers, thanks for that Max :+D

The extra context makes it easier to understand.
Topic archived. No new replies allowed.