Невозможно использовать явно типизированную лямбду

У меня есть этот код:

std::function<std::string&(std::string&)> change_str = [](std::string& str){
    return (str = "Hello world!");
};

std::string s;

std::cout << change_str(s) << std::endl;

Он не компилируется и не говорит:

main.cpp:8:47: error: no viable conversion from '(lambda at main.cpp:8:60)' to 'std::function<std::string &(std::string &)>'
    std::function<std::string&(std::string&)> change_str = [](std::string& str){
                                              ^            ~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/v1/functional:1448:5: note: candidate constructor not viable: no known conversion from '(lambda at main.cpp:8:60)' to 'nullptr_t' for 1st argument
    function(nullptr_t) _NOEXCEPT : __f_(0) {}
    ^
/usr/include/c++/v1/functional:1449:5: note: candidate constructor not viable: no known conversion from '(lambda at main.cpp:8:60)' to 'const std::__1::function<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > &(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > &)> &' for 1st argument
    function(const function&);
    ^
/usr/include/c++/v1/functional:1450:5: note: candidate constructor not viable: no known conversion from '(lambda at main.cpp:8:60)' to 'std::__1::function<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > &(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > &)> &&' for 1st argument
    function(function&&) _NOEXCEPT;
    ^
/usr/include/c++/v1/functional:1454:41: note: candidate template ignored: disabled by 'enable_if' [with _Fp = (lambda at main.cpp:8:60)]
                                        __callable<_Fp>::value &&
                                        ^
main.cpp:8:60: note: candidate function
    std::function<std::string&(std::string&)> change_str = [](std::string& str){
                                                           ^
1 error generated.

Однако, если я изменяю объявление std::function на auto, то он работает:

auto change_str = ...

Почему явный тип не работает для лямбда?

Ответ 1

Лямбда без типа возврата auto и автоматически удаляет внешнюю ссылку, поэтому вы не возвращаете string&, а просто string.

Просто объявите функционал как

std::function<std::string&(std::string&)> change_str = 
[](std::string& str) -> string&  ///<--- NOTE THIS
{
    return (str = "Hello world!");
};

Ответ 2

Выведенный тип возврата для вашей лямбды std::string, поэтому ваша декларация не соответствует. Но когда вы явно указываете тип возвращаемого значения, он работает:

std::function<std::string&(std::string&)> change_str = 
        [](std::string& str) -> std::string& 
{
    return (str = "Hello world!");
};

Ответ 3

Лямбда без типа возврата ведет себя как auto, которая следует за Правилами отклонения шаблона и ваш тип возврата выводится как std::string и не std::string&

Если тип явно указывается, все мелкие

std::function<std::string&(std::string&)> change_str = 
                                 [](std::string& str) -> std::string& {
  return (str = "Hello world!");
};

Ответ 4

Как говорят другие, проблема заключается в том, что вывод типа возвращаемого типа по умолчанию выводит std::string, который несовместим с ожидаемым std::string&.

Каталог различных объявлений, чтобы решить эту проблему:

// be completely explicit about the return type
[](std::string& str) -> std::string& {

 // be explicit about returning lvalue reference
[](std::string& str) -> auto& {

// be explicit that you're returning some kind of reference type,
// but use reference collapsing to determine if it an lvalue or rvalue reference
[](std::string& str) -> auto&& { 

// use C++14 feature to deduce reference type
[](std::string& str) -> decltype(auto) {

Они перечислены в порядке наименее общего. Однако в этом случае нет особой необходимости в общности: вы выводили только тип возврата, потому что это значение по умолчанию/наименьшее количество символов. Из них я бы сказал, что явное, вероятно, лучшее: [](std::string &str) -> std::string& {

quantdev удалил свой ответ, который, по моему мнению, делает еще одно хорошее предложение:

[](std::string& str) {
    return std::ref(str = "Hello world!");
};

Это работает, потому что std::function требует только подходящую конвертируемость в/из аргументов и возвращаемых типов, а возврат результата std::ref здесь соответствует этому требованию.

Оба с использованием std::ref и использование явного типа возвращаемого типа std::string & кажутся мне доступными для чтения. С оптимизацией моей реализации получается точно такая же вещь для обоих, поэтому, если вы предпочитаете внешний вид std::ref, там мало причин не использовать его.