[C++] Частичное упорядочение шаблонов функций - неоднозначный вызов


Answers

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

#include <iostream>
#include <type_traits>

template<typename T, std::size_t N>
void is_same( const T* _left, const char(&_right)[N] )
{
 typedef decltype(_left) LeftT;
 typedef decltype(_right) RightT;

 std::cout << std::is_same<LeftT,const char*>::value << std::endl;
 std::cout << std::is_same<LeftT,const char(&)[3]>::value << std::endl;
 std::cout << std::is_same<LeftT,const char(&)[4]>::value << std::endl;
 std::cout << std::is_same<RightT,const char*>::value << std::endl;
 std::cout << std::is_same<RightT,const char(&)[3]>::value << std::endl;
 std::cout << std::is_same<RightT,const char(&)[4]>::value << std::endl;
}

int main()
{
 std::cout << std::boolalpha;

 is_same( "ab", "cd" );

 return 0;
}

Результат дает: true false false false true false

Компилятор способен различать аргументы в этом случае.

Изменить 1: Вот еще код. Введение ссылок rvalue делает функции более различимыми.

#include <iostream>

// f
template<typename _T>
 void f( _T, const char* )
 {
  std::cout << "f( _T, const char* )" << std::endl;
 }

template<std::size_t _kN>
 void f( int, const char(&)[_kN] )
 {
  std::cout << "f( int, const char (&)[_kN] )" << std::endl;
 }

// g
template<typename _T>
 void g( _T, const char* )
 {
  std::cout << "g( _T, const char* )" << std::endl;
 }

template<std::size_t _kN>
 void g( int, const char(&&)[_kN] )
 {
  std::cout << "g( int, const char (&&)[_kN] )" << std::endl;
 }

// h
template<std::size_t _kN>
 void h( int, const char(&)[_kN] )
 {
  std::cout << "h( int, const char(&)[_kN] )" << std::endl;
 }

template<std::size_t _kN>
 void h( int, const char(&&)[_kN] )
 {
  std::cout << "h( int, const char (&&)[_kN] )" << std::endl;
 }

int main()
{
 //f( 7, "ab" ); // Error!
 //f( 7, std::move("ab") ); // Error!
 f( 7, static_cast<const char*>("ab") ); // OK
 //f( 7, static_cast<const char(&)[3]>("ab") ); // Error!
 //f( 7, static_cast<const char(&&)[3]>("ab") ); // Error!

 g( 7, "ab" ); // OK
 //g( 7, std::move("ab") ); // Error!
 g( 7, static_cast<const char*>("ab") ); // OK
 g( 7, static_cast<const char(&)[3]>("ab") ); // OK
 //g( 7, static_cast<const char (&&)[3]>("ab") ); // Error!

 h( 7, "ab" ); // OK (What? Why is this an lvalue?)
 h( 7, std::move("ab") ); // OK
 //h( 7, static_cast<const char*>("ab") ); // Error
 h( 7, static_cast<const char(&)[3]>("ab") ); // OK
 h( 7, static_cast<const char(&&)[3]>("ab") ); // OK

 return 0;
}
Question

Рассмотрим этот кусок кода C ++ 11:

#include <iostream>
#include <cstddef>

template<typename T> void f(T, const char*) //#1
{ 
    std::cout << "f(T, const char*)\n"; 
}

template<std::size_t N> void f(int, const char(&)[N]) //#2
{ 
    std::cout << "f(int, const char (&)[N])\n"; 
}

int main()
{
    f(7, "ab");
}

Хорошо, так ... какая перегрузка выбрана? Перед тем, как пролить бобы с выходом компилятора, давайте попробуем рассуждать об этом.

(Все ссылки на разделы предназначены для окончательного стандартного документа для C ++ 11, ISO / IEC 14882: 2011.)

T из # 1 выводится int , N из # 2 выводится в 3 , обе специализации являются кандидатами, обе являются жизнеспособными, настолько хорошими. Какой из них лучше?

Во-первых, рассматриваются неявные преобразования, необходимые для сопоставления аргументов функции с параметрами функции. Для первого аргумента преобразование не требуется в любом случае ( преобразование идентичности ), int везде, поэтому обе функции одинаково хороши. Для второго типа аргумента является const char[3] , и два преобразования:

  • для # 1 , преобразование матрицы в указатель, преобразование категории lvalue , согласно [13.3.3.1.1] ; эта категория преобразования игнорируется при сравнении последовательностей преобразований в соответствии с [13.3.3.2] , так что это в основном то же самое, что и преобразование идентичности для этой цели;
  • для # 2 параметр имеет ссылочный тип и привязывается непосредственно к аргументу, поэтому, согласно [13.3.3.1.4] , это снова означает преобразование идентичности .

Опять же, не повезло: эти две функции по-прежнему одинаково хороши. Оба являются шаблонами специализации, теперь мы должны увидеть, какой шаблон функции, если таковой имеется, является более специализированным ( [14.5.6.2] и [14.8.2.4] ).

EDIT 3: приведенное ниже описание близко, но не совсем точно. См. Мой ответ за то, что я считаю правильным описанием процесса.

  • Вывод аргумента шаблона с №1 в качестве параметра и # 2 в качестве аргумента: мы изобретаем значение M для замены N , T выводится как int , const char* поскольку параметр может быть инициализирован из аргумента type char[M] , все в порядке. Насколько я могу судить, № 2 по крайней мере так же специализирован, как №1 для всех задействованных типов.
  • Вывод аргумента шаблона с # 2 как параметр и # 1 в качестве аргумента: мы изобретаем тип U для замены T , параметр типа int не может быть инициализирован из аргумента типа U (несвязанные типы), параметра типа char[N] не может быть инициализирован из аргумента type const char* , а значение параметра non-type N не может быть выведено из аргументов, поэтому ... все не удается. Насколько я могу судить, № 1 не является, по крайней мере, таким же специализированным, как №2 для всех задействованных типов.

РЕДАКТИРОВАТЬ 1: Вышеизложенное было отредактировано на основе комментариев от Columbo и dyp, чтобы отразить тот факт, что ссылки удаляются, прежде чем пытаться вычесть аргумент шаблона в этом случае.

EDIT 2: на основе информации с hvd также исключаются cv-квалификаторы верхнего уровня. В этом случае это означает, что const char[N] становится char[N] , потому что cv-квалификаторы на элементах массива также применяются к самому массиву ( array of const также является const array , так сказать); это вообще не было очевидным в стандарте C ++ 11, но было уточнено для C ++ 14.

Исходя из вышеизложенного, я бы сказал, что частичный порядок шаблонов функций должен выбрать # 2 как более специализированный, и вызов должен решить его без какой-либо двусмысленности.

Теперь вернемся к суровой реальности. Как GCC 4.9.1, так и Clang 3.5.0 со следующими параметрами

-Wall -Wextra -std=c++11 -pedantic 

отклонить вызов как неоднозначный, с похожими сообщениями об ошибках. Ошибка от Clang:

prog.cc:16:2: error: call to 'f' is ambiguous
    f(7, "ab");
    ^
prog.cc:4:27: note: candidate function [with T = int] 
template<typename T> void f(T, const char*) //#1 
                         ^
prog.cc:9:30: note: candidate function [with N = 3] 
template<std::size_t N> void f(int, const char(&)[N]) //#2 
                            ^

Visual C ++ 2013 IntelliSense (на основе компилятора EDG, насколько я знаю) также помещает вызов как неоднозначный. Как ни странно, компилятор VC ++ идет вперед и компилирует код без ошибок, выбирая # 2 . (Я согласен со мной, так что это должно быть правильно.)

Очевидным вопросом для экспертов является то, почему вызов неоднозначен? Что мне не хватает (в частичной области заказа, я бы догадался)?




Links