Подтвердить что ты не робот

Шаблон выбирает ссылку const по указателю const

Рассмотрим следующее:

template <class T> void Foo(const T* x) {
  std::cout << "I am the pointer overload" << std::endl;
}

template <class T> void Foo(const T& x) {
  std::cout << "I am the reference overload" << std::endl;
}

Учитывая вышеизложенное, я ожидаю, что следующее вызовет перегрузку указателя:

int* x;
Foo(x);

но это не так. Мне кажется странным, что const T* может четко связываться с неконстантным T так же, как и с const T&, но вариант указателя кажется "лучше подходит".

Для моего приложения я хочу, чтобы был вызывается вариант указателя. Я могу выполнить эту работу с помощью дополнительной специализации:

template <class T> void Foo(T* x) {
  const T* const_x = x;
  Foo(const_x);
}

но это кажется неправильным и ненужным. Есть ли способ лучше? Что я не понимаю (кроме раздела x.y.z стандарта говорит это так)?

4b9b3361

Ответ 1

Вы думаете: если мой параметр не будет постоянным (или если аргумент будет постоянным), то указатель будет идеальным совпадением. Это верно. Разница в том, что у меня есть, это const. Поэтому просто добавив const к вышеприведенному сценарию, я также должен получить перегрузку указателя. Это тоже правильно. Теперь, как это может быть, так как вы явно получаете эталонную перегрузку? Ну, код не соответствует вашей мысли. Вот код, который будет соответствовать вашей линии мышления, и это действительно выбрало бы перегрузку указателя:

template <class T> void Foo(T* const x);    
template <class T> void Foo(T const &x);

Обратите особое внимание на место const. Я добавил const как квалификатор верхнего уровня. Пока T const &x эквивалентен тому, что у вас есть const T& x, T* const x не совпадает с const T* x.

Позволяет увидеть разрешение перегрузки в этом случае:

 fun                                     | T    | parameter    | argument
-----------------------------------------+------+--------------+-----------
 template <class T> void Foo(T* const x) | int  | int* const   | int*
 template <class T> void Foo(T const &x) | int* | int* const & | int*

Позволяет увидеть перегрузку с вашей версией:

 fun                                     | T    | parameter    | argument
-----------------------------------------+------+--------------+-----------
 template <class T> void Foo(const T* x) | int  | const int*   | int*
 template <class T> void Foo(T const &x) | int* | int* const & | int*

Как вы можете видеть в первой версии, просто добавление верхнего уровня const предпочтительнее и выбрана перегрузка указателя. Для преобразования указателя не требуется.

Во втором случае для перегрузки указателя требуется преобразование указателя от pointer to mutable до pointer to const. Это разные типы указателей. Но со второй перегрузкой не требуется преобразования указателя. Просто добавьте верхний уровень const.

Это в лучшем случае лучшее, что я могу объяснить, не переходя к тому, что говорится в разделе стандарта x.y.z

Ответ 2

Ваша проблема заключается в том, что при компиляции Foo(x); будет выполняться вывод типа шаблона, и поскольку ваш x является int*, это будет интерпретироваться сначала как вызов Foo<int*>(x), а ваша перегрузка template <class T> void Foo(const T& x) - идеальное совпадение для этого, таким образом, заканчивается вывод типа.

Если вы будете заниматься классами (вместо функций шаблона), вы можете использовать частичные специализированные шаблоны для различения типов указателей и типов без указателей, но для функций допускаются только полные специализации.

Что вы можете сделать, это использовать SFINAE, например:

template <class T> void Foo(const T* x) {
    std::cout << "I am the pointer overload" << std::endl;
}

template <class T> 
typename std::enable_if<!std::is_pointer<T>::value>::type 
Foo(const T& x) {
    std::cout << "I am the reference overload" << std::endl;
}

В этом случае эталонная перегрузка не будет соответствовать (при вычитании типов), если T является указателем типа aa, поэтому компилятор также попытается Foo<int>(x), и ваша перегрузка template <class T> void Foo(const T* x) будет идеальным сочетанием (только то, что вы ожидали).

Пример:

int x = 12;
int* pX = new int(5);

Foo(x);
Foo(pX);

выход:

I am the reference overload
I am the pointer overload