Почему еще важен std::forward
#опытным
Подписчик @Ivaneo предложил новую рубрику #ЧЗХ, в рамках которой мы будем рассматривать мозголомательные примеры кода и пытаться объяснить, почему они работают так криво.
Также спасибо ему за предоставление следующего примера:
Как думаете, что выведется на консоль? Подумайте пару секунд.
Ну нормальный человек ответит:
Однако командная строка вам выдаст следующее:
Если не верите, по посмотрите в годболте. И можете уже сейчас написать в комментах: "ЧЗХ", "WTF", "WAT" и прочее.
А нам пораразбирацца.
Тут используется auto в аргументах функции, значит эта функция неявно шаблонная. Посмотрим, что нам выдаст cppinsights по этому коду:
Просто прекрасно. Какого черта компилятор кастит переменные к противоположным типам?
Первое, что важно понимать: внутри функции foo переменная v - это уже lvalue, так как имеет имя. Значит просто так вызвать перегрузки для правых ссылок он не может.
Но у компилятора в кармане есть стандартные преобразования, которые и идут в ход, когда нет подходящих перегрузок. Обычно это неявные преобразования из одного типа в другой. Не преобразования из одного типа ссылочности в другой тип ссылочности, а прям в другие типы данных.
То есть происходит следующее: компилятор понимает, что подходящей перегрузки нет, поэтому начинает применять стандартные преобразования в другие типы. Любой каст дает временный объект. А временный объект типа int легко биндится к float&&, как и временный объект float легко биндится к int&&.
Вот и получается обмен вызовами.
Чтобы такого не происходило, применяйте перед сном std::forward. Если есть контекст вывода типов, то он помогает правильно передавать категорию выражения объекта во внутренние вызовы.
В этом случае вывод будет ожидаемым.
Be amazed. Stay cool.
#cppcore #cpp11 #template
#опытным
Подписчик @Ivaneo предложил новую рубрику #ЧЗХ, в рамках которой мы будем рассматривать мозголомательные примеры кода и пытаться объяснить, почему они работают так криво.
Также спасибо ему за предоставление следующего примера:
#include <iostream>
void bar(float&& x) { std::cout << "float " << x << "\n"; }
void bar(int&& x) { std::cout << "int " << x << "\n"; }
void foo(auto&& v) { bar(v); }
int main() {
foo(1);
foo(2.0f);
}
Как думаете, что выведется на консоль? Подумайте пару секунд.
Ну нормальный человек ответит:
int 1
float 2
Однако командная строка вам выдаст следующее:
float 1
int 2
Если не верите, по посмотрите в годболте. И можете уже сейчас написать в комментах: "ЧЗХ", "WTF", "WAT" и прочее.
А нам пораразбирацца.
Тут используется auto в аргументах функции, значит эта функция неявно шаблонная. Посмотрим, что нам выдаст cppinsights по этому коду:
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void foo<int>(int && v)
{
bar(static_cast<float>(v));
}
#endif
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void foo<float>(float && v)
{
bar(static_cast<int>(v));
}
#endif
Просто прекрасно. Какого черта компилятор кастит переменные к противоположным типам?
Первое, что важно понимать: внутри функции foo переменная v - это уже lvalue, так как имеет имя. Значит просто так вызвать перегрузки для правых ссылок он не может.
Но у компилятора в кармане есть стандартные преобразования, которые и идут в ход, когда нет подходящих перегрузок. Обычно это неявные преобразования из одного типа в другой. Не преобразования из одного типа ссылочности в другой тип ссылочности, а прям в другие типы данных.
То есть происходит следующее: компилятор понимает, что подходящей перегрузки нет, поэтому начинает применять стандартные преобразования в другие типы. Любой каст дает временный объект. А временный объект типа int легко биндится к float&&, как и временный объект float легко биндится к int&&.
Вот и получается обмен вызовами.
Чтобы такого не происходило, применяйте перед сном std::forward. Если есть контекст вывода типов, то он помогает правильно передавать категорию выражения объекта во внутренние вызовы.
#include <iostream>
void bar(float&& x) { std::cout << "float " << x << "\n"; }
void bar(int&& x) { std::cout << "int " << x << "\n"; }
void foo(auto&& v) { bar(std::forward<decltype(v)>(v)); }
int main() {
foo(1);
foo(2.0f);
}
В этом случае вывод будет ожидаемым.
Be amazed. Stay cool.
#cppcore #cpp11 #template
🔥47🤯30❤8👍7❤🔥2