Что выведется на консоль и почему это вообще компилируется? [C++]

Наткнулся на такой с++ код, мне не понятны две вещи:

  1. Почему это вообще компилируется без std::forward?
  2. Почему вывод в обратном порядке, то есть "if"?

Вот код:

#include <iostream>

void func(float&&) {
    std::cout << "f";
}

void func(int&&) {
    std::cout << "i";
}

template <typename T>
void test(T&& v) {
    func(v);
}

int main() {
    test(1.0f);
    test(2);
}

Мои рассуждения зашли в тупик в таком порядке:

  1. К примеру, мы передали rvalue значение "2" в функцию test, которая принимает универсальную ссылку
  2. Тип T вывелся как int и затем на него нацепилось &&, на данном этапе имеем int&&
  3. Мы передаем в функцию func переменную v ( func(v) ), где v уже имеет категорию значения lvalue (любое обращение к переменной по имени - это lvalue)
  4. Ищем функцию func с подходящей сигнатурой, которая может принять lvalue и не находим
  5. Тупик...

Ответы (1 шт):

Автор решения: Den

Достаточно интересный вопрос, я сначала тоже немного растерялся, но если подумать, то все не так уж и сложно.

Как вы и описали первые пункты они все верны. Но что происходит дальше?

Как вы верно заметили в 3 пункте

/*
Именно при передаче в функцию наше rvalue, превращается -> T&& v,где
значение "value" этого выражения = lvalue, а тип это ссылка на rvalue(т.к.
это универсальная ссылка и мы передали rvalue)  
*/

template <typename T>
void test(T&& v) {
    func(v); 
}

Надеюсь вы прочитали комментарии.. Продолжая мысль, нам не важен тип, а важно что сама v это lvalue, поэтому при вызове func(v) он ищет функцию, которая принимает ссылку на lvalue, но такой нет! По сути в этот момент, если взять классический пример, то он вообще не должен компилироваться:

void func(int&& a) {
    std::cout << "i&& " << a << std::endl;
}

int main(){
    int a = 10;
    func(a);
}

Но это с++, так что нет ничего удивительного, что он такое пропускает, если говорить кратко, это ситуация близка к неопределенному поведению(ub). Добавить просто нечего, лучше не пишите так вообще!

Теперь, что можно сделать, чтобы решить эту проблему:

  1. использовать std::move()
template <typename T>
void test(T&& v) {
    func(std::move(v)); 
}
  1. использовать std::forward<T>()
template <typename T>
void test(T&& v) {
    func(std::forward<T>(v)); 
}

Но в случает использования std::forward<T>(), если мы передадим lvalue у нас опять будет ub, так что предпочтительнее использоваться std::move() 3. Добавить перегрузку для lvalue

void func(float& a) {
    std::cout << "f& " << a << std::endl;
}

void func(int& a) {
    std::cout << "i& " << a << std::endl;
}
→ Ссылка