qtty-cpp 0.4.5
Header-only C++ wrapper for qtty
Loading...
Searching...
No Matches
ffi_core.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: BSD-3-Clause
2// Copyright (C) 2026 Vallés Puig, Ramon
3
4#pragma once
5
11#include <algorithm>
12#include <cmath>
13#include <iomanip>
14#include <iostream>
15#include <limits>
16#include <optional>
17#include <ostream>
18#include <sstream>
19#include <stdexcept>
20#include <string>
21#include <string_view>
22#include <type_traits>
23extern "C" {
24#include "qtty_ffi.h"
25}
26
27namespace qtty {
28
29// ============================================================================
30// Exception Hierarchy
31// ============================================================================
32// All qtty exceptions derive from std::runtime_error for compatibility with
33// standard exception handling practices. The hierarchy provides fine-grained
34// error types for different failure modes when interacting with the FFI layer.
35
36// Exception types for error handling
40class QttyException : public std::runtime_error {
41public:
42 explicit QttyException(const std::string &msg) : std::runtime_error(msg) {}
43};
44
49public:
50 explicit InvalidUnitError(const std::string &msg) : QttyException(msg) {}
51};
52
57public:
58 explicit IncompatibleDimensionsError(const std::string &msg) : QttyException(msg) {}
59};
60
65public:
66 explicit NullPointerError(const std::string &msg) : QttyException(msg) {}
67};
68
73public:
74 explicit ConversionError(const std::string &msg) : QttyException(msg) {}
75};
76
77// ============================================================================
78// Error Translation from C FFI to C++ Exceptions
79// ============================================================================
80// Maps C-style integer error codes from the Rust FFI layer to typed C++
81// exceptions. This provides idiomatic error handling for C++ users while
82// maintaining compatibility with the C FFI boundary.
83
84// Helper function to check status and throw appropriate exceptions
91inline void check_status(QttyStatus status, const char *operation) {
92 if (status == QTTY_STATUS_OK) {
93 return;
94 }
95
96 std::string msg = std::string(operation) + " failed: ";
97 switch (status) {
99 throw InvalidUnitError(msg + "unknown unit");
101 throw IncompatibleDimensionsError(msg + "incompatible dimensions");
103 throw NullPointerError(msg + "null output pointer");
105 throw QttyException(msg + "output buffer too small");
106 default:
107 throw QttyException(msg + "unknown error");
108 }
109}
110
111// ============================================================================
112// Forward Declarations and Type Traits
113// ============================================================================
114
115// Forward declarations
116template <typename UnitTag> class Quantity;
117template <typename NumeratorTag, typename DenominatorTag> struct CompoundTag;
118
119// Type trait to detect compound (derived) unit tags
120template <typename T> struct is_compound : std::false_type {};
121template <typename N, typename D> struct is_compound<CompoundTag<N, D>> : std::true_type {};
122template <typename T> inline constexpr bool is_compound_v = is_compound<T>::value;
123
124// Template trait to get unit ID from unit tag
125// Each unit tag (e.g., MeterTag) must specialize this template to provide
126// its corresponding C FFI unit ID constant (e.g., UNIT_ID_METER).
127// Specializations are auto-generated in include/qtty/units/*.hpp
128//
129// For compound tags, specializations provide numerator_unit_id() and
130// denominator_unit_id() instead of a single unit_id().
131template <typename UnitTag> struct UnitTraits {
132 // Default symbol returns empty string. Specialize for units that have
133 // symbols.
134 static constexpr std::string_view symbol() { return ""; }
135};
136
137// Detect whether a unit tag's UnitTraits specialization exposes dimension().
138template <typename Tag, typename = void> struct has_dimension : std::false_type {};
139template <typename Tag>
140struct has_dimension<Tag, std::void_t<decltype(UnitTraits<Tag>::dimension())>> : std::true_type {};
141
142// True for simple unit tags whose dimension is Dimensionless. Compound tags and
143// tags without a generated dimension() are treated as non-dimensionless.
144template <typename Tag> constexpr bool is_dimensionless_tag() {
145 if constexpr (has_dimension<Tag>::value) {
147 } else {
148 return false;
149 }
150}
151template <typename Tag> inline constexpr bool is_dimensionless_v = is_dimensionless_tag<Tag>();
152
153// Helper to extract tag from either a tag or Quantity<Tag>
154// This allows .to<>() to accept both Quantity<KilometerTag> and KilometerTag,
155// making the API more flexible and user-friendly.
156template <typename T> struct ExtractTag {
157 using type = T;
158};
159
160template <typename Tag> struct ExtractTag<Quantity<Tag>> {
161 using type = Tag;
162};
163
164// ============================================================================
165// Quantity Template Class
166// ============================================================================
167// The core abstraction representing a physical quantity with compile-time
168// type safety. Each instantiation (e.g., Quantity<MeterTag>) is a distinct
169// type, preventing accidental mixing of incompatible units at compile time.
170//
171// Key Design Decisions:
172// - Header-only for zero-cost abstraction (all code inlined)
173// - constexpr constructors enable compile-time quantity creation
174// - explicit constructor prevents implicit double-to-Quantity conversions
175// - Conversions go through the Rust FFI layer for correctness
176
177// Base Quantity template class
178template <typename UnitTag> class Quantity {
179private:
180 double m_value;
181
182public:
184
185 // Constructors
186 constexpr Quantity() : m_value(0.0) {}
187 constexpr explicit Quantity(double value) : m_value(value) {}
188
189 // Get the unit ID for this quantity type
190 static constexpr UnitId unit_id() { return UnitTraits<UnitTag>::unit_id(); }
191
192 // Get the raw value
193 constexpr double value() const { return m_value; }
194
195 // Get a reference to the raw value (mirrors Rust `value_ref`).
196 constexpr const double &value_ref() const { return m_value; }
197
198 // Strip the unit and return the underlying scalar (mirrors Rust
199 // `erase_unit_raw`). Semantically identical to `value()` but named to make
200 // the intent of discarding dimensional information explicit.
201 constexpr double erase_unit_raw() const { return m_value; }
202
203 // ========================================================================
204 // Scalar Constructors / Constants (mirror Rust associated constants)
205 // ========================================================================
206
207 // Additive / multiplicative identities.
208 static constexpr Quantity zero() { return Quantity(0.0); }
209 static constexpr Quantity one() { return Quantity(1.0); }
210
211 // IEEE-754 special values (mirror Rust `NAN`, `INFINITY`, `NEG_INFINITY`).
212 static Quantity nan() { return Quantity(std::numeric_limits<double>::quiet_NaN()); }
213 static Quantity infinity() { return Quantity(std::numeric_limits<double>::infinity()); }
214 static Quantity neg_infinity() { return Quantity(-std::numeric_limits<double>::infinity()); }
215
216 // ========================================================================
217 // Unit Conversion
218 // ========================================================================
219 // Converts this quantity to a different unit of the same dimension.
220 // The conversion is performed by the Rust qtty-ffi library to ensure
221 // correctness and consistency with the authoritative conversion factors.
222 //
223 // Accepts either a tag type (e.g., KilometerTag) or a Quantity type
224 // (e.g., Kilometer) for convenience, thanks to the ExtractTag helper.
225 //
226 // Throws IncompatibleDimensionsError if attempting to convert between
227 // different dimensions (e.g., length to time).
228
229 // Convert to another unit type (accepts either Tag or Quantity<Tag>)
230 template <typename TargetType> Quantity<typename ExtractTag<TargetType>::type> to() const {
232
233 if constexpr (is_compound_v<UnitTag>) {
234 // Compound → compound conversion via qtty_derived_convert
235 static_assert(is_compound_v<TargetTag>, "Cannot convert compound unit to simple unit");
238
241 check_status(status, "Creating derived source quantity");
242
245 check_status(status, "Converting derived units");
246
247 return Quantity<TargetTag>(dst_qty.value);
248 } else {
249 // Simple unit conversion via qtty_quantity_convert
252
254 check_status(status, "Creating source quantity");
255
257 check_status(status, "Converting units");
258
259 return Quantity<TargetTag>(dst_qty.value);
260 }
261 }
262
263 // ========================================================================
264 // Arithmetic Operators (Same Unit)
265 // ========================================================================
266 // Addition and subtraction only work between quantities of the same unit.
267 // This enforces dimensional correctness at compile time. To add quantities
268 // of different units, explicitly convert one to match the other first.
269
270 // Arithmetic operators - same unit
271 Quantity operator+(const Quantity &other) const { return Quantity(m_value + other.m_value); }
272
273 Quantity operator-(const Quantity &other) const { return Quantity(m_value - other.m_value); }
274
275 // ========================================================================
276 // Scalar Operations
277 // ========================================================================
278 // Multiplying or dividing a quantity by a scalar preserves the unit.
279 // E.g., 10 meters * 2 = 20 meters
280
281 // Scalar multiplication and division
282 Quantity operator*(double scalar) const { return Quantity(m_value * scalar); }
283
284 Quantity operator/(double scalar) const { return Quantity(m_value / scalar); }
285
286 // Friend function for scalar * quantity
287 friend Quantity operator*(double scalar, const Quantity &q) { return q * scalar; }
288
289 // ========================================================================
290 // Comparison Operators
291 // ========================================================================
292 // All standard comparison operators are provided for convenience.
293 // Comparisons only work between quantities of the same unit type,
294 // enforcing type safety at compile time.
295
296 // Comparison operators
297 bool operator==(const Quantity &other) const { return m_value == other.m_value; }
298
299 bool operator!=(const Quantity &other) const { return m_value != other.m_value; }
300
301 bool operator<(const Quantity &other) const { return m_value < other.m_value; }
302
303 bool operator>(const Quantity &other) const { return m_value > other.m_value; }
304
305 bool operator<=(const Quantity &other) const { return m_value <= other.m_value; }
306
307 bool operator>=(const Quantity &other) const { return m_value >= other.m_value; }
308
309 // ========================================================================
310 // Compound Assignment Operators
311 // ========================================================================
312 // Modify the quantity in place for efficiency.
313
314 // Compound assignment operators
316 m_value += other.m_value;
317 return *this;
318 }
319
321 m_value -= other.m_value;
322 return *this;
323 }
324
326 m_value *= scalar;
327 return *this;
328 }
329
331 m_value /= scalar;
332 return *this;
333 }
334
335 // ========================================================================
336 // Unary Operators and Utilities
337 // ========================================================================
338
339 // Unary operators
340 Quantity operator-() const { return Quantity(-m_value); }
341
342 Quantity abs() const { return Quantity(std::abs(m_value)); }
343
344 // ========================================================================
345 // Scalar Reductions (same unit) — mirror Rust `min`/`max`/`clamp`/`mean`
346 // ========================================================================
347
348 Quantity min(const Quantity &other) const { return Quantity(std::min(m_value, other.m_value)); }
349
350 Quantity max(const Quantity &other) const { return Quantity(std::max(m_value, other.m_value)); }
351
353 return Quantity(std::max(min_val.m_value, std::min(m_value, max_val.m_value)));
354 }
355
356 // Arithmetic mean of two same-unit quantities.
357 Quantity mean(const Quantity &other) const { return Quantity((m_value + other.m_value) * 0.5); }
358
359 // ========================================================================
360 // Floating-point Predicates — mirror Rust `is_nan`/`is_infinite`/`is_finite`
361 // ========================================================================
362
363 bool is_nan() const { return std::isnan(m_value); }
364 bool is_infinite() const { return std::isinf(m_value); }
365 bool is_finite() const { return std::isfinite(m_value); }
366
367 // ========================================================================
368 // Scalar Math — mirror Rust `signum`/`scalar_sqrt`/rounding helpers
369 // ========================================================================
370
371 // Sign of the underlying scalar (raw, unitless). Mirrors Rust `signum`.
372 double signum() const { return std::copysign(1.0, m_value); }
373
374 // Square root of the underlying scalar, returned raw (unitless). Mirrors
375 // Rust `scalar_sqrt` (NOT the dimension-changing `sqrt`).
376 double scalar_sqrt() const { return std::sqrt(m_value); }
377
378 // Rounding helpers preserve the unit (mirror Rust).
379 Quantity floor() const { return Quantity(std::floor(m_value)); }
380 Quantity ceil() const { return Quantity(std::ceil(m_value)); }
381 Quantity round() const { return Quantity(std::round(m_value)); }
382 Quantity trunc() const { return Quantity(std::trunc(m_value)); }
383 Quantity fract() const { return Quantity(m_value - std::trunc(m_value)); }
384
385 // Euclidean remainder by a scalar (mirrors Rust `rem_euclid`).
386 Quantity rem_euclid(double rhs) const {
387 double r = std::fmod(m_value, rhs);
388 if (r < 0.0) {
389 r += std::abs(rhs);
390 }
391 return Quantity(r);
392 }
393
394 // ========================================================================
395 // Unit-aware Comparison — mirror Rust `eq_unit`/`cmp_unit`
396 // ========================================================================
397 // Compare against a quantity expressed in a different unit of the same
398 // dimension. The other quantity is converted into this unit (through the
399 // Rust FFI) before comparison, so the comparison is value-correct across
400 // units. Throws IncompatibleDimensionsError if the dimensions differ.
401
402 template <typename V> bool eq_unit(const Quantity<V> &other) const {
403 return m_value == other.template to<UnitTag>().value();
404 }
405
406 // Returns -1, 0 or 1 when ordered, or std::nullopt when either operand is
407 // NaN (mirrors Rust `cmp_unit` returning `Option<Ordering>`).
408 template <typename V> std::optional<int> cmp_unit(const Quantity<V> &other) const {
409 double rhs = other.template to<UnitTag>().value();
410 if (std::isnan(m_value) || std::isnan(rhs)) {
411 return std::nullopt;
412 }
413 if (m_value < rhs) {
414 return -1;
415 }
416 if (m_value > rhs) {
417 return 1;
418 }
419 return 0;
420 }
421
422 // ========================================================================
423 // String Formatting
424 // ========================================================================
425 // Format the quantity as a human-readable string, mirroring Rust's format
426 // annotations. The mapping is:
427 //
428 // Rust C++
429 // {} format()
430 // {:.2} format(2)
431 // {:e} format(-1, QTTY_FMT_LOWER_EXP)
432 // {:.4e} format(4, QTTY_FMT_LOWER_EXP)
433 // {:E} format(-1, QTTY_FMT_UPPER_EXP)
434 // {:.4E} format(4, QTTY_FMT_UPPER_EXP)
435 //
436 // The formatting logic lives in the Rust qtty-ffi library, so precision
437 // semantics are identical on both sides of the FFI boundary.
438
454 std::string format(int precision = -1, uint32_t flags = QTTY_FMT_DEFAULT) const {
457 check_status(make_status, "format: creating quantity");
458
459 char buf[512];
462 // Retry with a generous large buffer (quantities should never need this)
463 char big_buf[4096];
465 if (result < 0) {
466 throw QttyException("format: buffer too small even at 4096 bytes");
467 }
468 return std::string(big_buf);
469 }
470 if (result < 0) {
471 check_status(result, "format: formatting quantity");
472 }
473 return std::string(buf);
474 }
475};
476
477// ============================================================================
478// Stream Insertion Operator
479// ============================================================================
480// Prints a quantity with its unit symbol, e.g., "1500 m" or "42.5 km".
481//
482// Because this streams `q.value()` (a plain double) directly into the
483// `std::ostream`, all standard stream format manipulators are respected:
484//
485// std::cout << std::fixed << std::setprecision(2) << qty; // "1234.57 m"
486// std::cout << std::scientific << qty; // "1.23457e+003
487// m" std::cout << std::scientific << std::setprecision(4)
488// << qty; // "1.2346e+003
489// m"
490//
491// For `std::format` (C++20) see the std::formatter specialisation below.
492
493template <typename UnitTag> std::ostream &operator<<(std::ostream &os, const Quantity<UnitTag> &q) {
494 os << q.value() << " " << UnitTraits<UnitTag>::symbol();
495 return os;
496}
497
498} // namespace qtty
499
500// ============================================================================
501// C++20 std::formatter specialisation
502// ============================================================================
503// Allows `std::format` and `std::print` to be used with any Quantity type,
504// honouring the same format specifiers as std::formatter<double>:
505//
506// std::format("{}", qty) → "1234.56789 s"
507// std::format("{:.2f}", qty) → "1234.57 s"
508// std::format("{:e}", qty) → "1.23457e+03 s"
509// std::format("{:.4e}", qty) → "1.2346e+03 s"
510// std::format("{:E}", qty) → "1.23457E+03 s"
511// std::format("{:>15.2f}", qty) → " 1234.57 s" (number padded, not
512// symbol)
513//
514// Note: width / fill / align specifications are applied to the numeric part
515// only; the unit symbol is always appended directly after without padding.
516// This mirrors the behaviour of the Rust Display/LowerExp/UpperExp impls.
517
518#if __cplusplus >= 202002L
519#include <format>
520
521namespace std {
522
523template <typename UnitTag> struct formatter<qtty::Quantity<UnitTag>> {
524private:
525 std::formatter<double> double_fmt_;
526
527public:
529 template <typename ParseContext> constexpr auto parse(ParseContext &ctx) {
530 return double_fmt_.parse(ctx);
531 }
532
535 template <typename FormatContext>
536 auto format(const qtty::Quantity<UnitTag> &qty, FormatContext &ctx) const {
537 auto out = double_fmt_.format(qty.value(), ctx);
538 return std::format_to(out, " {}", qtty::UnitTraits<UnitTag>::symbol());
539 }
540};
541
542} // namespace std
543#endif // __cplusplus >= 202002L
Raised when value conversion fails at the FFI boundary.
Definition ffi_core.hpp:72
ConversionError(const std::string &msg)
Definition ffi_core.hpp:74
Raised when mixing incompatible dimensions in conversion/arithmetic.
Definition ffi_core.hpp:56
IncompatibleDimensionsError(const std::string &msg)
Definition ffi_core.hpp:58
Raised when an unknown or invalid unit identifier is used.
Definition ffi_core.hpp:48
InvalidUnitError(const std::string &msg)
Definition ffi_core.hpp:50
Raised when a required output pointer was null.
Definition ffi_core.hpp:64
NullPointerError(const std::string &msg)
Definition ffi_core.hpp:66
Base exception for all qtty wrapper failures.
Definition ffi_core.hpp:40
QttyException(const std::string &msg)
Definition ffi_core.hpp:42
constexpr Quantity()
Definition ffi_core.hpp:186
static Quantity infinity()
Definition ffi_core.hpp:213
Quantity trunc() const
Definition ffi_core.hpp:382
bool is_finite() const
Definition ffi_core.hpp:365
bool is_infinite() const
Definition ffi_core.hpp:364
Quantity & operator*=(double scalar)
Definition ffi_core.hpp:325
bool operator<=(const Quantity &other) const
Definition ffi_core.hpp:305
static constexpr Quantity zero()
Definition ffi_core.hpp:208
Quantity round() const
Definition ffi_core.hpp:381
static Quantity neg_infinity()
Definition ffi_core.hpp:214
constexpr const double & value_ref() const
Definition ffi_core.hpp:196
bool operator!=(const Quantity &other) const
Definition ffi_core.hpp:299
Quantity mean(const Quantity &other) const
Definition ffi_core.hpp:357
bool operator<(const Quantity &other) const
Definition ffi_core.hpp:301
Quantity max(const Quantity &other) const
Definition ffi_core.hpp:350
double signum() const
Definition ffi_core.hpp:372
std::string format(int precision=-1, uint32_t flags=QTTY_FMT_DEFAULT) const
Format this quantity as a string.
Definition ffi_core.hpp:454
Quantity operator+(const Quantity &other) const
Definition ffi_core.hpp:271
bool is_nan() const
Definition ffi_core.hpp:363
static Quantity nan()
Definition ffi_core.hpp:212
Quantity abs() const
Definition ffi_core.hpp:342
Quantity rem_euclid(double rhs) const
Definition ffi_core.hpp:386
bool operator==(const Quantity &other) const
Definition ffi_core.hpp:297
double scalar_sqrt() const
Definition ffi_core.hpp:376
constexpr double value() const
Definition ffi_core.hpp:193
Quantity ceil() const
Definition ffi_core.hpp:380
Quantity operator*(double scalar) const
Definition ffi_core.hpp:282
Quantity operator-(const Quantity &other) const
Definition ffi_core.hpp:273
Quantity fract() const
Definition ffi_core.hpp:383
Quantity operator-() const
Definition ffi_core.hpp:340
Quantity & operator+=(const Quantity &other)
Definition ffi_core.hpp:315
Quantity floor() const
Definition ffi_core.hpp:379
constexpr double erase_unit_raw() const
Definition ffi_core.hpp:201
Quantity & operator-=(const Quantity &other)
Definition ffi_core.hpp:320
Quantity & operator/=(double scalar)
Definition ffi_core.hpp:330
bool operator>(const Quantity &other) const
Definition ffi_core.hpp:303
Quantity operator/(double scalar) const
Definition ffi_core.hpp:284
std::optional< int > cmp_unit(const Quantity< V > &other) const
Definition ffi_core.hpp:408
static constexpr Quantity one()
Definition ffi_core.hpp:209
static constexpr UnitId unit_id()
Definition ffi_core.hpp:190
friend Quantity operator*(double scalar, const Quantity &q)
Definition ffi_core.hpp:287
constexpr Quantity(double value)
Definition ffi_core.hpp:187
Quantity min(const Quantity &other) const
Definition ffi_core.hpp:348
UnitTag unit_tag
Definition ffi_core.hpp:183
bool eq_unit(const Quantity< V > &other) const
Definition ffi_core.hpp:402
bool operator>=(const Quantity &other) const
Definition ffi_core.hpp:307
Quantity clamp(const Quantity &min_val, const Quantity &max_val) const
Definition ffi_core.hpp:352
Quantity< typename ExtractTag< TargetType >::type > to() const
Definition ffi_core.hpp:230
constexpr bool is_angular_v
Definition angles.hpp:49
constexpr bool is_compound_v
Definition ffi_core.hpp:122
void check_status(QttyStatus status, const char *operation)
Convert qtty FFI status codes into typed C++ exceptions.
Definition ffi_core.hpp:91
constexpr bool is_dimensionless_v
Definition ffi_core.hpp:151
constexpr bool is_dimensionless_tag()
Definition ffi_core.hpp:144
std::ostream & operator<<(std::ostream &os, const Quantity< UnitTag > &q)
Definition ffi_core.hpp:493
static constexpr std::string_view symbol()
Definition ffi_core.hpp:134