【问题标题】:Modify the content of a std container through a different container通过不同的容器修改一个std容器的内容
【发布时间】:2018-01-25 14:46:34
【问题描述】:

我不知道这是否是正确的方法,但我认为它解释了我想要实现的目标。

我有三个向量:

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]);

所以我可以这样做:

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

然后得到:

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

它几乎可以工作,执行以下操作会修改原始向量:

++v[0];

但是分配不起作用:

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

std::rotate 也没有任何影响。

我怎样才能做到这一点?

代码

#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;
}

【问题讨论】:

标签: c++ algorithm iterator


【解决方案1】:

std::reference_wrapper (std::reference_wrapper::operator=) 的赋值运算符不会为引用的元素分配新值,它会重新绑定包装器。所以基本上:

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

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

如果你想为被引用的元素分配一个新值,你需要显式:

v[0].get() = a;

如果您希望 v[0] = a; 按预期工作,甚至是 std::rotate(因为它实际上交换了引用,而不是值),您可以编写自己的包装器:

/**
 * 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_; }
};

您需要为大多数算法提供自定义的swap 函数才能正常工作,例如:

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);
}

【讨论】:

  • 如果我想旋转它们?这就是我的目标。旋转似乎会旋转引用,但不会旋转值。也许有一种不同的方法而不是使用reference_wrapper
  • @WooWapDaBug 您可以编写自己的参考包装器。 std::reference_wrapper 是一个非常小的类(参见上一个链接中可能的实现),所以应该不会那么难。
  • @WooWapDaBug 我添加了一个可能的实现 - 虽然没有经过广泛测试。
  • 也许可以提供swap() 的特殊版本,但看起来这个解决方案更好。奇怪的是他们设计了这么糟糕的参考包装器
  • @WooWapDaBug rotate 的问题在于它使用了swap,这将与single_bind_reference_wrapper 表现得“时髦”(它会复制引用,然后分配给它,这是没有意义的)。您需要在定义single_bind_reference_wrapper 的命名空间中提供swap 的重载。我在答案中添加了一个可能的实现。
【解决方案2】:

将引用旋转到v 不会旋转三个向量的值。所以 我想出的解决方案是复制旋转值并将它们分配回v引用的值。

执行旋转:

std::vector<int> v_rotated;

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

向量v_rotated 包含我想要的顺序的值。 现在我需要将它们分配给v 中引用的值。使用诸如std::copy 之类的算法不是一个可行的解决方案,因为这将分配引用而不是值。事实上,正如其他答案已经说明的那样,

std::reference_wrapper (std::reference_wrapper::operator=) 的赋值操作符没有给被引用的元素赋值,它重新绑定了包装器

但我可以利用隐式转换运算符1 并使用std::transform2 进行复制:

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

这个 lambda 表达式将在 std::transform 中用作二元运算。当std::reference_warpper&lt;int&gt; 作为参数之一传递时,它返回存储的引用。

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

由于std::transform 需要一个目标容器,我选择使用v 作为目标容器,copy 返回对保持向量不变的引用。 这相当于:

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;
}

或者使用显式转换

for(size_t i = 0; i < v.size(); i++)
{
    v[i].get() = v_rotated[i];
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-07
    • 2021-08-19
    相关资源
    最近更新 更多