Проблема с функциями высшего порядка и лямбдами в C ++ 0x - PullRequest
4 голосов
/ 06 июня 2011

У меня есть программа, в которой я должен распечатать много векторов STL на экране после выполнения некоторых вычислений для каждого компонента.Поэтому я попытался создать такую ​​функцию:

template <typename a> 
void printWith(vector<a> foo, a func(a)){
  for_each(foo.begin(), foo.end(), [func](a x){cout << func(x) << " "; });
}

И затем использовать ее так:

int main(){
  vector<int> foo(4,0);
  printWith(foo, [](int x) {return x + 1;});
  return 0;
}

К сожалению, у меня ошибка компиляции о типе лямбдыВыражение, которое я поместил в printWith вызов:

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

Конечно, если я сделаю:

int sumOne(int x) {return x+1;}

, тогда printWith(foo, sumOne); будет работать как задумано.Я думал, что тип лямбда-выражения будет типом функции с предполагаемым типом возврата.Я также подумал, что я мог бы установить лямбду в любом месте, где я мог бы соответствовать нормальной функции.Как мне сделать эту работу?

Ответы [ 4 ]

6 голосов
/ 06 июня 2011

У меня работает следующее:

#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;
}

Тестирование:

$ g++-4.5 -std=gnu++0x -Wall test.cpp
$ ./a.out                            
2 3 4 5 6 

В качестве альтернативы, вы можете использовать тот факт, что типы закрытия не имеют лямбда-захвата может быть неявно преобразовано в указатели на функции.Это ближе к вашему исходному коду, а также сокращает количество экземпляров шаблона функции (в исходном решении вы получаете новый экземпляр каждый раз, когда используете шаблон функции с другим типом объекта функции; обратите внимание, что он не 'В данном конкретном случае это не имеет большого значения, поскольку функция printWith очень короткая и, скорее всего, всегда будет встроенной):

#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;
}

К сожалению, неявное преобразование не очень хорошо работает с выводом аргумента шаблона: asВы можете видеть, я должен был указать аргументы шаблона при вызове printWith.

Другой альтернативой является использование std::function.Это также помогает минимизировать количество экземпляров шаблона и работает даже для лямбда-выражений с лямбда-захватом, но имеет те же проблемы с выводом аргумента шаблона:

#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;
}
5 голосов
/ 06 июня 2011

Причиной возникновения проблемы является то, что вы пытаетесь использовать функцию.Свободные функции имеют определенное представление (как указатель на функцию), которое не взаимозаменяемо с объектами функций любого вида.Следует избегать указателей на функции (что в основном и есть).Вам нужно взять объект функции напрямую с типом, указанным в шаблоне.

template <typename a, typename Func> 
void printWith(vector<a> foo, Func func){
  for_each(foo.begin(), foo.end(), [&](a x){cout << func(x) << " "; });
}

В качестве альтернативы, взять объект полиморфной функции, такой как 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) << " "; });
}
3 голосов
/ 06 июня 2011
void printWith(vector<a> foo, b func(a)){

Это неправильно, вы не можете этого сделать, и компилятор не учитывает этот код, так как он недействителен.

У вас есть два способа исправить это:

1) не запрашивать тип параметра, просто запросить функтор:

   void printWith(vector<a> foo, b func ){ // keep the rest of the code the same

Остальная часть вашей функции не будет компилироваться, если func все равно не примет a в качестве параметра.

2) форсировать тип функтора:

template <typename a> 
void printWith(vector<a> foo, std::function< void (a) > func ){

Тогда это как если бы вы использовали указатель на функцию. Нет (или меньше) оптимизации во время компиляции, но, по крайней мере, вы применяете подпись функтора. Подробности смотрите в std :: function или boost :: function.

2 голосов
/ 06 июня 2011

Причина, по которой это не работает, заключается в том, что вы смешиваете вывод аргументов шаблона с неявными преобразованиями.Если вы избавляетесь от вывода, он работает:

printWith<int>(foo, [](int x) {return x + 1;});

Однако было бы лучше (внутри printWith), чтобы тип func был другим параметром шаблона, как рекомендуют другие.

Если, с другой стороны, вы действительно хотите добавить ограничения к этому типу, есть лучшие способы сделать это, используя SFINAE (для мягких ошибок) или static_assert (для серьезных ошибок).

Например:

// A constraints metafunction
template<typename T, typename Element>
struct is_element_printer
    : std::is_convertible<T, Element (*)(Element)>
{};

Здесь is_element_printer<T, Element>::value равно true тогда T неявно преобразуется в Element (*)(Element).Я использую это только в иллюстративных целях и не могу рекомендовать его для реального использования: есть множество вещей, которые можно квалифицировать как «элементный принтер» во многих ситуациях, которые не являются указателями на функции.Я делаю это только потому, что std::is_convertible легко доступен из <type_traits>, и нет более доступного теста.Вы должны написать свой собственный.

Тогда:

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) << ' '; });
}
...