为什么lambda函数默认情况下会删除推断的返回类型引用?

Dan*_*nra 5 c++ lambda clang auto c++14

在C++ 14中,为什么lambda函数带有推导的返回类型默认情况下从返回类型中删除引用?IIUC,因为C++ 14 lambda函数具有推导的返回类型(没有显式的尾随返回类型)具有返回类型auto,它会丢弃引用(以及其他内容).

为什么做出这个决定?在我看来,当你的返回语句返回时,就像删除引用一样.

这种行为给我带来了以下令人讨厌的错误:

class Int {
public:
   Int(int i) : m_int{i} {}
   int m_int;
};

class C {
public:
    C(Int obj) : m_obj{obj} {}
    const auto& getObj() { return m_obj; }
    Int m_obj;
};

class D {
public:
    D(std::function<const Int&()> f) : m_f{f} {}
    std::function<const Int&()> m_f;
};

Int myint{5};
C c{myint};
D d{ [&c](){ return c.getObj(); } } // The deduced return type of the lambda is Int (with no reference)
const Int& myref = d.m_f(); // Instead of referencing myint, myref is a dangling reference; d.m_f() returned a copy of myint, which is subsequently destroyed.
Run Code Online (Sandbox Code Playgroud)

初始化时指定所需的返回类型d可解决问题:

D d{ [&c]() -> const Int& { return c.getObj(); } }
Run Code Online (Sandbox Code Playgroud)

有趣的是,即使auto返回类型推导有意义,是不是一个错误,std::function<const Int&>用一个返回非引用的函数快乐地初始化?我也明确地写了这个:

D d{ [&c]() -> Int { return c.getObj(); } }
Run Code Online (Sandbox Code Playgroud)

编译没有问题.(上Xcode 8,clang 8.0.0)

M.M*_*M.M 5

我认为你是绊脚的地方实际上是表达c.getObj()的线return c.getObj();.

你认为表达式c.getObj()有类型const Int&.但事实并非如此; 表达式永远不会有引用类型 正如Kerrek SB在评论中指出的那样,我们有时会将表达式称为具有引用类型,作为节省冗余的快捷方式,但这会导致误解,因此我认为了解实际情况非常重要.

在声明中使用引用类型(包括作为声明中的返回类型getObj)会影响声明的事物的初始化方式,但一旦初始化,就不再有任何证据表明它最初是一个引用.

这是一个更简单的例子:

int a; int &b = a;  // 1
Run Code Online (Sandbox Code Playgroud)

int b; int &a = b;  // 2
Run Code Online (Sandbox Code Playgroud)

这两个代码完全相同(除了结果decltype(a)decltype(b)对系统有点破解).在这两种情况下,表达式ab两者都具有类型int和值类别"lvalue"并且表示相同的对象.它不是a"真实对象" 的情况,而是b某种伪装的指针a.他们都处于平等地位.这是一个有两个名字的对象.

现在回到你的代码:除了访问权限之外,表达式c.getObj()具有完全相同的行为c.m_obj.类型是Int,值类别是"左值".该&在的返回类型getObj()只决定了这是一个左值,它也将指定一个已经存在的(近似地说)的对象.

因此推导出的返回类型与return c.getObj();它的相同return c.m_obj;,其与模板类型推导兼容,如其他地方所述 - 不是引用类型.

NB.如果您理解这篇文章,您也会理解为什么我不喜欢被称为"自动取消引用的伪装指针"的"参考"教学法,这种错误与危险之间存在差异.


归档时间:

查看次数:

809 次

最近记录:

8 年,11 月 前