【问题标题】:Improving a variadic template function using new c++14 / c++17 features使用新的 c++14 / c++17 功能改进可变参数模板函数
【发布时间】:2021-05-02 20:24:25
【问题描述】:

我是可变参数模板的新手,但我仍然设法使用它在 c++11 中编写了一些代码,但我仍然对结果感到不满,因为它缺乏表现力。

问题是要实现一个函数,该函数接受多个bool 条件(从1 到其他)并返回一个整数代码,指示第一个“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

我当前的代码代表(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";
}

我想知道这个“error_code()”函数在哪里可以使用来自 c++14 / c++17 的新展开功能进行改进,因此它获得了表现力并且使用了少于 3 个函数。

欢迎任何帮助!

【问题讨论】:

    标签: c++ c++14 c++17 variadic-templates fold-expression


    【解决方案1】:

    具有折叠功能的 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, false) || (rv += 0, true)
      
    • 最后的rv % (sizeof...(Bs) + 1); 是为了处理没有找到false 值的情况,我们应该返回0。示例:

      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 - 这不适用于用户定义的运算符。

    【讨论】:

    • 对不起,我错误地删除了我的最后一条评论。我再写一遍:非常感谢您的回答!我需要一段时间才能真正理解代码,但现在我有了一些清晰的代码来不断提高我(现在很少)可变参数模板技能。另外,我要感谢您的 c++20 分享。作为开发人员,我期待开始使用 c++20 及其新功能,特别是范围,因此欢迎任何新的学习。
    • 顺便说一句,我不知道“sizeof...(Bs)”构造可以摆脱我用来计算函数中参数数量的可怕元组黑客攻击。
    • @Pablo sizeof... 真的很棒!我在日常生活中很少使用它:-)
    • @max66 谢谢!我也喜欢你的!我花了足够长的时间来制作 C++17 解决方案,并且太累了,无法像你一样重新启动我的大脑来制作 C++14 版本。然而,我在工作中坚持使用 C++14 :-)
    • @TedLyngmo - 你是对的;对不起:我只转移了折叠表达式。算了。
    【解决方案2】:

    下面的(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;
     }
    

    【讨论】:

    • 非常感谢您的回答!我需要一段时间和整个调试器会话才能理解它是如何工作的,但至少现在我有一些可靠的代码可以继续深入研究可变参数模板。
    【解决方案3】:

    既然你知道你的参数都将被转换为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 语言的可变参数函数。您的解决方案已经记住了我,但您的解决方案使用“std::initializer_list”而不是 va_start、va_end... 令人毛骨悚然的关键字变得更好。我真的很喜欢你的第一个解决方案,到目前为止它的可读性最好,而且任何人都很容易理解
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-01
    • 2016-03-05
    • 1970-01-01
    • 2017-07-29
    相关资源
    最近更新 更多