Следующий код демонстрирует ядро шаблона метапрограммирования шаблона 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;
}
Таким образом, проблема, похоже, связана с тем, как обрабатываются указатели.
Потому что вторая перегрузка опустит верхний уровень 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) даст вам больше информации о выведенных типах выбранного шаблона. Затем вы можете закомментировать определенные перегрузки, чтобы увидеть, как это влияет на разрешение перегрузки. Видеть это живой пример.
Ваша вторая версия не дает ожидаемого ответа, потому что первая версия 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