Lazward
Lazward

Reputation: 205

Type deduction for template class taking a lambda

I am trying to write a class which is able to call a lambda with no arguments later in time. I was expecting C++17 class template argument deduction to avoid the need for a factory function. However, trying to instantiate an object without specifying the type fails. I am fine using the factory function but I would like to understand why this happens.

I am using VC++2017, with the C++17 toolset enabled. Is this expected behavior? Why? Can the factory function be avoided or is it needed due to different type deduction rules for template functions and template classes? Any help would be appreaciated.

template <typename F>
class WillInvoke
{
public:
    WillInvoke(std::decay_t<F> f) : f(std::move(f)) { }

    void CallNow() { f(); }

private:
    std::decay_t<F> f;
};

template <typename F>
WillInvoke<F> make_WillInvoke(F && f)
{
    return WillInvoke<F>(std::forward<F>(f));
}

int main()
{
    // OK
    auto w = make_WillInvoke([](){ std::cout << "Hello World"; });
    w.CallNow();

    // Won't compile
    WillInvoke w2([](){ std::cout << "Hello World"; }); // No instance of constructor matches argument list
    w2.CallNow();
}

Upvotes: 1

Views: 87

Answers (1)

Guillaume Racicot
Guillaume Racicot

Reputation: 41750

This is because a member type alias like std::decay<T>::type is not deductible.

template<typename T>
void call_me(std::decay_t<T>) {}

// won't work :(
// call_me(1);

I don't think your class should decay the type. Instead, your class should state that it requires an object type and move the decay into the make function:

template <typename F> // requires std::is_object_v<F>
class WillInvoke
{
    static_assert(std::is_object_v<F>,
        "WillInvoke requires F to be an object type"
    );
public:
    WillInvoke(F f) : f(std::move(f)) { }

    void CallNow() { f(); }

private:
    F f;
};

template <typename F>
auto make_WillInvoke(F && f) -> WillInvoke<std::decay_t<F>>
{
    return WillInvoke<std::decay_t<F>>(std::forward<F>(f));
}

The nice thing is that in C++20, you can uncomment the requires and let the compiler enforce that in the call site.

Upvotes: 5

Related Questions