Date: Tue, 10 Feb 2026 12:43:58 -0500
Hello all,
For some of these I can attempt answer as I use Unreal Engine quite a bit
in my daily life. I'll offer some of my insight underneath some of the
questions I can answer.
On Tue, Feb 10, 2026 at 11:32 AM John McFarlane via SG14 <
sg14_at_[hidden]> wrote:
> Hi Roman,
>
> Thanks for sharing this. Perhaps we can add it to the agenda in the next
> meeting (tomorrow?).
>
> I've left a few comments below...
>
> On Sat, 7 Feb 2026 at 20:03, Root Tool via SG14 <sg14_at_[hidden]>
> wrote:
>
>> Hi SG14,
>> I'm a 17-year-old hobbyist programmer with a background in Unreal Engine
>> and embedded systems. I've implemented a proof-of-concept for a
>> null-propagating member access operator '?->' in Clang and would like to
>> share it for feedback.
>>
>> *Motivation*
>> In game development (Unreal Engine for example), this pattern appears
>> constantly:
>>
>>> UMySubsystem* UMySubsystem::Get(const UObject* WorldContextObject)
>>> {
>>> if(!WorldContextObject) return nullptr;
>>
>> What does it mean for this pointer to be null? If it is correct that it
> can be null, then the function name isn't entirely accurate as it doesn't
> always 'get' the desired object.
>
>
Perhaps we don't "want" the pointer to be null but sometimes, the caller is
> careless and passes null anyway. If so, maybe that's a bug in the calling
> code, in which case do we really want to be 'helping' this way? Instead one
> might wish to change the contract of the function so that null isn't
> allowed, maybe by:
> * using a reference instead of a pointer,
> * specifying a precondition (using a comment, or `pre`),
> * asserting here instead, `check(WorldContextObject != nullptr)`, though
> you'll like halt program execution even if you do nothing.
>
> A: Within Unreal Engine these types of Get() functions are usually an
> implementation of the Singleton design pattern. So in this context a
> WorldContextObject is simply any object that exists inside of a level and
> thus at the time of calling should never be null. Ignorning issues with
> garbage collection, a user would typically pass the level itself as the
> WorldContextObject and while the WorldContextObject should never be null
> the check is necessary because we can never guarantee what a caller would
> pass to the Get() function. The passing of the pointer in my opinion is
> simply a matter of following the coding standard within Unreal Engine
> itself, thus it's totally reasonable to pass a reference, you'd simply have
> to derefence whatever object you're passing beforehand. Likewise, an assert
> would also be fine, but since these subsystems for example, are used in a
> game, asserts would be stripped away in distribution (shipping) builds
> causing problems down the line.
>
> One way or another, we'd be saying "don't call this function if you don't
> have the requisite object to hand".
>
>>
>> UWorld* World = WorldContextObject->GetWorld();
>>
>> if(!World) return nullptr;
>>
>> Same question: is it ever OK for this to be null? And semantically, what
> does that mean? I'm guessing this only happens if the engine is not done
> starting up. It would seem like a precondition that you cannot get
> subsystems until the system is initialised.
>
> A: In theory it's okay for a UWorld object to be null, however in
> practice it isn't since a UWorld object represents a level in the game.
> Semantically this means "Does this level in which this object lives exist
> yet?" Because you have different targets when working within Unreal Engine
> the order in which objects are created are different depending on which
> target you're building for (Editor or Standalone), an editor build
> represents the game being run within the Unreal Engine editor, and
> standalone represents the game being run as a standalone executable similar
> to how a player would recieve it and play it. You are correct in that you
> typically cannot use a subsystem before the Engine is ready. The only issue
> is that there's a lot of context here that can be assumed from the code.
> This is something you'd generally use within a gameplay setting but, you
> can have subsystems that aren't used for gameplay as well. In short,
> Subsystems are just objects that have a lifetime managed by the engine
> itself. This answer also basically applies to the question beneath as well.
>
>>
>>
>> UGameInstance* GI = World->GetGameInstance();
>>>
>> if(!GI) return nullptr;
>>
>> Similar thing here.
>
>>
>>
>> return GI->GetSubsystem<UMySubsystem>();
>>
>> }
>>
>> Or in C++17 with "if with initializer":
>>
>>> UMySubsystem* UMySubsystem::Get(const UObject* WorldContextObject)
>>> {
>>> if(WorldContextObject)
>>> if(UWorld* World = WorldContextObject->GetWorld())
>>> if(UGameInstance* GI = World->GetGameInstance())
>>> return GI->GetSubsystem<UMySubsystem>();
>>
>> return nullptr;
>>> }
>>
>> And with '?->' this becomes:
>>
>>> UMySubsystem* UMySubsystem::Get(const UObject* WorldContextObject)
>>> {
>>> return
>>> WorldContextObject?->GetWorld()?->GetGameInstance()?->GetSubsystem<UMySubsystem>();
>>
>> }
>>
>>
>> *What I implemented*
>> Parser change (ParseExpr.cpp):
>> - Handle '?->' token in postfix expression suffix
>> - Distinguish field access (obj?->field) vs method call (obj?->method())
>> - Parse method arguments immediately to avoid intermediate state
>> Sema change (SemaExprMember.cpp):
>> - Lower '?->' to ConditionalOperator with OpaqueValueExpr for CSE
>> - Condition: explicit != nullptr comparison (no implicit bool conversion)
>> - Result type must be pointer or void (compile-time check)
>> - Chainable: each '?->' nests properly
>>
>> *Example*
>> C++:
>>
>>> struct AActor { void Destroy() {} };
>>>
>>> struct UWorld
>>> {
>>> AActor Actor = AActor();
>>> AActor* GetActor() { return &Actor; }
>>
>> };
>>>
>>> UWorld MyWorld = UWorld();
>>> UWorld* GetWorld() { return &MyWorld; }
>>>
>>> int main()
>>> {
>>> GetWorld()?->GetActor()?->Destroy();
>>> return 0;
>>> }
>>
>> AST (simplified):
>>
>>> ConditionalOperator 'void'
>>> |-BinaryOperator 'bool' '!='
>>> | |-OpaqueValueExpr 'AActor*'
>>> | | `-CallExpr 'AActor*' GetActor
>>> | `-CXXNullPtrLiteralExpr
>>> |-CXXMemberCallExpr 'void' Destroy
>>> | `-MemberExpr 'AActor*'
>>> `-ImplicitCastExpr 'void' <ToVoid>
>>> `-IntegerLiteral 'int' 0
>>
>>
>> *Key properties visible in AST*
>> - GetWorld() called once, stored in OpaqueValueExpr
>> - Explicit != nullptr comparison (BinaryOperator, not implicit cast)
>> - GetActor() result reused in condition and call
>> - Proper nesting for chaining
>>
>> *Limitations (honest)*
>> - Frontend only: I don't know LLVM IR/backend, so this is purely AST
>> transformation
>> - No new AST node: Reuses ConditionalOperator instead of proper
>> NullPropagatingExpr
>> - No optimizations: I rely on existing CSE and hope LLVM optimizes the
>> nested ternary
>> - Not production-ready: Missing error recovery, some edge cases with
>> templates
>>
>> *Design Decisions - p**ointer and void types only*
>> I intentionally restricted ?-> to expressions where the result is a
>> pointer or void. These are the only two types where we can safely represent
>> "something exists" versus "nothing" (nullptr).
>> For pointers - the semantics are clear: either we have a valid pointer to
>> the member, or we return nullptr.
>> For void methods - we either execute the call or do nothing (void).
>> Other types don't have this natural "null" representation. What should
>> "obj?->field" return if the field is int? Zero? Negative one? A magic
>> constant? What if the field is an object type like AActor? Return a
>> default-constructed instance? That changes semantics default constructor
>> might have side effects. The answer depends entirely on the domain, so I
>> chose to forbid it rather than guess wrong.
>>
>>> // Valid
>>> UWorld* Wold = Object?->GetWorld(); // pointer - OK
>>> World?->Tick(); // void - OK
>>
>>
>>> // Invalid - compile error
>>> int x = obj?->GetWorld()?->GetID(); // ERROR: int is not pointer or void
>>
>>
>> *Questions for SG14*
>> - Is this direction worth pursuing formally, or does it need a different
>> approach?
>>
>
> Have you considered a library-side solution involving a metaclass-based
> smart pointer?
>
A: Because of how Unreal Engine garbage collection works it's kind of a
> pain to use std smart pointers within them. While Unreal does have their
> own implementation of smart pointers they're restricted in their use in
> that you can only use them for non-UObjects, so that's any class that
> prefixes a "U", or "A" before the class name. So even if you were to wrap
> this in an std smart pointer I don't believe there's a guarantee that it
> wouldn't still point to garbage this is something I'll have to look into on
> my own time.
>
>> - Is the "pointer or void result only" restriction correct for game dev
>> use cases?
>>
>
> I'm not sure we can answer that, though individual game devs here can
> share their opinions. I doubt its value would be limited to game
> development.
>
>
>> - Who could help with the LLVM IR side if I continue?
>> - And what is the path to standardization? Should this go through EWG, or
>> is there prior art (P-paper number) I should be aware of? What would make
>> this "C++29-ready"?
>>
>> I'm not affiliated with any company, just a student who writes C++ daily.
>> Any feedback appreciated.
>>
>
> - Have you looked for existing proposals related to this? It seems like
> something that might have been floated previously.
> - Does this work in C?
> - What do other languages do?
> - How does this compare to monadic extensions to std::optional and other
> similar patterns for handling disappointing pointers?
>
> Cheers,
> John
>
>>
>> Sincerely
>> Roman Tikhostup
>> GitHub:
>> https://github.com/RootTool0/null-propagating-member-access-operator
>> Unreal Engine / Embedded / C++ enthusiast
>> _______________________________________________
>> SG14 mailing list
>> SG14_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/sg14
>>
> _______________________________________________
> SG14 mailing list
> SG14_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/sg14
>
For some of these I can attempt answer as I use Unreal Engine quite a bit
in my daily life. I'll offer some of my insight underneath some of the
questions I can answer.
On Tue, Feb 10, 2026 at 11:32 AM John McFarlane via SG14 <
sg14_at_[hidden]> wrote:
> Hi Roman,
>
> Thanks for sharing this. Perhaps we can add it to the agenda in the next
> meeting (tomorrow?).
>
> I've left a few comments below...
>
> On Sat, 7 Feb 2026 at 20:03, Root Tool via SG14 <sg14_at_[hidden]>
> wrote:
>
>> Hi SG14,
>> I'm a 17-year-old hobbyist programmer with a background in Unreal Engine
>> and embedded systems. I've implemented a proof-of-concept for a
>> null-propagating member access operator '?->' in Clang and would like to
>> share it for feedback.
>>
>> *Motivation*
>> In game development (Unreal Engine for example), this pattern appears
>> constantly:
>>
>>> UMySubsystem* UMySubsystem::Get(const UObject* WorldContextObject)
>>> {
>>> if(!WorldContextObject) return nullptr;
>>
>> What does it mean for this pointer to be null? If it is correct that it
> can be null, then the function name isn't entirely accurate as it doesn't
> always 'get' the desired object.
>
>
Perhaps we don't "want" the pointer to be null but sometimes, the caller is
> careless and passes null anyway. If so, maybe that's a bug in the calling
> code, in which case do we really want to be 'helping' this way? Instead one
> might wish to change the contract of the function so that null isn't
> allowed, maybe by:
> * using a reference instead of a pointer,
> * specifying a precondition (using a comment, or `pre`),
> * asserting here instead, `check(WorldContextObject != nullptr)`, though
> you'll like halt program execution even if you do nothing.
>
> A: Within Unreal Engine these types of Get() functions are usually an
> implementation of the Singleton design pattern. So in this context a
> WorldContextObject is simply any object that exists inside of a level and
> thus at the time of calling should never be null. Ignorning issues with
> garbage collection, a user would typically pass the level itself as the
> WorldContextObject and while the WorldContextObject should never be null
> the check is necessary because we can never guarantee what a caller would
> pass to the Get() function. The passing of the pointer in my opinion is
> simply a matter of following the coding standard within Unreal Engine
> itself, thus it's totally reasonable to pass a reference, you'd simply have
> to derefence whatever object you're passing beforehand. Likewise, an assert
> would also be fine, but since these subsystems for example, are used in a
> game, asserts would be stripped away in distribution (shipping) builds
> causing problems down the line.
>
> One way or another, we'd be saying "don't call this function if you don't
> have the requisite object to hand".
>
>>
>> UWorld* World = WorldContextObject->GetWorld();
>>
>> if(!World) return nullptr;
>>
>> Same question: is it ever OK for this to be null? And semantically, what
> does that mean? I'm guessing this only happens if the engine is not done
> starting up. It would seem like a precondition that you cannot get
> subsystems until the system is initialised.
>
> A: In theory it's okay for a UWorld object to be null, however in
> practice it isn't since a UWorld object represents a level in the game.
> Semantically this means "Does this level in which this object lives exist
> yet?" Because you have different targets when working within Unreal Engine
> the order in which objects are created are different depending on which
> target you're building for (Editor or Standalone), an editor build
> represents the game being run within the Unreal Engine editor, and
> standalone represents the game being run as a standalone executable similar
> to how a player would recieve it and play it. You are correct in that you
> typically cannot use a subsystem before the Engine is ready. The only issue
> is that there's a lot of context here that can be assumed from the code.
> This is something you'd generally use within a gameplay setting but, you
> can have subsystems that aren't used for gameplay as well. In short,
> Subsystems are just objects that have a lifetime managed by the engine
> itself. This answer also basically applies to the question beneath as well.
>
>>
>>
>> UGameInstance* GI = World->GetGameInstance();
>>>
>> if(!GI) return nullptr;
>>
>> Similar thing here.
>
>>
>>
>> return GI->GetSubsystem<UMySubsystem>();
>>
>> }
>>
>> Or in C++17 with "if with initializer":
>>
>>> UMySubsystem* UMySubsystem::Get(const UObject* WorldContextObject)
>>> {
>>> if(WorldContextObject)
>>> if(UWorld* World = WorldContextObject->GetWorld())
>>> if(UGameInstance* GI = World->GetGameInstance())
>>> return GI->GetSubsystem<UMySubsystem>();
>>
>> return nullptr;
>>> }
>>
>> And with '?->' this becomes:
>>
>>> UMySubsystem* UMySubsystem::Get(const UObject* WorldContextObject)
>>> {
>>> return
>>> WorldContextObject?->GetWorld()?->GetGameInstance()?->GetSubsystem<UMySubsystem>();
>>
>> }
>>
>>
>> *What I implemented*
>> Parser change (ParseExpr.cpp):
>> - Handle '?->' token in postfix expression suffix
>> - Distinguish field access (obj?->field) vs method call (obj?->method())
>> - Parse method arguments immediately to avoid intermediate state
>> Sema change (SemaExprMember.cpp):
>> - Lower '?->' to ConditionalOperator with OpaqueValueExpr for CSE
>> - Condition: explicit != nullptr comparison (no implicit bool conversion)
>> - Result type must be pointer or void (compile-time check)
>> - Chainable: each '?->' nests properly
>>
>> *Example*
>> C++:
>>
>>> struct AActor { void Destroy() {} };
>>>
>>> struct UWorld
>>> {
>>> AActor Actor = AActor();
>>> AActor* GetActor() { return &Actor; }
>>
>> };
>>>
>>> UWorld MyWorld = UWorld();
>>> UWorld* GetWorld() { return &MyWorld; }
>>>
>>> int main()
>>> {
>>> GetWorld()?->GetActor()?->Destroy();
>>> return 0;
>>> }
>>
>> AST (simplified):
>>
>>> ConditionalOperator 'void'
>>> |-BinaryOperator 'bool' '!='
>>> | |-OpaqueValueExpr 'AActor*'
>>> | | `-CallExpr 'AActor*' GetActor
>>> | `-CXXNullPtrLiteralExpr
>>> |-CXXMemberCallExpr 'void' Destroy
>>> | `-MemberExpr 'AActor*'
>>> `-ImplicitCastExpr 'void' <ToVoid>
>>> `-IntegerLiteral 'int' 0
>>
>>
>> *Key properties visible in AST*
>> - GetWorld() called once, stored in OpaqueValueExpr
>> - Explicit != nullptr comparison (BinaryOperator, not implicit cast)
>> - GetActor() result reused in condition and call
>> - Proper nesting for chaining
>>
>> *Limitations (honest)*
>> - Frontend only: I don't know LLVM IR/backend, so this is purely AST
>> transformation
>> - No new AST node: Reuses ConditionalOperator instead of proper
>> NullPropagatingExpr
>> - No optimizations: I rely on existing CSE and hope LLVM optimizes the
>> nested ternary
>> - Not production-ready: Missing error recovery, some edge cases with
>> templates
>>
>> *Design Decisions - p**ointer and void types only*
>> I intentionally restricted ?-> to expressions where the result is a
>> pointer or void. These are the only two types where we can safely represent
>> "something exists" versus "nothing" (nullptr).
>> For pointers - the semantics are clear: either we have a valid pointer to
>> the member, or we return nullptr.
>> For void methods - we either execute the call or do nothing (void).
>> Other types don't have this natural "null" representation. What should
>> "obj?->field" return if the field is int? Zero? Negative one? A magic
>> constant? What if the field is an object type like AActor? Return a
>> default-constructed instance? That changes semantics default constructor
>> might have side effects. The answer depends entirely on the domain, so I
>> chose to forbid it rather than guess wrong.
>>
>>> // Valid
>>> UWorld* Wold = Object?->GetWorld(); // pointer - OK
>>> World?->Tick(); // void - OK
>>
>>
>>> // Invalid - compile error
>>> int x = obj?->GetWorld()?->GetID(); // ERROR: int is not pointer or void
>>
>>
>> *Questions for SG14*
>> - Is this direction worth pursuing formally, or does it need a different
>> approach?
>>
>
> Have you considered a library-side solution involving a metaclass-based
> smart pointer?
>
A: Because of how Unreal Engine garbage collection works it's kind of a
> pain to use std smart pointers within them. While Unreal does have their
> own implementation of smart pointers they're restricted in their use in
> that you can only use them for non-UObjects, so that's any class that
> prefixes a "U", or "A" before the class name. So even if you were to wrap
> this in an std smart pointer I don't believe there's a guarantee that it
> wouldn't still point to garbage this is something I'll have to look into on
> my own time.
>
>> - Is the "pointer or void result only" restriction correct for game dev
>> use cases?
>>
>
> I'm not sure we can answer that, though individual game devs here can
> share their opinions. I doubt its value would be limited to game
> development.
>
>
>> - Who could help with the LLVM IR side if I continue?
>> - And what is the path to standardization? Should this go through EWG, or
>> is there prior art (P-paper number) I should be aware of? What would make
>> this "C++29-ready"?
>>
>> I'm not affiliated with any company, just a student who writes C++ daily.
>> Any feedback appreciated.
>>
>
> - Have you looked for existing proposals related to this? It seems like
> something that might have been floated previously.
> - Does this work in C?
> - What do other languages do?
> - How does this compare to monadic extensions to std::optional and other
> similar patterns for handling disappointing pointers?
>
> Cheers,
> John
>
>>
>> Sincerely
>> Roman Tikhostup
>> GitHub:
>> https://github.com/RootTool0/null-propagating-member-access-operator
>> Unreal Engine / Embedded / C++ enthusiast
>> _______________________________________________
>> SG14 mailing list
>> SG14_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/sg14
>>
> _______________________________________________
> SG14 mailing list
> SG14_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/sg14
>
Received on 2026-02-10 17:44:12
