Страницы

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

среда, 26 февраля 2020 г.

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

#cpp #шаблоны_с++


Читаю книжку Джосатиса про шаблоны. Наткнулся там на один пример (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 )

?    


Ответы

Ответ 1



Хороший способ в неизвестной ситуации понять, какая именно подстановка происходит в том или ином случае состоит в том, чтобы слегка видоизменить фрагменты кода. Рассмотрим первый дополнительный пример: 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 и умными указателями. Это практически полностью исключает не-ссылочные типы из шаблонов и сильно уменьшает мозговые затраты на анализ подстановок.

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

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