std::pair of references
Asked Answered
T

8

47

Is it valid to have a std::pair of references ? In particular, are there issues with the assignment operator ? According to this link, there seems to be no special treatment with operator=, so default assignement operator will not be able to be generated.

I'd like to have a pair<T&, U&> and be able to assign to it another pair (of values or references) and have the pointed-to objects modified.

Tangram answered 22/9, 2010 at 13:35 Comment(6)
Why not use pointers? Certainly no problems (except possible memory leaks) with std::pair<A*,B*>Systemic
You can have boost::ref s as well.Longfaced
"except possible memory leaks" would be a strong reason in my book. However, since in this case the pair doesn't seem to own the objects, this shouldn't be a problem. But syntactical inconveniences are a pretty good reason as well.Romero
@Matt: boost::reference_wrapper is not assignable.Setscrew
@rubenvb, @sbi: It is a syntax sugar thing, I don't want pointers.Tangram
std::pair of references is valid in C++11 but not in C++03, (not sure if because a change of the semantics of the language or because the standard library has changed.) boost::tuple is a solution in C++03 but it sounds like and overkill at first glance.Divulgate
W
30

No, you cannot do this reliably in C++03, because the constructor of pair takes references to T, and creating a reference to a reference is not legal in C++03.

Notice that I said "reliably". Some common compilers still in use (for GCC, I tested GCC4.1, @Charles reported GCC4.4.4) do not allow forming a reference to a reference, but more recently do allow it as they implement reference collapsing (T& is T if T is a reference type). If your code uses such things, you cannot rely on it to work on other compilers until you try it and see.

It sounds like you want to use boost::tuple<>

int a, b;

// on the fly
boost::tie(a, b) = std::make_pair(1, 2);

// as variable
boost::tuple<int&, int&> t = boost::tie(a, b);
t.get<0>() = 1;
t.get<1>() = 2;
Wooer answered 22/9, 2010 at 18:16 Comment(3)
@Romero The question was "Is it valid to have a std::pair of references ? I want ..." and I answered correctly "No you cannot do this reliably in C++03".Wooer
Anyway, IIUC, then you're saying that the reference collapsing feature wasn't in C++03? Edit: Ah, I see, James and you have already sorted that out.Romero
Hmmm, I've just tested gcc 4.4.4 (more recent that 4.1) and it correctly disallows forming a reference to a reference.Yonah
L
42

In C++11 you can use std::pair<std::reference_wrapper<T>, std::reference_wrapper<U>> and the objects of that type will behave exactly as you want.

Lunisolar answered 27/8, 2013 at 11:23 Comment(4)
Or std::tr1::reference_wrapper if you are stuck using an older compiler that happens to include TR1 support.Amherst
@Nemo: That would now be a very old compiler (i.e. not distros from the last 3 years).Diversify
@einpoklum: True, but some of us work on code bases where we are stuck on older compilers. (In my case, even pre-1998 in some areas...)Amherst
reference_wrapper will kinda do what OP wants, within the limits of its interface. Sure, it sugars away the sight of pointers - as a class holding a pointer, which tries to pretend it's not a pointer... AFAICT it was made to work around limitations of template deduction with functors, etc. - not really as a general-purpose thing to use. IMO it's an awful lot more typing just to sate an aversion to raw pointers, which to me seem unfairly derided, as good advice not to use them as owners is extrapolated to odd extremes elsewhere... as Sutter says, they are exactly suited to an observing roleHeaviside
W
30

No, you cannot do this reliably in C++03, because the constructor of pair takes references to T, and creating a reference to a reference is not legal in C++03.

Notice that I said "reliably". Some common compilers still in use (for GCC, I tested GCC4.1, @Charles reported GCC4.4.4) do not allow forming a reference to a reference, but more recently do allow it as they implement reference collapsing (T& is T if T is a reference type). If your code uses such things, you cannot rely on it to work on other compilers until you try it and see.

It sounds like you want to use boost::tuple<>

int a, b;

// on the fly
boost::tie(a, b) = std::make_pair(1, 2);

// as variable
boost::tuple<int&, int&> t = boost::tie(a, b);
t.get<0>() = 1;
t.get<1>() = 2;
Wooer answered 22/9, 2010 at 18:16 Comment(3)
@Romero The question was "Is it valid to have a std::pair of references ? I want ..." and I answered correctly "No you cannot do this reliably in C++03".Wooer
Anyway, IIUC, then you're saying that the reference collapsing feature wasn't in C++03? Edit: Ah, I see, James and you have already sorted that out.Romero
Hmmm, I've just tested gcc 4.4.4 (more recent that 4.1) and it correctly disallows forming a reference to a reference.Yonah
R
9

I think it would be legal to have a std::pair housing references. std::map uses std::pair with a const type, after all, which can't be assigned to either.

I'd like to have a pair<T&, U&> and be able to assign to it another pair

Assignment won't work, since you cannot reset references. You can, however, copy-initialize such objects.

Romero answered 22/9, 2010 at 13:39 Comment(12)
@Romero - is reference to a reference ever legal? Wouldn't that be required if using the pair in many places in STL?Quadriplegia
I don't want to reset references. I'd like the referenced objects to be assigned.Tangram
@Alexandre: There will be no compiler-generated assignment operator for classes with reference member. But you can always derive from std::pair and give the derived class its own operator=() which does what you want.Romero
@Steve: I'm not sure what you're after. A std::pair<F&,S&>& would certainly be legal. What's not legal is T& &.Romero
@sbi: I like this solution. I give it a try.Tangram
@Romero - my question speaks to how useful std::pair<x,y> is by itself. Typically I've seen this in the context of associative containers - e.g. would std::map<F&, S&> also be both legal and fully available, ie. no methods that barf because of F& & or S& & ?Quadriplegia
@Steve: In a template, if you have a type T that is instantiated with a reference type (say, int&) and you try to form a reference with it (so, you use T&), the references collapse (so T& -> int& not T& -> int&&). It's called reference collapsing. If I recall correctly (and I may be wrong here), that wasn't actually in C++98, but there was a very early defect against the standard and the resolution was to allow this (and most compilers should support it); it is certainly in C++0x, though the rules are a bit more complex due to the introduction of rvalue references.Muhammadan
@James McNellis - perfect, thanks. If this was an answer I would upvote.Quadriplegia
@Steve Townsend: reference to reference does not make sense. A reference is not a variable in that type of sense it is an alternative name for an existing variable (ie and Alias). So a reference IS the other variable just named differently.Prather
@Martin York - thanks, this is all becoming a little clearer. I found an in-depth discussion to clarify this further for myself.Quadriplegia
Alexandre, according to James and Johannes, my answer is wrong. :( You should pick on of theirs instead.Romero
@sbi: MSVC has had this behaviour for as long as I can remember, and it's definitely in C++0x. Won't speak for C++03 but I thought it was Standard behaviour.Miraculous
P
5

You are right. You can create a pair of references, but you can't use operator = anymore.

Psychognosis answered 22/9, 2010 at 13:38 Comment(0)
S
4

Post c++14, you can do:

int a, b;
auto const p(std::make_pair(std::ref(a), std::ref(b)));

Using std::cref() is also possible.

Spirituel answered 3/8, 2021 at 13:4 Comment(0)
D
2

I was thinking along the same lines as you, I think. I wrote the following class to scratch this particular itch:

template <class T1, class T2> struct refpair{
    T1& first;
    T2& second;
    refpair(T1& x, T2& y) : first(x), second(y) {}
    template <class U, class V>
        refpair<T1,T2>& operator=(const std::pair<U,V> &p){
            first=p.first;
            second=p.second;
            return *this;
        }
};

It allows you to do horrible things like:

int main (){

    int k,v;
    refpair<int,int> p(k,v);

    std::map<int,int>m;
    m[20]=100;
    m[40]=1000;
    m[60]=3;

    BOOST_FOREACH(p,m){
        std::cout << "k, v = " << k << ", " << v << std::endl;      
    }
    return 0;
}

(remember the relevant includes).

The nastiness is of course that the references to k and v that I am assigning to are hidden inside p. It almost becomes pretty again if you do something like this:

template <class T1,class T2>
refpair<T1,T2> make_refpair (T1& x, T2& y){
    return ( refpair<T1,T2>(x,y) );
}

Which allows you to loop like this:

BOOST_FOREACH(make_refpair(k,v),m){
    std::cout << "k, v = " << k << ", " << v << std::endl;      
}

(All comments are welcome as I am in no way a c++ expert.)

Denyse answered 7/3, 2011 at 13:58 Comment(1)
I dont quite understand the purpose of the extra template for the assignment operator. Is that for dealing with inheritance structures and casting from different types like doubles to ints?Utilitarianism
D
1

I don't know what is "wrong" with std::pair in C++03 but if I reimplement it naively, I don't have any problem with it, (using the same compiler gcc and clang).

double a = 1.;
double b = 2.;
my::pair<double, double> p1(5., 6.);
my::pair<double&, double&> p2(a, b);
p2 = p1; // a == 5.

So a workaround could be to (1) reimplement pair (in a different namespace), or (2) specialize for std::pair<T&, T&>, or (3) simply use C++11 (where std::pair for refs works out of the box)

(1) Here it is the naive implementation

namespace my{
template<class T1, class T2>
struct pair{
    typedef T1 first_type;
    typedef T2 second_type;
    T1 first;
    T2 second;
    pair(T1 const& t1, T2 const& t2) : first(t1), second(t2){}
    template<class U1, class U2> pair(pair<U1, U2> const& p) : first(p.first), second(p.second){}
    template<class U1, class U2> 
    pair& operator=(const pair<U1, U2>& p){
      first = p.first;
      second = p.second;
      return *this;
    }
};
template<class T1, class T2>
pair<T1, T2> make_pair(T1 t1, T2 t2){
    return pair<T1, T2>(t1, t2);
}
}

(2) And here it is an specialization of std::pair (some people may complain that I am messing around overloading/specializing with the std namespace, but I think it is ok if it is to extend the capabilities of the class)

namespace std{
    template<class T1, class T2>
    struct pair<T1&, T2&>{
        typedef T1& first_type;    /// @c first_type is the first bound type
        typedef T2& second_type;   /// @c second_type is the second bound type
        first_type first;
        second_type second;
        pair(T1& t1, T2& t2) : first(t1), second(t2){}
        template<class U1, class U2> pair(pair<U1, U2> const& p) : first(p.first), second(p.second){}
        template<class U1, class U2> 
        pair& operator=(const pair<U1, U2>& p){
          first = p.first;
          second = p.second;
          return *this;
        }
    };
}

Maybe I am missing something obvious, I can edit the answer if some obvious flaws, are pointed.

Divulgate answered 18/4, 2013 at 2:18 Comment(0)
U
-1

I ended up solving a similar problem by just building a really simple structure. I didn't even worry about the assignment operator since the default one should work fine.

template<class U, class V>
struct pair
{
pair(U & first, V & second): first(first), second(second) {}
U & first;
V & second;
}
Utilitarianism answered 22/9, 2014 at 4:56 Comment(1)
"I didn't even worry about the assignment operator since the default one should work fine" Try before posting. Course it doesn't work. So it doesn't answer the Q test.cpp:14:4: error: use of deleted function ‘pair<int, int>& pair<int, int>::operator=(const pair<int, int>&)’ z = y; ^ test.cpp:2:8: note: ‘pair<int, int>& pair<int, int>::operator=(const pair<int, int>&)’ is implicitly deleted because the default definition would be ill-formed: struct pair ^ test.cpp:2:8: error: non-static reference member ‘int& pair<int, int>::first’, can’t use default assignment operatorHeaviside

© 2022 - 2024 — McMap. All rights reserved.