Jonathan Mee
Jonathan Mee

Reputation: 38969

Why can't I use make_pair to tie?

I'm trying to mimic the behavior of tie pre C++11.

pair<int, int> test() {
    return make_pair(13, 42);
}

int main() {
    int a = 1, b = 2;

    pair<int&, int&>(a, b) = test();

    cout << a << ' ' << b << endl;
}

This works however if I use make_pair instead to the pair constructor a and b are not assigned.
Why does the pair constructor work but not make_pair?

Upvotes: 3

Views: 1428

Answers (3)

Jonathan Mee
Jonathan Mee

Reputation: 38969

In 20.2.2[lib.pairs]8 the standard states that pair uses "explicit types" while make_pair's "types are deduced".

This is why the standard defines a constructor for pair:

template <class T1, class T2>
pair(const T1& x, const T2& y)

If you run your code on a C++03 compiler you will get this error:

non-static reference member int& std::pair<int&, int&>::first, can't use default assignment operator

The problem is that pair uses an implicitly-declared copy assignment operator which is not defined if the pair:

Has a non-static data member of a reference type

Whether defined by make_pair or the pair constructor, the template arguments will define both of the pair's members as int& so the implicitly-declared copy assignment operator will not be defined. So this cannot be accomplished with a pair in C++03.

If using return parameter is undesirable, you can write your own implementation of tie:

template <class T1, class T2>
struct tie{
    T1& first;
    T2& second;

    tie(T1& x, T2& y) : first(x), second(y) {}

    tie<T1, T2>& operator=(const pair<T1, T2>& rhs){
        first = rhs.first;
        second = rhs.second;

        return *this;
    }
};

This will allow assignment of a pair:

tie<int, int>(a, b) = test();

To get the exact C++11 behavior which doesn't require template arguments you'll need to define a function. If tie is nested in namespace details the function can be defined as:

template <class T1, class T2>
details::tie<T1, T2> tie(T1& x, T2& y) { 
    return details::tie<T1, T2>(x, y);
}

This will allow assignment of a pair just as in C++11:

tie(a, b) = test();

Live Example

Note that this is still intolerant of using int& template arguments, so details::tie<int&, int&> and tie<int&, int&> will fail just as before.

Upvotes: 3

W.F.
W.F.

Reputation: 13988

Actually you can use std::make_pair. But you need to implement reference_wrapper class to imitate reference. Examplary (not very polished, but working as expected) c++03 approach:

#include <iostream>
#include <utility>

using namespace std;

template <class T>
struct reference_wrapper {
   bool is_const;
   T* v;
   T const* cv;
   reference_wrapper(T& t): v(&t), is_const(false) { }
   reference_wrapper(T const& t): cv(&t), is_const(true) { }

   reference_wrapper &operator=(reference_wrapper const &rw) {
      if (rw.is_const) {
         *v = *rw.cv;
      } else {
         *v = *rw.v;
      }
   }
};

template <class T>
reference_wrapper<T> ref(T &t) {
   return reference_wrapper<T>(t);
}

pair<int, int> test() {
    return make_pair(13, 42);
}

int main() {
    int a = 1, b = 2;

    //pair<int&, int&>(a, b) = test();  // works
    make_pair(ref(a), ref(b)) = test(); // now it does work

    std::cout << a << ' ' << b << std::endl;
}

Upvotes: 3

krzaq
krzaq

Reputation: 16431

make_pair produces a pair of values, not references. That means it would produce pair<int, int> in your example and you'd be assigning results of test() to a temporary variable¹.

You can mimic tie with the following:

template<typename T, typename U>
std::pair<T&, U&> tie_pair(T& l, U& r)
{
    return std::pair<T&, U&>(l, r);
}

http://ideone.com/muAcaG

¹​ this is an unfortunate side-effect of C++03 not having ref-qualifiers. In C++≥11 you can delete operator= for rvalue this (in non-std classes) and make such cases a compiler error rather than silent surprising behaviour.

Upvotes: 2

Related Questions