Make macros constexpr-friendly (whenever possible)
- Dominant language
- C++
- Stars
- 21.5k
- Forks
- 3.5k
- Avg merge
- 3d 16h
- Merged PRs (30d)
- 2
Description
**Description**
To facilitate testing at compile-time, `Catch::ScopedMessage` should have a `constexpr` constructor that does nothing. Optionally make some other macros `constexpr`-friendly.
**Context**
I'm using LLVM's idiom of testing code both at compile time and runtime which looks something like this:
```cpp
constexpr auto do_test = [] {
INFO("foo");
CHECK(foo(1) == 4);
REQUIRE(foo(2) == 5);
return true;
};
do_test();
STATIC_CHECK(do_test());
```
Obviously, `CHECK` and `REQUIRE` don't support compile-time assertions, but it's trivial to create a wrapper in C++23:
```cpp
#define FRT_CHECK(...) do { \
if consteval { \
FR_PANIC_CHECK(__VA_ARGS__); \
} \
else { \
CHECK(__VA_ARGS__); \
} \
} while (false)
```
`REQUIRE` wrapper looks the same. `FR_PANIC_CHECK` is just a custom assertion macro that is always enabled and supports constexpr contexts.
The problem is that there is no way to wrap `INFO` macro. Simply branching by `if consteval` doesn't work because each branch creates a new scope, so `ScopedMessage` objects get destroyed before `CHECK` has a chance to run. I tried to do the following after looking into the `INTERNAL_CATCH_INFO` (and confirming Hyrum's Law in the process):
```cpp
#define FRT_INFO(...) \
const auto INTERNAL_CATCH_UNIQUE_NAME(scoped_message) = [&] { \
if consteval { \
return ::Catch::ScopedMessage{}; \
} \
else { \
return ::Catch::ScopedMessage{ \
::Catch::MessageBuilder( \
"INFO"##_catch_sr, \
CATCH_INTERNAL_LINEINFO, \
::Catch::ResultWas::Info \
) << (__VA_ARGS__) \
}; \
} \
}
```
It doesn't compile because there is no empty `constexpr` `ScopedMessage` constructor. Returning just an `int` instead of `ScopedMessage` also doesn't work since you can't have different return types in each `if consteval` branch.
Adding a `constexpr` constructor to `ScopedMessage` and classes of its members (`MessageInfo`, `SourceLineInfo`, `ResultWas::OfType`) would allow me to write `FRT_INFO`. Despite having a `std::string` member, creating an empty `ScopedMessage` object should compile as long as no memory allocation actually takes place.
**Additional context**
If I were to implement this, I would add constructors that accept some tag type:
```cpp
struct ConstexprInit {
explicit
ConstexprInit() = default;
};
class ScopedMessage {
public:
constexpr ScopedMessage( ConstexprInit ) { }
explicit ScopedMessage( MessageBuilder&& builder );
ScopedMessage( ScopedMessage& duplicate ) = delete;
ScopedMessage( ScopedMessage&& old ) noexcept;
CATCH_INTERNAL_CONSTEXPR_DTOR ~ScopedMessage() {
#if CATCH_INTERNAL_HAS_IF_CONSTEVAL
if !consteval {
#elif CATCH_INTERNAL_HAS_IS_CONSTANT_EVALUATED
if (!std::is_constant_evaluated()) {
#endif
destroyImpl();
#if CATCH_INTERNAL_HAS_IF_CONSTEVAL || CATCH_INTERNAL_HAS_IS_CONSTANT_EVALUATED
}
#endif
}
MessageInfo m_info;
bool m_moved = false;
};
```
This way, classes don't suddenly become default-constructible. Here is a [proof-of-concept](https://godbolt.org/z/9jTEcTvez) at Compiler Explorer. Works in C++11, C++20, and C++23 modes.
I would also appreciate to be able to use other macros at compile time. `SECTION` is not implementable, but `CHECK`, `REQUIRE`, and `INFO` would cover 90%+ of use cases.
Contributor guide
Research direction
Start by tracing INTERNAL_CATCH_INFO and ScopedMessage, then inspect the mentioned MessageInfo, SourceLineInfo, and ResultWas::OfType members. Use the linked Compiler Explorer proof of concept to compare C++11, C++20, and C++23 behavior; done means INFO can be wrapped in a constexpr context without changing runtime scope behavior, with CHECK and REQUIRE considered separately.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- testing
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100