Test if all elements are equal with C++17 fold-expression
The reason that doesn't work, unfortunately, is due to the fact that boolean operators don't chain in C++ like they do in other languages. So the expression:
a == (b == c)
(what your fold-expression would expand to) would compare a
to either true
or false
, nothing to do with what b
or c
actually are. I was hoping the operator<=>
would add chaining but apparently that part was dropped.
The fixes are that you have to break up the comparisons:
(a == b) && (b == c)
Of course that doesn't lend itself to folding very well, but you could instead compare everything to the first element:
(a == b) && (a == c)
Which is to say:
((a0 == args) && ... )
At that point, we just need to be able to pull out the first element. No problem, that's obviously what lambdas are for:
template <class... Args>
constexpr bool all_equal(Args const&... args) {
if constexpr (sizeof...(Args) == 0) {
return true;
} else {
return [](auto const& a0, auto const&... rest){
return ((a0 == rest) && ...);
}(args...);
}
}
As suggested by Piotr Skotnicki, a simple solution is separate the first argument from the followings and check it with using &&
as fold operator
By example, the following function that return true
if all arguments are equals
template <typename A0, typename ... Args>
bool foo (A0 const & a0, Args const & ... args)
{ return ( (args == a0) && ... && true ); }
Unfortunately this can't work with an empty list of arguments
std::cout << foo(1, 1, 1, 1) << std::endl; // print 1
std::cout << foo(1, 1, 2, 1) << std::endl; // print 0
std::cout << foo() << std::endl; // compilation error
but you can add the special empty argument foo()
bool foo ()
{ return true; }
If, for some reason, you can't split the args
in a a0
and the following args
?
Well... you can obviously use the preceding foo()
function (with special empty version)
template<typename... Args>
void func (Args... args)
{
ASSERT (foo(args));
// more code here...
}
or you can use the C++17 fold expression with comma operator and assignment as in the following bar()
template <typename ... Args>
bool bar (Args const & ... args)
{
auto a0 = ( (0, ..., args) );
return ( (args == a0) && ... && true );
}
Observe the initial zero in a0
assignment that permit the use of this solution also with an empty list of arguments.
Unfortunately, from the preceding auto a0
assignment I get a lot of warnings ("expression result unused", from clang++, and "left operand of comma operator has no effect", from g++) that I don't know how to avoid.
The following is a full working example
#include <iostream>
template <typename A0, typename ... Args>
bool foo (A0 const & a0, Args const & ... args)
{ return ( (args == a0) && ... && true ); }
bool foo ()
{ return true; }
template <typename ... Args>
bool bar (Args const & ... args)
{
auto a0 = ( (0, ..., args) );
return ( (args == a0) && ... && true );
}
int main ()
{
std::cout << foo(1, 1, 1, 1) << std::endl; // print 1
std::cout << foo(1, 1, 2, 1) << std::endl; // print 0
std::cout << foo() << std::endl; // print 1 (compilation error
// witout no argument
// version)
std::cout << bar(1, 1, 1, 1) << std::endl; // print 1
std::cout << bar(1, 1, 2, 1) << std::endl; // print 0
std::cout << bar() << std::endl; // print 1 (no special version)
}
-- EDIT --
As pointed by dfri (thanks!), for and empty args...
pack, the values for the following folded expressions
( (args == a0) && ... )
( (args == a0) || ... )
are, respectively, true
and false
.
So return instruction of foo()
and bar()
can be indifferently written
return ( (args == a0) && ... && true );
or
return ( (args == a0) && ... );
and this is true also in case sizeof...(args) == 0U
.
But I tend to forget this sort of details and prefer to explicit (with the final && true
) the empty-case value.