Allow implicit build only with reference to a longer value

I have implemented a reference class that erases a type that can be built from an l-value reference for any type. However, I ran into some dilemma as to whether or not to allow construction from an r-value.

There are two use cases:

  • Link building as a local variable

    int i = 42; Reference ref1 = i; // This is allowed. Reference ref2 = 42; // This should cause a compile error. 
  • Link building as a function parameter

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

In fact, I want to allow the implicit construction of a Reference instance from something with a longer lifespan than a link, but not with anything with a shorter lifespan.

Is there any way to do this, i.e. allow func(42) but deny ref = 42 ? I can make any necessary changes to the Reference , but the signature of func should remain unchanged.

+5
source share
2 answers

What you want at all cannot be done.

Removing a construct from T&& is reasonable, but T&& will not convert to T& unless T is const . This does not allow the case of a function argument.

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

A more general option is to build from a universal reference and possibly save a copy if your argument is 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 the extension of the link life cycle, where the temporary lifetime is extended by the link. Such a Reference behaves like a bare rvalue reference in C ++.

Now Reference ref2 = 42; works, and now this is a link to a local copy of 42 .

I find this technique better than an alternative when you create other types of views, like backwards_range , which stores a copy of its original range if it is rvalue and does not have lvalue.

This allows:

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

work. More generally, if a vector was created as temporary, returned by a function, we can call back and transfer it directly; without creating a local copy, we are faced with life-time problems, because extending the lifetime does not commute.

Of course, for the above code to work, you need to replace std::optional (e.g. boost::optional ) outside of C ++ 17.

+4
source

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

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

Since anonymous time cannot be associated with a const reference, Reference ref2 = 42; will fail with compilation due to the remote constructor. The construction of the int variable is allowed.

At the second point, compilation passes if you enter overload

 void func(int ref) { func(Reference(ref)); } 
+2
source

Source: https://habr.com/ru/post/1266554/


All Articles