Home > Back-end >  "Templated rvalue-reference" instead of universal reference as parameter?
"Templated rvalue-reference" instead of universal reference as parameter?

Time:12-24

I have a class object that takes universal parameters which are constrained to a certain concept. Now I want to forward this parameter (which type is still unknown) to the overload of construct that fits this reference type: Select either rvalue-ref overload or const ref overload based on the forwarded parameter. How can I achieve that?

The problem is that as soon as I put double ampersand the "templated rvalue ref" becomes an universal reference and I can't see a way to circumvent that. The following example shows, that in both cases the "greedy" universal ref overload is picked, even though I would like for the first instantiation to go with const ref.

Demo

#include <concepts>
#include <cstdio>
#include <utility>

template <typename... Args>
struct function
{
    template <std::invocable<Args...> Cb>
    function(Cb&& fn) {
        construct(std::forward<Cb>(fn));
    }

    template<typename Cb>
    auto construct(const Cb&) {
        printf("Overload for const-ref called!\n");
    }

    template <typename Cb>
    auto construct(Cb&&) {
        printf("Overload for rvalue-ref called!\n");
    }
};

struct functor
{
    auto operator()()
    {
        printf("Functor called!\n");
    }
};

int main()
{
    functor foo1;

    function myfunc{foo1};
    function myfunc2{functor{}};
}

Outputs:

Overload for rvalue-ref called!
Overload for rvalue-ref called!

Update:

As the above case is probably already too contrived to really showcase what I'm trying, you can find a more elaborate example here.

CodePudding user response:

The main answer is that you should try not to do that since there is usually no good reason to perform this kind of overload resolution. When you have a concrete type, it makes sense to have an overload that takes an lvalue reference and an overload that takes an rvalue reference, because only the latter has license to plunder the resources from its argument. But a function template that says "I accept anything, but it has to be expiring" usually cannot do anything useful with the information that its argument is expiring, other than to eventually forward it to an overload that takes a concrete type.

However, it is possible. One way is:

template <typename Cb>
auto construct(Cb&&) requires(!std::is_reference_v<Cb>) {
    printf("Overload for rvalue-ref called!\n");
}

This uses the fact that, if the argument to a forwarding reference is an lvalue, the template argument is deduced as an lvalue reference, but if the argument is an rvalue, the template argument is deduced as the referenced type (i.e. a non-reference).

  • Related