Dmitriano
Dmitriano

Reputation: 2060

Deduce template arguments from std::function parameter types

Why the following class A can't deduce its template parameters in the code below:

#include <functional>

template <class... Ts>
class A
{
public:

    using Func = std::function<void(std::decay_t<Ts>...)>;

    A(Func func) : m_func(func)
    {
    }

private:

    Func m_func;
};

int main()
{
    //compiles
    A<int, bool> a([](int, bool) {});

    //does not compile with error 'class template argument deduction failed'
    A b([](int, bool) {});

    return 0;
}

The class have a data member of type Func and needs to know the types of its parameters. How to make it compile?

EDIT1:

I was able to make this compile with std::tuple:

#include <tuple>

template <class... Ts>
class A
{
public:

    using Func = std::tuple<Ts...>;

    constexpr A(Func func) : m_func(func)
    {
    }

private:

    Func m_func;
};

int main()
{
    std::tuple<int, bool> t;
    
    //compiles
    A<int, bool> a(t);

    //do not compile with error 'class template argument deduction failed'
    A b(t);

    return 0;
}

Upvotes: 2

Views: 1000

Answers (2)

songyuanyao
songyuanyao

Reputation: 173024

Two issues.

  1. Implicit conversion (from lambda to std::function) won't be considered in template argument deduction; which causes deduction failing.

  2. The existence of std::decay_t results in non-deduced context; then Ts can't be deduced.

The following code compiles.

#include <functional>

template <class... Ts>
class A
{
public:

    using Func = std::function<void(Ts...)>;

    A(Func func) : m_func(func)
    {
    }

private:

    Func m_func;
};

int main()
{
    //compiles
    A<int, bool> a([](int, bool) {});

    //compiles
    A b(std::function([](int, bool) {}));

    return 0;
}

Upvotes: 1

Andrey Semashev
Andrey Semashev

Reputation: 10614

Template parameter deduction traditionally was an exclusive feature of a function call expression. Template parameters of a class template could not be deduced from a constructor call until the introduction of deduction guides in C++17. With that, you can explicitly specify how template parameters of a class should be deduced from a constructor call.

template <class... Ts>
class A
{
public:

    using Func = std::tuple<Ts...>;

    constexpr A(Func func) : m_func(func)
    {
    }

private:

    Func m_func;
};

template< class... Ts >
A(std::tuple< Ts... >) -> A< Ts... >;

int main()
{
    std::tuple<int, bool> t;
    
    A<int, bool> a(t);

    A b(t); // deduces int, bool types from t

    return 0;
}

The use of a deduction guide to deduce a function call signature from a lambda can be problematic. The lambda function does not define a type that corresponds to its signature. If the lambda is polymorphic (i.e. uses auto in its argument list) then such type is not possible to define in the first place. And a function can be called with arguments different from its signature, so there may be multiple different sets of argument types, with which the function is callable. You will most likely have to specify the parameter types explicitly in this case.

Deducing template parameters from function pointers is still possible, though:

template< class R, class... Ts >
A(R (*)(Ts...)) -> A< Ts... >;

Upvotes: 0

Related Questions