Давайте возьмем что-то конкретное:
#include <utility>
#include <vector>
template <typename ... Ts>
using void_t = void;
template <typename T, typename = void_t<>>
struct is_lt_comparable : std::false_type {};
template <typename T>
struct is_lt_comparable<T, void_t<decltype(std::declval<T>() < std::declval<T>())>> : std::true_type {};
template <typename T>
static constexpr bool is_lt_comparable_v = is_lt_comparable<T>::value;
struct test{};
int main()
{
#define error_message "detection doesn't work correctly"static_assert(is_lt_comparable_v<int>, error_message);
static_assert(!is_lt_comparable_v<test>, error_message);
static_assert(is_lt_comparable_v<std::vector<int>>, error_message);
}
В приведенном выше коде почему первое и последнее утверждения не вызывают двойное определение is_lt_comparable
?
void_t
с какими-либо аргументами еще void
, Таким образом, последний безымянный параметр для шаблона всегда void
, Псевдонимы типа IIRC не считаются отдельными типами, поэтому моя интуиция заставляет меня поверить, что я что-то упускаю.
конкретно, при условии выбора, что оба объявления являются действительными и приводят к одному и тому же типу, например, в первом is_lt_comparable<int, void>
Как он узнает, какой шаблон для создания экземпляра?
Ты пишешь: is_lt_comparable<int>
, Это то, что происходит.
Основной шаблон выбран, а второй аргумент шаблона является логическим выводом, потому что он используется по умолчанию. Итак, вы на самом деле is_lt_comparable<int, void>
,
Теперь рассматриваются специализации шаблона, чтобы увидеть, есть ли совпадение.
Он находит первую (и единственную) специализацию, и, поскольку это частичная, а не полная специализация, ему, в принципе, необходимо также создать ее экземпляр. Итак, вы получите:
is_lt_comparable<int, void_t<decltype(std::declval<int>() < std::declval<int>())>>
Теперь, если <
выражение плохо сформировано, тогда специализация не рассматривается, и компилятор возвращается к основному шаблону.
Но если оно действительно, то частичная специализация становится: is_lt_comparable<int, void>
, И это точное совпадение с шаблоном, который мы создали в 1), поэтому компилятор выбирает его. Формально это известно как правила частичного упорядочения.
Если вы все еще в замешательстве, подумайте об этом:
template<typename> void foo() {}
template<> void foo<int>() {}
Если я сделаю foo<int>()
также не будет двойной ошибки определения, как вы и сказали. Специализация лучше соответствует основной, поэтому компилятор даже не создает первичный шаблон с T = int
(это не может).
static_assert
это декларация и не определяет никаких сущностей. Увидеть cpprefrence статья об определениях и ODR. В вашем коде есть только экземпляры шаблонов, но нет определений.
В приведенном выше коде, почему первый и последний утверждения не вызывают двойное определение is_lt_comparable?
is_lt_comparable
это не тип. Это название шаблона.
is_lt_comparable_v<int>
приводит к расширению is_lt_comparable<int, void>
, который является типом.
is_lt_comparable_v<test>
приводит к расширению is_lt_comparable<test, void>
, который является другим типом.
И вновь, is_lt_comparable<std::vector<int>, void>
это еще один особый тип.