refactor(Storage): collapse at<T>/back<T>/data<T> into single template bodies#979
Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors Storage_base.cpp by replacing repetitive explicit template specializations of data<T>, at<T>, and back<T> with unified template implementations and explicit instantiations, utilizing a type_spelling variable template for error messages. Feedback suggests enforcing compile-time safety for type_spelling using a helper struct with static_assert to prevent potential runtime undefined behavior if an unspecialized type is instantiated.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| template <typename T> | ||
| constexpr const char *type_spelling = nullptr; |
There was a problem hiding this comment.
Currently, the primary template of type_spelling defaults to nullptr. If a new type is added to the explicit instantiations of data<T>, at<T>, or back<T> but is not specialized in type_spelling, it will compile successfully but result in undefined behavior at runtime when cytnx_error_msg attempts to format a nullptr with %s.
To prevent this, we can enforce compile-time safety by making the primary template trigger a compilation error if it is instantiated. Since always_false_v is already defined in Type.hpp, we can use a helper struct with a static_assert to block unspecialized types at compile time.
| template <typename T> | |
| constexpr const char *type_spelling = nullptr; | |
| template <typename T> | |
| struct type_spelling_holder { | |
| static_assert(always_false_v<T>, "type_spelling is not specialized for this type"); | |
| static constexpr const char *value = nullptr; | |
| }; | |
| template <typename T> | |
| constexpr const char *type_spelling = type_spelling_holder<T>::value; |
There was a problem hiding this comment.
Done in 79fb7d0. Routed the primary type_spelling template through a type_spelling_holder<T> with static_assert(always_false_v<T>, ...), so any type that reaches the at/back/data dtype-mismatch message without an explicit spelling is now a hard compile error instead of a silent nullptr fed to %s. The 11 specialized types are unchanged, and the hand-written GPU data<cuDoubleComplex>/data<cuFloatComplex> specializations never reference type_spelling, so they're unaffected. Verified: the CPU TU compiles clean with the real build flags, and an isolated probe confirms an unspecialized type now fails with "type_spelling is not specialized for this type".
|
Add to template <typename T> concept CytnxType = variant_contains_v<T, Type_list>;Then the functions can use template <CytnxType T>
T* Storage_base::data() const { ... }and attempt to convert to a type that has no corresponding template <typename T>
T* Storage_base:::data() const = delete; // unsupported T: compile-time error
template <CytnxType T>
T* Storage_base::data() const { ... }
#ifdef UNI_GPU
template <>
cuDoubleComplex* Storage_base::data<cuDoubleComplex>() const { ... }
template <>
cuFloatComplex* Storage_base::data<cuFloatComplex>() const { ... }
#endif |
Are these in #1004 |
…cialized Address @gemini-code-assist review on #979. The primary template of type_spelling defaulted to nullptr, so adding a new at<T>/back<T>/data<T> instantiation without a matching spelling would compile and then feed a null pointer to the "%s" conversion in cytnx_error_msg — runtime UB. Route the primary template through a type_spelling_holder<T> whose body is static_assert(always_false_v<T>). Any type that reaches the dtype-mismatch message without an explicit spelling is now rejected at compile time. The 11 specialized types are unchanged, and the hand-written GPU data<cuDoubleComplex>/ data<cuFloatComplex> specializations never reference type_spelling, so they are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
66fae90 to
79fb7d0
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #979 +/- ##
===========================================
+ Coverage 31.82% 55.67% +23.84%
===========================================
Files 230 230
Lines 33124 32979 -145
Branches 13852 77 -13775
===========================================
+ Hits 10543 18360 +7817
+ Misses 15539 14594 -945
+ Partials 7042 25 -7017
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 172 files with indirect coverage changes Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
…e bodies Each of at<T>, back<T>, and data<T> was 11 hand-written explicit specializations with identical bodies, differing only in the dtype enum and the type spelling inside the error message. That copy-paste already let one dtype check drift out of sync (#965). Replace each family with one template body that derives the expected dtype from the compile-time trait Type_class::cy_typeid_v<T>, plus explicit instantiations for the same 11 types, so the exported symbols and header declarations are unchanged. The GPU-only data<cuDoubleComplex>/data<cuFloatComplex> specializations keep their extra device check and remain hand-written. Error-message wording is preserved byte-for-byte (verified by diffing the what() text of all 11 mismatch cases plus bounds/empty errors against the previous implementation). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cialized Address @gemini-code-assist review on #979. The primary template of type_spelling defaulted to nullptr, so adding a new at<T>/back<T>/data<T> instantiation without a matching spelling would compile and then feed a null pointer to the "%s" conversion in cytnx_error_msg — runtime UB. Route the primary template through a type_spelling_holder<T> whose body is static_assert(always_false_v<T>). Any type that reaches the dtype-mismatch message without an explicit spelling is now rejected at compile time. The 11 specialized types are unchanged, and the hand-written GPU data<cuDoubleComplex>/ data<cuFloatComplex> specializations never reference type_spelling, so they are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
) Incorporate @ianmccul's review suggestion. Add a C++20 concept template <typename T> concept CytnxType = variant_contains_v<T, Type_list> && !std::is_void_v<T>; and constrain Storage_base::data<T>/at<T>/back<T> to it, with the unconstrained primary `= delete`d. Requesting an unsupported T (e.g. data<char>()) is now a hard compile error at the call site -- "use of deleted function" -- instead of relying on the downstream cy_typeid_v/type_spelling failures. This complements the existing type_spelling static_assert, which still guards a CytnxType added without a spelling. The GPU complex views are non-cytnx-dtype types, so they remain explicit specializations of the deleted primary. Following Ian's second note and #1004, data<> now offers both representations: the existing cuDoubleComplex / cuFloatComplex (cuBLAS/cuSOLVER ABI pointers) and the new cuda::std::complex<double/float> (what GPU kernels use internally). at<>/back<> gain no GPU specializations -- they were never valid for the cu* types. Verified: - Full CPU library + test suite build; Storage/Type/Tensor suites 300 passed, 0 failed (11 pre-existing skips). - data<char>() is rejected with "use of deleted function [with T = char]". - Storage_base.cpp compiles under UNI_GPU with the CCCL include path, so all four GPU data<> specializations (cuComplex + cuda::std::complex) type-check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
79fb7d0 to
867e5bf
Compare
|
Done in 867e5bf (rebased onto master first, so #1004's Added the concept to template <typename T>
concept CytnxType = variant_contains_v<T, Type_list> && !std::is_void_v<T>;(excluding the
template <typename T> T* data() const = delete; // unsupported T -> hard error
template <CytnxType T> T* data() const;so e.g. Per your second comment, the GPU complex overloads are provided both ways (they're explicit specializations of the deleted primary, since neither is a cytnx dtype):
Verified: full CPU library + test suite build; Storage/Type/Tensor suites 300 passed / 0 failed (11 pre-existing skips); |
Follow-up to constraining Storage_base. The thin Storage wrapper methods (Storage::data<T>/at<T>/back<T>, which just delegate to _impl) were still `template <class T>`, so an unsupported T errored one level deep inside the wrapper body instead of at the call site. Constrain all three to CytnxType so e.g. Storage::data<char>() fails at the call with "constraints not satisfied ... CytnxType<T>" naming the concept. They use CytnxType (not a wider GPU-inclusive concept) deliberately: the Storage_base GPU data<> specializations (cuComplex / cuda::std::complex) are defined only in Storage_base.cpp and are not declared in Storage.hpp, so they are not reachable through the wrapper from any external TU -- true on master as well. Advertising them in the wrapper constraint would let the constraint pass and then fail in the body at the deleted primary, which is worse than a clean call-site rejection. Making those views externally reachable (header-declaring the specializations) would be a separate change. Verified: full CPU library + test suite build; Storage/Tensor suites 280 passed / 0 failed (11 pre-existing skips); data<char>()/at<char>() rejected at the wrapper; header parses under UNI_GPU. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…apper Declare the four GPU data<> explicit specializations (cuDoubleComplex, cuFloatComplex, cuda::std::complex<double>, cuda::std::complex<float>) in Storage.hpp, at namespace scope before the Storage wrapper. Previously they were defined only in Storage_base.cpp, so from any other TU `_impl->data<cuComplex>()` resolved to the deleted primary -- the views were unreachable outside that one file (true on master too, and there were no callers). With the declarations visible, widen the Storage::data<T> wrapper constraint back to StorageDataType (CytnxType plus the four GPU views), re-adding the GpuComplexView / StorageDataType concepts. So `storage.data<cuDoubleComplex>()` / `data<cuda::std::complex<double>>()` now compile and link end-to-end, while `data<char>()` is still rejected at the call site. at<>/back<> stay CytnxType (they have no GPU specializations). Verified: - GPU (UNI_GPU + CCCL include): the wrapper compiles data<cuDoubleComplex>, data<cuFloatComplex>, and both cuda::std::complex views; data<char> and at<cuDoubleComplex> are rejected; Storage_base.cpp emits all four specializations as strong (T) symbols matching the header declarations. - CPU: full library + test suite build; header parses (decls are #ifdef'd out); data<char> rejected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Follow-up on the wrapper, @ianmccul — now fully wired through, in two commits on top of the concept change: 1. Constrained the 2. Made the GPU views actually reachable (
Now Verified:
I couldn't run a full GPU link here because |
Code ReviewCollapses The refactor is correct (verified in full)
🔴 Blocker — breaks
|
…oid guards, Sproxy consolidation Implements items B, C and D of the consolidated review checklist (A -- explicit constrained ctor + operator<=> -- is split into a follow-up PR, since it changes overload resolution codebase-wide and carries an unresolved complex-ordering semantic decision): B. Conversion surface (review threads on Scalar.hpp:354, Scalar.cpp:227/60/322): - One constrained template replaces the 9 per-type cast operators; conversions the language cannot perform error with Type_enum_name-based text (complex->real adds the real()/imag() hint). to_complex128()/to_complex64() delegate to it. - Type_enum_name<std::monostate> added (displays as "Void"). - astype() on a Void source now errors instead of silently staying Void; the dead convert_value/explicit_cast helpers are removed. C. Correctness items (codex findings): - conj() on a Void Scalar throws (old PIMPL contract) instead of silently returning Void. - Sproxy's 11 per-type assignment overloads collapse to one CytnxType-constrained template delegating through Scalar. - Type_class::norm_result_dtype(Void) rejects instead of mapping to Double. The CytnxType concept is the documented one-liner from #979. - Stale "lossy in-place combinations throw" comment updated to the promote semantics. D. pytest: test_inplace_same_dtype_keeps_dtype pins Int32 operands via astype() to sidestep the preexisting pybind ctor-collapse quirk that #1014 fixes (workaround documented for removal). Verified: C++ suite 1676 ran / 1665 passed / 11 skipped / 0 failed (4 new Void-guard tests); pytests/scalar_variant_test.py 43/43 against the freshly built extension; clang-format 14.0.6. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e_features PR #979 adds the first C++20 concept (CytnxType) to a universally-included public header (Type.hpp). cytnx only sets C++20 at build level (CMAKE_CXX_STANDARD), which does not propagate through the installed/exported target, so find_package(cytnx) consumers compile the public headers in their default standard and fail to parse `concept` -- breaking DownstreamFindPackage. Add target_compile_features(cytnx PUBLIC cxx_std_20) so the exported target carries the requirement to consumers. This also closes a pre-existing latent gap: the public headers already require C++20 (std::format, std::source_location); the concept just makes it undeniable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@ianmccul You're right, and my two "cuKron still broken" notes were stale — I was building a base that predated the fix. #1004 fixed it: commit 7bbd814 rewrote the kernel line to compute in the output dtype ( |
|
@pcchen Confirmed and fixed in this PR (f346f7b). Applied your recommended fix on the target_compile_features(cytnx PUBLIC cxx_std_20)so |
… master master's #979 landed `concept CytnxType` in include/Type.hpp, while this branch had added its own equivalent one-liner in include/backend/Scalar.hpp (checklist item C). Merging master in left both definitions present, so every TU that pulls in Scalar.hpp (via Storage.hpp) failed to compile with "redefinition of template<class T> concept cytnx::CytnxType" -- the DownstreamFindPackage CI failure. The two definitions are semantically identical (Type.hpp's `variant_contains_v<T, Type_list> && !is_void_v<T>` == the 11 concrete types this branch enumerated). Scalar.hpp already includes Type.hpp, so drop the local copy and rely on the canonical one, exactly the "the other rebases" resolution flagged in the #1011 review when #979 lands first. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Each of
Storage_base::at<T>,back<T>, anddata<T>was 11 hand-written explicit specializations with identical bodies, differing only in the dtype enum and the type spelling inside the error message. That copy-paste already let one dtype check drift out of sync (#965, fixed by #978). This collapses each family into a single template body:Type_class::cy_typeid_v<T>, so the dtype check now exists in exactly one place per function and cannot drift per-type again.type_spelling<T>trait supplies the C++ type spelling embedded in the error messages ("<float>","< std::complex<double> >", ...), preserving the existing wording byte-for-byte.include/backend/Storage.hpp) unchanged.data<cuDoubleComplex>/data<cuFloatComplex>specializations keep their extra device check and remain hand-written.Net: −413/+82 lines, no functional change.
Stacked on #978 (base:
fix/storage-at-unconditional-dtype-check) since it touches the same lines; retarget tomasterafter that merges.Verification
nmdiff of the old vs new object file: all 33 mangled symbols identical (binding goes strong → weak, the normal consequence of explicit instantiation definitions; the body is only visible in this TU, so these remain the only definitions).back, anddatamismatch errors, linked against both the old and new implementation —what()message lines are byte-identical in every case. Only the# Cytnx error occur at ...diagnostic line changes rendering (template pretty-function instead of the specialization's), which nothing asserts on.StorageTest.AtDtypeMismatchThrows*andStorageTest.AtOutOfBoundMessageReportsIndexAndSizerun explicitly; Tensor suite 9/9.🤖 Generated with Claude Code