36

The C++14 standard specifies the following declaration for std::exchange:

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

I am wondering why U is defaulted to T since U can be found thanks to new_value. In what case, this would lead to a different result than:

template <class T, class U>
T std::exchange(T& obj, U&& new_value);
TemplateRex
  • 69,038
  • 19
  • 164
  • 304
Vincent
  • 57,703
  • 61
  • 205
  • 388

2 Answers2

39

The std::exchange was proposed in N3511 without default template argument, and later N3608 with a default template argument. Note that in N3608 the following reasoning was provided:

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) /*...*/

The first example's usefullness is of course that an untyped temporary {} will be deduced to T. The second example is more involved:

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)

In the second example, the template parameter U is only used in a non-deduced context because the two overloads f(int) and f(double) both can be matched to U. Hence, argument deduction does not take place, and U becomes the supplied default value of T (int (*)(int) in this case, so f(int) is selected).

In addition, as explained by @VladfromMoscow, having a defaulted parameter allows for shorter code when passing std::exchange<T> (to a standard algorithm e.g.).

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
10

The function can be passed as an argument to some other function or for example algorithm. In this case it is enough to specify only the first template argument if the both arguments of the function will have the same type.

This makes the code more shorter and readable.

Here is an artificial example.:)

#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;
}

The output is

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

Or the example could include a conversion

#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;
}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335