Search code examples
c++templatestemplate-argument-deduction

Why can't I create a template function with an optional UnaryPredicate argument?


I'm trying to create a templated function with an optional argument and I'm having trouble understanding why the compile fails. This is my test (contrived) code:

#include <iostream>
#include <vector>

template <class UnaryPredicate>
int GetCountIf(std::vector<int> v, UnaryPredicate pred = [](auto) { return true; }) {
  int count=0;
  for (auto i: v) {
    if (pred(i)) {
      count++;
    }
  }
  return count;
}

int main() {
  auto v = std::vector<int>{0, 1, 2, 3, 4, 5};
  std::cout << "NumOddElements=" << GetCountIf(v, [](auto val) { return (val % 2 == 1); }) << '\n';
  // std::cout << "NumElements=" << GetCountIf(v) << '\n';
}

The code compiles only if I call GetCountIf() with both arguments. If I try to pass it only 1 argument, compilation fails with this error:

main.cpp:18:34: error: no matching function for call to 'GetCountIf'
std::cout << "NumElements=" << GetCountIf(v) << '\n'; ^~~~~~~~~~ main.cpp:5:5: note: candidate template ignored: couldn't infer template argument 'UnaryPredicate' int GetCountIf(std::vector v, UnaryPredicate pred = { return true; }) { ^ 1 error generated.

When the compiler comes across a call to GetCountIf with only 1 argument, why is it not able to deduce that the type of the optional lambda? If I explicitly specify the type of the predicate like this, it works:

template <typename T, class UnaryPredicate = std::function<bool(T)>>
int GetCountIf(std::vector<T> v, UnaryPredicate pred = [](T) { return true;}) {
  ...
}

Why does this work?

(I'm using C++14)


Solution

  • Note that the default value of function parameter won't be used for template argument deduction of template parameter; which leads to template argument deduction failure, the type of UnaryPredicate can't be deduced.

    See non-deduced contexts.

    In the following cases, the types, templates, and non-type values that are used to compose P do not participate in template argument deduction, but instead use the template arguments that were either deduced elsewhere or explicitly specified. If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails.

    4) A template parameter used in the parameter type of a function parameter that has a default argument that is being used in the call for which argument deduction is being done:

    template<typename T, typename F>
    void f(const std::vector<T>& v, const F& comp = std::less<T>());
    std::vector<std::string> v(3);
    f(v); // P1 = const std::vector<T>&, A1 = std::vector<std::string> lvalue
          // P1/A1 deduced T = std::string
          // P2 = const F&, A2 = std::less<std::string> rvalue
          // P2 is non-deduced context for F (template parameter) used in the
          // parameter type (const F&) of the function parameter comp,
          // that has a default argument that is being used in the call f(v)
    

    and

    Type template parameter cannot be deduced from the type of a function default argument:

    template<typename T> void f(T = 5, T = 7);
    
    void g()
    {
        f(1);     // OK: calls f<int>(1, 7)
        f();      // error: cannot deduce T
        f<int>(); // OK: calls f<int>(5, 7)
    }
    

    On the other hand, if you specify a default value std::function<bool(T)> for template parameter UnaryPredicate, then it'll be used as the type for UnaryPredicate if the argument for UnaryPredicate is not explicitly specified or not be deduced.