Почему компилятор не запрашивает двойное определение при использовании идиомы обнаружения?

Давайте возьмем что-то конкретное:

#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);

}

Wandbox.

В приведенном выше коде почему первое и последнее утверждения не вызывают двойное определение is_lt_comparable?

void_t с какими-либо аргументами еще void, Таким образом, последний безымянный параметр для шаблона всегда void, Псевдонимы типа IIRC не считаются отдельными типами, поэтому моя интуиция заставляет меня поверить, что я что-то упускаю.

конкретно, при условии выбора, что оба объявления являются действительными и приводят к одному и тому же типу, например, в первом is_lt_comparable<int, void>Как он узнает, какой шаблон для создания экземпляра?

1

Решение

Ты пишешь: is_lt_comparable<int>, Это то, что происходит.

  1. Основной шаблон выбран, а второй аргумент шаблона является логическим выводом, потому что он используется по умолчанию. Итак, вы на самом деле is_lt_comparable<int, void>,

  2. Теперь рассматриваются специализации шаблона, чтобы увидеть, есть ли совпадение.

  3. Он находит первую (и единственную) специализацию, и, поскольку это частичная, а не полная специализация, ему, в принципе, необходимо также создать ее экземпляр. Итак, вы получите:

    is_lt_comparable<int, void_t<decltype(std::declval<int>() < std::declval<int>())>>
    

    Теперь, если < выражение плохо сформировано, тогда специализация не рассматривается, и компилятор возвращается к основному шаблону.

  4. Но если оно действительно, то частичная специализация становится: is_lt_comparable<int, void>, И это точное совпадение с шаблоном, который мы создали в 1), поэтому компилятор выбирает его. Формально это известно как правила частичного упорядочения.

Если вы все еще в замешательстве, подумайте об этом:

template<typename> void foo() {}
template<> void foo<int>() {}

Если я сделаю foo<int>()также не будет двойной ошибки определения, как вы и сказали. Специализация лучше соответствует основной, поэтому компилятор даже не создает первичный шаблон с T = int (это не может).

4

Другие решения

static_assert это декларация и не определяет никаких сущностей. Увидеть cpprefrence статья об определениях и ODR. В вашем коде есть только экземпляры шаблонов, но нет определений.

1

В приведенном выше коде, почему первый и последний утверждения не вызывают двойное определение 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> это еще один особый тип.

0
По вопросам рекламы [email protected]