Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass a Callable object in c++17 to be used with std::invoke

Why the following does not compile when passing i to the constructor. The other similar constructs compile.

#include <iostream>
#include <functional>

int RetXPrintU(int x, uint u)
{
    std::cout << "RetXprintU(): " << u << std::endl;
    return x;
}

template <typename Fn, typename... Args>
void Call(Fn&& fun, Args&&... args)
{
    std::invoke(std::forward<Fn>(fun), std::forward<Args>(args)...);
}

template <typename Fn, typename... Args>
class CallableObj
{
public:
    explicit CallableObj(Fn&& fun, Args&&... args)
    {
        std::invoke(std::forward<Fn>(fun), std::forward<Args>(args)...);
    }
};

int main() {
    int i = 4;
    std::invoke(RetXPrintU, i, 8u);
    Call(RetXPrintU, i, 8u);
    CallableObj co(RetXPrintU, i, 8u); // WHY I DO NOT COMPILE?
    //CallableObj co(RetXPrintU, 0, 8u); // WHY I COMPILE?

    return 0;
}
like image 554
pettitpeon Avatar asked Mar 03 '23 22:03

pettitpeon


2 Answers

The issue here is you need to move the template from the class to the constructor. When you have

template <typename Fn, typename... Args>
class CallableObj
{
public:
    explicit CallableObj(Fn&& fun, Args&&... args)
    {
        std::invoke(std::forward<Fn>(fun), std::forward<Args>(args)...);
    }
};

Args&&... is not a variadic forwarding reference because it is deduced for the class itself and when you call CallableObj co(RetXPrintU, i, 8u); the class is instantiated and the constructor gets stamped out as

explicit CallableObj(int(int, unsigned int)&& fun, int&& arg1, unsigned int&& arg2)

What we want is

class CallableObj
{
public:
    template <typename Fn, typename... Args>
    explicit CallableObj(Fn&& fun, Args&&... args)
    {
        std::invoke(std::forward<Fn>(fun), std::forward<Args>(args)...);
    }
};

so that now Args will be deduced when the constructor is called and Args&& is now a forwarding reference.

The reason CallableObj co(RetXPrintU, 0, 8u); worked in your example is because 0 is a prvalue and a prvalue can bind to an rvalue reference.

like image 150
NathanOliver Avatar answered Mar 15 '23 23:03

NathanOliver


As the error message says, the compiler can't deduce the template parameters for CallableObj. You need add a deduction guide for that:

template <typename Fn, typename... Args>
CallableObj(Fn&& fun, Args&&... args) -> CallableObj<Fn, Args>...>;

The whole code reads:

template <typename Fn, typename... Args>
class CallableObj
{
public:
    explicit CallableObj(Fn fun, Args... args)
    {
        std::invoke(std::forward<Fn>(fun), std::forward<Args>(args)...);
    }
};

template <typename Fn, typename... Args>
CallableObj(Fn&& fun, Args&&... args) -> CallableObj<Fn, Args...>;

As @Jarod42 pointed out there's no need to make the constructor a template itself (as it was in the first version of the answer).

And here is a live example (corrected version by @Jarod42).

I was assuming your code is just a minimal example and you need the class to be a template. If that is not the case you better go with the other solution.

like image 20
florestan Avatar answered Mar 15 '23 23:03

florestan