为什么我需要默认求和函数来获取可变参数模板求和?

Why I need default sum function, for a variadic template sum?

提问人:Aditya Garg 提问时间:7/31/2023 最后编辑:JeJoAditya Garg 更新时间:8/1/2023 访问量:99

问:

我想计算给定给函数的任意数量的参数的总和。假设给定给函数的整数将满足 。sumoperator+

如果我注释掉函数(没有参数的函数),代码就不会编译。如果我取消注释,代码确实会编译并运行,但永远不会命中函数。sum()sum()

我似乎不明白为什么我们需要有功能,因为我正在使用条件sum()sizeof...(Args)

如果有人能帮助我理解这一点,我会不胜感激吗?

/*
int sum() 
{
    std::cout << "Sum with 0 Args" << std::endl;
    return 0;
}
*/

template <typename T, typename...Args>
T sum(T first, Args...args) 
{
    // std::cout << sizeof...(Args) << std::endl;
    if (sizeof...(Args) != 0) 
    {
        return first + sum(args...);
    }
    else 
    {
        std::cout << "Found 0 args" << std::endl;
        return first;
    }
}

int main()
{
    std::cout << sum(1, 2, 3) << std::endl;
    std::cout << sum(1.2, 3.5) << std::endl;
    return 0;
}

一旦我取消注释函数sum(),我得到以下输出 -

Found 0 args
6
Found 0 args
4.7

基本上从来没有被叫到这是预期的,但是为什么我们首先需要它呢?sum()

C++ 可变 函数模板 17 C++11

评论

3赞 Sam Varshavchik 7/31/2023
你知道它是什么,为什么需要它,它是如何工作的,以及如何使用它吗?在 C++ 中,即使某些东西“从未被调用”,它仍然必须是有效的 C++。除非涉及。if constexprif constexpr
2赞 273K 7/31/2023
为什么选择两个 C++ 版本?您是否在单个代码中同时使用它们?

答:

0赞 Pepijn Kramer 7/31/2023 #1

从 C++17 开始,您可以对递归位使用 fold 表达式,并且必须使用 if constexpr 进行编译时 if。

#include <iostream>

template<typename... args_t>
auto sum(args_t&&... args)
{
    if constexpr (sizeof...(args_t) == 0)
    {
        return 0;
    }
    else
    {
        return (args + ...);
    }
}

int main()
{
    std::cout << sum() << "\n";
    std::cout << sum(1, 2, 3);
    return 0;
}

评论

0赞 273K 7/31/2023
OP选择了没有if constexpr的C++14。
0赞 chrysante 7/31/2023
你也可以,所以你不需要零参数的特殊情况:-)我认为 anwer 仍然有效,即使它是 C++17 对于任何可能不限于 C++14 有相同问题的人。return (0 + ... + args);
0赞 273K 7/31/2023
@chrysante 有一堆这样的问题有 C++17 的答案,因此当被问到 C++14 时,不需要再有一个这样的 C++17 答案。
0赞 Pepijn Kramer 8/1/2023
@chrysante谢谢你的小提示......不知何故,我总是将我的折叠表达式限制为两个部分;)
0赞 Pepijn Kramer 8/1/2023 #2

我之前错过了 C++ 11 的要求。下面是一个应该与 C++ 兼容的示例。在这里演示 : https://onlinegdb.com/js_WXEeiC

#include <iostream>

namespace details
{
    template<typename arg1_t>
    inline constexpr bool same_type()
    {
        return true;
    }

    template<typename arg1_t, typename arg2_t, typename... args_t>
    inline constexpr bool same_type()
    {
        return std::is_same<arg1_t, arg2_t>::value && same_type<arg2_t, args_t...>();
    }

    template<typename arg_t>
    arg_t sum(arg_t value)
    {
        return value;
    }


    template<typename arg_t, typename... args_t>
    arg_t sum(arg_t value, args_t... values)
    {
        return value + sum(std::forward<args_t>(values)...);
    }
}

template<typename arg_t, typename... args_t>
auto sum(arg_t value, args_t... values)
-> typename std::enable_if<details::same_type<arg_t, args_t...>(), arg_t>::type
{
    return details::sum(value, values...);
}

int main()
{
    static_assert(details::same_type<int>());
    static_assert(details::same_type<int, int>());
    static_assert(details::same_type<int, int, int>());
    static_assert(!details::same_type<int, int, double>());


    std::cout << sum(1, 2, 3);
    return 0;
}
5赞 JeJo 8/1/2023 #3

基本上从来没有被叫过,这是意料之中的,但为什么我们首先需要它呢?sum()

当您使用普通语句时,就像在代码中一样,这两个分支都会被检查,并且都必须是可编译的。当以递归方式调用 时,最后一个函数调用不带参数。要使这种情况成立,编译器需要一个没有参数的函数。ifsumsum

另一方面,从 开始,我们有 if constexpr,通过它我们可以在编译时只保留真正的分支。这意味着,只需按如下方式更改代码,您就不再需要 with no 参数了。sum()

在这里阅读更多: if constexprif 之间的区别?

template <typename T, typename...Args>
T sum(T first, Args...args) 
{
    if constexpr (sizeof...(Args) != 0) 
    // ^^^^^^^^^^
    {
       // .... as before
    }
    else {
       // .... as before
    }
}

但是,在 c++ 中,您可以通过稍微棘手/笨拙的折叠表达式来实现单个函数sum

#include <type_traits> // std::common_type

template <typename...Args>
auto sum(Args...args) -> typename std::common_type<Args...>::type
{
    using unused = int[];
    typename std::common_type<Args...>::type  total{};
    return static_cast<void>(unused{ 0, (total += args, 0)...}), total;
}

观看 godbolt.org 中的现场演示


引用: