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*) { ... }
5 комментариев:
А Foo(0); что вызовет Foo(const char*); или Foo(int);? :-) Указатель тоже ведь нулем можно инициализировать. VS 2005 вызывает Foo(int); у меня. Однако я почему то сомневаюсь в правильности такого поведения.
Даже Foo(NULL) вызывает Foo(int). В новом стандарте обещают отдельный nullptr, видимо чтобы избегать путаницы 0 (он же NULL) с нулевым указателем.
gcc version 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)
тоже вызывает Foo(int) по точному соответствию типов (все правильно, зря я сомневался - все как в стандарте). Выходит просто в случае с указателями у комитета неувязочка вышла, которую и призван решить null_ptr
Вы пытаетесь сделать более строгую типизацию, которой изначально в языке не предусматривалось.
>> Перегрузка функций работает отлично со времен plain C.
Перегрузка функций это предоставление компилятору возможности выбора одной функции с различными наборами параметров. В С этого как раз не было
Отправить комментарий