std::reference_wrapper cannot be bound to rvalue reference to prevent dangling pointer.
However, with combination of std::optional, it seems that rvalue could be bound.
That is, std::is_constructible_v<std::reference_wrapper<const int>, int&&>) is false but std::is_constructible_v<std::optional<std::reference_wrapper<const int>>, std::optional<int>&&> is true.
Here's an example:
#include <iostream>
#include <optional>
auto make() -> std::optional<int>
{
return 3;
}
int main()
{
std::optional<std::reference_wrapper<const int>> opt = make();
if (opt)
std::cout << opt->get() << std::endl;
return 0;
}
I expected this code will be rejected by compiler, but it compiles well and opt contains dangling pointer.
Is this a bug of standard library? Or, is it just not possible to prevent dangling pointer here because of some kind of limitaion of C++ language specification?
If it is a bug of standard library, how can I fix it when I implement my own optional type?
It it's a limitation of current C++ specification, could you tell me where this problem comes from?
@Jarod42 already pointed out the core reason why this code compiles, but I will elaborate a bit.
The following two constructor templates for
std::optional<T>are relevant to this question:Note that the requires-clauses above are for exposition only. They might not be present in the actual declarations provided by the library implementation. However, the standard requires constructor templates 1 and 2 to only participate in overload resolution when the corresponding
std::is_constructible_vconstraint is satisfied.The second overload will not participate in overload resolution because
std::reference_wrapper<const int>is not constructible fromint(meaning an rvalue of typeint), which is the feature that is intended to prevent dangling references. However, the first overload will participate, becausestd::reference_wrapper<const int>is constructible fromconst int&(meaning an lvalue of typeconst int). The problem is that, whenUis deduced and thestd::optional<int>rvalue is bound to theconst optional<U>&constructor argument, its rvalueness is "forgotten" in the process.How might this issue be avoided in a user-defined
optionaltemplate? I think it's possible, but difficult. The basic idea is that you would want a constructor of the formwhere the trait
is_derived_from_optionaldetects whether the argument type is a specialization ofoptionalor has an unambiguous base class that is a specialization ofoptional. Then,Vis an lvalue reference, constructor 3 has the additional constraint that the constraints of constructor 1 above must be satisfied (whereUis the element type of theoptional).Vis not a reference (i.e., the argument is an rvalue), then constructor 3 has the additional constraint that the constraints of constructor 2 above must be satisfied, where the argument isconst_cast<std::remove_cv_t<V>&&>(other).Assuming the constraints of constructor 3 are satisfied, it delegates to constructor 1 or 2 depending on the result of overload resolution. (In general, if the argument is a
constrvalue, then constructor 1 will have to be used since you can't move from aconstrvalue. However, the constraint above will prevent this from occurring in the danglingreference_wrappercase.) Constructors 1 and 2 would need to be made private and have a parameter with a private tag type, so they wouldn't participate in overload resolution from the user's point of view. And constructor 3 might also need a bunch of additional constraints so that its overload resolution priority relative to the other constructors (not shown) is not higher than that of constructors 1 and 2. Like I said, it's not a simple fix.