Ошибка вывода аргумента шаблона std :: function из лямбда-функции - PullRequest
0 голосов
/ 15 ноября 2018

При изучении шаблонов в C ++ я наткнулся на пример в следующем коде:

#include <iostream>
#include <functional>

template <typename T>
void call(std::function<void(T)> f, T v)
{
    f(v);
}

int main(int argc, char const *argv[])
{
    auto foo = [](int i) {
        std::cout << i << std::endl;
    };
    call(foo, 1);
    return 0;
}

Для компиляции этой программы я использую GNU C ++ Compiler g ++:

$ g++ --version // g++ (Ubuntu 6.5.0-1ubuntu1~16.04) 6.5.0 20181026

После компиляции для C ++ 11 я получаю следующую ошибку:

$ g++ -std=c++11 template_example_1.cpp -Wall

template_example_1.cpp: In function ‘int main(int, const char**)’:
template_example_1.cpp:15:16: error: no matching function for call to ‘call(main(int, const char**)::<lambda(int)>&, int)’
     call(foo, 1);
                ^
template_example_1.cpp:5:6: note: candidate: template<class T> void call(std::function<void(T)>, T)
 void call(std::function<void(T)> f, T v)
      ^~~~
template_example_1.cpp:5:6: note:   template argument deduction/substitution failed:
template_example_1.cpp:15:16: note:   ‘main(int, const char**)::<lambda(int)>’ is not derived from ‘std::function<void(T)>’
     call(foo, 1);
                ^

(то же самое для C ++ 14 и C ++ 17 )

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

Глядя на предыдущие вопросы ( 1 , 2 , 3 и 4 ) относительно этой ошибки, я все еще в замешательствеоб этом.

Как указано в ответах на вопросы 3 и 4, эту ошибку можно исправить, явно указав аргумент шаблона, например, так:

int main(int argc, char const *argv[])
{
    ...
    call<int>(foo, 1); // <-- specify template argument type
    // call<double>(foo, 1) // <-- works! Why?
    return 0;
}

Однако, когда я использую другие типы вместоint, как double, float, char или bool, это также работает, что меня запутало.

Итак, мои вопросы таковы:

  • Почему это работает, когда я явно указываю int (и другие) в качестве аргумента шаблона?
  • Есть более общий способ решить эту проблему?

1 Ответ

0 голосов
/ 15 ноября 2018

A std::function - это не лямбда, а лямбда - это не std::function.

Лямбда - это анонимный тип с operator() и некоторыми другими второстепенными утилитами. Ваш

auto foo = [](int i) {
    std::cout << i << std::endl;
};

является сокращением для

struct __anonymous__type__you__cannot__name__ {
  void operator()(int i) { 
    std::cout << i << std::endl;
  }
};
__anonymous__type__you__cannot__name__ foo;

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

Но обратите внимание, что не наследуется от std::function<void(int)>.


Лямбда не будет выводить параметры шаблона std::function, потому что это не связанные типы. Вывод типа шаблона - это точное сопоставление с типами передаваемых аргументов и их базовых классов. Он не пытается использовать преобразование любого вида.


A std::function<R(Args...)> - это тип, который может хранить все, что можно копировать, может вызываться со значениями, совместимыми с Args..., и возвращает что-то, совместимое с R.

Таким образом, std::function<void(char)> может хранить что угодно , которое может быть вызвано с помощью char. Поскольку int функции могут быть вызваны с помощью char, это работает.

Попробуйте:

void some_func( int x ) {
  std::cout << x << "\n";
}
int main() {
  some_func('a');
  some_func(3.14);
}

std::function делает это некоторым преобразованием из своей подписи в хранимую в нем функцию вызова.


Самое простое решение:

template <class F, class T>
void call(F f, T v) {
  f(v);
}

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

template<class T>
void call(std::function<void(T)> f, T v) {
  f(v);
}
template<class F, class T>
void call(F f_in, T v) {
  std::function f = std::forward<F>(f_in);
  call(std::move(f), std::forward<T>(v));
}

Наконец, ваш call является ограниченной версией std::invoke от . Подумайте об этом; если нет, используйте версии с бэкпортом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...