catchorg / catchorg/Catch2

Make macros constexpr-friendly (whenever possible)

Open
#3,185 2 comments 0 reactions 0 assignees View on GitHub
Feature Request
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.