Search code examples
c++templatesvariadic-templates

Template argument deduction/substitution failed when calling


Either there is something wrong with my C++ understanding, or there's a hidden typo somewhere... I wasted hours on those few LOCs.

First, I defined a start() method as such:

template <typename FunctionResult, typename ...FunctionArgs>
inline std::shared_ptr<StoppableThread> start(
        std::function<FunctionResult(const bool&, FunctionArgs...)> function, FunctionArgs... args)
{ ... }

And also, some threadedSearch() lambda function:

std::function<void(const bool&,
        const Source&, Population&,
        const std::vector<Parameter>&,
        const size_t&, const size_t&)> threadedSearch = ... ;

But when I attempt to perform this:

Source source = { ... };
Population population = { ... };
const std::vector<Parameter> variationsParameters = { ... };
const size_t randomCandidatesCount = ..., lineageCount = ...;

auto searchThread = start(
    threadedSearch,
    source, population,
    variationsParameters,
    randomCandidatesCount, lineageCount);

The compiler disagrees with the latter call to starŧ(), and tells me:

# with g++
error: no matching function for call to
‘start(std::function<void(const bool&, const Source&, Population&, const std::vector<Parameter>&, const long unsigned int&, const long unsigned int&)>&,
Source&, Population&, const std::vector<Parameter>&, const size_t&, const size_t&)’
[...]
note: candidate: ‘template<class FunctionResult, class ... FunctionArgs> std::shared_ptr<StoppableThread>
start(std::function<FunctionResult(const bool&, FunctionArgs ...)>, FunctionArgs ...)
note: template argument deduction/substitution failed:
note: inconsistent parameter pack deduction with ‘const Source&’ and ‘Source’

# with clang++
error: no matching member function for call to 'start'
note: candidate template ignored: deduced conflicting types for parameter 'FunctionArgs'
(<const Source&, Population&, const std::vector<Parameter>&, const unsigned long&, const unsigned long&>
vs.
<Source, Population, std::vector<Parameter>, size_t, size_t>)

My question is: WTF?

Also: what could I do? Explicitely specifying template parameters in the start<...>() call didn't even work...

I don't know how to make the compiler understand what it should see as the "real" parameters types...

A fully crashing minimal example can be found here: https://onlinegdb.com/FtBIGmkH-


Solution

  • whatever_t start(std::function<FunctionResult(const bool&, FunctionArgs...)> function, 
              FunctionArgs... args) 
    

    This has two occirrences of FunctionArgs.... When a part of std::function type, the context is non-deducible, and the pack will be taken from the std::function as is. In the other occurerence the context is deducible, so the pack will be deduced from the actual function argument.

    The two packs should be exactly the same in both cases. However FunctionArgs... args will never deduce a reference type, and your function accepts const references. So one pack will be full of const reference types and the other full of non-reference types. This is a substitution failure.

    One way to fix the problem is to make the other occurrence non-deducible too.

    whatever_t start(std::function<FunctionResult(const bool&, FunctionArgs...)> function, 
          std::type_identity_t<FunctionArgs>... args)
    

    will do the job (requires C++20, but an equivalent of std::type_identity_t can be implemented in a pinch).

    Another way is to decouple the function type from the arguments.

    template<typename Func, typename ... Args>
    whatever_t start(Func&& func, Args&& ... args) 
    

    (to make sure func can be called with args you can use a requires clause or good old SFINAE—or just call it, and deal with less helpful error messages if the call cannot be made),