【问题标题】:How to store either rvalue or lvalue references in template如何在模板中存储右值或左值引用
【发布时间】:2014-06-12 01:45:11
【问题描述】:

我正在尝试创建一个简单的模板enumerator 类,该类应该接受定义了: 运算符的任何对象,以及稍后在(i, v[i]) 形式的打印对上。一个简单的实现如下:

template<typename T>
struct enumerator {
    T &v; // reference to minimize copying
    enumerator(T &_v) : v(_v) {}
    void do_enumerate() {
        size_t i = 0;
        for(auto x : v) {
            cout << i << x << endl;
            i++;
        }
    }
};

这适用于以下情况:

案例A

vector<int> v({1,2,6,2,4});
auto e = enumerator(v);
e.do_enumerate();

不过,我也希望它能够处理临时对象,例如:

案例 B

auto e = enumerator(vector<int>({2,3,9});
e.do_enumerate();

这不起作用,编译器抛出:

no matching function for call to ‘enumerator<std::vector<int> >::enumerator(std::vector<int>)

所以,我尝试添加一个

enumerator(T _t) : t(_T) {}

构造函数来解决这个错误。现在案例A不起作用,出现错误:

error: call of overloaded ‘enumerator(std::vector<int>&)’ is ambiguous

另外,情况B,枚举的输出不正确。

解决这个问题的最干净的方法是什么?我会

  • 真的很喜欢这两种情况都可以工作
  • 不希望使用 stdc++ 以外的任何库
  • 希望尽可能少地复制(因此不能只在结构中存储 T t
  • C++11 不是问题。我有 g++-4.8,我认为它有足够的 C++11 支持。

【问题讨论】:

  • 您尝试过使用T const&amp; T &amp;&amp; 的构造函数吗?您确实意识到,一旦超出范围,右值就会被销毁。在 enumerator 类中保留对它的引用不会改变这一点。
  • 没有:operator 这样的东西,这是基于范围的for 语句的语法。它为begin()end() 创建迭代器,在后台对容器进行迭代。有关于 SO 处理它们如何工作的问题,搜索它们。
  • 而您尝试做的事情没有意义。要么复制参数,要么不接受右值。
  • 好吧,如果参数是右值,我想复制,如果不是,则不复制。这可能吗?
  • 考虑到您的目标是避免不必要的复制,您可能应该使用for (auto&amp; x : v) 而不是for (auto x : v)

标签: c++ c++11 rvalue


【解决方案1】:

好吧,如果参数是右值,我想复制,如果不是,我不复制。这可能吗?

这可以使用make_enumerator 辅助函数来完成,如图所示。

template <class T>
struct enumerator {
    T v;
    enumerator(T&& _v) : v(std::forward<T>(_v)) {}
    void do_enumerate() {
        size_t i = 0;
        for(auto x : v) {
            cout << i << x << endl;
            i++;
        }
    }
};

template <class T>
enumerator<T> make_enumerator(T&& x) {
    return enumerator<T>(std::forward<T>(x));
}

int main() {
    vector<int> v {5, 2, 9, 1};
    make_enumerator(v).do_enumerate();
    make_enumerator(std::move(v)).do_enumerate();
}

这是如何工作的?

如果make_enumerator 的参数是A 类型的左值,则T 推导出为A&amp;,我们得到枚举器enumerator&lt;A&amp;&gt;,而如果它是A 类型的右值,则@ 987654329@ 推导出为A,我们得到枚举器enumerator&lt;A&gt;

在第一种情况下,成员 enumerator::v 将具有类型 A&amp;,这是一个绑定到构造函数参数的左值引用(无复制)。在第二种情况下,成员的类型为Astd::forward 的使用将参数_v 强制转换为右值,因此它将在用于初始化v 时被移动。

【讨论】:

  • @0x499602D2 如果T 是左值引用,那么T&amp;&amp; 是相同类型(不是右值引用)。此外,我已经尝试过这段代码。
  • 好的,我现在知道了。谢谢。
  • 酷。这适用于所有场景。我会尝试更多地了解std::forward。 :)
【解决方案2】:

这是一个典型的例子,你实际上不需要class/struct(实际上引入了无用的代码),你可以使用好的旧函数:

template<typename Container>
void enumerate(const Container& t) {
    std::size_t i = 0;
    for(auto it = t.begin(); it != t.end(); ++it, ++i)
        std::cout << i << *it << std::endl;
}

然后将其称为:

enumerate(std::vector<int>{2,3,9});

Live demo

使用此方法,您还可以免费获得参数类型推断(struct 无法获得)。

【讨论】:

  • 谢谢。这样可行。但是,最好有一个对象,因为我计划实际为enumerator 类本身定义一个迭代器接口。这样我就可以做到for(auto p : enumerate&lt;vector&lt;int&gt;&gt;(whatever)) {}
  • @SubhasisDas 对,这真的很有用,因为 vector 本身不支持...哦等等
  • @SubhasisDas 如果您打算创建一个类似范围的结构,请考虑使用Boost.Range(或至少从中获得灵感)。
  • 我知道 boost 可能拥有我想要的所有功能。但是,我想对自己了解 C++11 中的新功能,因此这更像是一种教育努力。我会研究 boost.range api。
猜你喜欢
  • 2018-07-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多