TL; DR:
In your second function, the parameter is not an ordinary reference-to-rvalue, but a so-called universal reference , which handles both lvalue and rvalue equally well.
In this case, it worked like std::string & . And this option is better suited than const std::string & , so the second function is called.
More details?
Sorry for the sheet of text.
What else for universal links
Universal links are called like this because it is equally good to pass both lvalue and rvalue in them. And then inside the template, determine if it is lvalue or rvalue, and act accordingly.
In essence, the "universal link" is a link-to-rvalue ( && ), but not to any type, but to a template parameter. But not on any, but only on the one that when you called the function you allowed the compiler to determine for you. (Otherwise, the special properties of the universal link disappear, and it turns into a regular link-to-rvalue.) (If this is not a parameter, but an ordinary variable, or a lambda parameter, then auto will be used instead of the template parameter.)
Why does it work
Why does such a link accept lvalue too, although, it would seem, it should work only with rvalue?
First you need to tell about ...
Link Collapse Rules
I think it is clear that it is impossible to write like this: int & && - there are no references to references, the compiler forbids them to be created.
However, what to write like that?
using A = int &; A &&my_reference = что-нибудь;
This option will compile.
my_reference will be of type int & . Why? Because of these very rules of link collapse.
If you are trying to create a link to a link (not directly, because this is a mistake, but through using and or template parameters), then the type of link that happens: on lvalue or on rvalue - is determined according to these rules:
& + & -> & && + & -> & & + && -> & && + && -> &&
Why exactly? I have no idea; I heard that these rules were specially customized for universal links ... More below.
Defining a template parameter using a universal link
It would seem, what's the point in the rules of link collapse? Here is what:
If you are trying to pass into a universal reference rvalue, then the template parameter is determined by the compiler according to the usual rules:
template <typename T> void foo(T &&) {} foo(1); // T = int
Everything is clear with this, and no questions arise.
However, if you pass lvalue, like this:
template <typename T> void foo(T &&) {} foo(1); // T = int int x = 1; foo(x); // T = int &
That template parameter itself will be made reference-to-lvalue . Further, according to the rules of link collapsing, the entire parameter becomes a link-to-lvalue.
If you transmit a constant object, the type will be defined, respectively, as const T or const T & .
(Since int above this did not happen because of some feature of the built-in types. However, if there were, for example, a constant std::string , then the type would be defined as constant.)
Now it should be clear why in your case the second overload is called both times.
In this example:
std::string s("BBBBBBB"); factory<std::string>(s);
The function parameter, due to the universal reference, will get the type std::string & .
This is a more appropriate type than const std::string & , so the option with a universal link is selected.
How to use universal links
All the convenience that we are now inside the template can determine what was passed to us - lvalue or rvalue. And based on this, decide whether to do std::move or not.
How exactly to determine? Whether the template parameter was determined as a link:
template <typename T> void foo(T &&) { if (std::is_reference_v<T>) // Или is_lvalue_reference, без разницы. std::cout << "lvalue\n"; else std::cout << "rvalue\n"; }
For reference: You might think that checking for lvalue / rvalue is a useless exercise, and try to write like this:
template <typename T, typename Arg> std::shared_ptr<T> factory(Arg &&arg) { return std::shared_ptr<T>(new T(arg)); }
But it will not work. After the rvalue link is created, it itself becomes an lvalue, and you need to apply std::move .
Well, and how on the basis of it to make conditional std::move ? The naive version looks like this:
template <typename T, typename Arg> std::shared_ptr<T> factory(Arg &&arg) { if (std::is_reference_v<T>) return std::shared_ptr<T>(new T(arg)); else return std::shared_ptr<T>(new T(std::move(arg))); }
However, there is an easier option. The standard library already has everything you need:
template <typename T, typename Arg> std::shared_ptr<T> factory(Arg &&arg) { return std::shared_ptr<T>(new T(std::forward<Arg>(arg))); }
std::forward is essentially a conditional std::move . If its template parameter is a lvalue link, then it does nothing. Otherwise, it works like std::move .
If you don’t like the standard library, you can even do this:
template <typename T, typename Arg> std::shared_ptr<T> factory(Arg &&arg) { return std::shared_ptr<T>(new T((Arg &&)arg)); }
The effect is exactly the same. This works because of the link collapse rules described above.
Total
Thus, you do not need two overloads.
It is enough one omnivorous:
template <typename T, typename Arg> std::shared_ptr<T> factory(Arg &&arg) { return std::shared_ptr<T>(new T(std::forward<Arg>(arg))); }