使用新的 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...);
}