Can I forbid temporary objects as parameters?
Asked Answered
P

2

6

Let's say I have the function:

void foo(Object& o) {
    /* only query o, dont alter it*/
}

Is it possible to call this function only with already constructed objects and have Visual Studio throw a compile error if I call the function with a temporary object?

struct Object {
    /*Members*/
}

void foo(Object& o) {
    /* only query o, dont alter it*/
}

int main() {
    Object o = Object();
    foo(o); // allow this
    foo(Object()) // but disallow this
}
Panne answered 20/7, 2020 at 8:30 Comment(4)
That's the default for non-constant references, it's not possible to have a non-constant reference to a temporary (or rvalue) object. Except for the Visual Studio C++ compiler, which adds a non-portable extension that allows it (it can be turned off IIRC).Pasley
That's already what happens?Reitareiter
btw Object o = Object(); is Object o;Reitareiter
I am indeed using MSVC, and it does not complain about the temporary object. GCC however does.Panne
E
15

If your parameter is not const, the function won't accept temporaries.

If your parameter is const, the function accepts both temporary and regular objects.

But if you want to prevent that, you can use the following

struct Object{};
void foo(const Object& o) {
    /*only query o, don't alter it*/
}
void foo(Object&& ) = delete;

int main() {
    Object o;
    foo(o); // allow this
    foo(Object{}); // but disallow this
}

Live

Erdman answered 20/7, 2020 at 8:36 Comment(3)
thank you very much! I am using MSVC and the compiler does not complain about the temporary object in my case. GCC however does!Panne
@Panne msvc doesn't compile it too. See godbolt.org/z/9cYse7Erdman
I was talking about my case in the question :)Panne
G
4

Explicitly deleting the const && overload

A temporary object can have its lifetime extended by binding it to a const lvalue reference (in a function call), whereas it cannot bind to a non-const lvalue reference. This means that your original example implements the sought after behaviour (cannot be invoked with temporary objects), but at the cost of making the parameter non-const (even though the implementation only queries and does not mutate the object). This arguably violates const correctness.

As your free function API is inspecting an Object object, you could consider changing it into a member function and use ref-qualifiers to explicitly delete the overload that will be chosen by overload resolution for temporary objects. A first approach could be to simply delete the && overload:

struct Object {
    // ...
    
    void foo() const & {}
    void foo()       && = delete;
};

int main() {
    Object o = Object();
    const Object co = Object();

    o.foo();
    co.foo();
    
    //Object().foo();  // error: use of deleted function
}

However, this does not prohibit the, albeit somewhat contrived, case of const temporary objects as well as movable from const objects (const xvalues), as the deleted non-const rvalue ref-qualifier overload is not viable for a const rvalue argument:

std::move(co).foo();  // Accepted.
static_cast<const Object&&>(Object()).foo();  // Accepted.

Thus, instead of explicitly deleting the && overload, we can remove also the corner case by instead explicitly deleting the const && overload, as this will also be the overload of choice for non-const temporary objects:

struct Object {
    // ...
    
    void foo() const & {}
    void foo() const && = delete;
};

int main() {
    Object o = Object();
    const Object co = Object();

    o.foo();
    co.foo();
    
    //std::move(o).foo();   // error: use of deleted function
    //std::move(co).foo();  // error: use of deleted function

    //Object().foo();  // error: use of deleted function
    //static_cast<const volatile Object&&>(Object()).foo();  // error: use of deleted function
}

We may note that the same approach is used e.g. for the std::ref and std::cref helper functions of std::reference_wrapper; from [functional.sym]:

// [refwrap], reference_­wrapper
// ...
template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

// ...

as you naturally want to delete a reference wrapper for temporary objects.

Gullett answered 20/7, 2020 at 8:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.