4

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;
}
pettitpeon
  • 87
  • 4

2 Answers2

4

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.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
3

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.

florestan
  • 4,405
  • 2
  • 14
  • 28
  • No need of template constructor: [Demo](https://godbolt.org/z/z3fmiK) – Jarod42 Jan 14 '20 at 15:24
  • Looks great. I just do not understand the following part. How does it work? `template CallableObj(Fn&& fun, Args&&... args) -> CallableObj, std::decay_t...>; ` Is it a deduction hint? – pettitpeon Jan 14 '20 at 15:30
  • This is a c++17 class template deduction guide. It allows the compiler to deduce the class template arguments from the constructor args: https://en.cppreference.com/w/cpp/language/class_template_argument_deduction (See the section "User-defined deduction guides" – florestan Jan 14 '20 at 15:35
  • @Jarod42: OK, but you changed the signature of the constructor from forwarding refs to values... – florestan Jan 14 '20 at 15:36
  • @florestan: Not necessary values, type given for `CallableObj` (and indeed, with your deduction guide, you also drop l-value reference, so `CallableObj co{[](int&){}, i)` won't work in your version. – Jarod42 Jan 14 '20 at 15:41
  • @Jarod: right, i had the wrong template params in the `std::forward`s in the constructor. I updated the post and the live example. Now your example compiles. Thanks – florestan Jan 14 '20 at 15:48
  • I meant something like [that](https://godbolt.org/z/SFd1aW). Your forwarding is actually a `std::move` – Jarod42 Jan 14 '20 at 15:53
  • Ok, yeah. Right. That's definitely the better solution. Thanks for pointing it out! I'll change the answer. – florestan Jan 14 '20 at 15:55
  • In your previous version, `template class CallableObj`, `Args...` is unused moreover... – Jarod42 Jan 14 '20 at 15:56
  • Right, but as I wrote in the last sentence I was assuming the user needs the class template parameters in his/her real code. – florestan Jan 14 '20 at 16:00
  • @florestan Your assumption was correct. I do need the class template parameters otherwise the class is useless. My example was only to simplify my problem, but the real class obviously needs the parameters – pettitpeon Jan 15 '20 at 08:13