2

From these SE questions, Passing lambda as function pointer, Lambda as function parameter, Cannot pass lambda function as function reference?, I'm given to understand that I may pass stateless, non-capturing lambdas to functions that expect function pointers. Accordingly, I try a template to lift a binary function of type T (*bf)(T, T) over std::vectors:

template<typename T>
vector <T> lift_binary (const vector<T> & v1, const vector<T> & v2, 
        T (* bf)(T, T))
{   auto result = vector<T> ();
    result.resize(v1.size());
    transform (v1.begin(), v1.end(), v2.begin(), result.begin(), bf);
    return result;   }

This works when bf is a named function, for example

template<typename T> T minus (T x, T y) { return x - y; }

template<typename T>
vector<T> operator- (const vector<T> &v1, const vector<T> &v2)
{   return lift_binary (v1, v2, minus);   }

int main()
{   auto v1 = vector<double> ({1, 2, 3});
    auto v2 = vector<double> ({10, 20, 30});
    auto v3 = v1 - v2;
    cout << v3[0] << " " << v3[1] << " " < v3[2] << " " << endl;
    return 0;   }

produces

-9 -18 -27

But it's no good with any of the three following lambda functions (the second one is uncommented, for instance):

template<typename T>
vector<T> operator- (const vector<T> &v1, const vector<T> &v2)
// {   return lift_binary (v1, v2, [] (T x, T y) -> T { return x - y; } );   }
   {   return lift_binary (v1, v2, [] (T x, T y) { return x - y; } );   }
// {   return lift_binary (v1, v2, [] (auto x, auto y) { return x - y; } );   }
// {   return lift_binary (v1, v2, minus);   }

When compiled like this

g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out

The compiler can't match the parameter type T (*bf)(T, T) against the types of the lambdas:

main.cpp: In instantiation of 'std::vector<_RealType> operator-(const std::vector<_RealType>&, const std::vector<_RealType>&) [with T = double]':
main.cpp:37:20:   required from here
main.cpp:26:31: error: no matching function for call to 'lift_binary(const std::vector<double>&, const std::vector<double>&, operator-(const std::vector<_RealType>&, const std::vector<_RealType>&) [with T = double]::<lambda(double, double)>)'
        {   return lift_binary (v1, v2, [] (T x, T y) { return x - y; } );   }
                               ^
main.cpp:13:16: note: candidate: template<class T> std::vector<_RealType> lift_binary(const std::vector<_RealType>&, const std::vector<_RealType>&, T (*)(T, T))
     vector <T> lift_binary (const vector<T> & v1, const vector<T> & v2, T (* bf)(T, T))
                ^
main.cpp:13:16: note:   template argument deduction/substitution failed:
main.cpp:26:31: note:   mismatched types 'T (*)(T, T)' and 'operator-(const std::vector<_RealType>&, const std::vector<_RealType>&) [with T = double]::<lambda(double, double)>'
        {   return lift_binary (v1, v2, [] (T x, T y) { return x - y; } );   }

I get comparable errors with the other two lambda expressions. This leads me to think that no kind of generic lambda will match the parameter's function pointer type, but that doesn't jibe with what I have been able to find and read. I must be doing something else wrong.

Here is a coliru project with this sample online:

http://coliru.stacked-crooked.com/a/77756b84eb401156

and here is the entire code snippet in a block:

#include<iostream>
#include<vector>
#include<algorithm> 

using std::cout;
using std::endl;
using std::vector;

template<typename T>
vector <T> lift_binary (const vector<T> & v1, const vector<T> & v2, T (* bf)(T, T))
{   auto result = vector<T> ();
    result.resize(v1.size());
    transform (v1.begin(), v1.end(), v2.begin(), result.begin(), bf);
    return result;   }

template<typename T>
T minus (T x, T y)
{   return x - y;   }

template<typename T>
vector<T> operator- (const vector<T> &v1, const vector<T> &v2)
// {   return lift_binary (v1, v2, [] (T x, T y) -> T { return x - y; } );   }
   {   return lift_binary (v1, v2, [] (T x, T y) { return x - y; } );   }
// {   return lift_binary (v1, v2, [] (auto x, auto y) { return x - y; } );   }
// {   return lift_binary (v1, v2, minus);   }

template<typename T>
vector<T> operator+(const vector<T> & v1, const vector<T> & v2)
{   return lift_binary (v1, v2, [] (T x, T y) -> T { return x + y; } );   }

int main()
{   auto v1 = vector<double> ({1, 2, 3});
    auto v2 = vector<double> ({10, 20, 30});
    auto v3 = v1 - v2;
    cout << v3[0] << " " 
         << v3[1] << " " 
         << v3[2] << " " << endl;
    return 0;   }

EDIT:

The following works:

template<typename T>
vector<T> operator- (const vector<T> &v1, const vector<T> &v2)
{   auto result = vector<T> ();
    result.resize(v1.size());
    transform (v1.begin(), v1.end(), v2.begin(), result.begin(),
               [] (T x, T y) { return x - y; } );
    return result;   }

and the whole motivation for the original question is just to allow me to abstract over this pattern so that I DRY (don't repeat your(my)self) when implementing other binary operators like +, *, etc.

I expected to be able to abstract out the lambda expression by writing a higher-order function lift_binary that takes a T(*)(T,T) as a parameter because I thought that's the type of the lambda.

Community
  • 1
  • 1
Reb.Cabin
  • 5,426
  • 3
  • 35
  • 64

1 Answers1

4

Since minus is a function template, a compiler won't attempt to deduce type T based on it when used as an argument in a function call for parameter T(*bf)(T,T), as it's one of the non-deduced contexts:

§ 14.8.2.5 [temp.deduct.type]/p5:

The non-deduced contexts are:

(5.5) — A function parameter for which argument deduction cannot be done because the associated function argument is a function, or a set of overloaded functions (13.4), and one or more of the following apply:

(5.5.1) — more than one function matches the function parameter type (resulting in an ambiguous deduction), or

(5.5.2) — no function matches the function parameter type, or

(5.5.3)the set of functions supplied as an argument contains one or more function templates.

As such, type T is deduced based on the first and the second arguments (vectors), and then used to define the function pointer type itself. This is why it works for a plain function.


I expected to be able to abstract out the lambda expression by writing a higher-order function lift_binary that takes a T(*)(T,T) as a parameter because I thought that's the type of the lambda.

A lambda has some implementation-defined and unique type. Conversion to a function pointer is possible, since the lambda's type defines a conversion operator for that purpose. However, a compiler doesn't consider a lambda expression as a set of overloaded functions and tries to deduce type T based on what you pass as the argument for T(*bf)(T,T). Obviously, this is bound to fail, because lambda's (class-)type can't be matched against a function pointer type.

To work around that, you can make T for this parameter non-deducible on your own (this is known as the identity trick):

template <typename T> struct identity { using type = T; };
template <typename T> using identity_t = typename identity<T>::type;

template<typename T>
vector<T> lift_binary (const vector<T> & v1
                     , const vector<T> & v2
                     , identity_t<T(T,T)>* bf);
//                     ~~~~~~~~~~~~~~~~~~^

// ...

lift_binary(v1, v2, [] (auto x, auto y) { return x - y; } );

DEMO

For a non-generic lambda, you can instead force its conversion to a pointer to a function at the call site with an unary plus operator:

lift_binary(v1, v2, +[] (T x, T y) { return x - y; });
//                  ^

DEMO 2

Or explicitly cast a generic lambda, (also at the call site), so that it can match T(*)(T,T):

lift_binary(v1, v2, static_cast<T(*)(T,T)>([] (auto x, auto y) { return x - y; }));
//                  ~~~~~~~~~~~~~~~~~~~~~^

DEMO 3

...Or, since T has been already deduced in operator-, you can pass it in a template argument list:

return lift_binary<T>(v1, v2, [] (auto x, auto y) { return x - y; } );
//                ~~^

Side note: you can always declare the lift_binary function as follows:

template <typename T, typename F>     
auto lift_binary(const vector<T>& v1, const vector<T>& v2, F bf)
Community
  • 1
  • 1
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160