Инстанцирование tuple<void>
плохо сформировано, но просто называть это не так. Разница здесь сводится к одному случаю, требующему полной реализации, а другому просто нужно взглянуть на аргументы шаблона.
Когда вы делаете полный вызов std::tuple_cat
, поиск имени просто находит что-то с именем tuple_cat
в пространстве имен std
. Это будет некоторый шаблон функции, который займет кучу tuple
с и выяснит, как объединить их аргументы. Как ни удивительно, ни одна часть определения типа возврата этого шаблона функции на самом деле нигде не требует инстанцирования.
Но когда вы делаете неквалифицированный вызов tuple_cat
, у нас есть два разных вида поиска:
Регулярный неквалифицированный поиск - который в конечном итоге делает то же самое, что и выше, поскольку у вас есть using namespace std;
- он найдет std::tuple_cat
и сможет в конечном итоге определить «правильный» ответ (для некоторого определения справа, что позволяет tuple<void>
для начала).
Поиск, зависящий от аргумента. ADL требует, чтобы мы рассмотрели все связанные пространства имен и другие функции, которые исходят из наших аргументов. К ним относятся «скрытые друзья» - friend
функции, которые определены в теле класса. Чтобы узнать, есть ли какие-то скрытые друзья, нам нужно полностью создать экземпляры этих типов - и в этот момент мы сталкиваемся с ошибкой, и все взрывается.
Этот шаг ADL должен произойти - мы не будем знать, что std::tuple_cat
является единственным tuple_cat
, пока мы не выполним этот шаг.
<ч />
Пример того, что скрытый друг:
template <typename T>
int foo(T) { return 42; }
template <typename T>
struct A {
friend bool foo(A) { return true; } // this is a hidden friend
};
using R = decltype(foo(declval<A<int>>()));
Чтобы определить, что такое R
, нам нужно создать экземпляр A<int>
, чтобы увидеть, есть ли у него какие-то скрытые друзья - так и есть, именно так мы получаем bool
для R
. Если бы мы сделали квалифицированный звонок на foo
, мы получили бы int
.