使用新的 c++14 / c++17 功能改进可变参数模板函数
Improving a variadic template function using new c++14 / c++17 features
我是可变参数模板的新手,我仍然设法使用它在 c++11 中编写了一些代码,但我仍然对结果感到不满,因为它缺乏表现力。
问题是实现一个函数,该函数接受多个 bool
条件(从 1
到任何条件)和 returns 一个整数代码,指示第一个“false
" 参数,或者 0
如果它们都是 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
我目前的代码代表(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";
}
我想知道在哪里可以使用 c++14/c++17 的新展开功能改进这个“error_code()
”函数,因此它获得了表现力并使用了少于 3 个函数。
欢迎提供帮助!
(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;
}
带折叠的 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)
第二部分|| ...
是折叠(或展开)的地方。第一个表达式是copy/pasted重复直到包中没有更多参数。对于 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, false) || (rv += 0, true)
最后的rv % (sizeof...(Bs) + 1);
是为了处理找不到false
值的情况,我们应该return0
。示例:
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
为什么 (void)
?
编译器喜欢对他们认为未使用的表达式发出警告。一个小心放置的 (void)
告诉它我们不关心,所以它让编译器保持沉默。
1 - 这不适用于用户定义的运算符。
因为你知道你的参数都将被转换为 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, 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... });
}
编译器似乎以类似于您的可变参数解决方案的方式对其进行了优化(它甚至可以在 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...);
}
我是可变参数模板的新手,我仍然设法使用它在 c++11 中编写了一些代码,但我仍然对结果感到不满,因为它缺乏表现力。
问题是实现一个函数,该函数接受多个 bool
条件(从 1
到任何条件)和 returns 一个整数代码,指示第一个“false
" 参数,或者 0
如果它们都是 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
我目前的代码代表(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";
}
我想知道在哪里可以使用 c++14/c++17 的新展开功能改进这个“error_code()
”函数,因此它获得了表现力并使用了少于 3 个函数。
欢迎提供帮助!
(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;
}
带折叠的 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)
第二部分
|| ...
是折叠(或展开)的地方。第一个表达式是copy/pasted重复直到包中没有更多参数。对于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, false) || (rv += 0, true)
最后的
rv % (sizeof...(Bs) + 1);
是为了处理找不到false
值的情况,我们应该return0
。示例: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
为什么
(void)
?
编译器喜欢对他们认为未使用的表达式发出警告。一个小心放置的(void)
告诉它我们不关心,所以它让编译器保持沉默。
1 - 这不适用于用户定义的运算符。
因为你知道你的参数都将被转换为 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, 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... });
}
编译器似乎以类似于您的可变参数解决方案的方式对其进行了优化(它甚至可以在 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...);
}