Date: Sat, 7 Feb 2026 20:01:27 +0300
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;
UWorld* World = WorldContextObject->GetWorld();
if(!World) return nullptr;
UGameInstance* GI = World->GetGameInstance();
>
if(!GI) return nullptr;
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?
- Is the "pointer or void result only" restriction correct for game dev use
cases?
- 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.
Sincerely
Roman Tikhostup
GitHub: https://github.com/RootTool0/null-propagating-member-access-operator
Unreal Engine / Embedded / C++ enthusiast
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;
UWorld* World = WorldContextObject->GetWorld();
if(!World) return nullptr;
UGameInstance* GI = World->GetGameInstance();
>
if(!GI) return nullptr;
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?
- Is the "pointer or void result only" restriction correct for game dev use
cases?
- 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.
Sincerely
Roman Tikhostup
GitHub: https://github.com/RootTool0/null-propagating-member-access-operator
Unreal Engine / Embedded / C++ enthusiast
Received on 2026-02-07 17:01:43
