c++ С每隔2个参数调用add函数

lpwwtiir  于 2023-03-14  发布在  其他
关注(0)|答案(3)|浏览(105)

我有一个函数,它对两个元素进行如下运算:

template <typename Type>
Type add(const Type& a, const Type& b) 
{
    // some logic
    if(!((b >= 0) && (a > std::numeric_limits<T>::max() - b))
        return a + b;
}

我想写另一个模板函数,它将以同样的方式执行一些逻辑,接受N个参数,并对所有这些参数应用前一个函数,例如:

template <typename... Args>
idk_how_type_need add_n_elements(Args... args)
{
    return //here i want smth like -> add(add(arg0, arg1), add(arg2, arg3)...);
}

是真实的的吗?或者也许有其他的选择?

elcex8rz

elcex8rz1#

您可以通过在std::initializer_list上调用std::reduce来完成所需的操作。

**注意:**此解决方案仅适用于被调用函数是可交换函数的情况,即add(a, b) == add(b, a)。您可以将std::reduce替换为std::accumulate以获得保证顺序add(add(add(a, b), c), d) ...。如果您确实需要add(add(a, b), add(c, d)) ...,此解决方案将不起作用!

#include <concepts> // for optional C++20 concept line
#include <iostream>
#include <numeric>

template <typename T>
T add(const T& a, const T& b) {
    return a + b;
}

template <typename T, typename ... U>
requires (std::same_as<T, U> && ...) // optional C++20 concept line
T reduce(const T& first, const U& ... args) {
    auto const list = std::initializer_list<T>{args ...};
    return std::reduce(list.begin(), list.end(), first, add<T>);
}

int main() {
    std::cout << reduce(1) << '\n';
    std::cout << reduce(1, 2) << '\n';
    std::cout << reduce(1, 2, 3) << '\n';
    std::cout << reduce(1, 2, 3, 4) << '\n';
    std::cout << reduce(1, 2, 3, 4, 5) << '\n';
}
1
3
6
10
15

如果你想让你调用的add函数成为接口的一部分,你可以将其更改为:

#include <concepts> // for optional C++20 concept line
#include <iostream>
#include <numeric>

template <typename T>
T add(const T& a, const T& b) {
    return a + b;
}

template <typename Fn, typename T, typename ... U>
requires (std::same_as<T, U> && ...) && std::invocable<Fn, T, T> // optional C++20 concept line
T reduce(Fn&& fn, const T& first, const U& ... args) {
    auto const list = std::initializer_list<T>{args ...};
    return std::reduce(list.begin(), list.end(), first, std::forward<Fn>(fn));
}

int main() {
    std::cout << reduce(add<int>, 1) << '\n';
    std::cout << reduce(add<int>, 1, 2) << '\n';
    std::cout << reduce(add<int>, 1, 2, 3) << '\n';
    std::cout << reduce(add<int>, 1, 2, 3, 4) << '\n';
    std::cout << reduce(add<int>, 1, 2, 3, 4, 5) << '\n';
}

如果您的add函数只是一个operator+(或另一个二元运算符),则可以使用C++17 Fold Expression

#include <iostream>

template <typename ... T>
auto accumulate(const T& ... args) {
    return (args + ...); // or (... + args)
}

int main() {
    std::cout << accumulate(1) << '\n';
    std::cout << accumulate(1, 2) << '\n';
    std::cout << accumulate(1, 2, 3) << '\n';
    std::cout << accumulate(1, 2, 3, 4) << '\n';
    std::cout << accumulate(1, 2, 3, 4, 5) << '\n';
}

请注意,(args + ...)表示... (a + (b + (c + d))(... + args)表示(((a + b) + c) + d) ...

ghhaqwfi

ghhaqwfi2#

嗯,用户Benjamin Buch的回答似乎是
1.对于此使用情形来说有点过于复杂
1.并且没有完全解决OP的问题,因为他想为他的N个参数应用一个函数
关于1.:对于只添加几个值,我们不需要std::reduce。因此,首先将值放入容器中,这里是std::initializer_list,然后缩减此容器,这对于添加值来说太复杂了。std::reduce有很大的优势,如果你想使用关联运算符和交换运算符并行化或矢量化大数据,习惯上首选的解决方案是使用fold expressions。我将在下面解释这一点。
关于2.:这个问题根本没有解决。我将在下面展示一个解决方案,也是基于折叠表达式的。
使用fold表达式构建和。如果你看一下CPP参考here,你会发现,参数包(在你的例子Args... args中)是如何容易地减少的。如果你看一下那里,你会看到:

Syntax:

( pack op ... )             (1)     
( ... op pack )             (2)     
( pack op ... op init )     (3)     
( init op ... op pack )     (4)     

1) unary right fold
2) unary left fold
3) binary right fold
4) binary left fold

对于您的用例,如果您将“pack”替换为“args”,将“op”替换为“+",那么它将如下所示:

( args + ... )
( ... + args )          
( args + ... + sum ) 
( sum + ... + args )

所有变体都有用例,但让我先向你展示基于一元右折叠的最简单的解决方案:

#include <iostream>

template <typename...Args>     // Unary right fold
auto sum1(Args ... args) {
    return (args + ...);
}
int main() {
    std::cout << sum1(1, 2, 3, 4) << '\n';
}

这真的非常简洁。
你可以在CPP参考中读到这里会发生什么。
解释fold表达式的示例化如下扩展表达式e:
1.一元右折叠(E运算...)变为(E1运算(...运算(EN-1运算EN)))
对于我们上面的折叠表达式(args + ...),我们将得到如下:(1 + ( 2 + (3 + 4)))。对于“+”运算符,我们可以省略所有大括号,最后得到:1 + 2 + 3 + 4 .
尼斯
请参见下面的一段代码,其中我们使用了折叠表达式的所有4个变体:

#include <iostream>
#include <tuple>

template <typename...Args>     // Unary right fold
auto sum1(Args ... args) {
    return (args + ...);
}
template <typename...Args>     // Unary left fold
auto sum2(Args ... args) {
    return (... + args);
}
template <typename...Args>     // Binary right fold
auto sum3(Args ... args) {
    std::tuple_element_t<0, std::tuple<Args...>> sum{};
    sum = (args + ... + sum);
    return sum;
}
template <typename...Args>     // Binary left fold
auto sum4(Args ... args) {
    std::tuple_element_t<0, std::tuple<Args...>> sum{};
    sum = (sum + ... + args);
    return sum;
}
int main() {
    std::cout << sum1(1, 2, 3, 4) << '\n';
    std::cout << sum2(1, 2, 3, 4) << '\n';
    std::cout << sum3(1, 2, 3, 4) << '\n';
    std::cout << sum4(1, 2, 3, 4) << '\n';
}


接下来,到第二部分,如何在参数包的每个参数上应用函数,也有很多可能的解决方案,但是,因为我在解释折叠表达式,我将展示一个小函数的解决方案,特别是lambdas,通过使用折叠表达式。
这里的解决方案的主要部分是结合使用'逗号'运算符和一元右折叠。
由于逗号运算符不常用,我们在这里再读一遍。
因此,lhs, rhs执行以下操作:
首先,计算左操作数lhs并丢弃其结果值。然后,出现一个序列点,以便完成lhs的所有副作用。然后,计算右操作数rhs并由逗号运算符将其结果作为非左值返回。
这可以和一元右折完美结合。
让我们以lambda的最简单形式为例:[]{}。你只看到了捕获和主体。这是一个完全有效的Lambda。如果你想调用这个lambda,那么你可以简单地写[]{} ()。这调用了一个空的lambda,什么也不做。只是为了演示的目的:如果我们使用求和示例:

template <typename...Args>
auto sum5(Args ... args) {
    std::tuple_element_t<0, std::tuple<Args...>> sum{};

    // comma lhs: Lambda     comma   rhs
    ([&]{sum += args;} ()      ,     ...);

    return sum;
}

则将发生以下情况:

  • 我们定义一个lambda并通过引用获取所有外部变量,这里特别是sum
  • 在正文中,我们将args添加到总和中
  • 然后我们用()调用函数,这是逗号运算符的“lhs
  • 然后我们做一个一元右折叠,用后面的参数再次调用lambda
  • 等等等等。

如果你一步一步地分析一下,你就会明白了。
下一步就很简单了,你可以在你的lambda中填充更多的功能,这样,你就可以在一个参数包的所有参数上应用一个函数。
以下是您预期功能的示例:

#include <iostream>
#include <tuple>
#include <utility>
#include <limits>
#include <iomanip>

template <typename...Args>
using MyType = std::tuple_element_t<0, std::tuple<Args...>>;

template <typename...Args>
std::pair<bool, MyType<Args...>> sumsWithOverflowCheck(Args ... args) {
    bool overflow{};
    MyType <Args...>sum{};

    ([&]    // Lambda Capture
        {   // Lambda Body
            const MyType<Args...> maxDelta{ std::numeric_limits<MyType<Args...>>::max() - sum };
            if (args > maxDelta) overflow = true;
            sum += args; 
        }   // End of lambda body
        ()  // Call the Lampda
        , ...);  // unary right fold over comma operator

    return { overflow, sum };
}
int main() {
    {   // Test 1
        const auto& [overflow, sum] = sumsWithOverflowCheck(1, 2, 3, 4);
        std::cout << "\nSum: " << std::setw(12) << sum << "\tOverflow: " << std::boolalpha << overflow << '\n';
    }
    {   //Test 2
        const auto& [overflow, sum] = sumsWithOverflowCheck(1,2,(std::numeric_limits<int>::max()-10),  20, 30);
        std::cout << "Sum: " << std::setw(12) << sum << "\tOverflow: " << overflow << '\n';
    }
}

当然,您可以消除lambda中所有不必要的换行符,使其更紧凑。

wbrvyc0a

wbrvyc0a3#

如前所述,有很多方法可以做到这一点。也许最简单的解决方案是添加一个重载函数,该函数将处理3个或更多参数,并递归地调用自身,如下所示:

#include <iostream>
#include <numeric>    

//original function
template <typename Type>
Type add(const Type& a, const Type& b) 
{
    // some logic
    if(!((b >= 0) && (a > std::numeric_limits<Type>::max() - b)))
        return a + b;        
    throw std::runtime_error("some error");
}

//overloaded function
template <typename Type, typename ...Type2>
Type add(const Type& a, const Type& b, const Type2 & ...other)
{
    return add(add(a,b), other...); //calls itself if num of params > 2, or original "add" otherwise
}

int main() {
    std::cout << add(1, 2) << '\n';
    std::cout << add(1, 2, 3) << '\n';
    std::cout << add(1, 2, 3, 4) << '\n';
    std::cout << add(1, 2, 3, 4, 5) << '\n';
    return 0;
}

所以,我只添加了一行函数,这很容易理解和解释它是如何工作的,一般来说,通过将可变参数模板与递归结合起来,你可以使很多复杂的东西变得容易得多

相关问题