Изучая шаблоны на 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 ++ 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 (и другие) в качестве аргумента шаблона?
  • Есть ли более общий способ решить эту проблему?
3
omar 15 Ноя 2018 в 21:57

1 ответ

Лучший ответ

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, потому что они не связаны между собой типами. Вывод типа шаблона - это точное сопоставление шаблонов с типами переданных аргументов и их базовыми классами. Он не пытается использовать какое-либо преобразование.


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 из . Рассмотрите возможность его использования; в противном случае используйте версии с обратным переносом.

10
Yakk - Adam Nevraumont 15 Ноя 2018 в 19:09