#include <iostream>
#include <string>
#include <memory>
template<typename T, typename Arg>
std::shared_ptr<T> factory(const Arg& arg) {
std::cout << "factory1\n";
return std::shared_ptr<T>(new T(arg));
}
template<typename T, typename Arg>
std::shared_ptr<T> factory(Arg&& arg) {
std::cout << "factory2\n";
return std::shared_ptr<T>(new T(std::move(arg)));
}
int main()
{
auto sp1 = factory<std::string>(std::string("AAAAA"));
std::string s("BBBBBBB");
auto sp2 = factory<std::string>(s);
}
output:
factory2
factory2
Шаблонные функции взяты из примеров ю-туб лекции по с++11. Второй вызов factory должен был привести к вызову второй функции, то есть, в консоли ожидалось:
factory2
factory1
Почему всегда вызывается factory2? Как вызвать factory1?
TL;DR:
У вас во второй функции параметр - не обычная ссылка-на-rvalue, а так называемая универсальная ссылка, которая одинаково хорошо обрабатывает и lvalue, и rvalue.
В этом случае она сработала как std::string &
. А этот вариант подходит лучше, чем const std::string &
, так что вызывается вторая функция.
Подробнее?
Пардон за простыню текста.
Что еще за универсальные ссылки
Универсальные ссылки называются так, потому что в них одинаково хорошо можно передать и lvalue, и rvalue. А потом внутри шаблона определить, lvalue это или rvalue, и действовать соответственно.
По сути, "универсальная ссылка" - это ссылка-на-rvalue (&&
), но не на любой тип, а на параметр шаблона. Но не на любой, а только на тот, который при вызове функции вы позволили компилятору определить за вас. (Иначе особые свойства универсальной ссылки пропадают, и она превращается в обычную ссылку-на-rvalue.) (Если это не параметр, а обычная переменная, или же параметр лямбды, то вместо параметра шаблона подойдет auto
.)
Почему это работает
Почему такая ссылка принимает и lvalue тоже, хотя, казалось бы, должна работать только с rvalue?
Сначала нужно рассказать про...
Правила схлопывания ссылок
Думаю понятно, что так писать нельзя: int & &&
- ссылок на ссылки не бывает, компилятор запрещает их создавать.
Однако, что есть написать так?
using A = int &;
A &&my_reference = что-нибудь;
Этот вариант скомпилируется.
my_reference
будет иметь тип int &
. Почему? Из-за этих самых правил схлопывания ссылок.
Если вы пытаетесь создать ссылку на ссылку (не напрямую, ведь это ошибка, а через using
и или параметры шаблонов), то вид ссылки, которая получится: на lvalue или на rvalue - определяется по этим правилам:
& + & -> &
&& + & -> &
& + && -> &
&& + && -> &&
Почему именно так? Понятия не имею; слышал, что эти правила специально подгоняли под универсальные ссылки... Подробнее ниже.
Определение параметра шаблона по универсальной ссылке
Казалось бы, какой смысл в правилах схлопывания ссылок? Вот какой:
Если вы пытаетесь передать в универсальную ссылку rvalue, то параметр шаблона определяется компилятором по обычным правилам:
template <typename T> void foo(T &&) {}
foo(1); // T = int
С этим все понятно, и никаких вопросов не возникает.
Однако, если передать lvalue, вот так:
template <typename T> void foo(T &&) {}
foo(1); // T = int
int x = 1;
foo(x); // T = int &
То шаблонный параметр сам по себе будет сделан ссылой-на-lvalue. Далее, по правилам схлопывания ссылок весь параметр становится ссылкой-на-lvalue.
Если передавать константный объект, то тип будет определен, соответственно, как const T
или const T &
.
(С int
ом выше этого не произошло из-за кое-какой особенности встроенных типов. Однако, будь там, например, константная std::string
, то тип определился бы как константный.)
Теперь должно быть понятно, почему в вашем случае оба раза вызывается вторая перегрузка.
В этом примере:
std::string s("BBBBBBB");
factory<std::string>(s);
Параметр функции, благодаря универсальной ссылке, получит тип std::string &
.
А это более подходящий тип, чем const std::string &
, так что выбирается вариант с универсальной ссылкой.
Как пользоваться универсальными ссылками
Все удобство в том, что мы теперь внутри шаблона можем определить, что нам передали - lvalue или rvalue. И на основе этого решить, делать std::move
или нет.
Как именно определить? По тому, определился ли параметр шаблона как ссылка:
template <typename T> void foo(T &&)
{
if (std::is_reference_v<T>) // Или is_lvalue_reference, без разницы.
std::cout << "lvalue\n";
else
std::cout << "rvalue\n";
}
Для справки: Можно подумать, что проверка на lvalue/rvalue - бесполезное занятие, и попробовать написать так:
template <typename T, typename Arg> std::shared_ptr<T> factory(Arg &&arg)
{
return std::shared_ptr<T>(new T(arg));
}
Но это не сработает. После того, как rvalue ссылка создана, она сама
по себе становится lvalue, и к ней нужно применять std::move
.
Ладно, а как на основе этого сделать условный std::move
? Наивный вариант выглядит так:
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)));
}
Однако, есть вариант проще. В стандартной библиотеке уже есть все необходимое:
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
- это, по сути, условный std::move
. Если его параметр шаблона - это lvalue ссылка, то он ничего не делает. А иначе он работает как std::move
.
Если не нравится стандартная библиотека, можно даже так:
template <typename T, typename Arg> std::shared_ptr<T> factory(Arg &&arg)
{
return std::shared_ptr<T>(new T((Arg &&)arg));
}
Эффект точно такой же. Это работает из-за правил схлопывания ссылок, описанных выше.
Итог
Таким образом, вам не нужно две перегрузки.
Достаточно одной всеядной:
template <typename T, typename Arg> std::shared_ptr<T> factory(Arg &&arg)
{
return std::shared_ptr<T>(new T(std::forward<Arg>(arg)));
}
factory(Arg&& arg)
это «всеядный» вызов, который в обоих случаях лучше первого варианта. Для второго случая, чтобы вызвать первый вариант, нужно добавить к аргументу const
, а для второго варианта — ничего. Поэтому второй побеждает. Если Вы определите свой объект так:
const std::string s("BBBBBBB");
То победит первый вариант.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Есть приложение которое защищено проверкой подписи, но есть патч который подменяет оригинальную подпись -> она всегда вернаПатч изменяет...
Уже второй день пытаюсь прикрутить google карту, но все четноВ основном, брал инфу из документации google
Нашел один хороший - Sourcetrail, но он не работает(ошибка 87, не найдена java-либа, подозреваю что ему нужна eclipse-jdt)Есть еще похожие?