How does std::pair constructor overload taking tuples tell if tuple contents are r-val references?

46 Views Asked by At

Apologies for the awkwardly worded question. My uncertainty is based on a situation that can roughly be reduced to the following scenaio.

I have a Foo class, and I'd like a std::pair<int, Foo> object. Foo is defined as follows:

class Foo
{
public:
    Foo(int& a, int& b) { std::cout << "constructor with lvals called\n"; ... }
    Foo(int&& a, int&& b) { std::cout << "constructor with rvals called\n"; ... }
...
};

Now if I create my std::pair as follows:

std::pair<int, Foo> myPair{std::piecewise_construct,
                           std::forward_as_tuple(1),
                           std::forward_as_tuple(2, 3)};

The function signature for std::forward_as_tuple is

template< class... Types >
tuple<Types&&...> forward_as_tuple( Types&&... args ) 

Hence my call to the constructor of std::pair should resolve to something like the following:

std::pair<int, Foo> myPair{std::piecewise_construct,
                           tuple<int&&>(1),
                           tuple<int&&, int&&>(2, 3)};

This will (I imagine) call the following constructor of std::pair:

template< class... Args1, class... Args2 >
constexpr pair( std::piecewise_construct_t,
                std::tuple<Args1...> first_args,
                std::tuple<Args2...> second_args );

It looks like this constructor will deduce Args1 as (int) and Args2 as (int, int), and therefore that second_args will bind to a tuple<int, int> object copied from the tuple<int&&, int&&> argument that was passed to it. However when I run this code I do get "constructor with r-vals called" printed to screen, so clearly the contents of second_args are r-val references, and there's a gap in my understanding. I imagine I might be missing something during the template deduction in std::pair's constructor, but I'm not sure what it is.

Any help appreciated!

1

There are 1 best solutions below

1
Mohamed Mahmoud On

You are right about the fact that in the deduction process, references collapse. But, collapsing doesn't affect the types of the tuple elements themselves. So, even though int&& collapses to int during deduction, the actual types in the tuples remain unchanged.

if you do something like this, you will get the lvalue print.

int a = 2, b = 3;
std::pair<int, Foo> myPair{std::piecewise_construct,
                           std::forward_as_tuple(1),
                           std::forward_as_tuple(a, b)};

This link is very good for template argument deduction. https://en.cppreference.com/w/cpp/language/template_argument_deduction