2
votes

Consider a function like this:

auto&& just(auto&& arg) { return std::forward<decltype(arg)>(arg); }

This will perfect-forward all the references and will return a dead reference when called for a temporary object.

In more details:

struct Something {};
auto something() { return Something{}; }

auto&& ref = just(something()); // ERROR: Here ref refers to already destructed Something.

auto&& hold = something();
auto&& ref = just(hold);   // OK: Here ref refers to lifetime-extended Something.

So the question is: how can one define just, so the first call will fail to compile, while the second call will compile?

1
Require a lvalue reference? - πάντα ῥεῖ
just(auto&& arg), um, what version of C++ are you using? That function shouldn't compile in c++11, 14, or 17 without -fconcepts. - Elliott
@Elliott Yes, gcc-9.3 with -std=c++17 -fconcepts - Vahagn
@Vahagn, no problem. Also, the concept answer wouldn't have worked when the type of the template argument is rvalue. So just<Something&&>(std::move(hold)) would compile, which I think wasn't what AndyG meant to do. It could've easily been fixed by changing it to something like requires std::is_lvalue_reference_v<T>, or more intuitively is_lvalue_reference_v<T&&>. - Elliott

1 Answers

0
votes

As πάντα ῥεῖ pointed out in the comments, if you require an lvalue then you should write your function that way (single &):

[from your comment, I've used c++17 style]

template <typename Arg>
auto && just(Arg & arg);

However, I'll consider your real world problem isn't so simple and that you prefer to code the requirements inside the function:

#include <type_traits>

template <typename Arg>
auto && just(Arg && arg) {

    static_assert(std::is_lvalue_reference_v<Arg&&>);

    return std::forward<Arg>(arg);
}

Example: It's practical for a function to do some work then return the output of a function with perfect forwarding to avoid copies, but you need to check its return type. For those not able to use concepts it's neater to do the check inside the function:

#include <iostream>
#include <type_traits>

template <typename Func>
auto && just (Func && func)
{
    using return_type = decltype(func());
    
    static_assert(std::is_reference_v<return_type>, "returning temportary");

    // calculate arguments for func
    // (no args here for simplicity)

    return func(); // always return ref
}

int global;
int ReturnCopy () { return int(); }
int & ReturnGlobal () { return global; }
int && ReturnTemporary () { return std::move(global); }

int main ()
{
    just(ReturnCopy); // <--- error
    just(ReturnGlobal);
    just(ReturnTemporary);
}