使用新的 c++14/c++17 特性改进可变参数模板函数

问题描述

我是可变参数模板的新手,我仍然设法使用它在 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...);
}

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...