Can someone please explain which language rules are in play in the following example:
#include <iostream>
#include <type_traits>
template<typename T>
struct Holder{
T val;
constexpr operator T const&() const& noexcept{
std::cerr << "lvalue\n";
return val;
}
constexpr operator T () && noexcept(std::is_nothrow_move_constructible_v<T>) {
std::cerr << "rvalue\n";
return std::move(val);
}
};
int main(){
Holder h1{5};
const int& rh1 = h1;
const int& rh2 = Holder{7};
std::cout << rh1 << ' ' << rh2 << '\n';
}
lvalue
lvalue
While I expected either ambiguity or rvalue for the second call.
If this example is run with address sanitizer it reveals the stack-use-after-scope error. I'd be really grateful if one could explain to me precisely that why isn't this ambigous or why isn't the rvalue ref overload selected?
My way of thinking is that, even though the target type is a perfect match, the const& member function uses an implicit conversion from rvalue to lvalue ref on the object itself,but the second overload is better in terms of member function ref qualifier, but uses an implicit rvalue to const lvalue ref conversion on the return type. I was under the strong impression that this initialization is driven by the initialization expression, but after analyzing the AST with clang I realized the source of the problem is that the target type is resolved first selecting the const& overload.
Note:I tried all possible combinations in terms of ref, cv qualification on both the function and the return type