09 января 2008

Function overloading vs template specialization

Перегрузка функций работает отлично со времен plain C. Пример:

Foo.h:
void Foo(bool);
void Foo(int);
void Foo(const char*);

Foo.cpp:
void Foo(bool) { ... }
void Foo(int) { ... }
void Foo(const char*) { ... }

Test.cpp:
#include "Foo.h"

void Test()
{
bool b(true);
int i(10);
const char *c("hello");

Foo(b);
Foo(i);
Foo(c);

Foo(*c); // хм... компилируется!

shared_ptr<int> p;
Foo(p); // тоже компилируется
}

Неприятность доставляет тип bool, к которому конвертируются многие другие типы. Например, можно забыть разыменовать [умный] указатель, и компилятор нам ничего не подскажет.

Все было бы замечательно, если бы можно было написать void Foo(explicit bool);, но ведь в случае параметра функции explicit не прокатит...

Выход - вместо перегрузки функций использовать специализацию шаблонов. Правим Foo.h:
template <typename T> void Foo(T);
template <> void Foo(bool);
template <> void Foo(int);
template <> void Foo(const char*);

Кстати, у меня под Visual C++ 2005 не пришлось даже менять и/или перекомпилировать Foo.cpp - сингатуры функций в объектных файлах для template <> void Foo(xxx) и void Foo(xxx) видимо совпадают.

Теперь Foo(*c) и Foo(p) из приведенного выше примера не пройдут - линкер скажет, что не нашел определений нужных символов. Можно попросить ругаться не линкер, а компилятор, добавив немного кода в первую строчку Foo.h:
template <typename T> void Foo(T) { typename T::IncorrectParameterType; }


Из минусов такого подхода - все вызовы Foo() требуют теперь точного указания типа параметра:
class ConvertableToInt
{
public:
operator int() { return 0; }
};
Foo(ConvertableToInt()) // теперь так не получится :(
Foo(static_cast<int>(ConvertableToInt())) // только так;

Если такие минусы не устраивают - придется идти на более радикальные меры, например разделить реализации bool и не-bool на функции с разными именами. Интерфейс при этом останется тот же - Foo(x):

Новый Foo.h:
template <typename T> void Foo(T t) { FooImpl(t); }
void Foo(bool);
void FooImpl(int);
void FooImpl(const char*);

Новый Foo.cpp:
void Foo(bool) { ... }
void FooImpl(int) { ... }
void FooImpl(const char*) { ... }