Приведение указателя на функцию по иерархии классов

Рассмотрим код ниже:

#include <type_traits>
#include <iostream>

struct B {
virtual const char* whoami() const { return "I am a B!\n"; };
};

struct D : B {
const char* whoami() const override { return "I am a D!\n"; };
};

void foo_impl( B*(*pf)() )
{
B* b = pf();
std::cout << b->whoami();
}

template <class C>
auto foo( C*(*pf)() ) -> std::enable_if_t<std::is_base_of<B, C>::value> {
foo_impl(reinterpret_cast<B*(*)()>(pf));
}

D *bar() {
static D d_; // kludge to work around dangling pointer problem.
// Unrelated to the actual question
return &d_;
}

int main() {
foo(bar); // prints "I am a D!" on gcc 5.1
return 0;
}

Указатели функций, конечно, не могут быть приведены в соответствие со стандартом, и, возможно, в этом нет необходимости (мы всегда можем просто вернуть B*), но, пожалуйста, приколите меня. Насколько я могу судить, возможного нарушения LSP нет, поэтому, если это было возможно, функция, возвращающая D* может быть использован вместо функции, возвращающей B* в совершенно непрозрачной манере.

Тонкий foo шаблон преформует статическую проверку типов, которую хотел бы сделать компилятор, а затем выбрасывает информацию о типе на ветер. На данный момент я знаю, что если я не приведу обратно к исходному типу указателя, поведение не определено (C ++ 11 §5.2.10 / 6).

Поэтому мой вопрос:

Есть ли практическая причина, не связанная со стандартом, которая может привести к сбою вышеуказанного кода? Или есть другая стандартная ссылка, которая может смягчить неприятность UB в приведенном выше коде?

0

Решение

Следующее утверждение вашего поста неверно:

функция, возвращающая D *, может использоваться вместо функции, возвращающей
B * совершенно непрозрачным образом

Это не правда, потому что преобразование D* в B* это не просто приведение: может потребоваться смена адреса. В вашем примере это не так, потому что вы не используете множественное наследование.

Рассмотрим этот пример:

#include <type_traits>
#include <iostream>

struct A {
int i = 0;
int whoami() const { return i; };
};

struct B {
int j = 1;
int whoami() const {
std::cout << "Woohoo B is called.\n";
return j;
};
};

struct D : A, B {
};

void foo_impl(B *(*pf)()) {
B *b = pf();
char const * identity = (0 == b->whoami() ? "A" : "B");
std::cout << "I am " << identity << std::endl;
}

template <class C>
auto foo( C*(*pf)() ) -> std::enable_if_t<std::is_base_of<B, C>::value> {
foo_impl(reinterpret_cast<B*(*)()>(pf));
}

D *bar() {
static D d_;
return &d_;
}

int main() {
foo(bar);
return 0;
}

Это печатает I am A, хотя вы думали, что использовали B, Вызываемая функция эффективно B::whomai, но элемент данных под ним является одним из A, так как адрес не был сдвинут, как это было бы с надлежащим static_cast указателя.

1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]