FieldHolder
C++:
phynexis::fields::FieldHolderPython:phynexis.fields.FieldHolderHeader:src/fields/core/field_holder.hpp
Owning move-only smart pointer around a heap-allocated FieldBase. C++ code uses
FieldHolder to return polymorphic field instances by value while keeping
ownership clear (it deletes the inner pointer on destruction). The most common
way to obtain one from Python is via Field<T>.subset(mask).
FieldHolder is non-copyable; the move constructor is not exposed to Python, so
treat each holder as single-use.
Constructors
| Signature | Description |
|---|---|
FieldHolder() | Empty holder; get() is None and bool(h) is False. |
FieldHolder(ptr) | Adopts an existing FieldBase*. Pass None to construct an empty holder. |
Practical sources of holders:
field.subset(mask)— returns a new holder containing the masked subset.
Example:
import numpy as np
import phynexis
F = phynexis.fields
empty = F.FieldHolder()
print("empty bool:", bool(empty), "get():", empty.get())
sf = F.ScalarField.from_numpy(np.array([10.0, 20.0, 30.0, 40.0, 50.0]))
mask = F.BoolField.from_numpy(np.array([True, False, True, False, True]))
holder = sf.subset(mask)
print("holder bool:", bool(holder))
print("inner field:", holder.get().to_numpy())
Output:
empty bool: False get(): None
holder bool: True
inner field: [10. 30. 50.]
Methods
get()
Returns the contained field without transferring ownership. The Python object
returned reflects the runtime type of the field (e.g. ScalarField), so you
can call its full interface directly. Returns None if the holder is empty.
Returns: ScalarField / Int32Field / Int64Field / BoolField / None.
release()
Transfers ownership of the inner field out of the holder. After release the
holder is empty (bool(holder) is False) and the returned object is owned by
the Python interpreter — it remains alive even if the holder is garbage
collected.
Returns: the inner field (typed concretely, e.g. ScalarField) or None
if the holder was already empty.
reset(ptr=None)
Replaces the inner pointer. The previous content (if any) is deleted. Calling
reset() with no argument or reset(None) empties the holder.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
ptr | FieldBase or None | None | New pointer to adopt. |
as_field_double()
Down-cast the inner field to Field<double> (alias ScalarField). Raises
RuntimeError("std::bad_cast") if the inner field is not a double field, or if
the holder is empty.
Returns: ScalarField.
as_field_int()
Down-cast the inner field to Field<Int32> (alias Int32Field). Raises
RuntimeError("std::bad_cast") on type mismatch or empty holder.
Returns: Int32Field.
__bool__()
True when the holder owns a non-null pointer.
Examples
Lifetime semantics
get() returns a borrowed reference — the holder still owns the data, so the
returned field becomes invalid after the holder is collected. release()
transfers ownership to the returned object and is safe to keep beyond the
holder's lifetime.
import numpy as np
import phynexis
F = phynexis.fields
sf = F.ScalarField.from_numpy(np.array([10.0, 20.0, 30.0]))
mask = F.BoolField.from_numpy(np.array([True, False, True]))
h = sf.subset(mask)
inner = h.release()
print("released:", inner.to_numpy())
del h # safe — ownership already transferred
print("still alive after del h:", inner.to_numpy())
# Re-releasing an empty holder yields None
print("second release:", h.release()) # noqa: F821
(Re-using h here would actually fail because del h removes the binding —
the comment is illustrative.)
Down-cast and type mismatch
import numpy as np
import phynexis
F = phynexis.fields
double_holder = F.ScalarField.from_numpy(
np.array([1.0, 2.0, 3.0])
).subset(F.BoolField.from_numpy(np.array([True, True, True])))
int_holder = F.Int32Field.from_numpy(
np.array([10, 20, 30], dtype=np.int32)
).subset(F.BoolField.from_numpy(np.array([True, False, True])))
print("as double:", double_holder.as_field_double().to_numpy())
print("as int:", int_holder.as_field_int().to_numpy())
# Wrong cast raises bad_cast
try:
double_holder.as_field_int()
except RuntimeError as e:
print("wrong cast:", repr(e))
Output:
as double: [1. 2. 3.]
as int: [10 30]
wrong cast: RuntimeError('std::bad_cast')
Reset
import numpy as np
import phynexis
F = phynexis.fields
sf = F.ScalarField.from_numpy(np.array([1.0, 2.0]))
mask = F.BoolField.from_numpy(np.array([True, True]))
h = sf.subset(mask)
print("before reset:", bool(h))
h.reset() # equivalent to reset(None)
print("after reset():", bool(h))
Output:
before reset: True
after reset(): False
Known Issues
- The casts only cover
doubleandInt32. There is noas_field_int64()oras_field_bool(), soInt64Field/BoolFieldsubsets are reachable only throughget(). subset()does not propagate the source field'sname; the resulting field hasname == "".- Because
Field<T>is not registered as a subclass ofFieldBase(seebinding-repair-backlog.md), passing a genericFieldBase-typed reference back to Python loses no information in practice — pybind already resolves to the concrete subclass via runtime type info — butisinstance(holder.get(), FieldBase)is stillFalse.
Unexposed C++ API
template <typename T> T &as()— generic templated cast; only theDoubleandInt32instantiations are exposed via the namedas_field_double/as_field_intlambdas.- Move constructor / move assignment — not bound (pybind does not need them
for ownership transfer;
release()is the Python-visible equivalent).