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

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

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

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

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

4b9b3361

Ответ 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* -overload.


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

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;
}

Демо.