Search code examples
c++c++11lambdastd-function

Passing a lambda function to a template method


I have the following templated method:

auto clusters = std::vector<std::pair<std::vector<long>, math::Vector3f>>

template<class T> 
void eraserFunction(std::vector<T>& array, std::function<int(const T&, const T&)> func)
{
}

And I have a function that looks like

  auto comp1 = [&](
    const std::pair<std::vector<long>, math::Vector3f>& n1,
    const std::pair<std::vector<long>, math::Vector3f>& n2
  ) -> int  {
       return 0;
  };
  math::eraserFunction(clusters, comp1);

However, I get a syntax error saying:

  116 | void eraserFunction(std::vector<T>& array, std::function<int(const T&, const T&)> func)
      |      ^~~~~~~~~~~~~~
core.hpp:116:6: note:   template argument deduction/substitution failed:
geom.cpp:593:23: note:   'math::method(const at::Tensor&, const at::Tensor&, int, float, int, int, float)::<lambda(const std::pair<std::vector<long int>, Eigen::Matrix<float, 3, 1> >&, const std::pair<std::vector<long int>, Eigen::Matrix<float, 3, 1> >&)>' is not derived from 'std::function<int(const T&, const T&)>'
  593 |   math::eraserFunction(clusters, comp1);

Solution

  • The function call tries to deduce T from both the first and second function parameter.

    It will correctly deduce T from the first parameter, but fail to deduce it from the second parameter, because the second function argument is a lambda type, not a std::function type.

    If deduction isn't possible from all parameters that are deduced context, deduction fails.

    You don't really need deduction from the second parameter/argument here, since T should be fully determined by the first argument. So you can make the second parameter a non-deduced context, for example by using std::type_identity:

    void eraserFunction(std::vector<T>& array, std::type_identity_t<std::function<int(const T&, const T&)>> func)
    

    This requires C++20, but can be implemented easily in user code as well if you are limited to C++11:

    template<typename T>
    struct type_identity { using type = T; };
    

    and then

    void eraserFunction(std::vector<T>& array, typename type_identity<std::function<int(const T&, const T&)>>::type func)
    

    std::identity_type_t<T> is a type alias for std::identity_type<T>::type. Everything left to the scope resolution operator :: is a non-deduced context, which is why that works.


    If you don't have any particular reason to use std::function here, you can also just take any callable type as second template argument:

    template<class T, class F>
    void eraserFunction(std::vector<T>& array, F func)
    

    This can be called with a lambda, function pointer, std::function, etc. as argument. If the argument is not callable with the expected types, it will cause an error on instantiation of the function body containing the call. You can use SFINAE or since C++20 a type constraint to enforce this already at overload resolution time.