12

I'm writing a custom iterator that, when dereferenced returns a tuple of references. Since the tuple itself is ephemeral, I don't think I can return a reference from operator*(). I think my iterator makes sense semantically, since it has reference semantics, even though operator* returns a value.

The issue is, when I try to call std::swap (or rather, when std::sort does), like below, I get errors because the swap expects l-values. Is there an easy fix to this problem?

#include <vector>

class test {
  public:
  test()
    :v1(10), v2(10)
  {}

  class iterator {
    public:
    iterator(std::vector<int>& _v1,
             std::vector<int>& _v2)
      :v1(_v1), v2(_v2){}

    std::tuple<int&, int&> operator*(){
      return std::tuple<int&, int&>{v1[5], v2[5]};
    }
    std::vector<int>& v1;
    std::vector<int>& v2;
  };

  std::vector<int> v1, v2;
};



int main(){
  test t;
  //error, Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/type_traits:3003:1: note: candidate function [with _Tp = std::__1::tuple<int &, int &>] not viable: expects an l-value for 1st argument

  //deep within the bowels of std::sort ...
  std::swap(*(test::iterator(t.v1, t.v2)),
            *(test::iterator(t.v1, t.v2)));
}
Ben Jones
  • 919
  • 1
  • 8
  • 22
  • You'll need a custom tuple-like type that supports the semantics you want. – JoeG Feb 05 '14 at 21:26
  • At least in g++ `sort` calls `iter_swap` which calls `__iter_swap` which calls an unqualified `swap` which means you can create your own `swap` function that ADL will pick up. I don't have a standard reference that this is guaranteed though. – Mark B Feb 05 '14 at 21:43
  • 1
    To do ADL, I'd need to write my own type that holds a tuple and write a swap for that(probably with implicit conversion to std::tuple), right? That seems like the best solution I've heard so far. – Ben Jones Feb 05 '14 at 21:46

3 Answers3

7

On rare occasions it may be desirable to get an lvalue reference to temporary. This easily achieved with a cast opposite to std::move:

template <typename T>
T & stay(T && t) { return t; }

Usage:

std::swap(stay(foo()), stay(bar()));

As you already said, if you can't change the call site, your best option may be to write your own reference wrapper and use ADL for that:

namespace detail
{
    struct IntRefPair
    {
        int & a, & b;
        IntRefPair(int & x, int & y) : a(x), b(y) {}
    };

    void swap(IntRefPair && lhs, IntRefPair && rhs)
    { 
        std::swap(lhs.a, rhs.a);
        std::swap(lhs.b, rhs.b);
    }
}

// ...

IntRefPair operator*() { return IntRefPair(v1[5], v2[5]); } }
Community
  • 1
  • 1
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • I can't change the call to std::swap, since it's within the depths of std::sort. How can your solution still be applied? – Ben Jones Feb 05 '14 at 21:14
  • Buried in there: "The issue is, when I try to call std::swap (or rather, when std::sort does)". I'll edit to make it more clear in the code snippet. – Ben Jones Feb 05 '14 at 21:20
  • @BenJones: Yes, I see. That's a problem. Perhaps you can write your own reference wrapper, rather than rely on `std::tuple`, and use ADL? (As you already discovered.) – Kerrek SB Feb 05 '14 at 22:28
  • Using my own wrapper looks like it will break existing code that expected the dereferenced iterator to be a std::tuple, since even with an implicit conversion operator to tuple, calls to std::get fail template argument deduction. Specializing std::get for myself isn't allowed is it :)? – Ben Jones Feb 05 '14 at 22:45
  • @BenJones: I'm not sure, it might be? We should check. That might be a solution. – Kerrek SB Feb 05 '14 at 23:50
  • From what I could tell, it's not customizable. I wrote my own get function and used ADL. – Ben Jones Feb 05 '14 at 23:57
3

The answer I came up with was to write a tuple wrapper class:

template<typename ... Types>
struct tupleWrapper{
   std::tuple<Types...> data;
   tupleWrapper(std::tuple<Types...> _data) : data{_data}
   {}
   operator std::tuple<Types...> () {return data;}
   std::tuple<Types...>& asTuple() {return data;}
 };

template<typename ... Types>
void swap(tupleWrapper<Types ...> t1, tupleWrapper<Types ...> t2){
  std::swap(t1.data, t2.data);
}

And a get function that can be found with ADL, since the conversion operator doesn't get called when doing TAD for std::get.

template<int N, typename ...Ts>
  auto get(tupleWrapper<Ts...> tw)->decltype(std::get<N>(tw.data)){
    return std::get<N>(tw.data);
}

It's not ideal, but I think it will work well enough.

Ben Jones
  • 919
  • 1
  • 8
  • 22
  • I know this post is ages old, but I'm using this snippet now. Why do you pass `tw` by value in your `get` implementation? – MivVG Sep 08 '18 at 20:15
  • I can't remember if it was intentional or not... sorry! I think development of rangesv3 has made more code use stuff like iter_swap that avoids needing to do stuff like this, so you might be able to avoid this hack altogether! – Ben Jones Sep 17 '18 at 21:51
  • Thanks for your answer, that's an interesting approach, I'll look into it. – MivVG Sep 18 '18 at 05:43
  • For reference, I'm making a more generic version of OP's `iterator` (can iterate over any amount of any type of container) – MivVG Sep 18 '18 at 05:46
0

You can place the std::tuple as a data member and return a reference to that:

class iterator
{
public:
    iterator(std::vector<int>& _v1,
             std::vector<int>& _v2)
      : tup(_v1[5], _v2[5]) {}

    tuple_of_references& operator*() const
    {
        return tup;
    }
private:
    typedef std::tuple<int&, int&> tuple_of_references; // just to cut down size
    tuple_of_references tup;
};
David G
  • 94,763
  • 41
  • 167
  • 253
  • In my actual use case, the tuple isn't constant, so it seems like this might have issues if I do something like: auto t1 = *it; ++it; auto t2 = *it; //now the tuples are aliased – Ben Jones Feb 05 '14 at 21:41
  • @BenJones Are you saying it's the `const`-ness of `operator*` that's the problem? If it is, then just take away the `const`. – David G Feb 05 '14 at 21:47
  • 1
    No, I think the example above isn't quite what I meant. auto& t1 = *it; ++it; //operator ++ would probably change the tuple held in iterator, so t1's value changed. auto& t2 = *it; //t1 and t2 point alias the same tuple, which doesn't make much sense – Ben Jones Feb 05 '14 at 21:50
  • I am not sure this approach will work unless the tuple container lives a long life... So, it should be still alive after the sort procedure for sure.. – Nik Ved Jun 04 '20 at 19:17