Страницы

Поиск по вопросам

среда, 15 мая 2019 г.

Вопрос по перегрузке шаблоных функций в C++

Читаю книжку Джосатиса про шаблоны. Наткнулся там на один пример (40-41 стр.) и не могу его понять. Вот код #include #include
template < class T > inline T const& mymax( T const& arg1, T const& arg2 ) { std::cout << "max1" << std::endl; return ( arg1 > arg2 ) ? arg1 : arg2; }
template < class T > inline char const* mymax( char const* arg1, char const* arg2 ) { std::cout << "max2" << std::endl; return strcmp( arg1, arg2 ) < 0 ? arg1 : arg2; }
template < class T > inline T const& mymax( T const& arg1, T const& arg2, T const& arg3 ) { std::cout << "max3" << std::endl; return mymax( mymax( arg1, arg2 ), arg3 ); }
int main( int argc, char* argv[] ) { const char* s1 = "Hello"; const char* s2 = "World"; const char* s3 = "All"; std::cout << mymax( s1, s2, s3 ) << std::endl; return 0; } В результате выдается: max3 max1 max1 All Там даётся объяснение, но я всё равно его не понял почему не используется вот эта функция: inline char const* mymax( char const* arg1, char const* arg2 ) У Джосатиса написано: Проблема заключается в том, что если мы вызываем max() для трёх С-строк, то инструкция mymax( mymax( arg1, arg2 ), arg3 ) становится некорректной. Это происходит потому, что в max() для с-строк создаётся новая временная локальная переменная, которую функция возвращает по ссылке. Как можно вернуть ссылку локальной переменной? Она же уничтожится после } и как мне сделать так, чтобы использовалась эта функция: inline char const* mymax( char const* arg1, char const* arg2 ) ?


Ответ

Хороший способ в неизвестной ситуации понять, какая именно подстановка происходит в том или ином случае состоит в том, чтобы слегка видоизменить фрагменты кода. Рассмотрим первый дополнительный пример: template inline T const& mymax(T const& arg1, T const& arg2) { std::cout << "max1" << std::endl; return ( arg1 > arg2 ) ? arg1 : arg2; }
inline char const* mymax( char const* arg1, char const* arg2 ) { std::cout << "max1" << std::endl; return ( arg1 > arg2 ) ? arg1 : arg2; }
template inline T const& mymax(T const& arg1, T const& arg2, T const& arg3) { std::cout << "max3" << std::endl; return mymax(mymax(arg1, arg2), arg3); } В данном примере я сделал вторую функцию нешаблонной. Приоритет выбора у нешаблонной функции больше, нежели у шаблонной, поэтому подставляется именно она. Этот код компилится, однако он некорректен. Дело в том, что результат выражения mymax(arg1, arg2) должен положиться в какую-то временную переменную (типа const char*), чтобы передаться в функцию mymax(..., arg3). Когда мы потом попытаемся вернуть T const& на эту переменную, произойдет плохая вещь. Рассмотрим второй дополнительный пример: template inline T const& mymax(T const& arg1, T const& arg2) { std::cout << "max1" << std::endl; return ( arg1 > arg2 ) ? arg1 : arg2; }
template <> inline const char* const& mymax(const char* const& arg1, const char* const& arg2) { std::cout << "max2" << std::endl; return strcmp( arg1, arg2 ) < 0 ? arg1 : arg2; }
template inline T const& mymax(T const& arg1, T const& arg2, T const& arg3) { std::cout << "max3" << std::endl; return mymax( mymax( arg1, arg2 ), arg3 ); } Это - один из корректных способов вызвать функцию, работающую с const char* в нашем примере. Функция max2 здесь сделана специализацией функции max1 для типа . Отмечу, что, несмотря на то, что функции возвращают значения по константным ссылкам, в данной ситуации ничего очень страшного в этом нет. Обычно же возвращение константной ссылки из standalone функции всегда привлекает внимание как потенциальная ошибка и достаточно часто ей является. Рассмотрим третий дополнительный пример: (самый важный) template inline const char* mymax(const char* arg1, const char* arg2) { std::cout << "max2" << std::endl; return strcmp( arg1, arg2 ) < 0 ? arg1 : arg2; }
template inline T const& mymax(T const& arg1, T const& arg2, T const& arg3) { std::cout << "max3" << std::endl; return mymax( mymax( arg1, arg2 ), arg3 ); } Этот пример не компилируется. Дело в том, что по стандарту C++ implicit преобразование типа const char* const& ---> const char* допустимо, но не в шаблонной функции. Это означает, что для функции с двумя аргументами mymax(arg1, arg2) не найти подходящую подстановку и, естественно, это ошибка компиляции. Вывод: По третьему примеру мы увидели, что шаблонная функция с таким аргументом вообще не рассматривается как потенциальная подстановка для соответствующего шаблона. Именно по этой причине в исходном примере из книжки Вандервурта происходит подстановка max1 (т.к она - единственная возможная). Как сделать так, чтобы вызывалась подстановка max2, я уже написал. Дополнительные примечания: За такое в нормальном проекте бьют и бьют беспощадно. Люди, которые пишут template-based код, должны уметь писать код на шаблонах, который не заставляет задумываться о выбираемой подстановке. В этом случае это можно было решить с помощью, например, boost::enable_if и трейтсы boost::is_pointer. В общем случае - схожим образом, пользуясь принципом SFINAE и другими возможностями современного шаблонного C++. При желании могу продемонстрировать, в какую красоту может выродиться даже этот пример. И еще. Пользуйтесь std::string и умными указателями. Это практически полностью исключает не-ссылочные типы из шаблонов и сильно уменьшает мозговые затраты на анализ подстановок.

Комментариев нет:

Отправить комментарий