Это проблема дизайна.
Однако, в интересах ответа на реальный вопрос, есть несколько способов, которыми вы могли бы сделать это без редизайна (но на самом деле, вам следует изменить его).
Один (ужасный) вариант - вызвать метод newstyle и перехватить исключение, которое возникает, если оно не переопределено.
void dispatch() {
try {
newStyle(42);
} catch (const char *) {
oldStyle(1, 2);
}
}
Если newStyle был переопределен, вызывается переопределение. В противном случае базовая реализация сгенерирует, что диспетчеризация поймает и затем вернется к oldStyle. Это злоупотребление исключениями, и оно будет работать плохо.
Другой (чуть менее ужасный) подход - сделать базовую реализацию newStyle перенаправленной на oldStyle.
void dispatch() {
newStyle(42);
}
virtual void newStyle(int) { oldStyle(1, 2); }
virtual void oldStyle(int, int) { throw "implement me"; }
Это, по крайней мере, движется в направлении лучшего дизайна. Смысл наследования заключается в том, чтобы позволить высокоуровневому коду использовать объекты взаимозаменяемо, независимо от их специализации. Если диспетчер должен проверить фактический тип объекта, то вы нарушили принцип подстановки Лискова. Диспетчер должен иметь возможность обрабатывать все объекты одинаково, и любые различия в поведении должны возникать из-за самих переопределенных методов (а не из-за наличия переопределений).