Как избавиться от неоднозначности этого шаблона?

У меня есть класс, который принимает размер в качестве параметра шаблона (живое демо):

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 существует неоднозначный вызов.

Итак, мои вопросы:

  • Почему конструктор 3 предпочтительнее конструктора 5 в CASE 1?
  • Есть ли способ устранения неоднозначности конструкторов 2, 3, 4, 5, когда объект A<SIZE> построен со статическим массивом того же размера параметра шаблона?

Спасибо за внимание.

4

Решение

Преобразование массива в указатель считается точным совпадением при ранжировании последовательностей преобразования во время разрешения перегрузки (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

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

  • Почему конструктор 3 предпочтительнее конструктора 5 в случае 1?

    Ответ:
    Из-за разрешение перегрузки. Функции не шаблонного класса являются гражданами первого класса и, как таковые, имеют более высокий рейтинг разрешения перегрузки, чем шаблонные функции. Таким образом, конструктор 3 предпочтительнее шаблонного конструктора 5.

2

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