使用初始化程序列表和返回引用的用户定义的转换运算符时,将复制返回值

问题描述

我正在尝试围绕shared_ptr编写一个包装程序,该包装程序可以隐式取消对基础类型的引用。代码如下:

#include <memory>

template<typename T>
class PtrWrapper {
public:
  PtrWrapper(std::shared_ptr<T> ptr) : ptr_(ptr) {}
  operator T& () {
    return *ptr_;
  }
  T& ref() {
    return *ptr_;
  }
private:
  std::shared_ptr<T> ptr_;
};

看起来没有任何问题。我尝试了几种使用包装器的方法

#include <iostream>

class nothing {
public:
  nothing() {
    std::cout << "Construct " << this << std::endl;
  }
  nothing(nothing const& parent) {
    std::cout << "copy " << &parent << " " << this << std::endl;
  }
  nothing(nothing && parent) {
    std::cout << "Move " << &parent << " " << this << std::endl;
  }
  ~nothing() {
    std::cout << "Destruct " << this << std::endl;
  }
};

int main() {

  PtrWrapper<nothing> wrapper{std::make_shared<nothing>()};

  // #1: OK
  nothing & by_assignment = wrapper;
  // #2: OK
  nothing & by_operator{wrapper.operator nothing &()};
  // #3: OK
  nothing & by_function{wrapper.ref()};
  // #4: OK
  nothing & by_initialization(wrapper);

  // #5: Compile error: non-const lvalue reference to type 'nothing' cannot bind to an initializer list temporary
  // nothing & by_initialization_2{wrapper};

  // #6: The `nothing` class is copied,which is not expected
  nothing const& by_initialization_3{wrapper};

  return 0;
}

包装器类可很好地与赋值和括号初始化一起使用。

奇怪的是,当我尝试使用初始化列表(上面代码中的#5和#6)初始化nothing&时,将值复制并且必须使用const引用。但是,当我像wrapper.operator nothing &()那样显式调用转换运算符时(上面的代码中为#2),我正确地引用了第一行中构造的原始对象。

我已经读过cppreference,发现初始化列表是一个复制初始化的临时列表,但是在显式调用operator nothing &()时为什么代码可以工作并没有意义。

任何人都可以帮助我弄清楚这里发生了什么事?非常感谢!

解决方法

您实际上是在这里进行reference initialization

Nothing & by_initialization_2{wrapper};

规则说,由于初始化程序与绑定的引用不是同一类型,因此考虑使用用户定义的转换运算符,因为您有适当的转换运算符,所以很好。

但是,如果转换函数返回的l值通过大括号初始化列表传递,则将实现一个临时值。由于您无法将非常量引用绑定到临时目录,因此初始化失败。