Are there any realistic use cases for `decltype(auto)` variables?

前端 未结 2 890
借酒劲吻你
借酒劲吻你 2021-01-31 09:44

Both from my personal experience and from consulting answers to questions like What are some uses of decltype(auto)? I can find plenty of valuable use cases for decltype(a

2条回答
  •  独厮守ぢ
    2021-01-31 10:06

    Essentially, the case for variables is the same for functions. The idea is that we store the result of an function invocation with a decltype(auto) variable:

    decltype(auto) result = /* function invocation */;
    

    Then, result is

    • a non-reference type if the result is a prvalue,

    • a (possibly cv-qualified) lvalue reference type if the result is a lvalue, or

    • an rvalue reference type if the result is an xvalue.

    Now we need a new version of forward to differentiate between the prvalue case and the xvalue case: (the name forward is avoided to prevent ADL problems)

    template 
    T my_forward(std::remove_reference_t& arg)
    {
        return std::forward(arg);
    }
    

    And then use

    my_forward(result)
    

    Unlike std::forward, this function is used to forward decltype(auto) variables. Therefore, it does not unconditionally return a reference type, and it is supposed to be called with decltype(variable), which can be T, T&, or T&&, so that it can differentiate between lvalues, xvalues, and prvalues. Thus, if result is

    • a non-reference type, then the second overload is called with a non-reference T, and a non-reference type is returned, resulting in a prvalue;

    • an lvalue reference type, then the first overload is called with a T&, and T& is returned, resulting in an lvalue;

    • an rvalue reference type, then the second overload is called with a T&&, and T&& is returned, resulting in an xvalue.

    Here's an example. Consider that you want to wrap std::invoke and print something to the log: (the example is for illustration only)

    template 
    decltype(auto) my_invoke(F&& f, Args&&... args)
    {
        decltype(auto) result = std::invoke(std::forward(f), std::forward(args)...);
        my_log("invoke", result); // for illustration only
        return my_forward(result);
    }
    

    Now, if the invocation expression is

    • a prvalue, then result is a non-reference type, and the function returns a non-reference type;

    • a non-const lvalue, then result is a non-const lvalue reference, and the function returns a non-const lvalue reference type;

    • a const lvalue, then result is a const lvalue reference, and the function returns a const lvalue reference type;

    • an xvalue, then result is an rvalue reference type, and the function returns an rvalue reference type.

    Given the following functions:

    int f();
    int& g();
    const int& h();
    int&& i();
    

    the following assertions hold:

    static_assert(std::is_same_v);
    static_assert(std::is_same_v);
    static_assert(std::is_same_v);
    static_assert(std::is_same_v);
    

    (live demo, move only test case)

    If auto&& is used instead, the code will have some trouble differentiating between prvalues and xvalues.

提交回复
热议问题