1

I've discovered an interesting piece of code and I wonder if it is UB or not? At least, according to cppreference it should not be. Is it a valid case for using an r-value reference?

The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference...

Something similar was discussed in this post, but it does not fully address my case.

template <typename T>
class ResourceLocator {
public:
    static void load(ResourceLocator<T>&& instance) {
        instance_ = std::move(instance);
    }

protected:
    static ResourceLocator<T>&& instance_;
    // static ResourceLocator<T>& instance_; // does not extend lifetime
    // static const ResourceLocator<T>&& instance_; // const is too limiting
Volodymyr Lashko
  • 656
  • 6
  • 19
  • No, such usage does not extend lifetime. The lifetime is only extended in the most straightforward case, like `{ const t& x = getX(); ... }` here lifetime is extended until end of block. – SergeyA Apr 26 '21 at 16:15
  • 1
    References must be bound at initialization. Assigning to `instance_ ` assigns to the (non-existent) object that it references. Also, if you pass a temporary to `load`, it is bound to the argument, and its lifetime only lasts as long as that argument's – until the function returns. – molbdnilo Apr 26 '21 at 16:22
  • A lifetime of a temporary might be extended, but you don't have a temporary. You just have an r-reference. – Mooing Duck Apr 26 '21 at 16:56

1 Answers1

4

TL;DR: This won't work the way you want it to. It likely won't be UB though, as long as instance_ references something that lives long enough to support an operator=(...) call from load(...).

Explanation

References of any kind, whether rvalue or lvalue, cannot be rebound once initialized, and cannot be left uninitialized. Fundamentally what you're wanting here cannot work using references.

The static instance_ will have to have already been initialized to some value on program startup, which would make:

instance_ = std::move(instance);

assign to the object that instance_ originally references, meaning this uses the assignment operator operator= for the ServiceLocator<T>, rather than rebinding the reference.

Even if it could work, RValues can only extend lifetimes to the natural scope where its initialized (as if they are object-values), and can only extend the lifetime when initialized with a true temporary object (e.g. a PR-value, such as a T() expression or the result of a function returning a by-value object).

So even if rebinding was possible, this extension would not apply since the parameters are not producing temporary expressions.

Possible Alternative

If you're wanting to create a locator of an object that is immovable/uncopyable, maybe consider either using std::optional or std::unique_ptr to allow for a null-state, and offer an emplace-like function to construct it in-place. Such as:

template <typename T>
class ResourceLocator {
public:
    template <typename...Args>
    static void emplace(Args&&...args) {
        instance_.emplace(std::forward<Args>(args)...);
    }
private:
    static std::optional<ResourceLocator<T>> instance_;
};

Or, better yet, just don't use resource locators. They're just singletons pretending to be an abstraction, but they're just as evil.

Human-Compiler
  • 11,022
  • 1
  • 32
  • 59
  • 1
    you cannot rebind `instance_` from `static load` (and create dangling ref from that) afaict OP's code actually move assign the object. – apple apple Apr 26 '21 at 16:34
  • 2
    You know, I completely overlooked that fact. Thank you for pointing that out. – Human-Compiler Apr 26 '21 at 16:36
  • 1
    @ Human-Compiler this is probably not UB then, it's still not what OP want anyway (at least not in the sense of extend lifetime) – apple apple Apr 26 '21 at 16:45
  • 1
    @appleapple Right, forgot to update that part -- thanks again. I probably should have taken more time to think about my answer first. Fixed. I originally head it in my head that the assignment in question was `static auto&& instance_ = ...` which completely changed the meaning and my answer. – Human-Compiler Apr 26 '21 at 16:47
  • Like Human-Compiler I completely missed the fact that this reference is initialized once in cpp-file (not shown here) and later the same object is reused via operator=. – Volodymyr Lashko Apr 27 '21 at 05:32