简体   繁体   English

使用新的 c++14 / c++17 功能改进可变参数模板 function

[英]Improving a variadic template function using new c++14 / c++17 features

I am newbie to variadic templates, still I managed to program some code in c++11 using it, but I still feel sour about result because it lacks expressivity.我是可变参数模板的新手,我仍然设法使用它在 c++11 中编写了一些代码,但我仍然对结果感到不满,因为它缺乏表现力。

The issue is to implement a function that accept several bool conditions (from 1 to whatever) and returns an integer code indicating in what place is the first " false " argument, or 0 if all of them are true .问题是实现一个 function,它接受几个bool条件(从1到任意)并返回一个 integer 代码,指示第一个“ false ”参数在什么位置,如果所有参数都为true ,则返回0

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

My current code stands for (live link to coliru: http://coliru.stacked-crooked.com/a/1b557f2819ae9775 ):我当前的代码代表(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";
}

I wonder where this " error_code() " function can be improved using new unroll features from c++14 / c++17, so it gains in expressivity and uses less than 3 functions.我想知道这个“ error_code() ” function 可以使用 c++14 / c++17 中的新展开功能在哪里进行改进,因此它获得了表现力并使用了少于 3 个函数。

Any help will be grateful welcomed!欢迎任何帮助!

C++17 with folding: C++17 带折叠:

template<class... Bools>
constexpr unsigned error_code(Bools... Bs) {
    unsigned rv = 1;
    (void) ((rv += Bs, !Bs) || ...);
    return rv % (sizeof...(Bs) + 1);
}

Unasked for so it's just a bonus - the same idea, C++20:未被要求,所以这只是一个奖励——同样的想法,C++20:

constexpr unsigned error_code(auto... Bs) {
    unsigned rv = 1;
    (void) ((rv += Bs, !Bs) || ...);
    return rv % (sizeof...(Bs) + 1);
}

Explanation:解释:

  • The first part of the fold expression contains two parts separated by a , .折叠表达式的第一部分包含由,分隔的两部分。 The result of the left part is discarded and the result of such an expression is the rightmost part, !Bs .左边部分的结果被丢弃,这种表达式的结果是最右边的部分, !Bs

     (rv += Bs, !Bs)
  • The second part ||... is where the folding (or unfolding ) comes in. The first expression is copy/pasted repeatedly until there are no more arguments in the pack.第二部分||...是折叠(或展开)的地方。重复复制/粘贴第一个表达式,直到包中不再有 arguments。 For true, false, true it becomes:对于true, false, true它变成:

     (rv += 1, ,true) || (rv += 0, !false) || (rv += 1, !true)

    or要么

    (rv += 1, false) || (rv += 0, true) || (rv += 1, false)
  • Short-circuit evaluation kicks in. When the built-in 1 operator ||短路评估开始。当内置1运算符|| has a true on the left side, the right side is not evaluated.左侧为true ,右侧未求值。 That's why only one of the rv += 1 's is done in this example.这就是为什么在此示例中只完成了rv += 1之一。 The (rv += 0, true) stops the evaluation so only this is evaluated: (rv += 0, true)停止求值,所以只求值:

     (rv += 1, false) || (rv += 0, true)
  • The final rv % (sizeof...(Bs) + 1);最后的rv % (sizeof...(Bs) + 1); is to take care of the case where no false values are found and we should return 0 .是为了处理没有发现false值的情况,我们应该返回0 Example:例子:

     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
  • Why (void) ?为什么(void)
    Compilers like to warn about what they see as unused expressions.编译器喜欢对他们认为未使用的表达式发出警告。 A carefully placed (void) tells it that we don't care, so it keeps the compiler silent.一个小心放置的(void)告诉它我们不关心,所以它让编译器保持沉默。


1 - This does not apply to user defined operators. 1 - 这不适用于用户定义的运算符。

What about (C++17) as follows?下面的 (C++17) 怎么样?

template <typename... Args>
int error_code (Args... args)
 {
   int ret = 0;
   int val = 1;

   ( (args || ret ? 0 : ret = val, ++val), ... );

   return ret;
 }

In C++11 and C++14 a little (little.) more typewriting is required.在 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;
 }

Since you know your arguments are all going to be converted to a bool , it's better to not use variadic arguments at all:因为你知道你的 arguments 都将被转换为bool ,所以最好不要使用可变参数 arguments :

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... });
}

The compiler seems to optimise it similarly to your variadic solution (and it even works in C++11).编译器似乎对它进行了优化,类似于您的可变参数解决方案(它甚至可以在 C++11 中工作)。


Here's a more fun solution that uses C++17 fold expressions:这是一个更有趣的解决方案,它使用 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...);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM