summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaan De Meyer <daan.j.demeyer@gmail.com>2019-08-12 09:11:42 +0200
committerViktor Kirilov <vik.kirilov@gmail.com>2019-08-12 10:11:42 +0300
commit5e493d3f54eb389e3788ef431fba580d6bf6b141 (patch)
tree1c45284ac0e366b6babce88660e67f5d70da8975
parent5ee84b48c228e14f1f11d40a30cdd44894388dbc (diff)
Rework `INFO` lazy evaluation to use lambdas. (#270)
Instead of capturing everything by pointer, we capture the given expression into a lazily evaluated lambda which we store as a reference. This allows passing any expression to `INFO` including rvalues.
-rw-r--r--doc/markdown/logging.md23
-rw-r--r--doctest/doctest.h190
-rw-r--r--doctest/parts/doctest.cpp58
-rw-r--r--doctest/parts/doctest_fwd.h132
4 files changed, 72 insertions, 331 deletions
diff --git a/doc/markdown/logging.md b/doc/markdown/logging.md
index 2f4b851..ac5c8d8 100644
--- a/doc/markdown/logging.md
+++ b/doc/markdown/logging.md
@@ -4,7 +4,7 @@ Additional messages can be logged during a test case (safely even in [**concurre
## INFO()
-The ```INFO()``` macro allows heterogenous sequences of values to be streamed using the insertion operator (```<<```) in the same way that ```std::ostream```, ```std::cout```, etc support it.
+The ```INFO()``` macro allows heterogenous sequences of expressions to be streamed using the insertion operator (```<<```) in the same way that ```std::ostream```, ```std::cout```, etc support it.
```c++
INFO("The number is " << i);
@@ -12,29 +12,14 @@ INFO("The number is " << i);
This message will be relevant to all asserts after it in the current scope or in scopes nested in the current one and will be printed later only if an assert fails.
-Note that there is no initial ```<<``` - instead the insertion sequence is placed in parentheses.
-
-The message is **NOT** constructed right away - instead it gets lazily stringified only when needed. This means that rvalues (temporaries) cannot be passed to the ```INFO()``` macro. All literals are treated as rvalue references by the standard - except for C string literals (```"like this one"```). That means that even normal integer literals cannot be used directly - they need to be stored in a variable/constant before being passed to ```INFO()```. If C++14 is used (or Visual Studio 2017+) doctest provides the ```TO_LVALUE()``` macro to help in this regard - it turns any literal or constexpr value to an lvalue and can be used like this:
-
-```c++
-constexpr int foo() { return 42; }
-TEST_CASE("playing with literals and constexpr values") {
- INFO(TO_LVALUE(6) << " and this is a C string literal " << TO_LVALUE(foo()));
- CHECK(false);
-}
-```
-
-```TO_LVALUE()``` can also help in contexts where you might want to avoid a ```static constexpr``` member to be ODR-used - see [**```DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE```**](configuration.md#doctest_config_assertion_parameters_by_value).
+The expression is **NOT** evaluated right away - instead it gets lazily evaluated only when needed.
Some notes:
-- the lazy stringification means the values will be stringified when an assert fails and not at the point of capture - so the value might have changed
-- if the library is built with C++11 rvalue reference support (see [**```DOCTEST_CONFIG_WITH_RVALUE_REFERENCES```**](configuration.md#doctest_config_with_rvalue_references)) then deleted overloads are provided to prohibit rvalues from being captured in an **```INFO()```** call - since the lazy stringification actually caches pointers to the objects. For C++98 temporaries will again not work but there will be horrible compilation errors
-- the [**```DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK```**](configuration.md#doctest_config_num_captures_on_stack) config identifier can be used to control how much stack space is used to avoid heap allocations for the streaming macros
-- stream manipulators (from ```<iomanip>```) can be used but need to be created as local variables and used as lvalues
+- the lazy stringification means the expressions will be evaluated when an assert fails and not at the point of capture - so the value might have changed by then
- refer to the [**stringification**](stringification.md) page for information on how to teach doctest to stringify your types
-The lazy stringification and the stack usage means that in the common case when no asserts fail the code runs super fast. This makes it suitable even in loops - perhaps to log the iteration.
+The lazy evaluation means that in the common case when no asserts fail the code runs super fast. This makes it suitable even in loops - perhaps to log the iteration.
There is also the **```CAPTURE()```** macro which is a convenience wrapper of **```INFO()```**:
diff --git a/doctest/doctest.h b/doctest/doctest.h
index ca4987a..8d304ed 100644
--- a/doctest/doctest.h
+++ b/doctest/doctest.h
@@ -1528,107 +1528,27 @@ namespace detail {
DOCTEST_INTERFACE void toStream(std::ostream* s, int long long in);
DOCTEST_INTERFACE void toStream(std::ostream* s, int long long unsigned in);
- class DOCTEST_INTERFACE ContextBuilder
- {
- friend class ContextScope;
-
- struct DOCTEST_INTERFACE ICapture
- {
- DOCTEST_DELETE_COPIES(ICapture);
- ICapture();
- virtual ~ICapture();
- virtual void toStream(std::ostream*) const = 0;
- };
-
- template <typename T>
- struct Capture : public ICapture //!OCLINT destructor of virtual class
- {
- const T* capture;
-
- explicit Capture(const T* in)
- : capture(in) {}
- void toStream(std::ostream* s) const override { detail::toStream(s, *capture); }
- };
-
- struct DOCTEST_INTERFACE Chunk
- {
- char buf[sizeof(Capture<char>)] DOCTEST_ALIGNMENT(
- 2 * sizeof(void*)); // place to construct a Capture<T>
-
- DOCTEST_DECLARE_DEFAULTS(Chunk);
- DOCTEST_DELETE_COPIES(Chunk);
- };
+ // ContextScope base class used to allow implementing methods of ContextScope
+ // that don't depend on the template parameter in doctest.cpp.
+ class DOCTEST_INTERFACE ContextScopeBase : public IContextScope {
+ protected:
+ ContextScopeBase();
- struct DOCTEST_INTERFACE Node
- {
- Chunk chunk;
- Node* next;
-
- DOCTEST_DECLARE_DEFAULTS(Node);
- DOCTEST_DELETE_COPIES(Node);
- };
-
- Chunk stackChunks[DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK];
- int numCaptures = 0;
- Node* head = nullptr;
- Node* tail = nullptr;
-
- ContextBuilder(ContextBuilder& other);
-
- ContextBuilder& operator=(const ContextBuilder&) = delete;
-
- void stringify(std::ostream* s) const;
-
- public:
- ContextBuilder();
- ~ContextBuilder();
-
- template <typename T>
- DOCTEST_NOINLINE ContextBuilder& operator<<(T& in) {
- Capture<T> temp(&in);
-
- // construct either on stack or on heap
- // copy the bytes for the whole object - including the vtable because we cant construct
- // the object directly in the buffer using placement new - need the <new> header...
- if(numCaptures < DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK) {
- my_memcpy(stackChunks[numCaptures].buf, &temp, sizeof(Chunk));
- } else {
- auto curr = new Node;
- curr->next = nullptr;
- if(tail) {
- tail->next = curr;
- tail = curr;
- } else {
- head = tail = curr;
- }
-
- my_memcpy(tail->chunk.buf, &temp, sizeof(Chunk));
- }
- ++numCaptures;
- return *this;
- }
-
- template <typename T>
- ContextBuilder& operator<<(const T&&) {
- static_assert(deferred_false<T>::value,
- "Cannot pass temporaries or rvalues to the streaming operator because it "
- "caches pointers to the passed objects for lazy evaluation!");
- return *this;
- }
+ void destroy();
};
- class DOCTEST_INTERFACE ContextScope : public IContextScope
+ template <typename L> class DOCTEST_INTERFACE ContextScope : public ContextScopeBase
{
- ContextBuilder contextBuilder;
+ const L &lambda_;
public:
- explicit ContextScope(ContextBuilder& temp);
+ explicit ContextScope(const L &lambda) : lambda_(lambda) {}
- DOCTEST_DELETE_COPIES(ContextScope);
+ ContextScope(ContextScope &&other) : lambda_(other.lambda_) {}
- ~ContextScope() override;
+ void stringify(std::ostream* s) const override { lambda_(s); }
- void stringify(std::ostream* s) const override;
+ ~ContextScope() override { destroy(); }
};
struct DOCTEST_INTERFACE MessageBuilder : public MessageData
@@ -1650,6 +1570,11 @@ namespace detail {
bool log();
void react();
};
+
+ template <typename L>
+ ContextScope<L> MakeContextScope(const L &lambda) {
+ return ContextScope<L>(lambda);
+ }
} // namespace detail
#define DOCTEST_DEFINE_DECORATOR(name, type, def) \
@@ -2063,9 +1988,17 @@ int registerReporter(const char* name, int priority, bool isReporter) {
DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
// for logging
-#define DOCTEST_INFO(x) \
- doctest::detail::ContextScope DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_)( \
- doctest::detail::ContextBuilder() << x)
+#define DOCTEST_INFO(expression) \
+ DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), expression)
+
+#define DOCTEST_INFO_IMPL(lambda_name, mb_name, expression) \
+ auto lambda_name = [&](std::ostream* s) { \
+ doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \
+ mb_name.m_stream = s; \
+ mb_name << expression; \
+ }; \
+ auto DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope(lambda_name)
+
#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := " << x)
#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, x) \
@@ -2085,14 +2018,7 @@ int registerReporter(const char* name, int priority, bool isReporter) {
#define DOCTEST_FAIL_CHECK(x) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, x)
#define DOCTEST_FAIL(x) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, x)
-// hack for macros like INFO() that require lvalues
-#if __cplusplus >= 201402L || (DOCTEST_MSVC >= DOCTEST_COMPILER(19, 10, 0))
-template <class T, T x>
-constexpr T to_lvalue = x;
-#define DOCTEST_TO_LVALUE(...) to_lvalue<decltype(__VA_ARGS__), __VA_ARGS__>
-#else // TO_LVALUE
-#define DOCTEST_TO_LVALUE(...) TO_LVALUE_CAN_BE_USED_ONLY_IN_CPP14_MODE_OR_WITH_VS_2017_OR_NEWER
-#endif // TO_LVALUE
+#define DOCTEST_TO_LVALUE(...) __VA_ARGS__ // Not removed to keep backwards compatibility.
#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS
@@ -3977,64 +3903,17 @@ namespace detail {
DOCTEST_THREAD_LOCAL std::vector<IContextScope*> g_infoContexts; // for logging with INFO()
- ContextBuilder::ICapture::ICapture() = default;
- ContextBuilder::ICapture::~ICapture() = default;
-
- ContextBuilder::Chunk::Chunk() = default;
- ContextBuilder::Chunk::~Chunk() = default;
-
- ContextBuilder::Node::Node() = default;
- ContextBuilder::Node::~Node() = default;
-
- // steal the contents of the other - acting as a move constructor...
- ContextBuilder::ContextBuilder(ContextBuilder& other)
- : numCaptures(other.numCaptures)
- , head(other.head)
- , tail(other.tail) {
- other.numCaptures = 0;
- other.head = nullptr;
- other.tail = nullptr;
- memcpy(stackChunks, other.stackChunks,
- unsigned(int(sizeof(Chunk)) * DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK));
- }
-
- DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wcast-align")
- void ContextBuilder::stringify(std::ostream* s) const {
- int curr = 0;
- // iterate over small buffer
- while(curr < numCaptures && curr < DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK)
- reinterpret_cast<const ICapture*>(stackChunks[curr++].buf)->toStream(s);
- // iterate over list
- auto curr_elem = head;
- while(curr < numCaptures) {
- reinterpret_cast<const ICapture*>(curr_elem->chunk.buf)->toStream(s);
- curr_elem = curr_elem->next;
- ++curr;
- }
- }
- DOCTEST_GCC_SUPPRESS_WARNING_POP
-
- ContextBuilder::ContextBuilder() = default;
-
- ContextBuilder::~ContextBuilder() {
- // free the linked list - the ones on the stack are left as-is
- // no destructors are called at all - there is no need
- while(head) {
- auto next = head->next;
- delete head;
- head = next;
- }
- }
-
- ContextScope::ContextScope(ContextBuilder& temp)
- : contextBuilder(temp) {
+ ContextScopeBase::ContextScopeBase() {
g_infoContexts.push_back(this);
}
DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17
DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
- ContextScope::~ContextScope() {
+ // destroy cannot be inlined into the destructor because that would mean calling stringify after
+ // ContextScope has been destroyed (base class destructors run after derived class destructors).
+ // Instead, ContextScope calls this method directly from its destructor.
+ void ContextScopeBase::destroy() {
if(std::uncaught_exception()) {
std::ostringstream s;
this->stringify(&s);
@@ -4046,7 +3925,6 @@ namespace detail {
DOCTEST_GCC_SUPPRESS_WARNING_POP
DOCTEST_MSVC_SUPPRESS_WARNING_POP
- void ContextScope::stringify(std::ostream* s) const { contextBuilder.stringify(s); }
} // namespace detail
namespace {
using namespace detail;
diff --git a/doctest/parts/doctest.cpp b/doctest/parts/doctest.cpp
index cd04ff7..71d7e41 100644
--- a/doctest/parts/doctest.cpp
+++ b/doctest/parts/doctest.cpp
@@ -1272,64 +1272,17 @@ namespace detail {
DOCTEST_THREAD_LOCAL std::vector<IContextScope*> g_infoContexts; // for logging with INFO()
- ContextBuilder::ICapture::ICapture() = default;
- ContextBuilder::ICapture::~ICapture() = default;
-
- ContextBuilder::Chunk::Chunk() = default;
- ContextBuilder::Chunk::~Chunk() = default;
-
- ContextBuilder::Node::Node() = default;
- ContextBuilder::Node::~Node() = default;
-
- // steal the contents of the other - acting as a move constructor...
- ContextBuilder::ContextBuilder(ContextBuilder& other)
- : numCaptures(other.numCaptures)
- , head(other.head)
- , tail(other.tail) {
- other.numCaptures = 0;
- other.head = nullptr;
- other.tail = nullptr;
- memcpy(stackChunks, other.stackChunks,
- unsigned(int(sizeof(Chunk)) * DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK));
- }
-
- DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wcast-align")
- void ContextBuilder::stringify(std::ostream* s) const {
- int curr = 0;
- // iterate over small buffer
- while(curr < numCaptures && curr < DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK)
- reinterpret_cast<const ICapture*>(stackChunks[curr++].buf)->toStream(s);
- // iterate over list
- auto curr_elem = head;
- while(curr < numCaptures) {
- reinterpret_cast<const ICapture*>(curr_elem->chunk.buf)->toStream(s);
- curr_elem = curr_elem->next;
- ++curr;
- }
- }
- DOCTEST_GCC_SUPPRESS_WARNING_POP
-
- ContextBuilder::ContextBuilder() = default;
-
- ContextBuilder::~ContextBuilder() {
- // free the linked list - the ones on the stack are left as-is
- // no destructors are called at all - there is no need
- while(head) {
- auto next = head->next;
- delete head;
- head = next;
- }
- }
-
- ContextScope::ContextScope(ContextBuilder& temp)
- : contextBuilder(temp) {
+ ContextScopeBase::ContextScopeBase() {
g_infoContexts.push_back(this);
}
DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17
DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
- ContextScope::~ContextScope() {
+ // destroy cannot be inlined into the destructor because that would mean calling stringify after
+ // ContextScope has been destroyed (base class destructors run after derived class destructors).
+ // Instead, ContextScope calls this method directly from its destructor.
+ void ContextScopeBase::destroy() {
if(std::uncaught_exception()) {
std::ostringstream s;
this->stringify(&s);
@@ -1341,7 +1294,6 @@ namespace detail {
DOCTEST_GCC_SUPPRESS_WARNING_POP
DOCTEST_MSVC_SUPPRESS_WARNING_POP
- void ContextScope::stringify(std::ostream* s) const { contextBuilder.stringify(s); }
} // namespace detail
namespace {
using namespace detail;
diff --git a/doctest/parts/doctest_fwd.h b/doctest/parts/doctest_fwd.h
index 3c28460..fdfa0f8 100644
--- a/doctest/parts/doctest_fwd.h
+++ b/doctest/parts/doctest_fwd.h
@@ -1525,107 +1525,27 @@ namespace detail {
DOCTEST_INTERFACE void toStream(std::ostream* s, int long long in);
DOCTEST_INTERFACE void toStream(std::ostream* s, int long long unsigned in);
- class DOCTEST_INTERFACE ContextBuilder
- {
- friend class ContextScope;
-
- struct DOCTEST_INTERFACE ICapture
- {
- DOCTEST_DELETE_COPIES(ICapture);
- ICapture();
- virtual ~ICapture();
- virtual void toStream(std::ostream*) const = 0;
- };
-
- template <typename T>
- struct Capture : public ICapture //!OCLINT destructor of virtual class
- {
- const T* capture;
-
- explicit Capture(const T* in)
- : capture(in) {}
- void toStream(std::ostream* s) const override { detail::toStream(s, *capture); }
- };
-
- struct DOCTEST_INTERFACE Chunk
- {
- char buf[sizeof(Capture<char>)] DOCTEST_ALIGNMENT(
- 2 * sizeof(void*)); // place to construct a Capture<T>
-
- DOCTEST_DECLARE_DEFAULTS(Chunk);
- DOCTEST_DELETE_COPIES(Chunk);
- };
-
- struct DOCTEST_INTERFACE Node
- {
- Chunk chunk;
- Node* next;
-
- DOCTEST_DECLARE_DEFAULTS(Node);
- DOCTEST_DELETE_COPIES(Node);
- };
+ // ContextScope base class used to allow implementing methods of ContextScope
+ // that don't depend on the template parameter in doctest.cpp.
+ class DOCTEST_INTERFACE ContextScopeBase : public IContextScope {
+ protected:
+ ContextScopeBase();
- Chunk stackChunks[DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK];
- int numCaptures = 0;
- Node* head = nullptr;
- Node* tail = nullptr;
-
- ContextBuilder(ContextBuilder& other);
-
- ContextBuilder& operator=(const ContextBuilder&) = delete;
-
- void stringify(std::ostream* s) const;
-
- public:
- ContextBuilder();
- ~ContextBuilder();
-
- template <typename T>
- DOCTEST_NOINLINE ContextBuilder& operator<<(T& in) {
- Capture<T> temp(&in);
-
- // construct either on stack or on heap
- // copy the bytes for the whole object - including the vtable because we cant construct
- // the object directly in the buffer using placement new - need the <new> header...
- if(numCaptures < DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK) {
- my_memcpy(stackChunks[numCaptures].buf, &temp, sizeof(Chunk));
- } else {
- auto curr = new Node;
- curr->next = nullptr;
- if(tail) {
- tail->next = curr;
- tail = curr;
- } else {
- head = tail = curr;
- }
-
- my_memcpy(tail->chunk.buf, &temp, sizeof(Chunk));
- }
- ++numCaptures;
- return *this;
- }
-
- template <typename T>
- ContextBuilder& operator<<(const T&&) {
- static_assert(deferred_false<T>::value,
- "Cannot pass temporaries or rvalues to the streaming operator because it "
- "caches pointers to the passed objects for lazy evaluation!");
- return *this;
- }
+ void destroy();
};
- class DOCTEST_INTERFACE ContextScope : public IContextScope
+ template <typename L> class DOCTEST_INTERFACE ContextScope : public ContextScopeBase
{
- ContextBuilder contextBuilder;
+ const L &lambda_;
public:
- explicit ContextScope(ContextBuilder& temp);
+ explicit ContextScope(const L &lambda) : lambda_(lambda) {}
- DOCTEST_DELETE_COPIES(ContextScope);
+ ContextScope(ContextScope &&other) : lambda_(other.lambda_) {}
- ~ContextScope() override;
+ void stringify(std::ostream* s) const override { lambda_(s); }
- void stringify(std::ostream* s) const override;
+ ~ContextScope() override { destroy(); }
};
struct DOCTEST_INTERFACE MessageBuilder : public MessageData
@@ -1647,6 +1567,11 @@ namespace detail {
bool log();
void react();
};
+
+ template <typename L>
+ ContextScope<L> MakeContextScope(const L &lambda) {
+ return ContextScope<L>(lambda);
+ }
} // namespace detail
#define DOCTEST_DEFINE_DECORATOR(name, type, def) \
@@ -2060,9 +1985,17 @@ int registerReporter(const char* name, int priority, bool isReporter) {
DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
// for logging
-#define DOCTEST_INFO(x) \
- doctest::detail::ContextScope DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_)( \
- doctest::detail::ContextBuilder() << x)
+#define DOCTEST_INFO(expression) \
+ DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), expression)
+
+#define DOCTEST_INFO_IMPL(lambda_name, mb_name, expression) \
+ auto lambda_name = [&](std::ostream* s) { \
+ doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \
+ mb_name.m_stream = s; \
+ mb_name << expression; \
+ }; \
+ auto DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope(lambda_name)
+
#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := " << x)
#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, x) \
@@ -2082,14 +2015,7 @@ int registerReporter(const char* name, int priority, bool isReporter) {
#define DOCTEST_FAIL_CHECK(x) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, x)
#define DOCTEST_FAIL(x) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, x)
-// hack for macros like INFO() that require lvalues
-#if __cplusplus >= 201402L || (DOCTEST_MSVC >= DOCTEST_COMPILER(19, 10, 0))
-template <class T, T x>
-constexpr T to_lvalue = x;
-#define DOCTEST_TO_LVALUE(...) to_lvalue<decltype(__VA_ARGS__), __VA_ARGS__>
-#else // TO_LVALUE
-#define DOCTEST_TO_LVALUE(...) TO_LVALUE_CAN_BE_USED_ONLY_IN_CPP14_MODE_OR_WITH_VS_2017_OR_NEWER
-#endif // TO_LVALUE
+#define DOCTEST_TO_LVALUE(...) __VA_ARGS__ // Not removed to keep backwards compatibility.
#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS