Базовое значение для производного static_cast, невидимая связь

Можем static_cast от Base* до Derived*, но в моем случае не видно связи, когда должен произойти кастинг. Есть ли какой-нибудь трюк (кроме reinterpret_cast, конечно), чтобы это заработало?

struct Derived;

struct Base 
{
    Base()
    {
        //Is there a way to make it work?
        //Derived* d = static_cast<Derived*>(this);
    }
};

struct Derived: public Base
{
};

int main()
{
    Base b{};
    // Here it works as the compiler is aware of the relationship
    Derived* d = static_cast<Derived*>(&b);
}

Вы можете попробовать здесь.

Обновлять: Спасибо за ваши комментарии. Конечно, я знаю, что мой пример неработоспособен, я просто хотел продемонстрировать проблему. Реальная мировая проблема гораздо сложнее.

У меня есть непрозрачный указатель, который указывает либо на Base, либо на Derived (переключатель времени компиляции). И мне нужен этот непрозрачный указатель в конструкторе класса Base. Когда непрозрачный указатель равен Derived*, экземпляр Base не может быть создан только через Derived, поэтому он на 100% безопасен. В другом случае Base == Derived, чтобы приведения не произошло.

Поскольку это непрозрачный указатель, а мы находимся в Base, у нас есть только предварительное объявление Derived. Нравиться:

#ifdef PLATFORM_A
    typedef struct Derived UsedType;
#else
    typedef struct Base UsedType;
#endif

Поэтому мне понадобится UsedType* в конструкторе Base, виртуальный это либо Base*, либо Derived*. К сожалению, если мы не на PLATFORM_A, тогда Derived вообще не определен, поэтому, когда код достигает конструктора Base, компилятор не может знать о его связи с Derived (поскольку Derived может вообще не существовать, и в этом случае приведение будет от Base* до Base*).

Обновление 2: Реальный код

Обновление 3: я еще раз обновил код. Теперь есть член, который хранит непрозрачный указатель this. Я не использую его в конструкторе, использую только снаружи. Его необходимо инициализировать в конструкторе (поскольку член является константным).

#include <iostream>

#define PLATFORM_A

#ifdef PLATFORM_A
    typedef struct Derived UsedType;
#else
    typedef struct Base UsedType;
#endif

struct User 
{
    UsedType* const platform;
    void print() const;
};

struct Base 
{
    const char* const name{"Base"};
    User const iNeedThis;
#ifdef PLATFORM_A
  protected:
#endif
    Base()
      : iNeedThis{(UsedType*)this}
      //: iNeedThis{static_cast<UsedType*>(this)}
    {
    }
};

#ifdef PLATFORM_A
  struct Derived: public Base
  {
    const char* const name{"Derived"};
    Derived()
      : Base()
    {
    }
  };
#endif

void User::print() const
{
    std::cout << platform->name << std::endl;
}

int main()
{
    UsedType myObj{};
    myObj.iNeedThis.print();
}

https://godbolt.org/z/sMr5Kfxrn

Примечание: вряд ли когда-либо понадобится приводить объект к производному классу. Как вы думаете, почему вам это нужно?

Pepijn Kramer 01.08.2024 18:36

Есть ли у вас реальный пример использования, когда вам понадобится доступ к конструктору Derived* Within Base? Или это всего лишь мысленный эксперимент?

Eljay 01.08.2024 18:37

Невозможно использовать *this в качестве точки Derived. потому что объект, находящийся в this, не является Derived, поэтому любой доступ к нему как Derived будет неопределенным поведением, и никакое приведение не сможет это изменить, static_cast, reinterpret_cast или unobtainable_cast.

bolov 01.08.2024 18:41

Я не знаю, каков более широкий контекст. Однако это чем-то напоминает мне любопытно повторяющийся шаблон шаблона Возможно, вам стоит на это обратить внимание. В противном случае мне кажется, что это проблема дизайна на более высоком уровне.

Avi Berger 01.08.2024 18:56

@AviBerger да, я думал о CRTP, но не могу переместить конструктор Base в заголовок :( Да, это своего рода проблема дизайна, но преобразование Base to Derived может ее решить. Но, похоже, мне нужно подумать о План B :)

Broothy 01.08.2024 19:30

Принудительно отложить оценку с помощью шаблона конструктора Base. template<typename T = Derived> Base() { static_cast<T*>(this); }

Raymond Chen 01.08.2024 19:40

@Broothy Как UsedType связан с конструктором Base? Пожалуйста, отредактируйте свой вопрос, чтобы предоставить лучший пример РЕАЛЬНОЙ ПРОБЛЕМЫ, с которой вы столкнулись.

Remy Lebeau 01.08.2024 19:44

@RemyLebeau конечно. пожалуйста, найдите реальный код во втором обновлении.

Broothy 01.08.2024 20:01

@Broothy, как говорилось ранее, когда PLATFORM_A определен и, следовательно, UsedType является Derived, тогда UsedType* iNeedThis = static_cast<UsedType*>(this); вызовет неопределенное поведение, если вы попытаетесь разыменовать iNeedThis. До сих пор не ясно, ЗАЧЕМ вам вообще нужен этот приведённый указатель. Что именно конструктор Base собирается делать с этим указателем? Я начинаю задаваться вопросом, действительно ли это Проблема XY.

Remy Lebeau 01.08.2024 21:32

@RemyLebeau В этот момент конструктор передает iNeedThis участнику. Он обрабатывается как непрозрачный указатель, поэтому в конструкторе он не разыменовывается. Я мог бы установить его вне конструктора, но это было бы подвержено ошибкам. Я думаю, что пример кода завершен, но вполне возможно, что его невозможно решить таким образом.

Broothy 01.08.2024 21:40

@Broothy Даже если вы просто сохраните указатель, я думаю, что он все равно будет UB внутри конструктора Base, поскольку еще нет объекта Derived, на который this мог бы указать. Почему нельзя просто поставить this позже? Почему его нужно приводить внутри конструктора? Это НЕ минимальный воспроизводимый пример (где находится член? Как он используется? и т. д.)

Remy Lebeau 01.08.2024 21:48

Есть ли какая-то конкретная причина, по которой вы не делаете того, что вам нужно, полиморфно, используя что-то виртуальное?

Craig 01.08.2024 23:04

@RemyLebeau Я добавил участника по вашему желанию. Мне нужно, чтобы он был инициализирован в конструкторе, поскольку хранящий его член является константным. В конструкторе он обрабатывается как непрозрачный указатель, на этом этапе мы все равно мало что знаем о типе, просто typedef struct Derived UsedType; если PLATFORM_A определен.

Broothy 02.08.2024 07:23
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
13
78
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Чтобы ответить на ваш вопрос: вы не можете привести к неполному типу, поэтому вам нужно будет переместить реализацию Base() после объявления Derived, например:

struct Derived;

struct Base 
{
    Base();
};

struct Derived: public Base
{
};

Base::Base()
{
    // see note below!!!
    Derived* d = static_cast<Derived*>(this);
}

int main()
{
    Base b{};
    // see note below!!!
    Derived* d = static_cast<Derived*>(&b);
}

При этом то, что вы делаете, рискованно, потому что this внутри конструктора Base не указывает на объект Derived, и b внутри main() также не является объектом Derived. Если вы попытаетесь разыменовать приведенный указатель в любой ситуации, вы вызовете неопределенное поведение.

Невозможно сделать приведение внутри конструктора Base законным, поскольку ни один объект Derived просто не будет существовать, пока создается класс Base.

Но в main() вы можете изменить b на указатель на объект Derived, тогда вы можете безопасно привести этот указатель, например:

int main()
{
    Derived obj{};
    Base* b = &obj;

    // this is perfectly OK!
    Derived* d = static_cast<Derived*>(b);
}

Привет, спасибо за ваше предложение. К сожалению, я не могу переместить конструктор Base в точку, где виден Derived, смотрите обновление в описании. Было бы возможно (теоретически) иметь шаблонный конструктор, который получал бы «настоящий» указатель Derived от конструктора Derived, когда он вызывает Base, но, к сожалению, я не могу переместить конструктор в заголовок, поэтому его нельзя использовать в качестве шаблона.

Broothy 01.08.2024 19:27

Спасибо за ваше предложение, но, к сожалению, в моем конкретном случае это невозможно. Другой ответ также решает мой особый случай.

Broothy 02.08.2024 07:34
Ответ принят как подходящий

Я имею в виду, что сделать буквально то, что вы просили, тривиально, даже не перемещая конструктор:

struct Base 
{
  UsedType* self();

#ifdef PLATFORM_A
  protected:
#endif
    Base()
    {
        UsedType* iNeedThis = self();
    }
};

Позже в base.cpp или где угодно:

#ifndef PLATFORM_A
  UsedType* Base::self() { return this; }
#endif

и даже позже, в файле Derived.cpp

#ifdef PLATFORM_A
UsedType* Base::self()
{
  return static_cast<Derived*>(this);
}
#endif

Но это все еще кажется плохим дизайном.

Ааа здорово, я думал о таком трюке, спасибо!

Broothy 02.08.2024 07:28

Это плохой дизайн, но попытка добавить поддержку второй, совершенно другой платформы к базе кода, которая не была спроектирована как мультиплатформенная, без полной переписывания требует таких плохих дизайнерских решений...

Broothy 02.08.2024 07:32

Другие вопросы по теме