Определить, переопределена ли унаследованная функция

#c #language-lawyer

#c #язык-юрист

Вопрос:

Я придумал следующий код, определяющий во время компиляции, переопределены ли унаследованные функции в производном классе. Он работает со всеми основными компиляторами — gcc / clang / msvc. Но действительно ли этот подход поддерживается стандартом?

 #include <type_traits>

struct B {
    virtual void f1() {}
    virtual void f2() {}
    void f3() {}
    void f4() {}
};

struct D: B {
    void f1() override {}
    void f3() {}
};

int main()
{
    static_assert(!std::is_same_v<decltype(amp;B::f1), decltype(amp;D::f1)>, "overriden");
    static_assert(std::is_same_v<decltype(amp;B::f2), decltype(amp;D::f2)>, "base");
    static_assert(!std::is_same_v<decltype(amp;B::f3), decltype(amp;D::f3)>, "overriden");
    static_assert(std::is_same_v<decltype(amp;B::f4), decltype(amp;D::f4)>, "base");
    return 0;
}
 

Комментарии:

1. Мне это кажется странным, интуитивно типы void(B::*)() и void(D::*)() должны отличаться независимо от того, перегружена функция или нет. Но я не языковой юрист…

2.Как это работает с частным наследованием? И D::f3 точнее сказать, скрывать B::f3() , а не переопределять. B::f3 в этом примере никогда не следует считать переопределенным в virtual смысле. Вы хотите отличить переопределенные virtual функции от нефункциональных virtual ?

3. Наследование по умолчанию для struct общедоступно.

Ответ №1:

Найдено, это описано в разделе by 20.15.9 Member relationships , пункт 5 стандарта:

Примечание: Тип выражения указателя на член amp;C::b не всегда является указателем на член C , что приводит к потенциально неожиданным результатам при использовании этих функций в сочетании с наследованием.

Приведенный пример:

 struct A { int a; };
struct B { int b; };
struct C: public A, public B { };

// The following will succeed because, despite its appearance,
// amp;C::b has type "pointer to member of B of type int"
static_assert(is_pointer_interconvertible_with_class( amp;C::b ));
 

И так далее и тому подобное. Это объясняет, почему в вашем примере, amp;B::f2 а amp;D::f2 также amp;B::f4 и amp;D::f4 имеют один и тот же тип void(B::*)() .

Комментарии:

1. Не могли бы вы привести и подчеркнуть соответствующие отрывки? Завершающий комментарий немного сложен для чтения. И какое это имеет отношение к virtual функциям или функциям-членам вообще?

2. Хороший ответ, хорошо найден @Peter. Надеюсь, вы не возражаете против редактирования.

3. @alterigel: тот факт, что указатель на члены, с которыми мы здесь имеем дело, является указателем на виртуальные функции, не имеет отношения к тому, почему это работает, имеет значение только то, что указатель на член для унаследованного (не переопределенного и не скрытого) члена имеет тип указатель на член базового класса типа …