Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I forbid temporary objects as parameters?

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
}
like image 386
Raildex Avatar asked Jul 20 '20 08:07

Raildex


2 Answers

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

like image 83
asmmo Avatar answered Sep 30 '22 02:09

asmmo


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.

like image 38
dfrib Avatar answered Sep 30 '22 02:09

dfrib