2026, Jan 13 17:00
How to expose C++ nested structs in Python with SWIG: the flatnested fix for -c++ mode
Learn why C++ nested structs vanish in SWIG Python bindings with -c++ and how to fix it: use flatnested to flatten types and avoid Warning 325. Avoid surprises.
Wrapping C++ code with nested structs into Python can be deceptively tricky. You might expect inner types to show up in the generated Python module, only flattened out into the module’s top level. Instead, they simply don’t appear. Here’s what actually happens and how to make nested structs reliably accessible from Python with SWIG.
Problem overview
The goal is to expose a C++ struct containing another struct, plus a sibling struct, to Python. The expectation is that all three types become available in the Python module at the same scope. In practice, the inner struct disappears when generating C++ wrappers.
SWIG’s documentation says:
If the target language doesn't support nested classes directly, or the support is not implemented in the language module (like for Python currently), then the visible nested classes are moved to the same name space as the containing class (nesting hierarchy is "flattened").
That sounds like nested structs should be wrapped and made visible. Yet the inner type is missing unless you take an extra step.
Reproducible code showing the issue
Here’s a minimal header and interface demonstrating the problem. The names are generic but the structure mirrors the real-world case.
// wrap-demo.h
struct HolderAlpha {
double a1;
double a2;
struct NestedBeta {
double b1;
double b2;
};
};
struct PeerGamma {
double c1;
double c2;
};
// wrap-demo.i
%module wrapdemo
%{
#include "wrap-demo.h"
%}
%include "wrap-demo.h"
Generating wrappers in C mode versus C++ mode yields different results.
# C mode
swig -python wrap-demo.i
# C++ mode
swig -python -c++ wrap-demo.i
In C mode, the inner struct is wrapped. In C++ mode, the inner struct is dropped and SWIG emits a warning like:
Warning 325: Nested struct not currently supported (NestedBeta ignored)
On the Python side, attempting to instantiate the missing type fails as expected:
from wrapdemo import *
obj = NestedBeta() # AttributeError if generated with -c++ and no fix applied
What’s actually happening
SWIG treats nested classes differently depending on language support and generation mode. For Python, nested classes aren’t directly supported. The documentation indicates that such classes should be flattened into the outer scope. However, when you generate C++ wrappers, SWIG does not perform this flattening by default and instead ignores the inner struct with a warning. That’s why you see the mismatch between expectations and results.
When you generate C wrappers, the inner struct gets wrapped. When you generate C++ wrappers, you must explicitly request flattening.
Solution and corrected example
Enable flattening explicitly in the interface file. Adding a single directive makes SWIG generate the inner struct as a top-level Python class when using C++ mode.
// wrap-demo.i
%module wrapdemo
%feature("flatnested");
%{
#include "wrap-demo.h"
%}
%include "wrap-demo.h"
Now regenerate using C++ mode:
swig -python -c++ wrap-demo.i
With the feature enabled, the warning disappears and the inner struct is generated at module scope. You can use it directly from Python:
from wrapdemo import *
alpha = HolderAlpha()
beta = NestedBeta()
gamma = PeerGamma()
Why this matters
The difference between C and C++ generation modes is subtle but impactful. A build system that toggles -c++ can silently drop nested types, leading to runtime AttributeError and confusing discrepancies between environments. Watching for the explicit SWIG warning and turning on flattening ensures predictable, stable bindings. Relying on defaults here isn’t safe.
Conclusion
If you need nested C++ structs or classes available in Python, don’t assume automatic flattening. In C mode they may appear, but in C++ mode they won’t unless you enable flattening. Add %feature("flatnested"); to the interface file, build with -c++, and verify there are no Warning 325 messages. That keeps inner types like NestedBeta visible at the top level and avoids surprises during integration and deployment.