Похоже, я не понимаю чего-то фундаментального в правилах вывода/ссылки типов C++. Скажем, у меня есть объект entity
, который принимает ссылку rvalue в конструкторе и имеет элемент данных того же типа. Меня это устраивало, пока я не узнал, что выведенный тип выводится в соответствии с правилами свертывания ссылок, например:
Когда я привязываю xvalue Alloc&& к параметру Alloc&& alloc, выводимый тип Alloc будет Alloc&& в соответствии с:
- A& & становится A&
- A& && становится A&
- A&& & становится A&
- A&& && становится A&&
Итак, когда выведенный тип «Alloc» на самом деле является Alloc&& в следующем примере, почему этот класс, кажется, хранит тип значения Alloc, а не выведенную ссылку rvalue? Разве тип члена класса «Alloc» не должен быть тайно ссылкой на rvalue, поскольку я вызываю ctor с помощью xvalue (std::move)?
#include <memory>
#include <cstdio>
#include <type_traits>
template <typename Alloc>
struct entity
{
entity(Alloc&& alloc)
: alloc_{ alloc }
{}
auto print() {
if constexpr (std::is_rvalue_reference_v<Alloc>) {
printf("Is type is &&");
} else if constexpr (std::is_lvalue_reference_v<Alloc>) {
printf("Is type is &");
} else {
printf("Is type value");
}
}
Alloc alloc_;
};
int main()
{
std::allocator<int> a;
entity e(std::move(a));
e.print();
}
Выход:
Is type value
Alloc&&
не является ссылкой на пересылку, поскольку она не используется в шаблоне функции, это просто ссылка на rvalue
Свертывание ссылки происходит после вычета параметра шаблона. Они предназначены для совместной работы, но в конечном итоге являются отдельными. "выведенный тип Alloc
будет Alloc&&
" Что?
@HolyBlackCat Этого я не понимаю. Я думал, что типы выводятся таким образом, чтобы они соответствовали ожидаемому типу, включая ссылки, если это возможно. Я не знал, что правила для шаблонов CTAD и функций отличаются. Есть литература по этой теме?
Семантика перемещения C++ от Джосуттиса или его выступления на cppcon. Опубликованный код представляет то, что Джосуттис называет «шаблонной сущностью», но не шаблоном функции. Вы можете поставить лайк это, но обратите внимание на a. шаблонный конструктор б. предоставленное вручную руководство по дедукции.
типы выводятся таким образом, что они совпадают..." Да, но если вы посмотрите только на сворачивающиеся правила, у вас возникнут неясности. В вашем случае Alloc = T
и Alloc = T &&
оба дадут правильный тип параметра. Таким образом, правила дедукции имеют отдельную формулировку, которая убирает это. | Правила для неявного CTAD немного отличаются, в этом случае он не выведет Alloc = T &
. — Есть какая-нибудь литература по этой теме? Просто cppreference, но не уверен, что этого будет достаточно.
@HolyBlackCat Спасибо, я думаю, что я ищу что-то вроде пошагового руководства о том, как компилятор делает то, что он делает в этом отношении. Это было бы очень полезно. Но продолжаю искать :)
Alloc&&
не является ссылкой для пересылки, так как она не используется в шаблоне функции, это просто ссылка на rvalue. Следовательно, Alloc
выводится как std::allocator<int>
, а alloc
в вашем конструкторе является ссылкой на rvalue.
Чтобы увидеть ссылки на переадресацию, вам нужен шаблон функции. Например.:
#include <cstdio>
#include <memory>
template <typename Alloc>
void print(Alloc&& alloc)
{
if constexpr (std::is_rvalue_reference_v<Alloc>) {
printf("Is type is &&\n");
} else if constexpr (std::is_lvalue_reference_v<Alloc>) {
printf("Is type is &\n");
} else {
printf("Is type value\n");
}
}
int main()
{
print(std::allocator<int>());
std::allocator<int> a;
print(a);
print(std::move(a));
}
Однако обратите внимание, что Alloc
по-прежнему не будет ссылкой на rvalue, при передаче rvalue Alloc
выводит std::allocator
, но alloc
является ссылкой на rvalue:
#include <cstdio>
#include <memory>
template <typename Alloc>
void print(Alloc&& alloc)
{
if constexpr (std::is_rvalue_reference_v<Alloc>) {
printf("Is type is &&\n");
} else if constexpr (std::is_lvalue_reference_v<Alloc>) {
printf("Is type is &\n");
} else {
printf("Is type value\n");
}
if constexpr (std::is_rvalue_reference_v<decltype(alloc)>) {
printf("Argument is type is &&\n");
} else if constexpr (std::is_lvalue_reference_v<decltype(alloc)>) {
printf("Argument is type is &\n");
} else {
printf("Argument is type value\n");
}
}
int main()
{
print(std::allocator<int>());
std::allocator<int> a;
print(a);
print(std::move(a));
}
Отпечатки:
Is type value
Argument is type is &&
Is type is &
Argument is type is &
Is type value
Argument is type is &&
Alloc
не будет ссылкой в вашей функции с std::move(a)
. Неявно сгенерированный CTAD будет вести себя точно так же, как и ваша функция.
@PasserBy исправлено, я знал, что не должен был писать это на своем телефоне без предварительного тестирования
Спасибо, немного проясняешь ситуацию. Хотя все еще немного запутался, но я думаю, что теперь мне нужно прочитать кое-какую литературу... :)
Это два вопроса в одном: неявно сгенерированный CTAD и перенаправление ссылок. Мне кажется, вы путаетесь в обоих. Пожалуйста, спрашивайте по одному.