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

Специализация частичного шаблона, запускающая static_asserts

Рассмотрим этот код

template <typename T>
struct delay : std::false_type{};

template <typename T>
struct my_typelist {
    static_assert(delay<T>{}, "");
};

template <typename Tuple>
struct test;

template <typename T>
struct test<my_typelist<T>> {
    void pass(){}
};

template <typename T>
void fail(const test<T> &){}

int main()
{
    test<my_typelist<int>> t;
    t.pass();
    fail(t);
}

Без вызова fail() код компилируется и работает нормально. Однако использование t в любой функции, по-видимому, вызывает static_assert в классе my_typelist, хотя класс никогда не создается. Хотя этот пример надуман, я столкнулся с той же проблемой, используя неполные типы внутри std::tuple, хотя я просто использовал std::tuple как список типов и никогда не создавал его.

Почему триггер static_assert срабатывает только после использования переменной в качестве параметра, а не при вызове функции-члена? В каких контекстах создается my_typelist, а когда нет?

Обратите внимание, что я использовал бы вариационный шаблон, но ошибка возникает независимо от этого, поэтому я решил использовать самый низкий общий знаменатель.

4b9b3361

Ответ 1

В случае fail(t), если my_typelist<int> объявляет функцию друга с именем fail в определении класса, эта функция будет найдена зависимым от аргумента поиска (потому что my_typelist<int> является ассоциированным классом test<my_typelist<int>>). В некоторых случаях этот друг вместо глобальной функции fail может быть выбран с помощью разрешения перегрузки (demo). Поэтому определение my_typelist<int> должно быть инстанцировано и проверено, чтобы увидеть, произойдет ли это. Добавление круглых скобок вокруг fail будет подавлять зависящий от аргументов поиск и исключить необходимость создания экземпляра my_typelist<int>, и в этом случае static_assert не запускается (demo).

В случае t.pass() my_typelist<int> не создается, потому что известно, что t.pass() всегда вызывает функцию-член от test<my_typelist<int>>, и на него не будет влиять полнота my_typelist<int>,

Ответ 2

Итак, давайте пойдем следующим образом:

Сначала измените static_assert на рабочий код:

template <typename T>
struct my_typelist {
    static_assert(delay<T>{}, "");
};

:

template <typename T>
struct my_typelist {
    static_assert(false, "");
};

Он отправится огонь немедленно. Обратите внимание, что выражение false не зависит от какого-либо типа.


Теперь измените его на тип delay<T>, который не зависит от какого-либо параметра шаблона, скажем, char, int и т.д.:

template <typename T>
struct my_typelist {
    static_assert(delay<int>{}, "");
};

По-прежнему будет немедленно выстрелить.


Итак, что здесь происходит?

шаблоны классов неявно не создаются, даже если они используются, если только они не используются в контекстах, для которых требуются полностью определенные типы.

temp.inst/1

Если специализация шаблона класса не была явно экземпляр ([temp.explicit]) или явно специализированный ([temp.expl.spec]), специализация шаблона класса неявно создается, когда специализация ссылается в контексте, который требует полностью определенного типа объекта или когда полнота тип класса влияет на семантику программы.

См. @cpplearner answer, чтобы понять, почему вызов функции fail(...) создает экземпляр my_typelist<int>. В основном удары ADL, в которых действуют такие экземпляры, можно подавить с использованием квалифицированного имени ::foo или скобки.

Для полноты: в одном из правил среди других для ADL (основное внимание):

basic.lookup.argdep/2: для каждого типа аргумента T в вызове функции существует набор из нуля или более связанных пространства имен и набор из 0 или более ассоциированных классов считается.

  • ....

  • basic.lookup.argdep/2.2: Если T - тип класса (включая объединения), его ассоциированные классы: сам класс; класс которого он является членом, если таковой имеется; и его прямые и косвенные базовые классы. Его связанные пространства имен являются внутренними охватывающими пространствами имен связанных классов. Кроме того, , если T является специализацией шаблона класса, его связанные пространства имен и классы также включают в себя: пространства имен и классы, связанные с типами аргументов шаблона, предоставленными для параметров типа шаблона (исключая параметры шаблона шаблона); пространства имен, членами которых являются аргументы шаблона шаблона; и классы, в которых любые члены-шаблоны, используемые в качестве аргументов шаблона шаблона, являются членами. [Примечание. Аргументы шаблона непигового типа не вносят вклад в набор связанных пространств имен. - конец примечания]

  • ....