2

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;
}
Dmitriano
  • 1,878
  • 13
  • 29
  • 2
    Would you believe that this issue is called "non-deducible context'? Your template instantiation needs to deduce a type that ***after*** the transformation via `std::decay_t` results in the type used in the template instantiation. That's not deducible. And since a lambda is just an anonymous class, there is no way to deduce something like that, either. – Sam Varshavchik Jan 18 '21 at 16:44
  • @SamVarshavchik I think `std::function`'s deduction guide could get somewhere – Caleth Jan 18 '21 at 16:48
  • @SamVarshavchik yes, `std::tuple` is better than `std::function`, see EDIT1, removing `std::decay_t` does not help. – Dmitriano Jan 18 '21 at 16:49
  • That's good news, but what exactly is your question, then? And, as I mentioned, there's also the lambda becoming an anonymous closure, too. – Sam Varshavchik Jan 18 '21 at 16:50
  • CTAD relies on deduction guides. And those deduce stuff like good old function templates always did. And the issue of deducing a `std::function` from a lambda has been re-hashed already on SO. For example https://stackoverflow.com/questions/53326206/failure-to-deduce-template-argument-stdfunction-from-lambda-function – StoryTeller - Unslander Monica Jan 18 '21 at 16:55
  • @SamVarshavchik take a look at `A b(std::function([](int, bool) {}));` in the answer below. `std::function` deduces lambda argument types somehow. – Dmitriano Jan 18 '21 at 17:19

2 Answers2

1

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;
}
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • `std::function([](int, bool) {})` does not compile as it can't deduce template arguments of `std::function`. – Andrey Semashev Jan 18 '21 at 17:11
  • @AndreySemashev [Why not](https://wandbox.org/permlink/Nys0gcayOcvPiiao)? CTAD works for it. – songyuanyao Jan 18 '21 at 17:12
  • Also, your second point about non-deduced context is incorrect, since at the point where `std::decay_t` is used there is no template argument deduction in effect in the first place. – Andrey Semashev Jan 18 '21 at 17:13
  • @AndreySemashev Sorry I can't get what you mean; if not remove the part of `std::decay_t` the code won't compile even passing an `std::function` explicitly to the constructor . You meant the usage of `std::decay_t` causes not compiling for other reasons? – songyuanyao Jan 18 '21 at 17:15
  • @songyuanyao Hmm, I stand corrected and surprised then. I have no idea how `std::function` is able to deduce its template arguments. – Andrey Semashev Jan 18 '21 at 17:17
  • @AndreySemashev See [deduction guides](https://en.cppreference.com/w/cpp/utility/functional/function/deduction_guides) for `std::function`; it works for some cases, including this one. – songyuanyao Jan 18 '21 at 17:19
  • @songyuanyao > You meant the usage of `std::decay_t` causes not compiling for other reasons? -- Yes - because the deduced template arguments of `std::function` and the ones generated in the `Func` typedef become different, and there is no conversion between the two `std::function` specializations. – Andrey Semashev Jan 18 '21 at 17:21
0

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... >;
Andrey Semashev
  • 10,046
  • 1
  • 17
  • 27