简体   繁体   中英

Generic lambdas in c++14: weird behavior

maybe there's something I didn't fully get, however after reading "Use decltype on auto&& parameters to std::forward them" (from Effective Modern C++) I found something really weird.

Let's suppose we have

int size(std::string &&s) { std::cout <<"std::string&&" <<std::endl; return s.size(); }
int size(const std::string &s) { std::cout <<"std::string const &" <<std::endl; return s.size(); }

I defined two lambas (one with auto, and one that correctly forward an auto&& param):

auto f = [](auto x) { return size(x); };

std::string s{ "buonanotte" };
std::cout <<f(s) <<std::endl;
std::cout <<f("fiorellino") <<std::endl;

auto g = [](auto&& x) { return size(std::forward<decltype(x)>(x)); };
std::cout <<g(s) <<std::endl;
std::cout <<g("fiorellino") <<std::endl;

Well, the results are the same: f(s) and g(s) call size(const std::string&), while f("fiorellino") and g("fiorellino") call the overload of size for rvalues.

My question is: why is this happening? Shouldn't only the second lambda be able to distinguish between an lvalue and rvalue? I was expecting the first lambda (f()) calling twice size(const std::string&), but apparently this is not happening.

Am I doing something wrong?

Shouldn't only the second lambda be able to distinguish between an lvalue and rvalue? I was expecting the first lambda (f()) calling twice size(const std::string&)

The string literal "fiorellino" is an lvalue, but when passing it to size , a temporary std::string object will be constructed from that string literal, and that temporary is an rvalue.

That is, the type of x in f for f("fiorellino") will be const char* . That const char* is used to create a temporary std:string (an rvalue) when f calls size .

This in turn leads to the rvalue overload being selected for f("fiorellino") based on this: "When used as a function argument and when two overloads of the function are available, one taking rvalue reference parameter and the other taking lvalue reference to const parameter, an rvalue binds to the rvalue reference overload" ( source ).

OK,

so I'd like to publish a full answer (Michael, again, thanks). The thing I was getting confused was that "fiorellino" is an rvalue and a temporary is created for it and this temporary object, when a const T& and a T&& are available it will always bind to the T&& version. So far so good. This makes sense in effect (see http://en.cppreference.com/w/cpp/language/value_category where rvalues are described).

I have now changed my example in this way:

int size(std::string &&s) { std::cout <<"std::string&&" <<std::endl; return s.size(); }
int size(const std::string &s) { std::cout <<"std::string const &" <<std::endl; return s.size(); }

std::string generate(int x) { return std::to_string(x); }

auto f = [](auto x) { return size(x); };

std::string s{ "buonanotte" };
std::cout <<"f(): "; f(s);
std::cout <<"f(): "; f("fiorellino");
std::cout <<"f(): "; f(generate(123));
std::cout <<"f(): "; f(std::move(s));

auto g = [](auto&& x) { return size(std::forward<decltype(x)>(x)); };

std::string s2{ "buonanotte" };
std::cout <<"g(): "; g(s2);
std::cout <<"g(): "; g("fiorellino");
std::cout <<"g(): "; g(generate(123));
std::cout <<"g(): "; g(std::move(s2));

Now generate(123) creates a correct temporary that will be wrongly forwarded by the first lambda, but is correctly forwarded by the second one.

Look at the output:

f(): std::string const &
f(): std::string&&
f(): std::string const &
f(): std::string const &
g(): std::string const &
g(): std::string&&
g(): std::string&&
g(): std::string&&

even if I used std::move(s) (which creates an rvalue-reference) the first lambda always selects the const T& overload.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM