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