Catch2: Corrupt test expressions with MSVC + rvalues

There is a bug that manifests as failing CHECK(lhs == rhs) where (lhs == rhs) by itself would yield true. To reproduce, this seems to need the combination of all the following:

  1. Catch2, presumably its expression deconstruction,
  2. MSVC as of VS2017 in C++17 mode,
  3. rvalue-returning reinterpret_cast

In practice I use such constructs on containers with boost::units::quantity elements to get a view of the container with physical units of the elements stripped or changed.

Tested with VS2017, Catch2 v2.7.2 installed via vcpkg.

I have made a demo comprising the attached files:

  • CMakeLists.txt: For use with CMake and CTest
  • test2_catch2.cc.txt: Test code, remove the .txt extension before use
  • test2_catch2.log: CMake build and CTest output. The first line is the command issued in Git Bash. $CMAKE_TOOLCHAIN_FILE is the path to vcpkgs scripts/buildsystems/vcpkg.cmake.

I have tried to minimize the demo, except that I added code to also demonstrate versions that do not trigger the bug.

  • Needs MSVC: GCC9 works
  • Needs Catch2 expression deconstruction: Parenthesizing the test or applying unary + to the rvalue works.
  • Needs rvalues to trigger the bug: const lvalues work
  • Needs a sufficiently opaque rvalue: a mere std::move is not enough

You might ask: Is Catch2 or MSVC to blame for this? Well, MSVC seems to have a part in this, but Catch2’s expression deconstruction is involved as well. It makes expressions return different results when inside a CHECK macro. A test framework should not do that. If you want to support VS2017, you should look into this.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 17 (8 by maintainers)

Most upvoted comments

You don’t do conversions between pointers

Quoting §8.2.10 Reinterpret cast:

(11) A glvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. The result refers to the same object as the source glvalue, but with the specified type. [Note: That is, for lvalues, a reference cast reinterpret_cast<T&>(x) has the same effect as the conversion *reinterpret_cast<T*>(&x) with the built-in & and * operators (and similarly for reinterpret_cast<T&&>(x)). – end note] No temporary is created, no copy is made, and constructors or conversion functions are not called.

And, to preempt another round of objections: xvalues are both glvalues and rvalues (§6.10).