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:
template <class U>
constexpr optional(const optional<U>& other)
requires std::is_constructible_v<T, const U&>; // 1
template <class U>
constexpr optional(optional<U>&& other);
requires std::is_constructible_v<T, U>; // 2
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_v
constraint is satisfied.
The second overload will not participate in overload resolution because std::reference_wrapper<const int>
is not constructible from int
(meaning an rvalue of type int
), which is the feature that is intended to prevent dangling references. However, the first overload will participate, because std::reference_wrapper<const int>
is constructible from const int&
(meaning an lvalue of type const int
). The problem is that, when U
is deduced and the std::optional<int>
rvalue is bound to the const optional<U>&
constructor argument, its rvalueness is "forgotten" in the process.
How might this issue be avoided in a user-defined optional
template? I think it's possible, but difficult. The basic idea is that you would want a constructor of the form
template <class V>
constexpr optional(V&& other)
requires (is_derived_from_optional_v<std::remove_cvref_t<V>> && see_below) // 3
where the trait is_derived_from_optional
detects whether the argument type is a specialization of optional
or has an unambiguous base class that is a specialization of optional
. Then,
V
is an lvalue reference, constructor 3 has the additional constraint that the constraints of constructor 1 above must be satisfied (where U
is the element type of the optional
).V
is 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 is const_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 const
rvalue, then constructor 1 will have to be used since you can't move from a const
rvalue. However, the constraint above will prevent this from occurring in the dangling reference_wrapper
case.) 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.