[英]Improving a variadic template function using new c++14 / c++17 features
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.我是可变参数模板的新手,我仍然设法使用它在 c++11 中编写了一些代码,但我仍然对结果感到不满,因为它缺乏表现力。
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
.问题是实现一个 function,它接受几个
bool
条件(从1
到任意)并返回一个 integer 代码,指示第一个“ false
”参数在什么位置,如果所有参数都为true
,则返回0
。
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 ):我当前的代码代表(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.我想知道这个“
error_code()
” function 可以使用 c++14 / c++17 中的新展开功能在哪里进行改进,因此它获得了表现力并使用了少于 3 个函数。
Any help will be grateful welcomed!欢迎任何帮助!
C++17 with folding: C++17 带折叠:
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:未被要求,所以这只是一个奖励——同样的想法,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
.左边部分的结果被丢弃,这种表达式的结果是最右边的部分,
!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.第二部分
||...
是折叠(或展开)的地方。重复复制/粘贴第一个表达式,直到包中不再有 arguments。 For true, false, true
it becomes:对于
true, false, true
它变成:
(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-in 1 operator ||
短路评估开始。当内置1运算符
||
has a true
on the left side, the right side is not evaluated.左侧为
true
,右侧未求值。 That's why only one of the rv += 1
's is done in this example.这就是为什么在此示例中只完成了
rv += 1
之一。 The (rv += 0, true)
stops the evaluation so only this is evaluated: (rv += 0, true)
停止求值,所以只求值:
(rv += 1, false) || (rv += 0, true)
The final rv % (sizeof...(Bs) + 1);
最后的
rv % (sizeof...(Bs) + 1);
is to take care of the case where no false
values are found and we should return 0
.是为了处理没有发现
false
值的情况,我们应该返回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)
?为什么
(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.一个小心放置的
(void)
告诉它我们不关心,所以它让编译器保持沉默。
1 - This does not apply to user defined operators. 1 - 这不适用于用户定义的运算符。
What about (C++17) as follows?下面的 (C++17) 怎么样?
template <typename... Args>
int error_code (Args... args)
{
int ret = 0;
int val = 1;
( (args || ret ? 0 : ret = val, ++val), ... );
return ret;
}
In C++11 and C++14 a little (little.) more typewriting is required.在 C++11 和 C++14 中,需要稍微(一点点)打字。
template <typename... Args>
int error_code (Args... args)
{
using unused = int[];
int ret = 0;
int val = 1;
(void)unused{ 0, (args || ret ? 0 : ret = val, ++val)... };
return ret;
}
Since you know your arguments are all going to be converted to a bool
, it's better to not use variadic arguments at all:因为你知道你的 arguments 都将被转换为
bool
,所以最好不要使用可变参数 arguments :
inline int error_code(std::initializer_list<bool> args) {
int index = std::find(args.begin(), args.end(), false) - args.begin();
if (index == args.size()) return 0;
return 1 + index;
}
// Either directly call the above `error_code({ true, true, false, ... })`
// Or if you must have a parameter pack
template<typename... Args>
int error_code(Args... args) {
std::initializer_list<bool> v{ args... };
int index = std::find(v.begin(), v.end(), false) - v.begin();
if (index == sizeof...(args)) return 0;
return index + 1;
// If you have both functions, simply have: return error_code({ args... });
}
The compiler seems to optimise it similarly to your variadic solution (and it even works in C++11).编译器似乎对它进行了优化,类似于您的可变参数解决方案(它甚至可以在 C++11 中工作)。
Here's a more fun solution that uses C++17 fold expressions:这是一个更有趣的解决方案,它使用 C++17 折叠表达式:
template<typename... Args, int... I>
int error_code_impl(Args... args, std::integer_sequence<int, I...>) {
int result = 0;
([&result](bool arg){
if (!arg) result = I + 1;
return arg;
}(args) && ...);
// Can also be written without the lambda as something like:
// ((args ? true : ((result = I + 1), false)) && ...);
return result;
}
template<typename... Args>
int error_code(Args... args) {
std::make_integer_sequence<int, sizeof...(args)> indices;
return error_code_impl<Args...>(indices, args...);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.