SwiftMango
SwiftMango

Reputation: 15284

Cannot use explicitly typed lambda

I have this code:

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;

It does not compile, and say:

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.

However if I change the declaration of std::function to auto, then it works:

auto change_str = ...

Why is the explicit type not working for lambda?

Upvotes: 13

Views: 10283

Answers (4)

bames53
bames53

Reputation: 88155

As others say, the issue is that the default return type deduction deduces std::string which is not compatible with the expected std::string&.

Catalogue of the various declarations to solve this:

// 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's an lvalue or rvalue reference
[](std::string& str) -> auto&& { 

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

These are listed in order of least to most generic. However in this case there's no particular need for genericity: you were only deducing the return type because that's the default/least typing. Of these I'd probably say being explicit is probably the best: [](std::string &str) -> std::string& {

quantdev deleted his answer which I think makes another good suggestion:

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

This works because std::function only requires suitable convertibility to/from the argument and return types, and returning the result of std::ref here meets that requirement.

Both using std::ref and using an explicit std::string & return type seem readable to me. With optimizations on my implementation produces exactly the same thing for both, so if you prefer the look of std::ref there's little reason not to use it.

Upvotes: 3

Emilio Garavaglia
Emilio Garavaglia

Reputation: 20730

A lambda with no return type is auto, and auto remove the external reference, so you are not returning string& but just string.

Just declare the functional as

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

Upvotes: 18

Rafal Mielniczuk
Rafal Mielniczuk

Reputation: 1342

Deduced return type for your lambda is std::string, that's why your declaration does not match. But when you explicitly specify return type, it works:

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

Upvotes: 4

Marco A.
Marco A.

Reputation: 43662

A lambda with no return type behaves as auto which follows the Template Argument Deduction rules and your return type is deduced to be std::string and not std::string&

If the type gets specified explicitly everything's fine

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

Upvotes: 2

Related Questions