对可变参数模板使用 const 参数

Using const parameter for variadic template

我有两个关于以下可变参数的问题:

#include <iostream>

template <typename T>
T Add(const T& arg_a)
{
    return arg_a;
}

template <typename T, typename... Pack>
T Add(const T& arg_a, const Pack&... arg_list)
{
    return arg_a + Add(arg_list...);
}

int main(int argc, const char* argv[])
{

    auto itLocalSum = Add(1, 2, 3, 4, 5);
    std::cout << "Sum of Add: " << itLocalSum << "\n";

    return 0;
}
  1. 为什么参数使用const引用?
  2. 当我将浮点数与 add(2.3, 43, 32.2) 等整数组合使用时,它不会向我显示正确的值。我该如何解决?

1) 只是接收参数,不做copy。 const只是为了让他们"read-only",他们不能改变。此外,当您使用 const 引用时,您可以将右值参数绑定到左值参数,然后您还可以将文字传递给您的函数,例如。

2) 只要​​每对解包的第一个参数是整数,就会隐式转换为 int。如果将 43 值更改为 43.0,它将起作用。此外,对于 C++17,您可以放弃 Add 函数并使用折叠表达式:return (arg_a + ... + arg_list);.

可能更好的解决方案:

template <typename... Pack>
auto Add(const Pack&... arg_list) {
    return (arg_list + ...);
}

问题 1 的答案

因为某些类型的复制成本很高,如果您只是简单地做一个不修改底层对象的求和,通过 const 引用传递可以消除复制对象的成本。

例如,如果你通过值传递一个大的 vector,整个 vector 将被复制来调用函数,而传递一个 const 引用要快得多(可能实现为指针副本)。


问题 2 的答案

您的问题是,当您计算 43 + 32.2 时,return 的值是 int,舍弃了小数部分。这是因为T在此上下文中被推导为字面量43的类型,即int。针对您的问题的两种解决方案:

  1. 使用 auto 作为您的 return 值。 (如果您使用的是 C++ 14)
template <typename T>
T Add(const T& arg_a)
{
    return arg_a;
}

template <typename T, typename... Pack>
auto Add(const T& arg_a, const Pack&... arg_list)
{
    return arg_a + Add(arg_list...);
}
  1. 或者如果你有 c++ 17,只需使用 fold expressions
template <typename... Pack>
auto Add(const Pack&... arg_list)
{
    return (... + arg_list);
}
  1. 如果您使用的是 C++ 11,请使用 std::common_type
template <typename T>
T Add(const T& arg_a)
{
    return arg_a;
}

template <typename T, typename... Pack>
typename std::common_type<T, Pack...>::type Add(const T& arg_a, const Pack&... arg_list)
{
    return arg_a + Add(arg_list...);
}

std::common_type不起作用时(例如,Add('a', 'b', 'c', 'd')),您仍然可以在c++ 11中编写自己的求和类型推导器:

template <typename SumLeftT, typename ... Args>
struct sum_t_impl;

template <typename SumLeftT>
struct sum_t_impl<SumLeftT> {
    using type = SumLeftT;
};

template <typename SumLeftT, typename FirstT, typename ... Rest>
struct sum_t_impl<SumLeftT, FirstT, Rest...> {
    using type = typename sum_t_impl<decltype(std::declval<SumLeftT>() + std::declval<FirstT>()), Rest...>::type;
};

template <typename T, typename ... TArgs>
using sum_t = typename sum_t_impl<T, TArgs...>::type;

并将 return 值类型 typename std::common_type<T, Pack...> 替换为 sum_t<T, Pack...>

例如,这适用于具有 char 的包,其中 char + char -> int

int main()
{
    auto itLocalSum = Add('a', 'b', 'c', 'd');
    std::cout << typeid(itLocalSum).name() << std::endl;
    std::cout << "Sum of Add: " << itLocalSum << "\n";
    return 0;
}

将输出:int394

  1. 为什么不会你通过引用传递参数?对于整数没关系,但我也可以 Add(std::string{"QWER"}, std::string{"ASDF"}, std::string{"ZXCV"}) 并且复制它们可能会非常昂贵。

  2. 你总是 return 左侧类型,无论如何。这意味着如果你有Add(2, 3.5),结果必须是int,它会被转换成这样。

为了避免它,你可以推断出 returned 类型:

template <typename T, typename... Pack>
auto Add(const T& arg_a, const Pack&... arg_list)
{
    return arg_a + Add(arg_list...);
}

关于错误的金额...问题是

template <typename T, typename... Pack>
T Add(const T& arg_a, const Pack&... arg_list)
{
    return arg_a + Add(arg_list...);
}

return 与第一个参数 (T) 相同类型的值。

因此,如果 T 是一个整数,您将在以下参数中丢失浮点部分。

万一

Add(2.3, 43, 32.2)

你明白了,在43之后,32.2变成了32

要解决这个问题...如果你会用C++17,你可以使用模板折叠并简单地写

template <typename ... Ts>
auto Add (Ts const & ... as)
 { return (as + ...); }

如果不能用C++17但可以用C++14,可以模拟模板折叠如下

template <typename ... Ts>
auto Add (Ts const & ... as)
 {
   using unused = int[];

   typename std::common_type<Ts...>::type ret{};

   (void)unused { 0, ((void)(ret += as), 0)... };

   return ret;
 }

观察 std::common_type<Ts...>::type 的使用以获得 returned 类型的变量。

如果你只能使用 C++11(所以没有 auto 没有尾随 return 类型),你必须明确 return 类型

template <typename ... Ts>
typename std::common_type<Ts...>::type Add (Ts const & ... as)
 { 
   // same body as in C++14
 }

Why is it used const reference for parameters?

当您打算调用它时:

auto itLocalSum = Add(1, 2, 3, 4, 5);

参数必须是 const& 或者只是值。即

template <typename T>
T Add(const T& arg_a)
{
    return arg_a;
}

template <typename T, typename... Pack>
T Add(const T& arg_a, const Pack&... arg_list)
{
    return arg_a + Add(arg_list...);
}

template <typename T>
T Add(T arg_a)
{
    return arg_a;
}

template <typename T, typename... Pack>
T Add(T arg_a, Pack... arg_list)
{
    return arg_a + Add(arg_list...);
}

对于简单类型,两者都可以。如果 T 的复制成本很高,const& 会更有效率。

When I use floating numbers with a combination of integers like add(2.3, 43, 32.2) it doesn't show correct value to me. how can I fix it?

您可以使用 43.0 代替 43

当您使用 Add(2.3, 43, 32.2) 时,递归调用 Add(43, 32.2) return 是一个 int 并将 return 值截断为 75