I'm trying to write a wrapper around shared_ptr
that can implicitly dereference to underlying type. The code is as below:
#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_;
};
Looks like that there's nothing wrong with it. I tried a few methods to use the wrapper:
#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;
}
The wrapper class works well with assignment and parentheses initialization.
The weird thing is, when I'm trying to initialize Nothing&
with initializer list (#5 and #6 in the code above), the value is copied and I must use a const reference to it. However, when I explicitly call the conversion operator like wrapper.operator Nothing &()
(#2 in code above), I got the correct reference to the original object constructed at the first line.
I've read cppreference and found that initializer list is a copy-initialized temporary list, but it doesn't make sense why the code works when operator Nothing &()
is explicitly called.
Anyone can help me with figuring out what is happening here? Thanks very much!
You are actually doing reference initialization here:
Nothing & by_initialization_2{wrapper};
The rules say that since the initializer is not the same type as the reference being bound, user-defined conversion operators are considered, which is fine, since you have the appropriate conversion operator.
However, if the l-value returned by the conversion function is passed through a brace-init list, then a temporary is materialized. Since you can't bind a non-const reference to a temporary, the initialization fails.