Создание экземпляра шаблона decltype и declval с помощью std::tuple_cat

#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 на a std::tuple<int> , и все будет в порядке.

2. Это правда, что не может быть никакого экземпляра std::tuple<void> , но в метапрограммировании шаблонов он часто используется только для упаковки параметров шаблона.

Ответ №1:

Создание экземпляра tuple<void> неправильно сформировано, но просто присвоение ему имени — нет. Разница здесь сводится к тому, что в одном случае требуется полное создание экземпляра, а в другом просто нужно посмотреть аргументы шаблона.

Когда вы выполняете полный вызов std::tuple_cat , поиск по имени просто находит что-то с именем tuple_cat в пространстве std имен . Это будет некоторый шаблон функции, который принимает кучу tuple s и определяет, как объединить их аргументы. На удивление, никакая часть определения возвращаемого типа этого шаблона функции на самом деле не требует создания экземпляра где-либо.

Но когда вы выполняете неквалифицированный вызов tuple_cat , у нас есть два разных вида поиска:

  1. Обычный неквалифицированный поиск — который в конечном итоге выполняет то же самое, что и выше, поскольку у вас есть using namespace std; — он найдет std::tuple_cat и сможет в конечном итоге определить «правильный» ответ (для некоторого определения права, с которого можно tuple<void> начать).

  2. Поиск, зависящий от аргумента. 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 .