A problem with higher order functions and lambdas in C++0x
I have a program where I must print many STL vectors on the screen after doing some calculation on each component. So I tried to create a function like this:
template <typename a>
void printWith(vector<a> foo, a func(a)){
for_each(foo.begin(), foo.end(), [func](a x){cout << func(x) << " "; });
}
And then use it like this:
int main(){
vector<int> foo(4,0);
printWith(foo, [](int x) {return x + 1;});
return 0;
}
Unfortunately, I'm having a compiling error about the type of the lambda expression I've put inside the printWith
call:
g++ -std=gnu++0x -Wall -c vectest.cpp -o vectest.o
vectest.cpp: In function ‘int main()’:
vectest.cpp:16:41: error: no matching function for call to ‘printWith(std::vector<int>&, main()::<lambda(int)>)’
vectest.cpp:10:6: note: candidate is: void printWith()
make: *** [vectest.o] Error 1
Of course, if I do:
int sumOne(int x) {return x+1;}
then printWith(foo, sumOne);
works as intended. I thought the type of a lambda expressio开发者_运维知识库n would be the type of a function with the inferred return type. I also though that I could fit a lambda anywhere I could fit a normal function. How do I make this work?
The following works for me:
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
template <typename a, typename F>
void printWith(vector<a> foo, F f){
for_each(foo.begin(), foo.end(), [&](a x){cout << f(x) << " "; });
}
int main(){
vector<int> foo = {1,2,3,4,5};
printWith(foo, [](int x) {return x + 1;});
std::cout << '\n';
return 0;
}
Testing:
$ g++-4.5 -std=gnu++0x -Wall test.cpp
$ ./a.out
2 3 4 5 6
Alternatively, you can exploit the fact that closure types with no lambda-capture can be implicitly converted to function pointers. This is closer to your original code and also cuts down on the number of instantiations of the function template (in the original solution you get a new instantiation every time you use the function template with a different function object type; note though that it doesn't matter much in this specific case since the printWith
function is very short and most probably will be always inlined):
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
template <typename a, typename b>
void printWith(const vector<a>& foo, b f(a)){
for_each(foo.begin(), foo.end(), [=](a x){cout << f(x) << " "; });
}
int main(){
vector<int> foo = {1,2,3,4,5};
printWith<int, int>(foo, [](int x) {return x + 1;});
std::cout << '\n';
return 0;
}
Unfortunately, implicit conversion doesn't play very well with template argument deduction: as you can see, I had to specify template arguments in the call to printWith
.
Another alternative is to use std::function
. This also helps to minimize the number of template instantiations and works even for lambda expressions with lambda-capture, but has the same problems with template argument deduction:
#include <algorithm>
#include <functional>
#include <iostream>
#include <vector>
using namespace std;
template <typename a, typename b>
void printWith(const vector<a>& foo, std::function<b(a)> f){
for_each(foo.begin(), foo.end(), [&](a x){cout << f(x) << " "; });
}
int main(){
vector<int> foo = {1,2,3,4,5};
int y = 1;
printWith<int, int>(foo, [&](int x) { return x + y; });
std::cout << '\n';
return 0;
}
The reason that you're having a problem is because you're trying to use a function. Free functions have a specific representation (as a function pointer) which is not interchangable with function objects of any kind. Function pointers (which is basically what you have here) should be avoided. You need to take a function object directly with it's type specified by template.
template <typename a, typename Func>
void printWith(vector<a> foo, Func func){
for_each(foo.begin(), foo.end(), [&](a x){cout << func(x) << " "; });
}
Alternatively, take a polymorphic function object such as std::function
.
template<typename a>
void printWith(vector<a> foo, std::function<string(const a&)> func) {
for_each(foo.begin(), foo.end(), [&](a x) { cout << func(x) << " "; });
}
void printWith(vector<a> foo, b func(a)){
This is wrong, you can't do that and that makes the compiler not taking account of this code as it's not valid.
You have two ways to fix this :
1) don't ask for a parameter type, just ask for a functor:
void printWith(vector<a> foo, b func ){ // keep the rest of the code the same
The rest of your function will not compile if func don't take a a
as parameter anyway.
2) force the functor type:
template <typename a>
void printWith(vector<a> foo, std::function< void (a) > func ){
Then it's like if you were using a function pointer. No (or less) compile-time optimization, but at least you enforce the functor signature. See std::function or boost::function for details.
The reason this doesn't work is that you're mixing template argument deduction with implicit conversions. If you get rid of deduction it works:
printWith<int>(foo, [](int x) {return x + 1;});
However, it would be better (inside printWith) to let func
's type be another template parameter, as others recommend.
If on the other hand you really want to add constraints to this type there are better ways to do it using SFINAE (for soft errors) or static_assert
(for hard errors).
For instance:
// A constraints metafunction
template<typename T, typename Element>
struct is_element_printer
: std::is_convertible<T, Element (*)(Element)>
{};
Here, is_element_printer<T, Element>::value
is true
iff T
implicitly converts to Element (*)(Element)
. I'm only using this for illustrative purposes and I cannot recommend it for real use: there are plenty of things that could qualify as an 'element printer' in a lot of situations that are not function pointers. I'm only doing this because std::is_convertible
is readily available from <type_traits>
and there is no other more obvious test available. You should write your own.
Then:
template<typename Container, typename Functor>
void
printWith(Container&& container, Functor&& functor)
{
// avoid repetition
typedef typename std::decay<Container>::type::value_type value_type;
// Check our constraints here
static_assert(
std::is_element_printer<
typename std::decay<Functor>::type,
value_type
>::value,
"Descriptive error message here"
);
// A range-for is possible instead
std::for_each(container.cbegin(), container.cend(), [&functor](value_type const& v)
{ std::cout << functor(v) << ' '; });
}
精彩评论