Casting to void to avoid use of overloaded user-defined Comma operator

Viewed 153

I am learning about templates in C++, and came across an example where casting to void is used:

template<typename T>
auto func (T const& t) -> decltype( (void)(t.size()), T::size_type() )
{
return t.size();

}

In the explanation it is written that:

The cast of the expression to void is to avoid the possibility of a user-defined comma operator overloaded for the type of the expressions.

My question(s) is/are:

  1. How can a cast to void be used to "avoid the possibility of a user-defined comma operator overloaded for the type of the expressions"? I mean, can anyone give any example where if we don't use void then this code would give an error? For example, let's say we have a class called SomeClass which has overloaded the comma operator. Now, can this become a problem if we don't use void?

  2. Can static_cast be used in this case instead of a C style cast? For example, something like static_cast<void>(t.size()). I am reading examples that use C++17 features, and so I wonder why the author has used a C style cast in this case.

I have read What does casting to `void` really do?, from which I get the impression that if we use (void)x then this means to suppress compiler warnings, and also means "ignore the value of x". But then I can't understand the difference between the expression x and (void)x.

1 Answers

Consider this pathological type:

struct foo {
    struct size_type {
        bool operator,(size_type) { return false;}
    };
    size_type size() { return {};}  
};

It does have a size_type and it does have a size() method. However, without the cast to void, the template does not deduce the right return type, because decltype( (t.size()), typename T::size_type() ) is bool :

#include <type_traits>

template<typename T>
auto func (T const& t) -> decltype( (t.size()), typename T::size_type() )
{
return t.size();

}

struct foo {
    struct size_type {
        bool operator,(size_type) { return false;}
    };
    size_type size() const { return {};}  
};


int main()
{
   func(foo{});
}

Results in error:

<source>:6:8: error: no viable conversion from returned value of type 'foo::size_type' to function return type 'decltype((t.size()) , typename foo::size_type())' (aka 'bool')
return t.size();
       ^~~~~~~~
<source>:20:4: note: in instantiation of function template specialization 'func<foo>' requested here
   func(foo{});
   ^
1 error generated.
ASM generation compiler returned: 1
<source>:6:8: error: no viable conversion from returned value of type 'foo::size_type' to function return type 'decltype((t.size()) , typename foo::size_type())' (aka 'bool')
return t.size();
       ^~~~~~~~
<source>:20:4: note: in instantiation of function template specialization 'func<foo>' requested here
   func(foo{});
   ^

A static_cast can be used. However, as nothing is actually being cast (it is an unevaluated context), the c-style cast does not do much harm. Note how by using the cast to void the user defined operator, is bypassed and the correct return type is deduced: https://godbolt.org/z/jozx1YGWr. This is because void, whatever uses the built-in operator, whose result is of same type as whatever. You cannot override void, whatever with a user-defined operator,; there is no syntax that works.

I suppose the code is merely to illustrate this one effect, because even with the cast to void one can make up other examples that fail (eg the template does not explicitly test for t.size() actually returning T::size_type).


See also the section on "Rarely overloaded operators" here:

The comma operator, operator,. Unlike the built-in version, the overloads do not sequence their left operand before the right one. (until C++17) Because this operator may be overloaded, generic libraries use expressions such as a,void(),b instead of a,b to sequence execution of expressions of user-defined types. The boost library uses operator, in boost.assign, boost.spirit, and other libraries. The database access library SOCI also overloads operator,.

Related