I am newbie to variadic templates, still I managed to program some code in c++11 using it, but I still feel sour about result because it lacks expressivity.
The issue is to implement a function that accept several bool
conditions (from 1
to whatever) and returns an integer code indicating in what place is the first "false
" argument, or 0
if all of them are true
.
e.g. "error_code(true, true, true);" must return 0
e.g. "error_code(true, true, false);" must return 3
e.g. "error_code(true, false, false);" must return 2
e.g. "error_code(false, false, false);" must return 1
My current code stands for (live link to coliru: http://coliru.stacked-crooked.com/a/1b557f2819ae9775):
#include <tuple>
#include <iostream>
int error_impl(bool cond)
{
return cond;
}
template<typename... Args>
int error_impl(bool cond, Args... args)
{
return cond ? 1 + error_impl(args...) : 0;
}
template<typename... Args>
int error_code(Args... args)
{
constexpr std::size_t size = std::tuple_size<std::tuple<Args...>>::value + 1;
return (error_impl(args...) + 1) % size;
}
int main()
{
auto e1 = error_code(true, true, true);
auto e2 = error_code(true, true, false);
auto e3 = error_code(true, false, false);
auto e4 = error_code(false, false, false);
std::cout << std::boolalpha;
std::cout << "(true, true, true)==0 -> " << (e1 == 0) << "\n";
std::cout << "(true, true, false)==3 -> " << (e2 == 3) << "\n";
std::cout << "(true, false, false)==2 -> " << (e3 == 2)<< "\n";
std::cout << "(false, false, false)==1 -> " << (e4 == 1)<< "\n";
auto e5 = error_code(true, true, true, true);
auto e6 = error_code(true, true, true, false);
auto e7 = error_code(true, true, false, false);
auto e8 = error_code(true, false, false, false);
auto e9 = error_code(false, false, false, false);
std::cout << "(true, true, true, true)==0 -> " << (e5 == 0) << "\n";
std::cout << "(true, true, true, false)==4 -> " << (e6 == 4) << "\n";
std::cout << "(true, true, false, false)==3 -> " << (e7 == 3) << "\n";
std::cout << "(true, false, false, false)==2 -> " << (e8 == 2)<< "\n";
std::cout << "(false, false, false, false)==1 -> " << (e9 == 1)<< "\n";
}
I wonder where this "error_code()
" function can be improved using new unroll features from c++14 / c++17, so it gains in expressivity and uses less than 3 functions.
Any help will be grateful welcomed!
C++17 with folding:
template<class... Bools>
constexpr unsigned error_code(Bools... Bs) {
unsigned rv = 1;
(void) ((rv += Bs, !Bs) || ...);
return rv % (sizeof...(Bs) + 1);
}
Unasked for so it's just a bonus - the same idea, C++20:
constexpr unsigned error_code(auto... Bs) {
unsigned rv = 1;
(void) ((rv += Bs, !Bs) || ...);
return rv % (sizeof...(Bs) + 1);
}
Explanation:
The first part of the fold expression contains two parts separated by a ,
. The result of the left part is discarded and the result of such an expression is the rightmost part, !Bs
.
(rv += Bs, !Bs)
The second part || ...
is where the folding (or unfolding) comes in. The first expression is copy/pasted repeatedly until there are no more arguments in the pack. For true, false, true
it becomes:
(rv += 1, !true) || (rv += 0, !false) || (rv += 1, !true)
or
(rv += 1, false) || (rv += 0, true) || (rv += 1, false)
Short-circuit evaluation kicks in. When the built-in1 operator ||
has a true
on the left side, the right side is not evaluated. That's why only one of the rv += 1
's is done in this example. The (rv += 0, true)
stops the evaluation so only this is evaluated:
(rv += 1, false) || (rv += 0, true)
The final rv % (sizeof...(Bs) + 1);
is to take care of the case where no false
values are found and we should return 0
. Example:
unsigned rv = 1;
(rv += 1, !true) || (rv += 1, !true) || (rv += 1, !true);
// rv is now 4, and sizeof...(Bs) == 3, so:
4 % (3 + 1) == 0
Why (void)
?
Compilers like to warn about what they see as unused expressions. A carefully placed (void)
tells it that we don't care, so it keeps the compiler silent.
1 - This does not apply to user defined operators.