Разрешение перегрузки шаблона функции с аргументом указателя

Следующий код демонстрирует ядро ​​шаблона метапрограммирования шаблона C ++, который я использовал, чтобы определить, является ли тип T это экземпляр конкретного шаблона класса:

#include <iostream>

template<class A, class B>
struct S{};

template<class A, class B>
constexpr bool isS(const S<A,B>*) {return true;}

template<class T>
constexpr bool isS(const T*) {return false;}

int main() {
S<int,char> s;
std::cout<<isS(&s)<<std::endl;
return 0;
}

Он имеет две перегрузки constexpr шаблон функции isSи выводит 1, как и ожидалось. Если я уберу указатель со второго isSт.е. заменить его на

template<class T>
constexpr bool isS(const T) {return false;}

программа неожиданно выводит 0, Если обе версии isS перейдите к фазе компиляции с разрешением перегрузки, тогда вывод означает, что компилятор выбирает вторую перегрузку. Я проверил это под GCC, Clang и vc ++, используя онлайн-компиляторы Вот, и все они дают одинаковый результат. Почему это происходит?

Я прочитал Херб Саттер «Почему бы не специализировать шаблоны функций» статья несколько раз, и кажется, что оба isS функции следует рассматривать как базовые шаблоны. Если это так, то вопрос в том, какой из них является наиболее специализированным. По интуиции и этот ответ, Я бы ожидал первого isS быть наиболее специализированным, потому что T может соответствовать каждому экземпляру S<A,B>*и есть много возможных примеров T это не может соответствовать S<A,B>*, Я хотел бы найти абзац в рабочем проекте, который определяет это поведение, но я не совсем уверен, какая фаза компиляции вызывает проблему. Это как-то связано с «14.8.2.4 Вывод аргументов шаблона при частичном упорядочении»?

Эта проблема особенно удивительна, учитывая, что следующий код, в котором первый isS принимает ссылку на const S<A,B> а второй занимает const T, выводит ожидаемое значение 1:

#include <iostream>

template<class A, class B>
struct S{};

template<class A, class B>
constexpr bool isS(const S<A,B>&) {return true;}

template<class T>
constexpr bool isS(const T) {return false;}

int main() {
S<int,char> s;
std::cout<<isS(s)<<std::endl;
return 0;
}

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

5

Решение

Потому что вторая перегрузка опустит верхний уровень const внутри const T, он разрешит T* во время вывода аргумента. Первая перегрузка является худшим соответствием, потому что она разрешит S<int, char> const*, который требует преобразования const-квалификации.

Вам нужно добавить const перед вашей переменной s для того, чтобы включить более специализированную перегрузку:

#include <iostream>

template<class A, class B>
struct S {};

template<class A, class B>
constexpr bool isS(const S<A,B>*) {return true;}

//template<class T>
//constexpr bool isS(const T*) {return false;}

template<class T>
constexpr bool isS(const T) {return false;}

int main() {
S<int,char> const s{}; // add const here
std::cout<<isS(&s)<<std::endl;
return 0;
}

Живой пример

Изменение первой перегрузки на const S<A,B>&, даст правильный результат, потому что вместо корректировки квалификации происходит преобразование личности.

13.3.3.1.4 Ссылочная привязка [over.ics.ref]

1 Когда параметр ссылочного типа привязывается напрямую (8.5.3) к выражению аргумента,
Последовательность неявного преобразования является преобразованием идентичности, если только
Выражение аргумента имеет тип, являющийся производным классом
тип параметра, в этом случае последовательность неявного преобразования представляет собой
преобразование из базы в производную (13.3.3.1).

Заметка: если вы сомневаетесь в таких играх для вывода аргументов, удобно использовать __PRETTY_FUNCTION__ макрос, который (в gcc / clang) даст вам больше информации о выведенных типах выбранного шаблона. Затем вы можете закомментировать определенные перегрузки, чтобы увидеть, как это влияет на разрешение перегрузки. Видеть это живой пример.

6

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

Ваша вторая версия не дает ожидаемого ответа, потому что первая версия isS требует неявного преобразования, в то время как второй нет.

template<class A, class B>
constexpr bool isS(const S<A,B>*);

template<class T>
constexpr bool isS(const T);

S<int,char> s;
isS(&s);

Обратите внимание, что &s имеет тип S<int,char>*, Первый isS ищет const S<int,char>*, поэтому указатель нуждается в преобразовании. Второй isS это прямое совпадение.


Если вам часто нужен этот шаблон, вы можете обобщить его следующим образом:

template<template<typename...> class TT, typename T>
struct is_specialization_of : std::false_type {};

template<template<typename...> class TT, typename... Ts>
struct is_specialization_of<TT, TT<Ts...>> : std::true_type {};

Затем вы проверяете, является ли тип специализацией S как это:

is_specialization_of<S, decltype(s)>::value
3