1

In the following simple code I return a function local object from a function (factory function). Does the C++ standard guarantee in every case that this object is being returned as an rvalue reference or are there exceptions I have to look out for?

Demo

#include <cstdio>

template <typename T>
struct vectorlike
{
    vectorlike() = default;

    ~vectorlike() {
        if (ptr_) {
            delete ptr_;
        }
    }

    vectorlike(const vectorlike& other) {
        printf("%s(&) called!\n", __func__);
    }

    vectorlike(vectorlike&& other) {
        printf("%s(&&) called!\n", __func__);
    }

    auto operator=(const vectorlike& other) -> vectorlike& {
        printf("copy %s(&) called!\n", __func__);
        ptr_ = new T(*other.ptr_);
        return *this;
    }

    auto operator=(vectorlike&& other) noexcept -> vectorlike& {
        printf("move %s(&&) called!\n", __func__);
        ptr_ = other.ptr_;
        other.ptr_ = nullptr;
        return *this;
    }

    vectorlike(int i) {
        ptr_ = new T(i);
    }

    T* ptr_;
};

template <typename T>
auto vector_factory() {
    vectorlike<T> ret{2};

    return ret;
}

int main()
{
    vectorlike<int> hello;

    hello = vector_factory<int>();
}

Output:

move operator=(&&) called!
glades
  • 3,778
  • 1
  • 12
  • 34
  • 2
    Yes, it's guaranteed. Even better, NRVO could kick in and remove the variable completely. – HolyBlackCat Oct 06 '22 at 13:55
  • 2
    Aside: you move assignment leaks, and isn't safe for self-assignment. much better is to `std::swap(ptr_, other.ptr_);` – Caleth Oct 06 '22 at 14:02
  • @HolyBlackCat How does that work in the case of move assignement? The variable already exists? – glades Oct 06 '22 at 14:22
  • @Caleth You mean the move constructor? The assignement should be fine no? I usually try to avoid std::swap. It isn't save for self assignement you're right – glades Oct 06 '22 at 14:23
  • @glades Via compiler magic, the variable in the function becomes merely a different name for the variable that's going to receive the return value. – HolyBlackCat Oct 06 '22 at 14:28
  • @glades no, I mean assignment. consider `vectorlike first{2}; first = vectorlike{3}`, where's the delete for the pointer to 2? – Caleth Oct 06 '22 at 14:29
  • @HolyBlackCat That I understand. But assume that the varialbe was "in use" before, so its values are filled. Can NRVO kick in in this case or is the returned object is just move-flagged? – glades Oct 06 '22 at 14:30
  • "the variable that's going to receive the return value" i.e. the parameter `vectorlike&& other`, not `hello` in main. – Caleth Oct 06 '22 at 14:31
  • @Caleth Oh you're right. I put this together in about 10 secs sorry xD – glades Oct 06 '22 at 14:31
  • Aside 2: `new T(i)` creates one `T` with the value `i`, not an array `T[i]`, did you mean `new T[i]`? (and remember to `delete[]`, not `delete`) – Caleth Oct 06 '22 at 14:33
  • 1
    @glades Ah, I get it now. There are two potential copy elisions here. First, the copy from the local variable into the returned temporary. This one can get NRVOed regardless of how the return value is used. Then the second copy from the temporary to the variable it's used to initialize (if any). If there is a new variable, then the copy is always elided since C++17. Otherwise it's not elided. – HolyBlackCat Oct 06 '22 at 14:38
  • @HolyBlackCat Thank you that makes it clearer :) – glades Oct 06 '22 at 15:53
  • @Caleth I didn't bother to write a proper vectoralike, just for showcasing. Maybe confusing, sorry for that. – glades Oct 06 '22 at 15:53

0 Answers0