Короткий ответ: компоновка vtable определенно зависит от компилятора. Фактически, языковой стандарт не требует, чтобы компилятор даже использовал vtables для реализации диспетчеризации виртуальных функций.
При этом в конкретном случае Windows и Visual C ++:
C ++ vtables преднамеренно размечены так, чтобы быть совместимыми с соглашениями о вызовах COM, которые требуют, чтобы слоты были последовательно назначены для виртуальных функций;
также для взаимодействия COM простое наследование добавляет новые виртуальные функции в конец родительской таблицы;
однако COM не допускает перегрузок, т. е. одноименных функций с разными сигнатурами.
Случай OP нарушает последний пункт, поэтому гарантии COM здесь не применяются, поскольку интерфейс не является COM-совместимым с самого начала из-за перегрузок. Фактически, Microsoft явно предупреждает для C# до избегать перегрузок в видимых интерфейсах COM .
Таким образом, технически поведение компилятора VC ++ не нарушает никаких правил, ни языка, ни COM. Кроме того, я не знаю ни одного параметра / трюка / регресса для принудительного определения определенного порядка перегрузок в виртуальной таблице.
Одним из возможных (хотя и не очень) обходных путей может быть введение искусственного дополнительного класса в наследование дерево, так что каждое новое деривация добавляет только уникальную перегрузку.
struct MyHiddenStruct
{
virtual void foo(float) = 0;
};
struct MyStruct : MyHiddenStruct
{
MyHiddenStruct::foo;
virtual void foo(int) = 0;
};
class MyClass : public MyStruct
{
public:
void foo(float) { }
void foo(int) { }
};
[ EDIT ] Нашел похожие VS 2010 q & a в Visual C ++ методы в vfptr в обратный порядок со строгим указанием на то, что перегрузки в одном классе сгруппированы в vtable в обратном порядке объявления. Поэтому, что бы ни делал VS 2019 в наши дни, это не новый каприз.