Подтвердить что ты не робот

Почему пакет параметров шаблона используется в аргументе типа функции, поскольку его список аргументов шаблона не может быть явно указан

У меня есть следующий фрагмент кода:

template <typename, typename>
struct AAA{};

template<typename ...Args>
void f(AAA<Args...> *) {}

int main() {
    f<int, int>(nullptr);
}

Этот код приводит к ошибке компиляции. При компиляции с использованием g++ -std=c++1z ошибка показывает следующее:

prog.cc: In function 'int main()':
prog.cc:8:24: error: no matching function for call to 'f<int, int>(std::nullptr_t)'
     f<int, int>(nullptr);
                        ^
prog.cc:5:6: note: candidate: template<class ... Args> void f(AAA<Args ...>*)
 void f(AAA<Args...> *) {}
      ^
prog.cc:5:6: note:   template argument deduction/substitution failed:
prog.cc:8:24: note:   mismatched types 'AAA<Args ...>*' and 'std::nullptr_t'
     f<int, int>(nullptr);

Используя clang++ -std=c++1z, ошибка:

prog.cc:8:5: error: no matching function for call to 'f'
    f<int, int>(nullptr);
    ^~~~~~~~~~~
prog.cc:5:6: note: candidate template ignored: could not match 'AAA<int, int, Args...> *' against 'nullptr_t'
void f(AAA<Args...> *) {}
     ^
1 error generated.

Я запускаю эти выше в среде MSYS2 MinGW-w64. Моя версия GCC - GCC 7.1.0, а моя версия Clang 4.0.0; стандартная библиотека, которую я использую как в GCC, так и в Clang, - это libstdС++, связанный с моим компилятором GCC.

На мой взгляд, вызов шаблона функции foo имеет явно заданный параметр шаблона, поэтому пакет параметров шаблона и тип аргумента функции уже должны быть указаны. Однако приведенная выше диагностика ошибок показывает, что точный тип параметра функции и аргумент nullptr не совпадают, что, по-видимому, является проблемой, которая возможна только при выводе аргумента функции. Поэтому мой вопрос: почему возникает такая ошибка? Является ли это просто ошибкой компилятора или стандартом С++ есть некоторые правила, которые указывают, что исходный код просто плохо сформирован?

4b9b3361

Ответ 1

Вы можете подумать, что компилятор должен вывести пакет как int ,int, но стандарт С++ явно требует поведения, которое вы наблюдали.

[temp.arg.explicit/9]

Вывод аргумента шаблона может расширить последовательность шаблонов аргументы, соответствующие пакету параметров шаблона, даже если последовательность содержит явно заданные аргументы шаблона. [Пример:

template<class ... Types> void f(Types ... values);
void g() {
  f<int*, float*>(0, 0, 0);     // Types is deduced to the sequence int*, float*, int
}

- конец примера]

Вышеизложенное означает, что, хотя некоторые из параметров были указаны, вычет не заканчивается. Пакет параметров всегда должен расширяться путем дедукции аргумента. Это как если бы явные аргументы, которые были даны, являются экземпляром шаблона с завершающим пакетом параметров. В сочетании со следующим:

[temp.arg.explicit/3]

Трейлинг аргументов шаблона, которые можно вывести или получить из аргументы шаблона по умолчанию могут быть опущены из списка явных Шаблон-аргументы. Пакет параметров шаблона для трейлинга выведенный будет выведен на пустую последовательность аргументов шаблона....

Компилятор должен сопоставлять неиспользуемые аргументы с пустым пакетом. Но из этого ничего не вывести.

Таким образом, ваша попытка подключить Args... в AAA не может совпадать. Поскольку последовательность типов представляет собой два типа с конечным списком (который компилятор не может вывести как пустой из nullptr). Пока AAA ожидает только два типа.

Ответ 2

Если вы используете typename ...Args, компилятор не знает, есть ли int, int все используемые параметры шаблона или более доступны, выведя аргумент функции. Поэтому функция еще не создана и компилятор продолжает пытаться вывести все остальные возможные параметры пакета параметров из аргументов функции.

В других терминах это работает:

f<int>(new AAA<int, int>);

Потому что вы говорите, что первый параметр int, но компилятор ожидает список параметров и продолжает пытаться найти все больше и больше параметров из аргумента функции, затем он создает экземпляр шаблона функции.

Более или менее то же самое происходит в вашем случае, но компилятор не может вывести ничего из nullptr_t, поскольку аргументы функции не совпадают. Он ожидает указатель на A<...>, это не тот случай, когда вы проходите в nullptr.
Это будет работать вместо:

template <typename, typename>
struct AAA{};

template<typename A, typename B>
void f(AAA<A, B> *) {}

int main() {
    f<int, int>(nullptr);
}

Поскольку компилятор знает, что аргументы шаблона равны двум, и вы предоставляете их все, нечего выводить, и функция может быть создана. Это также имеет большее значение, поскольку AAA принимает только два параметра шаблона, поэтому пакет параметров для f кажется бесполезным здесь.

Ответ 3

Просто добавьте простое решение:

f<int, int>(nullptr); // doesn't work for the reasons explained by other answers
(*f<int, int>)(nullptr); // OK - does what you want

Последнее заставляет пакет Args... быть {int, int}, и теперь сам вызов не является вызовом шаблона функции - это просто вызов указателя функции. Мы вызываем функцию, которая принимает AAA<int, int>*, и, конечно же, передача в nullptr приемлема там.

Для удовольствия вы также можете добавить произвольно много * s:

(*****f<int, int>)(nullptr); // still OK - does what you want

... но, вы знаете... не делайте этого.

Ответ 4

Я хочу добавить другое решение, которое вызывает понятие {}

template <typename, typename>
struct AAA{};

template<typename ...Args>
void f(AAA<Args...> *) {}

int main() {
    f<int, int>({});
}

Когда аргумент {}, дедукция для параметра отключается (неопределенный контекст), поэтому не будет никакого рассогласования, и инициализация параметров также вызывает нулевой указатель.

Ответ 5

Отличный ответ от @skypjack.

Вам нужно помочь компилятору в выводе аргумента функции:

AAA<int, int> *a = nullptr;
f<int, int>(a); //works

f<int, int>( (AAA<int, int> *)nullptr );  //even this will work.

В принципе, nullptr представляет собой "no object", который может быть присвоен типу any.