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

Перегрузка функции шаблона в С++

У меня есть следующее определение.

using namespace std;

template <typename T>
void foo(const T &s) {
    cout << 1;
}

template <typename T>
void foo(const T *s) {
    cout << 2;
}

int main(int argc, const char * argv[]) {
    char str[] =  "ss";
    char *s = str;
    foo(s);

    return 0;
}

Затем он выдает

1

По моему мнению, обе версии должны пройти преобразование const. Тогда void foo(const T *s) более специализирован и должен быть вызван. Однако компилятор выбрал void foo(const T& s). Какое объяснение?

4b9b3361

Ответ 1

Некоторые люди указали, что параметры шаблонов, выбранные компилятором

void f(char * const&)
void f(const char *);

Здесь обратите внимание, что компилятор ожидает указателя на char, char* для первой функции и является ссылкой на константу. Это может стать неожиданностью, если вы обнаружите, что для вашего случая он предпочитает первый шаблон, но для следующих двух он предпочтет второй

template <typename T>
void foo(const T& s) {
    cout << 1;
}

template <typename T>
void foo(T &s) {
    cout << 2;
}

Итак, конечно, он будет иногда смотреть на const. Почему это не было в вашем случае? Потому что он будет рассматривать только const для ссылки, если другая функция также имеет ссылку.

В вашем случае от char* до const char* это преобразование указателя, но от lvalue до const lvalue оно фактически не является конверсией. Добавление константы по ссылке const игнорируется разрешением перегрузки, за исключением случаев, когда обе функции имеют ссылочные параметры, как в приведенном выше случае.

Ответ 2

Причина в том, что s является неконстантным указателем, поэтому int * const & на самом деле лучше, чем int const *, потому что ему не нужно добавлять const к типу указателя.

Если s было const, то это было бы точное соответствие для версии T const *.

Ответ 3

Я переместил const без изменения семантики кода, так что вы не удивляетесь, когда позиция const "изменяется позже.

template <typename T>
void foo(T const &s) {
    cout << 1;
}

template <typename T>
void foo(T const *s) {
    cout << 2;
}

char *x = "x";
foo(x);

Перегрузка 1 должна будет выводить T как char*, поэтому тип s будет char * const & (ссылка на константный указатель на non-const char). Такая ссылка может связываться с типом аргумента (char *; указатель на неконстантный char) без какого-либо преобразования.

Перегрузка 2 должна будет выводить T как char, поэтому тип s будет char const * (указатель на const char). Это приводит к преобразованию квалификации из типа аргумента (char *; указатель на не-const char) в тип параметра (char const *; указатель на const char).

Более наглядным примером принципа перегрузки 1 является следующее:

int n = 42;
int const &i = n;

Связывание ссылки const с неконстантной сущностью не связано с преобразованием, потому что это ссылка, которая добавляет квалификацию, а не квалификацию, добавляемую к сущности, чтобы соответствовать ей ссылочному типу.

Ответ 4

обе версии должны пройти преобразование const.

Первый не нуждается в конверсии. Для параметра шаблона const T& с аргументом шаблона char *, T будет выводиться как char*, а затем тип параметра функции будет char* const &, поэтому он идеально подходит. Аргумент функции будет привязан к const (Tconst T&, то есть от char* до char* const&), const - это не преобразование.

Для второго параметра шаблона const T* с аргументом шаблона char *, T будет выведено как char, а затем тип параметра функции будет const char*, а преобразование квалификации требуется для преобразования char* в const char*.

Из комментария

Обе подпрограммы добавляют нижний уровень const к s.

Первый добавляет const в s, а второй - нет. Он добавляет const к тому, что указывает s, а не s. В этом разница.

Ответ 5

Причина в том, что, когда аргумент является указателем, вы должны передать указатель. Например:

// Example program
#include <iostream>
#include <string>

using std::cout;
using std::endl;

void foo(int *a)
{
  cout << *a << endl;
}

int main()
{
  int a = 5;
  foo(&a);
}

Но если ваш аргумент является аргументом refrence, вы можете просто передать параметр, как он выглядит следующим образом:

// Example program
#include <iostream>
#include <string>

using std::cout;
using std::endl;

void foo(int &a)
{
  cout << a << endl;
}

int main()
{
  int a = 5;
  foo(a);
}