2

I don't know if this is the right approach, but I think it explains what I'm trying to achieve.

I have three vectors:

std::vector<int> v1 = {1,2,3};
std::vector<int> v2 = {5,6,7};
std::vector<int> v3 = {8,9,10};

I would like to create a vector that contains references to the first elements of these vectors, I've tried doing it as follows:

std::vector<std::reference_wrapper<int>> v;
v.push_back(v1[0]);
v.push_back(v2[0]);
v.push_back(v3[0]);

so I could do:

std::rotate(v.begin(),v.begin+1,v.end())

and get:

v1 = 5, 2, 3
v2 = 8, 6, 7
v3 = 1, 9, 10

it almost works, doing the following modifies the original vectors:

++v[0];

But assignment doesn't work:

v[0] = new_value; // doesn't compile

Nor std::rotate has any affect.

How could I make this work?

Code

#include <vector>
#include <iostream>
#include <algorithm>
#include <functional>

void print_vector(std::vector<int> &v) {
    std::for_each(v.begin(),v.end(),[](auto& x){std::cout << x << " ";});
    std::cout << "\n";
}

int main() {

    std::vector<int> v1 = {1,2,3};
    std::vector<int> v2 = {5,6,7};
    std::vector<int> v3 = {8,9,10};

    std::vector<std::reference_wrapper<int>> v;

    v.push_back(v1[0]);
    v.push_back(v2[0]);
    v.push_back(v3[0]);

    // This doesn't work, it rotates the references but not the values
    std::rotate(v.begin(),v.begin()+1,v.end());
    print_vector(v1);
    print_vector(v2);
    print_vector(v3);

    // Never the less this does work
    ++v[0];
    print_vector(v1);
    print_vector(v2);
    print_vector(v3);

    //v[0] = 3; // Assigment doesn't compile


    return 0;
}
Blasco
  • 1,607
  • 16
  • 30
  • Please edit your question to provide [mcve] that shows your problem – Slava Jan 25 '18 at 14:49
  • [`std::reference_wrapper::operator=`](http://en.cppreference.com/w/cpp/utility/functional/reference_wrapper) rebinds the wrapper, it does not assign a value to the existing element. – Holt Jan 25 '18 at 14:51

2 Answers2

4

The assignment operator of std::reference_wrapper (std::reference_wrapper::operator=) does not assign a new value to the referenced element, it rebinds the wrapper. So basically:

std::vector<std::reference_wrapper<int>> v;
int a = 0;
v[0] = a;

assert( &v[0].get() == &a ); // true

If you want to assign a new value to the referenced element, you need to be explicit:

v[0].get() = a;

If you want v[0] = a; to work as you expect, or even std::rotate (because it actually swaps the reference, not the value), you may write your own wrapper:

/**
 * Class implementing std::reference_wrapper that
 * cannot be rebound after creation.
 *
 **/
template <class T>
class single_bind_reference_wrapper {

    // pointer to the original element
    T *p_;

public: // typedefs

    using type = T;

    // construct/copy/destroy
    single_bind_reference_wrapper(T& ref) noexcept : p_(std::addressof(ref)) {}
    single_bind_reference_wrapper(T&&) = delete;

    // Enable implicit convertsion from ref<T> to ref<const T>,
    // or ref<Derived> to ref<Base>
    template <class U, std::enable_if_t<std::is_convertible<U*, T*>{}, int> = 0>
    single_bind_reference_wrapper(const single_bind_reference_wrapper<U>& other) noexcept :
        p_(&other.get()) { }

    // assignment
    template <class U>
    decltype(auto) operator=(U &&u) const 
          noexcept(std::is_nothrow_assignable<T, U>{}) {
        return get() = std::forward<U>(u);
    }

    decltype(auto) operator=(const single_bind_reference_wrapper& other) const
          noexcept(std::is_nothrow_assignable<T, T>{}) {
        return get() = other.get();
    }


    // access
    operator T& () const noexcept { return *p_; }
    T& get() const noexcept { return *p_; }
};

You will need to provide a custom swap functions for most algorithm to work properly, something like:

template <class T>
void swap(single_bind_reference_wrapper<T> &lhs,
          single_bind_reference_wrapper<T> &rhs)
    noexcept(std::is_nothrow_move_constructible<T>::value &&
             std::is_nothrow_move_assignable<T>::value){
    auto tmp = std::move(lhs.get());
    lhs = std::move(rhs.get());
    rhs = std::move(tmp);
}
Holt
  • 36,600
  • 7
  • 92
  • 139
  • and if I want to rotate them? That's my goal. Rotation seems to rotate the references, but not the values. Maybe there is a different approach instead of using the reference_wrapper – Blasco Jan 25 '18 at 14:59
  • @WooWapDaBug You could write your own reference wrapper. [`std::reference_wrapper`](http://en.cppreference.com/w/cpp/utility/functional/reference_wrapper) is a very small class (see the possible implementation in the previous link), so it should not be that hard. – Holt Jan 25 '18 at 15:03
  • @WooWapDaBug I have added a possible implementation - Not tested extensively though. – Holt Jan 25 '18 at 15:08
  • Probably it is possible to provide special version of `swap()` but looks like this solution is better. It is strange they designed such a bad reference wrapper – Slava Jan 25 '18 at 15:13
  • I think it does not have to be single bind, but rebinding should be just explicit. – Slava Jan 25 '18 at 15:14
  • @Slava This is just a possible implementation, you can easily add a `rebind()` method that rebinds the wrapper to something else. – Holt Jan 25 '18 at 15:16
  • I think you should provide const/non const accessors, in current implementation you could change referenced object through const wrapper – Slava Jan 25 '18 at 15:23
  • @Slava A `const single_bind_reference_wrapper` is different from a `single_bind_reference_wrapper`, same way smart pointers work and standard `reference_wrapper` works. And a `const single_bind_reference_wrapper` does not make sense since it is const by design. It is just a starting point, people can modify it if they need/want to. – Holt Jan 25 '18 at 15:28
  • "A const single_bind_reference_wrapper is different from a single_bind_reference_wrapper, same way smart pointers work" I am looking into this wrapper as a reference, so non const is lvalue reference and const is const reference. So it makes sense then. (const reference does not allow to change referenced object no matter if object itself const or not). – Slava Jan 25 '18 at 15:46
  • @Slava I understand your point, but I see things this way: All references are constant by definition, and a `const T&` is a reference to a const/immutable `T`, while `T&` is a reference to non-const/mutable `T`. That is also consistent with `std::reference_wrapper` behavior (a const `std::reference_wrapper` is implicitly convertible to `T&`, not only `const T&`). People are free to implement this the way they feel makes the most sense. – Holt Jan 25 '18 at 16:04
  • @Holt "People are free to implement this the way they feel makes the most sense" sure, I just provided my thoughts on how I would implement that for myself. – Slava Jan 25 '18 at 16:08
  • @Holt A beginner question, why do you need all those `noexcept`? – Blasco Jan 26 '18 at 10:26
  • @WooWapDaBug Because I started with a copy/paste of the possible implementation of `reference_wrapper` on cppreference ;) [This question](https://stackoverflow.com/questions/10787766/when-should-i-really-use-noexcept) contains some details regarding why it could be useful to mark functions (especially construtors/assignments) as `noexcept`. – Holt Jan 26 '18 at 10:31
  • @Holt I cannot make your code to work. I've open a [new question](https://stackoverflow.com/questions/48459931/implementing-a-binding-reference-wrapper-to-be-used-with-stl-algorithms), I'm not sure what I'm missing or how should I implement the `=` operator. Thank you for your help, I think this is the direction I was looking for. – Blasco Jan 26 '18 at 10:53
  • @WooWapDaBug I have updated the code - There was an issue in the way I was checking if the assignment was `noexcept`, using `std::is_nothrow_assignable` is much better here. I had to add an explicit assignment operator from `single_bind_reference_wrapper` because of the ambiguity, but I have to admit I don't know why... – Holt Jan 26 '18 at 11:03
  • @Holt Aggg, it almost works, but it seems it is still doing something funny with the references: `1 2 3 4 8 12 16 15 14 13 9 5` gets rotated into `2 3 4 8 12 16 15 14 13 9 5 5` instead of `2 3 4 8 12 16 15 14 13 9 5 1 ` – Blasco Jan 26 '18 at 11:17
  • 1
    @WooWapDaBug The problem with `rotate` is that it uses `swap`, which will behave "funkly" with `single_bind_reference_wrapper` (it will make a copy of the reference, and then assign to it, which is meaningless). You need to provide an overload of `swap` **in the namespace where you defined `single_bind_reference_wrapper`**. I've added a possible implementation in my answer. – Holt Jan 26 '18 at 11:40
1

Rotating the references into v doesn't rotate the values of the three vectors. So the solution I came up with is to make a copy of the rotated values and assign them back to the value referenced from v.

To perform the rotation:

std::vector<int> v_rotated;

std::rotate_copy(v.begin(), v.begin() + 1, v.end(),
    std::back_inserter(v_rotated));

The vector v_rotated contains the values with the order I want. Now I need to assign them to the values referenced in v. Using algorithms such as std::copy is not a viable solution because that would assign the references instead of values. In fact, as already stated from the other answer,

The assignment operator of std::reference_wrapper (std::reference_wrapper::operator=) does not assign a new value to the referenced element, it rebinds the wrapper

But I can take advantage of the implicit conversion operator 1 and using std::transform2 to carry out the copy:

    auto copy = [](int &from, int &to) {
    to = from;
    return std::ref(to);
};

This lambda will be used as a binary operation in std::transform. When an std::reference_warpper<int> is passed as one of the arguments, it returns the stored reference.

std::transform(v_rotated.begin(), v_rotated.end(),
    v.begin(), v.begin(),
    copy);

Since std::transform needs a destination container I choose to use v as a destination container and copy returns the reference to having the vector unchanged. That would be equivalent to:

auto it_rotated = v_rotated.begin();
for (auto it = v.begin(); it < v.end(); it++, it_rotated++)
{
    int &from = *it_rotated;
    int &to = *it;
    to = from;
}

Or using the explicit conversion

for(size_t i = 0; i < v.size(); i++)
{
    v[i].get() = v_rotated[i];
}
F. Renato
  • 21
  • 2
  • 4