Почему этот код на С++ неоднозначный метод вызывается только на компиляторе Microsoft?

Я пытаюсь скомпилировать библиотеку на microsoft С++ compiler 14.1 (Visual Studio 2017), но я получаю странную ошибку из-за неоднозначного вызова метода класса. После некоторого тестирования я выделил следующий фрагмент кода:

#include <iostream>

struct Event
{};

template<typename Derived>
struct State
{
public:
    template<typename Fsm>
    void onEvent(Fsm& fsm, const Event& event)
    {
        std::cout << "State::onEvent\n";
    }

};

struct DerivedState
    : State<DerivedState>
{
public:
    using State::onEvent;

    template<typename Fsm>
    void onEvent(Fsm& fsm, const Event& event)
    {
        std::cout << "DerivedState::onEvent\n";
    }

};

struct Context
{};


int main()
{
    DerivedState ds;
    Context context;
    ds.onEvent(context, Event());
}

Я получаю следующий вывод:

1 > c:\users\pmas\documents\visual studio

2017\projects\consoleapplication3\consoleapplication3\consoleapplication3.cpp(87): error C2668: 'DerivedState::onEvent': ambiguous call to overloaded function
1>c:\users\pmas\documents\visual studio 2017\projects\consoleapplication3\consoleapplication3\consoleapplication3.cpp(59): note: could be 'void DerivedState::onEvent<Context>(Fsm &,const Event &)'
1>        with
1>        [
1>            Fsm=Context
1>        ]
1>c:\users\pmas\documents\visual studio 2017\projects\consoleapplication3\consoleapplication3\consoleapplication3.cpp(45): note: or       'void State<DerivedState>::onEvent<Context>(Fsm &,const Event &)'
1>        with
1>        [
1>            Fsm=Context
1>        ]
1>c:\users\pmas\documents\visual studio 2017\projects\consoleapplication3\consoleapplication3\consoleapplication3.cpp(87): note: while trying to match the argument list '(Context, Event)'

Код выглядит мне совершенно законным и компилируется в gcc, clang и icc (и ведет себя так же, как и ожидалось).

После некоторого дополнительного тестирования я узнаю, что код компилируется отлично, если я избегаю CRTP-шаблона при выводе DerivedState:

#include <iostream>

struct Event
{};

struct State
{
public:
    template<typename Fsm>
    void onEvent(Fsm& fsm, const Event& event)
    {
        std::cout << "State::onEvent\n";
    }

};

struct DerivedState
    : State
{
public:
    using State::onEvent;

    template<typename Fsm>
    void onEvent(Fsm& fsm, const Event& event)
    {
        std::cout << "DerivedState::onEvent\n";
    }

};

struct Context
{};


int main()
{
    DerivedState ds;
    Context context;
    ds.onEvent(context, Event());
}

Может кто-нибудь объяснить эту разницу? Разве компилятор Microsoft не соответствует стандарту?

Ответ 1

В соответствии с документацией по cppreference (выделено мной)

Using-declaration вводит член базового класса в определение производного класса, например, чтобы открыть защищенный член базы как открытый член производного. В этом случае спецификатор вложенных имен должен указывать базовый класс определяемого. Если имя является именем перегруженной функции-члена базового класса, вводятся все функции-члены базового класса с этим именем. Если у производного класса уже есть член с тем же именем, списком параметров и квалификацией, член производного класса скрывает или переопределяет (не конфликтует) с членом, который вводится из базового класса.

Таким образом, в основном ваша декларация использования для метода на самом деле ничего не делает (метод был общедоступным в любом случае, поэтому нет необходимости в using). Ваш производный класс просто скрывает метод onEvent.

Итак, другие компиляторы правы, а Microsoft не прав.