mstdn.io is one of the many independent Mastodon servers you can use to participate in the fediverse.

Administered by:

Server stats:

378
active users

@codewiz What would Rust do? Not very familiar with Rust but it seems like it would give you a compile error if the function were a const fn , according to doc.rust-lang.org/reference/co. If it weren't a const fn, you'd get a panic in debug mode and silent overflow in release? And if it were a const fn that returned Option/Error, then you'd be safe but have to unwrap at runtime I think. Would be nice if you could configure a const fn to cause a compile error when returning None/Error.

doc.rust-lang.orgConstant Evaluation - The Rust Reference

@jeeves I had this same question on my mind, and I wanted to write a testcase as soon as I find some time.

Feel free to beat me at it, and please share your code on Godbolt!

@codewiz I got it to error out at compile time: rust.godbolt.org/z/qhhTxb8hr. Had to change from calling the const fn in the argument list of println!() to first assigning the result of the const fn to a const variable, then calling println!() with that variable, so it would evaluate the const fn in a const context (defined at doc.rust-lang.org/reference/co).

rust.godbolt.orgCompiler Explorer - Rust (rustc 1.57.0)const fn increment(x: u8) -> u8 { x + 1 } pub fn main () { print!("blah"); const X: u8 = increment(255u8); print!("{}", X); }

@jeeves Followup on the unnecessarily hard problem of getting compile-time errors for overflows in conversions between std::chrono::duration constants:
godbolt.org/z/7E9MMMMfz

TL;DR: too complicated and unergonomic until C++ adds constexpr function parameters.

godbolt.orgCompiler Explorer - C++ (x86-64 clang (trunk))// Problem: constant initialization of duration types with compile-time overflow checks // Solution: add constexpr function parameters to C++: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1045r1.html // // Meanwhile, here are some kludgy alternatives: // Used only by Syntax 3 // // Helper for the templated constructor of Duration // Note that the last parameter is a non-type parameter template <typename ToRep, typename ToPeriod, typename Rep, typename Period, Rep rep> constexpr ToRep checked_rep_cast() { using CT = std::common_type_t<ToRep, Rep, std::intmax_t>; using F = std::ratio_divide<Period, ToPeriod>; static_assert(std::numeric_limits<CT>::max() / F::num >= rep, "overflow"); constexpr auto to = rep * F::num / F::den; static_assert(std::numeric_limits<ToRep>::max() >= to, "truncation"); return ToRep{static_cast<ToRep>(to)}; } // Forward declaration, needed by StaticDuration template <typename Rep, typename Period> struct Duration; // Type-tag for the templated conversion constructor template<Duration d> struct StaticDuration {}; // Equivalent to std::chrono::duration, but also a structural type template <typename Rep, typename Period = std::ratio<1>> struct Duration { using rep = Rep; using period = Period; constexpr Duration() = default; constexpr Duration(const Duration&) = default; constexpr Duration(Rep _r) : r(_r) {} // Statically checked conversion constructor from other Duration types: // - The non-type parameter d is a structural type (requires C++20) // - The constructor argument is a type tag for type deduction // // Usage: // constexpr DestDuration var(StaticDuration<FromDuration{42}>{}); // template<typename Rep2, typename Period2, Duration<Rep2, Period2> d> constexpr explicit Duration(StaticDuration<d>) : r(checked_rep_cast<Rep, Period, Rep2, Period2, d.count()>()) {} constexpr Rep count() const { return r; } // Public because structural types can't have private members Rep r; }; // Used only by Syntax 1 and Syntax 2. // // The second argument is a non-type structal type (C++20 or later) // std::chrono::duration won't work because it's not a structural type template <class ToDuration, Duration d> consteval ToDuration StaticDurationCast() { using FromRep = typename decltype(d)::rep; using FromPeriod = typename decltype(d)::period; using ToRep = typename ToDuration::rep; using ToPeriod = typename ToDuration::period; using CT = std::common_type_t<ToRep, FromRep, std::intmax_t>; using F = std::ratio_divide<FromPeriod, ToPeriod>; static_assert(std::numeric_limits<CT>::max() / F::num >= d.count(), "overflow"); constexpr auto r = d.count() * F::num / F::den; static_assert(std::numeric_limits<ToRep>::max() >= r, "truncation"); return ToDuration{static_cast<ToRep>(r)}; } // Test duration types: // - CpuDuration is a 32bit tick counter at 100MHz (wraps every ~21 seconds) // - Seconds is 64bit, like std::chrono::seconds using CpuPeriod = std::ratio<1, 100'000'000>; using CpuDuration = Duration<int32_t, CpuPeriod>; using Seconds = Duration<int64_t>; // Syntax 1: similar to duration_cast<To>(x), but simple implementation, but unergonomic at definition site constexpr CpuDuration d1 = StaticDurationCast<CpuDuration, Seconds{5}>(); // Verify compile-time checking for syntax 1: // error: static_assert failed due to requirement 'std::numeric_limits<int>::max() >= rep' "truncation" // constexpr CpuDuration d1ovf = StaticDurationCast<CpuDuration, Seconds{30}>(); // Syntax 2: Same as syntax 1, with a helper to make conversions slightly more ergonomic template <Duration d> consteval CpuDuration MakeCpuDuration() { return StaticDurationCast<CpuDuration, d>(); } constexpr CpuDuration d2 = MakeCpuDuration<Seconds{5}>(); // Verify compile-time checking for syntax 2: // error: static_assert failed due to requirement 'std::numeric_limits<int>::max() >= rep' "truncation" // constexpr CpuDuration d2ovf = MakeCpuDuration<Seconds(30)>(); // Syntax 3: templated constructor + tag-dispatching // // Compiles with clang 13, fails with g++ 11.2.1 (maybe this bug: https://gcc.gnu.org/PR98824) constexpr CpuDuration d3 = CpuDuration(StaticDuration<Seconds{5}>{}); // Verify compile-time checking for syntax 3: // error: static_assert failed due to requirement 'std::numeric_limits<int>::max() >= t' "truncation" // constexpr CpuDuration d3ovf = CpuDuration(StaticDuration<Seconds(30)>{}); // Syntax 4: slight improvement over Syntax 3 using an alias template to declare static seconds template<int N> using StaticSeconds = StaticDuration<Seconds{N}>; constexpr CpuDuration d4 = CpuDuration(StaticSeconds<5>{}); // Syntax 5 (TODO): operator""_sd returning StaticDuration<Duration<Rep,Period> d> int main() { // Show that all 3 methods produced the correct CpuDuration constant std::cout << "d1 = " << d1.count() << '\n'; std::cout << "d2 = " << d2.count() << '\n'; std::cout << "d3 = " << d3.count() << '\n'; std::cout << "d4 = " << d4.count() << '\n'; return 0; }

@codewiz could you have the conversion function return the equivalent of Rust's Result type and create a STATIC_UNWRAP macro that hides the template grossness?

@jeeves Not feasible in C++ for cases like initializing global constants and passing durations to constructors (without using exceptions!)

Bernie

Even in Rust nightly, support for constant evaluation is still very limited compared to C++20's:
doc.rust-lang.org/reference/it

At this time, you can't even pass float parameters or enums, let alone structs.

There's ongoing work to expand the capabilities of constant evaluation in Rust, but it will take years before they can close the gap with C++, which is not standing still:
open-std.org/jtc1/sc22/wg21/do

@jeeves

doc.rust-lang.orgGeneric parameters - The Rust Reference