Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Allowing implicit construction only from reference to longer-lived value

Tags:

c++

c++11

I have implemented a type-erasing reference class that can be constructed from an l-value reference to any type. However, I have run into a bit of a dilemma as to whether or not to allow construction from an r-value.

There are two use cases I have encountered:

  1. Constructing the reference as a local variable

    int i = 42;
    Reference ref1 = i;   // This is allowed.
    Reference ref2 = 42;  // This should cause a compile error.
    
  2. Constructing the reference as a function parameter

    void func(Reference ref);
    
    int i = 42;
    func(i);   // This is allowed.
    func(42);  // This should also be allowed.
    

Effectively I want to allow implicit construction of a Reference instance from anything with a lifespan greater than the reference, but not from anything with a shorter lifespan.

Is there some way to accomplish this, i.e., to allow func(42) but disallow ref = 42? I am free to make any necessary changes to Reference, but the signature of func should remain the same.

like image 661
zennehoy Avatar asked Apr 10 '17 12:04

zennehoy


2 Answers

What you want in general cannot be done.

Deleting construct-from T&& is reasonable, but T&& does not convert to T& unless T is const. This doesn't permit the function argument case.

You could use a different type for the function argument version than the local variable version.

A more general option is to construct from a universal reference and optionally store a copy if your argument is an rvalue.

template<class T>
struct view_type_t {
  std::optional<T> possible_copy;
  T* ptr;
  view_type_t(T& t):ptr(std::addressof(t)) {}
  view_type_t(T&& t):
    possible_copy(std::forward<T>(t)),
    ptr(std::addressof(*possible_copy))
  {}
  view_type_t() = delete;
  // calls view_type_t&& ctor, possibly elided
  view_type_t(view_type_t const& src):
    view_type_t(
      src.possible_copy?
        view_type_t(T(*src.possible_copy)):
        view_type_t(*src.ptr)
    )
  {}
  // this is a bit tricky.  Forwarding ctors doesn't work here,
  // it MIGHT work in C++17 due to guaranteed elision
  view_type_t(view_type_t&& src) {
    if (src.possible_copy) {
      possible_copy.emplace(std::move(*src.possible_copy));
      ptr = std::addressof(*possible_copy);
    } else {
      ptr = src.ptr;
    }
  }
  view_type_t& operator=(view_type_t const& src); // todo
  view_type_t& operator=(view_type_t&& src); // todo
};

this emulates reference lifetime extension, where temporary lifetime is extended by the reference. Such a Reference behaves like a naked rvalue reference does in C++.

Now Reference ref2 = 42; works, and it is now a reference a local copy of 42.

I find this technique better than the alternative when making other kinds of view types, like backwards_range, which keeps a copy of its source range if it is an rvalue and doesn't if it is an lvalue.

This permits:

for( auto&& x : backwards( std::vector<int>{1,2,3} ) )

to work. More generally, if the vector was created as a temporary returned from a function, we can call backwards on it and iterate directly; without creating the local copy, we run into lifetime problems as the lifetime extension of temporaries doesn't commute.

Of course you need a std::optional replacement (such as boost::optional) outside of C++17 for the above code to work.

like image 76
Yakk - Adam Nevraumont Avatar answered Nov 09 '22 22:11

Yakk - Adam Nevraumont


The only way I can think of is to delete a constructor, and use an overloaded function:

struct Reference
{
    Reference(int& i){};
    Reference(const int&& i) = delete;
};

Since an anonymous temporary cannot bind to a non-const reference, Reference ref2 = 42; will fail at compile time due to the deleted constructor. Construction from an int variable is allowed though.

On the second point, compilation passes if you introduce the overload

void func(int ref)
{
    func(Reference(ref));
}
like image 2
Bathsheba Avatar answered Nov 09 '22 20:11

Bathsheba