问题描述
我是可变参数模板的新手,我仍然设法使用它在 c++11 中编写了一些代码,但我仍然对结果感到不满,因为它缺乏表现力。
问题是实现一个函数,该函数接受多个 bool
条件(从 1
到任何条件)并返回一个整数代码,指示第一个“false
”参数在哪个位置,或 0
,如果它们都是 true
。
e.g. "error_code(true,true,true);" must return 0
e.g. "error_code(true,false);" must return 3
e.g. "error_code(true,false,false);" must return 2
e.g. "error_code(false,false);" must return 1
我当前的代码代表(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);
auto e2 = error_code(true,false);
auto e3 = error_code(true,false);
auto e4 = error_code(false,false);
std::cout << std::boolalpha;
std::cout << "(true,true)==0 -> " << (e1 == 0) << "\n";
std::cout << "(true,false)==3 -> " << (e2 == 3) << "\n";
std::cout << "(true,false)==2 -> " << (e3 == 2)<< "\n";
std::cout << "(false,false)==1 -> " << (e4 == 1)<< "\n";
auto e5 = error_code(true,true);
auto e6 = error_code(true,false);
auto e7 = error_code(true,false);
auto e8 = error_code(true,false);
auto e9 = error_code(false,false);
std::cout << "(true,true)==0 -> " << (e5 == 0) << "\n";
std::cout << "(true,false)==4 -> " << (e6 == 4) << "\n";
std::cout << "(true,false)==3 -> " << (e7 == 3) << "\n";
std::cout << "(true,false)==2 -> " << (e8 == 2)<< "\n";
std::cout << "(false,false)==1 -> " << (e9 == 1)<< "\n";
}
我想知道使用来自 c++14/c++17 的新展开功能可以在何处改进这个“error_code()
”函数,因此它提高了表达能力并且使用的函数少于 3 个。
如有任何帮助,将不胜感激!
解决方法
C++17 带折叠:
template<class... Bools>
constexpr unsigned error_code(Bools... Bs) {
unsigned rv = 1;
(void) ((rv += Bs,!Bs) || ...);
return rv % (sizeof...(Bs) + 1);
}
没有被要求所以这只是一个奖励 - 同样的想法,C++20:
constexpr unsigned error_code(auto... Bs) {
unsigned rv = 1;
(void) ((rv += Bs,!Bs) || ...);
return rv % (sizeof...(Bs) + 1);
}
说明:
-
折叠表达式的第一部分包含由
,
分隔的两部分。左边部分的结果被丢弃,这种表达式的结果是最右边的部分,!Bs
。(rv += Bs,!Bs)
-
第二部分
|| ...
是折叠(或展开)出现的地方。第一个表达式被重复复制/粘贴,直到包中没有更多参数。对于true,false,true
,它变成:(rv += 1,!true) || (rv += 0,!false) || (rv += 1,!true)
或
(rv += 1,false) || (rv += 0,true) || (rv += 1,false)
-
短路评估开始。当内置1 运算符
||
在左侧有一个true
时,右侧不会被评估.这就是为什么在此示例中只完成了rv += 1
之一的原因。(rv += 0,true)
停止评估,因此只评估它:(rv += 1,true)
-
最后的
rv % (sizeof...(Bs) + 1);
是处理没有找到false
值的情况,我们应该返回0
。示例:unsigned rv = 1; (rv += 1,!true) || (rv += 1,!true); // rv is now 4,and sizeof...(Bs) == 3,so: 4 % (3 + 1) == 0
-
为什么是
(void)
?
编译器喜欢警告他们认为未使用的表达式。一个小心放置的(void)
告诉它我们不在乎,所以它让编译器保持沉默。
1 - 这不适用于用户定义的运算符。
,下面的 (C++17) 怎么样?
template <typename... Args>
int error_code (Args... args)
{
int ret = 0;
int val = 1;
( (args || ret ? 0 : ret = val,++val),... );
return ret;
}
在 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;
}
,
既然您知道您的参数都将被转换为 bool
,那么最好根本不使用可变参数:
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,... })`
// 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... });
}
编译器似乎对它的优化类似于您的可变参数解决方案(它甚至可以在 C++11 中运行)。
这是一个使用 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...);
}