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

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

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

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

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

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

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

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

Основная причина этой (стандартно-согласованной) двусмысленности, по-видимому, лежит в стоимости преобразования: разрешение перегрузки пытается минимизировать операции, выполняемые для преобразования аргумента в соответствующий параметр. Массив фактически является указателем на его первый элемент, хотя и украшен некоторой информацией типа времени компиляции. Преобразование от массива к указателю не стоит больше, чем, например, сохранение адреса самого массива или инициализация ссылки на него. С этой точки зрения двусмысленность кажется оправданной, хотя концептуально она неинтуитивная (и может быть подпара). Фактически, эта аргументация применяется ко всем преобразованиям 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  auto foo(T s) -> std::enable_if_t{}> { std::cout << "raw, size=" << std::strlen(s) << std::endl; } 

Демо .

Давайте будем гением компьютера.