#c #c 11 #c 14 #c 17
#c #c 11 #c 14 #c 17
Вопрос:
Следующий код компилируется с MSVC, GCC и Clang, если я выполняю полный, квалифицированный вызов std::tuple_cat
. Но он не компилируется ни на одном из этих компиляторов, если я выполняю неквалифицированный вызов tuple_cat
… несмотря на то, что я делаю using namespace std;
!
Если я вызываю неквалифицированную функцию, все три компилятора находят правильную функцию, но жалуются на недопустимое создание экземпляра std::tuple<void>
.
Почему это имеет значение? Разве это не должно иметь никакого значения?
#include <tuple>
auto Test() {
using A = std::tuple<void>;
using B = std::tuple<void>;
using namespace std;
using AB = decltype(
#ifdef QUALIFIED
std::
#endif
tuple_cat(std::declval<A>(), std::declval<B>())
);
AB* ptr = nullptr;
return ptr;
}
Смотрите демонстрацию.
Комментарии:
1. Реальный вопрос в том, почему он компилируется при полной квалификации. Наличие кортежа, содержащего a
void
, крайне нелогично, как сказал бы мистер Спок. Не говоря уже о том, что tuple_cat использует пару пустых кортежей. Измените оба A и B на astd::tuple<int>
, и все будет в порядке.2. Это правда, что не может быть никакого экземпляра std::tuple<void> , но в метапрограммировании шаблонов он часто используется только для упаковки параметров шаблона.
Ответ №1:
Создание экземпляра tuple<void>
неправильно сформировано, но просто присвоение ему имени — нет. Разница здесь сводится к тому, что в одном случае требуется полное создание экземпляра, а в другом просто нужно посмотреть аргументы шаблона.
Когда вы выполняете полный вызов std::tuple_cat
, поиск по имени просто находит что-то с именем tuple_cat
в пространстве std
имен . Это будет некоторый шаблон функции, который принимает кучу tuple
s и определяет, как объединить их аргументы. На удивление, никакая часть определения возвращаемого типа этого шаблона функции на самом деле не требует создания экземпляра где-либо.
Но когда вы выполняете неквалифицированный вызов 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
for R
. Если бы мы выполнили квалифицированный вызов foo
, мы бы получили int
.