在std::exchange中,为什么第二个模板参数默认了?

In std::exchange, why is the second template parameter defaulted?

C++14 标准为 std::exchange 指定了以下声明:

template <class T, class U = T>
T std::exchange(T& obj, U&& new_value);

我想知道为什么 U 默认为 T,因为 U 可以通过 new_value 找到。在什么情况下,这将导致与以下不同的结果:

template <class T, class U>
T std::exchange(T& obj, U&& new_value);

该函数可以作为参数传递给其他函数或算法。在这种情况下,如果函数的两个参数具有相同的类型,则仅指定第一个模板参数就足够了。

这使得代码更短更易读。

这里是一个人为的例子。:)

#include <iostream>
#include <numeric>
#include <iterator> 
#include <functional>


int main()
{
    int a[] = { 1, 2, 3 };
    int b[] = { 4, 5, 6 };

    std::cout << "a: ";
    for ( int x : a ) std::cout << x << ' ';
    std::cout << std::endl;
    std::cout << "b: ";
    for ( int x : b ) std::cout << x << ' ';
    std::cout << std::endl;

    auto sum = std::inner_product( std::begin( a ), std::end( a ),
                                   std::make_move_iterator( std::begin( b ) ), 0,
                                   std::plus<int>(), std::exchange<int> );

    std::cout << "sum = " << sum << std::endl;
    std::cout << "a: ";
    for ( int x : a ) std::cout << x << ' ';
    std::cout << std::endl;
}

输出为

a: 1 2 3 
b: 4 5 6 
sum = 6
a: 4 5 6 

或者示例可以包含转换

#include <iostream>
#include <numeric>
#include <iterator> 
#include <functional>


int main()
{
    int a[] = { 1, 2, 3 };
    double b[] = { 4.4, 5.5, 6.6 };

    std::cout << "a: ";
    for ( int x : a ) std::cout << x << ' ';
    std::cout << std::endl;
    std::cout << "b: ";
    for ( double x : b ) std::cout << x << ' ';
    std::cout << std::endl;

    auto sum = std::inner_product( std::begin( a ), std::end( a ),
                                   std::make_move_iterator( std::begin( b ) ), 0,
                                   std::plus<>(), std::exchange<int> );

    std::cout << "sum = " << sum << std::endl;
    std::cout << "a: ";
    for ( int x : a ) std::cout << x << ' ';
    std::cout << std::endl;
}

std::exchange 是在 N3511 without default template argument, and later N3608 中提出的,带有默认模板参数。请注意,在 N3608 中提供了以下推理:

Giving the second template argument a default value fixes the following two cases:

DefaultConstructible x = ...;
if (exchange(x, {})) { ... }

int (*fp)(int);
int f(int);
double f(double);
/*...*/ exchange(fp, &f) /*...*/

第一个例子的用处当然是一个无类型的临时 {} 将被推断为 T。第二个例子比较复杂:

14.8.2 Template argument deduction [temp.deduct]

5 The resulting substituted and adjusted function type is used as the type of the function template for template argument deduction. If a template argument has not been deduced and its corresponding template parameter has a default argument, the template argument is determined by substituting the template arguments determined for preceding template parameters into the default argument. If the substitution results in an invalid type, as described above, type deduction fails.

14.8.2.5 Deducing template arguments from a type [temp.deduct.type]

4 In most cases, the types, templates, and non-type values that are used to compose P participate in template argument deduction. That is, they may be used to determine the value of a template argument, and the value so determined must be consistent with the values determined elsewhere. In certain contexts, however, the value does not participate in type deduction, but instead uses the values of template arguments that were either deduced elsewhere or explicitly specified. If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails.

5 The non-deduced contexts are:

(5.5) — A function parameter for which argument deduction cannot be done because the associated function argument is a function, or a set of overloaded functions (13.4), and one or more of the following apply:

(5.5.1) — more than one function matches the function parameter type (resulting in an ambiguous deduction)

在第二个示例中,模板参数 U 仅在 non-deduced 上下文中使用,因为两个重载 f(int)f(double) 都可以匹配到 U。因此,参数推导不会发生,并且 U 成为 T 提供的默认值(在这种情况下为 int (*)(int),因此选择 f(int))。

此外,正如@VladfromMoscow 所解释的,具有默认参数允许在传递 std::exchange<T>(例如标准算法)时使用更短的代码。