Home > OS >  Preventing returning of reference to stack through a reference wrapper
Preventing returning of reference to stack through a reference wrapper

Time:01-22

I am working on a container who's subscript operator returns a wrapper reference, like this:

my_container::reference my_container::operator[](std::size_t i);

The internal element representation of the container cannot be directly referenced and thus needs the proxy object. The reference itself has deleted copy & move constructors, and all operations are only defined for rvalue references.

However, a problem arises when deducing the wrapper object via auto or decltype(auto). For example, in the following code:

my_container bar();
auto foo(std::size_t i)
{
    my_container c = bar();
    return c[i];
}

auto deduces to my_container::reference instead of my_container::value_type, since the reference proxy is an object type, and RVO guarantees that no copy- or move-constructors are being called. This is a problem, since in the above code a stack reference is being returned.

Is there any way to prevent such behavior? To my knowledge there is no way to bypass RVO, but perhaps I am missing something.

Edit: To give a bit more context, the container is a bit-mask over a vector of integers. Any assignments to the elements must also mask the assigned-to value, which is why the proxy reference is needed.

CodePudding user response:

C is not a safe language.

While clever and consistent use of certain language mechanisms can make the language safe-er, there are a number of construct in the language itself that are just not going to be safe without substantial language changes. The dangling reference problem being one of the biggest.

You cannot fix the dangling reference problem in its entirety by using in-language mechanisms. Any attempts to do so will run into two intractable problems:

  1. The solution will not be complete. There will be ways to get around it, and typically without being actively malicious (ie: someone can do them without explicitly trying to).
  2. The solution will make some legitimate uses of the type impossible.

The latter one is important. By making "all operations are only defined for rvalue references," you prevent something as simple as this from working:

for(auto &&ref : bool_vector)
  ref == true;

The user would have to type std::move(ref) == true;. And while your type would probably satisfy indirectly_writable by the letter of the definition, users are going to expect to be able to get a language reference to your proxy-reference and use it like an actual reference to a value. That is, they will expect *it = val; and auto &&ref = *it; ref = val; to be equally valid.

And they will be displeased when their legitimate code doesn't compile. So in attempting to provide some safety, you have made your type less usable by your users.

In any case, your particular problem is #1: you cannot provide this form of safety in the language. You cannot make a proxy iterator that provides this form of safety. Guaranteed elision is guaranteed, and any auto-returning function that does return prvalue_expression; will always work, regardless of any property of the type of prvalue_expression.

  • Related