After much digging and a good amount of debugging, I have the root cause of the issue.
The reasoning behind the byte offset.
While executing the python delegate, the SelectedItem is interpreted as an FNameProperty rather than a FStrProperty. This indirection is where we loose those extra few bytes causing the python cast to fail. Such a cool issue!
This lead me to believe there is a problem with the python delegate generation rather than the conversion tooling. Pretty nifty that the engine doesn’t crash because of this fluke.
The engine builds a dynamic delegate when we use the python script:
widget.on_selection_changed.add_callable_unique(...)
That runs, among other things, the delegate metadata scan to pull in information about said delegate (PyWrapperDelegate.h:L94)
TPyWrapperDelegateMetaData::GetMetaData(PyType);
That call should return us a structure containing the FStrProperty for the SelectedItem. Remember, the SelectedItem member in the ComboBoxString is an FString. but instead we’re getting a FNameProperty which breaks the chain.
The Reason
The ComboBoxString is not the only class to define a FOnSelectionChangedEvent - in fact, the ComboBoxKey defines a delegate with the same name, but uses an FName parameter instead of the FString.
Here, at last, is our issue.
The python registry for these callbacks uses a flat TMap<FName, PyTypeObject*> in PyWrapperTypeRegistry.h. Because these two delegates collide by name, (OnSelectionChangedEvent__DelegateSignature) we have a cache miss and poof! The error chain is complete and we cannot successfully assign to a ComboBoxString change delegate via python.
Possible Fix
With all of this data compiled, we need to have the python type registry capable of handling multiple delegates with the same name. But it would also be nice to avoid a complex mapping of structures.
The only difference between our delegates, besides their signature, is their owner. Each of them is owned by their respective class (ComboBoxKey and ComboBoxString). So, with a little extra digging and a small C++ change, I’ve submitted a PR for the engine:
https://github.com/EpicGames/UnrealEngine/pull/9069