Является ли корректным вывод этого аргумента вариационного шаблона? - PullRequest
0 голосов
/ 17 июня 2011

Я экспериментировал с шаблонами переменных и переадресацией аргументов.Я думаю, что нашел противоречивое поведение.

Чтобы проиллюстрировать, эта программа:

#include <iostream>
#include <typeinfo>
#include <tuple>
#include <cxxabi.h>

template <typename... Args> struct X {};

struct A {
    A () {}
    A (const A &) {
        std :: cout << "copy\n";
    }
};

template <typename T> const char * type_name () {
    return abi :: __cxa_demangle (typeid (T) .name (), 0, 0, NULL);
}

void foo () {}

template <typename... Args>
void foo (const A &, Args ... args) {
    std :: cout << type_name <X <Args...>> () << "\n“;    foo (args...);
}

int main () {
    foo (A(), A());
}

Выводит следующее:

X<A, A>
copy
X<A>

Несмотря на специализацию шаблонаfoo имеет первый аргумент const reference, аргументы variadic передаются по значению, потому что типы шаблонов выводятся как не-ссылочные типы, как показывает вывод X<A>.

Итак, я консультируюсь Thomas Becker:

template<typename T>
void foo(T&&);

Здесь применимо следующее:

  1. Когда foo вызывается для lvalue типа A, тогда T преобразуется в A &и, следовательно, согласно приведенным выше правилам свертывания ссылок, тип аргумента фактически становится A &.

  2. Когда foo вызывается для r-значения типа A, тогда T преобразуется в A, и, следовательно, аргументтип становится A &&.

И попробуйте это:

template <typename... Args>
void foo (const A &, Args && ... args) {
    std :: cout << type_name<X<Args...>>() << "\n";
    foo (args...);
}

Какие выходы:

X<A, A>
X<A&>

Теперь я в тупике.Здесь есть три вызова foo.В моей голове, main() должно вывести foo<A,A,A>(A&&,A&&,A&&) (потому что A () - безымянное значение r, следовательно, ссылка), которое перегрузит-разрешает до foo<A,A,A>(const A&,A&&,A&&).Это, в свою очередь, выводит foo<A,A>(A&&,A&&) и т. Д.

Вопрос в том, почему X<A,A> не имеет ссылки A s, а X<A&> имеет ссылку A?

Это вызывает проблему, потому что я не могу использовать std::forward в рекурсии.

1 Ответ

2 голосов
/ 17 июня 2011

Во-первых, я предполагаю, что вы вставили неправильный основной файл, и правильный:

 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, поэтому вывод будет правильным.

...