1

I want to introduce strong types with error checking at compile time. For my chrono type, I noticed literals are silently narrowed when the underlying type changes from int64_t to int32_t, leading to overflows. So I introduced an explicit check.

However this check is not checked at compile time even for constant parameters, like delay_t {10s}, which cannot be represented.

#include <chrono> #include <cstdint> #include <stdexcept> struct delay_t { std::chrono::duration<int32_t, std::nano> value {}; constexpr explicit delay_t(std::chrono::duration<int64_t, std::nano> delay) : value {delay} { if (value != delay) { throw std::runtime_error("delay cannot be represented."); } }; }; auto foo(delay_t delay) -> void {} auto main() -> int { using namespace std::chrono_literals; foo(delay_t {10s}); // here I want a compile time error, // but I get a runtime error. return 0; } 

godbolt

This unfortunately compiles and leads to a runtime error. I verified that the literal operator"" s is a constexpr and it works using consteval in the delay_t constructor. I also want to use the type with runtime values, so that's not an option.

How can I tell the compiler above to evaluate constant literals like time_t {10s} at compile time? I am using C++20.

1
  • Maybe just don't use an int32_t for nanosecond precision? Commented Jan 5, 2023 at 15:27

3 Answers 3

5

constexpr is evaluated at compile time only in constant expression, not depending of argument.

You might split your expression as

constexpr delay_t delay{10s}; // Compile time evaluated, so error at compile time foo(delay); 
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you Jarod, this is indeed a very simple and direct solution. I posted an alternative, that doesn't require cooperation by the user of the class. I am not sure yet, if I will go with that, as it requires quite some boilerplate.
2

Pass it through a consteval function.

template<typename T> consteval T force_constexpr(const T& value) noexcept { return value; } 

You can use it like this:

foo(force_constexpr(delay_t {0s})); // No compile-time error foo(force_constexpr(delay_t {10s})); // Compile-time error 

1 Comment

Thank you Eric, I realized through your example that to have the compile-time check we need a consteval function. I combined your idea, while providing an alternative constructor. I am not sure with what I will go, but your solution is definitely an interesting one.
0

To force checks at compile time the construction from the chrono type needs to be consteval. There is no way around it.

consteval explicit delay_t(duration<int64_t, std::nano> delay) : delay_t {wrapped_{delay}} {}; 

To allow also runtime construction we needs an alternative way to construct delay_t. We can for example use a static method that is constexpr.

constexpr static auto runtime(duration<int64_t, std::nano> delay) -> delay_t { return delay_t {wrapped_ {delay}}; } 

As this method needs to construct delay_t in some different way, we need a second non-consteval constructor. But as we already have one for chrono, we need to introduce a private wrapper type:

private: struct wrapped_ { duration<int64_t, std::nano> value; }; constexpr delay_t(wrapped_ delay) : value {delay.value} { if (value != delay.value) { throw std::runtime_error("delay cannot be represented."); } }; 

Using delay_t now enforces compile-time checks by default:

foo(delay_t {1s}); foo(delay_t {10s}); // gives compile time error foo(delay_t::runtime(10s)); // gives a runtime error 

Full example on godbolt

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.