У меня есть класс, который принимает размер в качестве параметра шаблона (живое демо):
template <std::size_t SIZE> class A
{
char b[SIZE];
}
Он имеет несколько конструкторов для разных целей:
using const_buffer_t = const char (&)[SIZE];
using my_type = A<SIZE>;
A() : b{} {} // (1) no params
A(const_buffer_t) : b{} {} // (2) copy contents of given buffer
A(const char * const) : b{} {} // (3) copy as many items as they fit into the size
explicit A(const my_type &) : b{} {} // (4) copy constructor
// (5) copy as many items as they fit into the size
template <std::size_t OTHER_SIZE>
A(const char (&)[OTHER_SIZE]) : b{} {}
// (6) copy constructor from another sized A
// copy as many items as they fit into the size
template <std::size_t OTHER_SIZE>
explicit A(const A<OTHER_SIZE> &) : b{} {}
С этим набором конструкторов нет проблем с этими инструкциями:
// CASE 1
// Calls constructor 3: A<5>(const char * const)
// Expecting constructor 5: A<5>(const char (&)[11])
A<5> a("0123456789");
// CASE 2
// As expected, calls constructor 1: A<5>()
A<5> b();
// CASE 3
// As expected, calls constructor 4: A<5>(const A<5> &)
A<5> c(b);
// CASE 4
// As expected, calls constructor 6: A<5>(const A<9> &)
A<9> c(b);
Но при звонке A<5>("five")
между конструкторами 2, 3, 4 и 5 существует неоднозначный вызов.
Итак, мои вопросы:
CASE 1
?A<SIZE>
построен со статическим массивом того же размера параметра шаблона?Спасибо за внимание.
Преобразование массива в указатель считается точным совпадением при ранжировании последовательностей преобразования во время разрешения перегрузки (C ++ 11 13.3.3.1.1 / 1 Таблица 12). Вопреки вашей интуиции, это означает, что (3) и (5) одинаково хорошо подходят для A<5> a("0123456789");
, Галстук разорван — как Xeo говорит в своем комментарии — в пользу не шаблона (3). Вы можете подумать об обмануть компилятор, превратив (3) в шаблон:
template <typename=void>
A(const char * const) : b{} {}
но так будет Единственный результат в неоднозначности конструкции. Там действительно нет простого способа устранить неоднозначность const char (&)[]
а также const char*
перегрузки: лучшим решением может быть изменение (3) для принятия указателя и длины:
A(const char * const, std::size_t) : b{} {
std::cout << "size: " << SIZE << " ctor 3\n";
}
Просто мимоходом отмечу, что добавление size_t
аргумент const char* const
конструктор также устраняет неоднозначность A("five")
дело.
РЕДАКТИРОВАТЬ: есть, однако, один разумный способ устранить неоднозначность char*
конструктор из массива конструктор, принимать аргументы указателя по ссылке:
template <typename T,
typename=typename std::enable_if<
std::is_same<typename std::remove_cv<T>::type, char>{}
>::type>
A(T* const&) : b{} { std::cout << "size: " << SIZE << " ctor 3\n"; }
[Кредит на этот конкретный трюк идет к DYP, и, возможно, Йоханнес Шоб или Якк или я (я уверен, что это был не я).]
Этот шаблон эффективно привязывается к фактическому типу по ссылке — до того, как может произойти преобразование массива в указатель — и затем ограничивает ссылки на типы без указателя.
Почему конструктор 3 предпочтительнее конструктора 5 в случае 1?
Ответ:
Из-за разрешение перегрузки. Функции не шаблонного класса являются гражданами первого класса и, как таковые, имеют более высокий рейтинг разрешения перегрузки, чем шаблонные функции. Таким образом, конструктор 3 предпочтительнее шаблонного конструктора 5.