Во-первых, я предполагаю, что вы вставили неправильный основной файл, и правильный:
int main () {
foo (A(), A(), A());
}
Надеюсь, я угадала, в остальном этот пост недействителен:)
Во-вторых, я не очень хорош со стандартной терминологией, поэтому надеюсь, что следующее не слишком неточно.
Я часто чувствую, что самый простой способ понять, что происходит с шаблоном с переменными значениями, это просто создать себе то, что компилятор будет эффективно делать.
Например, ваша первая попытка
void foo (const A &, Args ... args)
будет фактически распакован компилятором в нечто вроде этих 3 функций:
void foo3 (const A & a, A a1, A a2)
void foo2 (const A & a, A a0)
void foo1 (const A & a)
При вызове foo3(A(), A(), A());
в основном, компилятор выполнит оптимизацию и просто по умолчанию конструирует a1 и a2 вместо default-construct и copy. Однако при вызове foo2 внутри foo3 компилятор больше не может оптимизировать, поэтому при вызове foo2(a1, a2
) параметр a0 внутри foo2 должен быть сконструирован с использованием копирования. И это копия-конструкция a0, которую мы можем видеть в следах («копия»)
Кстати, я не понимаю, почему вы передаете первый элемент по const ref, а остальные по значению. Почему бы не передать все по const-ref?
void foo (const A &, const Args& ... args)
Хорошо, теперь о вашей второй попытке с помощью rvalue-ref:
void foo (const A &, Args && ... args)
При вызове foo3(A(), A(), A());
в основном A()
является значением, поэтому применяется это правило:
Когда foo вызывается для rvalue
тип A, затем T разрешается в A, и
следовательно, тип аргумента становится A &&.
Итак, foo3 выглядит так:
void foo3 (const A &, A && a1, A && a2) {
std :: cout << type_name<X<A, A>>() << "\n";
foo2 (a1, a2);
}
Но будьте осторожны. a1 и a2 больше не являются значениями. У них есть имя, следовательно, они lvalue.
Так что теперь внутри foo2 действует это правило:
Когда foo вызывается для lvalue
тип A, затем T преобразуется в A & и
следовательно, по ссылке рушится
правила выше, тип аргумента
эффективно становится A &.
Таким образом, тип a0 внутри foo2 будет по сути A &, поэтому мы можем видеть X<A&>
в следах.
Чтобы использовать идеальную пересылку, я думаю, вам нужно отказаться от этого странного предположения:
Это вызывает проблему, потому что я не могу
используйте std :: forward в рекурсии.
Я не понимаю, почему. Следующий код должен быть правильным:
void foo(A&&, Args&&... args)
{
std::cout << type-name<X<Args...>> () << "\n";
foo(std::forward<Args>(args...));
}
При вызове foo () для рекурсии std :: forward будет снова переводить все аргументы ... из lvalue в rvalue, поэтому вывод будет правильным.