BAKE ZQ
BAKE ZQ

Reputation: 807

Template argument deduction doesn't work the same as in structured binding

I formally been told(some books or in web) that the type deduction in auto is the same as argument type deduction in template.

However, codes below doesn't compile successfully because it's raises increment of read-only reference a error. Though the template version deduct T as type int instead of const int &. Why these two cases have different deduction results?

#include "bits/stdc++.h"

using namespace std;

template <typename T>
T minmax2(T n1, T n2) {
    n1++;
    return n2;
}

int main() {
    auto [a, b] = minmax(1, 2); // minmax() return const T &
    a++;                        // compile error: can not alter read-only varaible.
    const int & c = 1, & d = 2;
    minmax2(c, d);              // the same parameter type, don't raise error
}

Another example:

int tmp = 1;
const int & test() {
    return tmp;
}

template <typename T>
void test1(T n) {
    n++;  // no error
}
int main() {
    auto a = test(); // If the satement is true, I expect this is the same as test1.
    a++; // no compile error, which is different with the previous code exp
    const int & b = 2;
    test1(b);
}

Upvotes: 0

Views: 109

Answers (3)

Daniel Langr
Daniel Langr

Reputation: 23537

I think your confusion comes from the fact that structured bindings behave a bit differently than the ordinary auto, at least from the user's perspective. Consider the latter first:

const int& i = 0;
auto j = i;
std::cout << &i << std::endl;
std::cout << &j << std::endl; // different address
j++; // compiles

Here, i and j refer to different objects and the type of j is int (not const int or const int&). This exactly corresponds with the template argument deduction when the argument is passed by value.

But with structured bindings, the situation is different:

int c = 0, d = 0;
auto [a, b] = std::pair<const int&, const int&>(c, d);
std::cout << &a << std::endl;
std::cout << &c << std::endl; // same address
// a++; // would not compile

Here, the type of a and b is const int& and both a and c refer to the same object.

Live demo: https://godbolt.org/z/Wq1zsz


Actually, structured bindings also ignores const and reference parts of an initialization expression, but at an "outer" level, that is, at a level of std::pair in our case:

const auto& p = std::make_pair(c, d);
auto [x, y] = p;
std::cout << &p.first << std::endl;
std::cout << &x << std::endl; // different address

Here, x is a new object with type int. If we used const auto& [x, y] = p; instead, then x would refer to p.first and its type would be const int&.

Upvotes: 1

Thomas Sablik
Thomas Sablik

Reputation: 16448

The signature of std::minmax is

template< class T >
std::pair<const T&,const T&> minmax( const T& a, const T& b );

The call

auto [a, b] = minmax(1, 2);

has equivalent behavior to

std::pair<const int&,const int&> temp = minmax(1, 2);
const int &a = temp.first;
const int &b = temp.second;

That is how structured binding works.

As you can see the type T is deduced as int. std::minmax adds a const and a & to the type and makes a pair of it.

You could write your own minmax2 as

template <typename T>
std::pair<T,T> minmax2(T n1, T n2) {
    return std::make_pair(n1, n2);
}

The call

auto [a, b] = minmax2(1, 2);

has equivalent behavior to

std::pair<int, int> temp = minmax2(1, 2);
int &a = temp.first;
int &b = temp.second;

Here you can

++a;

Upvotes: 0

Amachi
Amachi

Reputation: 100

Your comment already states your problem: // minmax() return const T &

cppreference for std::minmax

After your binding, you essentially have the following variables: const int &a and const int &b, afterwards you're trying to increment your const reference of a which is obviously not working.

To fix your code, you can simply create a copy of a like this:

auto [a, b] = minmax(1, 2); // minmax() return const T &
auto a_cpy = a;
++a_cpy;

Upvotes: 0

Related Questions