Почему затухание указателя имеет приоритет над выведенным шаблоном?

Допустим, я пишу функцию для печати длины строки:

template <size_t N>
void foo(const char (&s)[N]) {
    std::cout << "array, size=" << N-1 << std::endl;
}

foo("hello") // prints array, size=5

Теперь я хочу расширить foo для поддержки не- массивов:

void foo(const char* s) {
    std::cout << "raw, size=" << strlen(s) << std::endl;
}

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

foo("hello") // now prints raw, size=5

Зачем? Разве это не потребует преобразования массива в указатель, тогда как шаблон будет точно соответствовать? Есть ли способ убедиться, что моя функция массива вызывается?

1 ответ

Решение

Основная причина этой (соответствующей стандарту) неоднозначности заключается в стоимости преобразования: разрешение перегрузки пытается минимизировать операции, выполняемые для преобразования аргумента в соответствующий параметр. Массив фактически является указателем на его первый элемент, хотя и украшен некоторой информацией о типе во время компиляции. Преобразование массива в указатель не стоит больше, чем, например, сохранение адреса самого массива или инициализация ссылки на него. С этой точки зрения двусмысленность кажется оправданной, хотя концептуально она неинтуитивна (и может быть некачественной). Фактически, эта аргументация применима ко всем преобразованиям Lvalue, как показано в приведенной ниже цитате. Другой пример:

void g() {}

void f(void(*)()) {}
void f(void(&)()) {}

int main() {
    f(g); // Ambiguous
}

Следующее является обязательным стандартом. Функции, которые не являются специализациями какого-либо шаблона функции, предпочтительнее тех, которые в противном случае одинаково хорошо совпадают (см. [Over.match.best]/(1.3), (1.6)). В нашем случае выполняемое преобразование представляет собой преобразование массива в указатель, которое является преобразованием Lvalue с рангом точного соответствия (согласно таблице 12 в [over.ics.user]). [Over.ics.rank]/3:

  • Стандартная последовательность преобразования S1 является лучшей последовательностью преобразования, чем стандартная последовательность преобразования S2 если

    • S1 это правильная последовательность S2 (сравнение последовательностей преобразования в канонической форме, определенной в 13.3.3.1.1, исключая любое преобразование Lvalue; последовательность преобразования идентификаторов считается подпоследовательностью любой последовательности преобразования, не связанной с идентичностью), или, если не так,

    • звание S1 лучше, чем звание S2, или же S1 а также S2 имеют одинаковый ранг и различаются по правилам в параграфе ниже, или, если не так,

    • [..]

Первая точка маркера исключает наше преобразование (так как это преобразование Lvalue). Второй требует разницы в рангах, чего нет, так как оба преобразования имеют точный ранг соответствия; "Правила в параграфе ниже", то есть в [over.ics.rank]/4, также не охватывают преобразования массива в указатель.
Так что, хотите верьте, хотите нет, ни одна из последовательностей преобразования не лучше, чем другая, и, следовательно, char const*перегрузка выбрана.


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

template <typename T>
auto foo(T s)
  -> std::enable_if_t<std::is_convertible<T, char const*>{}>
{
    std::cout << "raw, size=" << std::strlen(s) << std::endl;
}

Демо

Другие вопросы по тегам