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


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

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   51   2015-01-30 22:39:06

1 ответ:

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

void g() {}

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

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

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

  • стандартное преобразование последовательности S1 - это лучше, последовательность конвертации, чем стандартное преобразование последовательности S2 Если

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

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

    • [..]

первый маркер исключает наше преобразование (так как это преобразование Lvalue). Второй требует разницы в рангах, которой нет, поскольку оба преобразования имеют точный ранг соответствия; "правила в параграфе ниже", т. е. в [over.микросхема.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;
}

демо.