- Notifications
You must be signed in to change notification settings - Fork51
The most over-engineered C++ assertion library
License
jeremy-rifkin/libassert
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
The most over-engineered C++ assertion library
- 30-Second Overview
- Philosophy
- Features
- Methodology
- Considerations
- In-Depth Library Documentation
- Integration with Test Libraries
- ABI Versioning
- Usage
- Platform Logistics
- Replacing <cassert>
- FAQ
- Cool projects using libassert
- Comparison With Other Languages
Library philosophy: Provide as much helpful diagnostic info as possible.
Some of the awesome things the library does:
#include<libassert/assert.hpp>voidzoog(const std::map<std::string,int>& map) {DEBUG_ASSERT(map.contains("foo"),"expected key not found", map);}
ASSERT(vec.size() > min_items(), "vector doesn't have enough items", vec);std::optional<float>get_param();float f = *ASSERT_VAL(get_param());
Types of assertions:
Conditional assertions:
DEBUG_ASSERT: Checked in debug but does nothing in release (analogous to the standard library'sassert)ASSERT: Checked in both debug and releaseASSUME: Checked in debug and serves as an optimization hint in release
Unconditional assertions:
PANIC: Triggers in both debug and releaseUNREACHABLE: Triggers in debug, marked as unreachable in release
Prefer lowecaseassert?
You can enable the lowercasedebug_assert andassert aliases with-DLIBASSERT_LOWERCASE.
Summary of features:
- Automatic decomposition of assertion expressions without macros such as
ASSERT_EQetc. - Assertion messages
- Arbitrary extra diagnostics
- Syntax highlighting
- Stack traces
DEBUG_ASSERT_VALandASSERT_VALvariants that return a value so they can be integrated seamlessly into code, e.g.FILE* f = ASSERT_VAL(fopen(path, "r") != nullptr)- Smart literal formatting
- Stringification of user-defined types
- Custom failure handlers
- Catch2/Gtest integrations
- {fmt} support
- Programatic breakpoints on assertion failures for more debugger-friendly assertions, more infobelow
include(FetchContent)FetchContent_Declare( libassert GIT_REPOSITORY https://github.com/jeremy-rifkin/libassert.git GIT_TAG v2.2.1# <HASH or TAG>)FetchContent_MakeAvailable(libassert)target_link_libraries(your_target libassert::assert)# On windows copy libassert.dll to the same directory as the executable for your_targetif(WIN32) add_custom_command(TARGET your_target POST_BUILDCOMMAND${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:libassert::assert> $<TARGET_FILE_DIR:your_target> )endif()
Be sure to configure with-DCMAKE_BUILD_TYPE=Debug or-DDCMAKE_BUILD_TYPE=RelWithDebInfo for symbols and lineinformation.
On macOS it is recommended to generate a .dSYM file, seePlatform Logistics below.
For other ways to use the library, such as through package managers or a system-wide installation, seeUsagebelow.
Fundamentally the role of assertions is to verify assumptions made in software and identify violations close to theirsources. Assertion tooling should prioritize providing as much information and context to the developer as possible toallow for speedy triage. Unfortunately, existing language and library tooling provides very limited triage information.
For example with stdlib assertions an assertion such asassert(n <= 12); provides no information upon failure aboutwhy it failed or what led to its failure. Providing a stack trace and the value ofn greatly improves triage anddebugging. Ideally an assertion failure should provide enough diagnostic information that the programmmer doesn't haveto rerun in a debugger to pinpoint the problem.
Version 1 of this library was an exploration looking at how much helpful information and functionality could be packedinto assertions while also providing a quick and easy interface for the developer.
Version 2 of this library takes lessons learned from version 1 to create a tool that I personally have foundindispensable in development.
The most important feature this library supports is automatic expression decomposition. No need forASSERT_LT or othersuch hassle,assert(vec.size() > 10); is automatically understood, as showcased above.
Values involved in assert expressions are displayed. Redundant diagnostics like2 => 2 are avoided.
DEBUG_ASSERT(map.count(1) == 2);
Only the full assert expression is able to be extracted from a macro call. Showing which parts of the expressioncorrespond to what values requires some basic expression parsing. C++ grammar is ambiguous but most expressions can bedisambiguated.
All assertions in this library support optional diagnostic messages as well as arbitrary other diagnostic messages.
FILE* f = ASSERT_VAL(fopen(path,"r") !=nullptr,"Internal error with foobars", errno, path);
Special handling is provided forerrno, and strerror is automatically called.
Note: Extra diagnostics are only evaluated in the failure path of an assertion.
A lot of work has been put into generating pretty stack traces and formatting them as nicely as possible.Cpptrace is used as a portable and self-contained solution for stacktracespre-C++23. Optional configurations can be found in the library's documentation.
One feature worth noting is that instead of always printing full paths, only the minimum number of directories needed todifferentiate paths are printed.
Another feature worth pointing out is that the stack traces will fold traces with deep recursion:
The assertion handler applies syntax highlighting wherever appropriate, as seen in all thescreenshots above. This is to help enhance readability.
Libassert can provide diff highlighting on output:
This is opt-in withlibassert::set_diff_highlighting(true);
Libassert supports custom assertion failure handlers:
voidhandler(const assertion_info& info) {throwstd::runtime_error("Assertion failed:\n" + assertion.to_string());}intmain() {libassert::set_failure_handler(handler);}
More detailsbelow.
A lot of care is given to producing debug stringifications of values as effectively as possible: Strings, characters,numbers, should all be printed as you'd expect. Additionally containers, tuples, std::optional, smart pointers, etc. areall stringified to show as much information as possible. If a user defined type overloadsoperator<<(std::ostream& o, const S& s), that overload will be called. Otherwise it a default message will be printed. Additionally, astringification customization point is provided:
template<>structlibassert::stringifier<MyObject> { std::stringstringify(const MyObject& type) {return ...; }};
Assertion values are printed in hex or binary as well as decimal if hex/binary are used on eitherside of an assertion expression:
ASSERT(get_mask() == 0b00001101);Because expressions are already being automatically decomposed, you can opt into having signed-unsigned comparisons doneautomatically done with sign safety with-DLIBASSERT_SAFE_COMPARISONS:
ASSERT(18446744073709551606ULL == -10);
Libassert provides two headers<libassert/assert-catch2.hpp> and<libassert/assert-gtest.hpp> for use with catch2and GoogleTest.
Example output from gtest:
More informationbelow.
Libassert provides three types of assertions, each varying slightly depending on when it should be checked and how itshould be interpreted:
| Name | Effect |
|---|---|
DEBUG_ASSERT | Checked in debug, no codegen in release |
ASSERT | Checked in both debug and release builds |
ASSUME | Checked in debug,if(!(expr)) { __builtin_unreachable(); } in release |
Unconditional assertions
| Name | Effect |
|---|---|
PANIC | Triggers in both debug and release |
UNREACHABLE | Triggered in debug, marked as unreachable in release. |
One benefit toPANIC andUNREACHABLE overASSERT(false, ...) is that the compiler gets[[noreturn]] information.
ASSUME marks the fail path as unreachable in release, potentially providing helpful information to the optimizer. Thisisn't the default behavior for all assertions because the immediate consequence of this is that assertion failure in-DNDEBUG can lead to UB and it's better to make this very explicit.
Assertion variants that can be used in-line in an expression, such asFILE* file = ASSERT_VAL(fopen(path, "r"), "Failed to open file");, are also available:
| Name | Effect |
|---|---|
DEBUG_ASSERT_VAL | Checked in debug, must be evaluated in both debug and release |
ASSERT_VAl | Checked in both debug and release builds |
ASSUME_VAL | Checked in debug,if(!(expr)) { __builtin_unreachable(); } in release |
Note: Even in release builds the expression forDEBUG_ASSERT_VAL must still be evaluated, unlikeDEBUG_ASSERT. Ofcourse, if the result is unused and produces no side effects it will be optimized away.
Performance: As far as runtime performance goes, the impact at callsites is very minimal when optimizations are on.The happy-path in the code (i.e., where the assertion does not fail) will be fast. The failure path may be relativelyslow, but, assertion failures are rare and performance in such a situation is the least of a program's problems.
Compile speeds: There is some compile-time cost associated with the library's machinery, however, it tends to notmake a huge difference in compile speeds. If enum stringification with magic enum is enabled that can slow down builds alot.
Other:
Note
Because of expression decomposition,ASSERT(1 = 2); compiles.
libassert/assert.hpp: The main library headerlibassert/assert-gtest.hpp: Libassert macros for gtestlibassert/assert-catch2.hpp: Libassert macros for catch2
Additionally, thelibassert/ include folder containsexpression-decomposition.hpp,platform.hpp,stringification.hpp, andutilities.hpp. These are mostly library details.
All assertion functions are macros. Here are some pseudo-declarations for interfacing with them:
voidDEBUG_ASSERT (expression, [optional message], [optional extra diagnostics, ...]);voidASSERT (expression, [optional message], [optional extra diagnostics, ...]);voidASSUME (expression, [optional message], [optional extra diagnostics, ...]);decltype(auto) DEBUG_ASSERT_VAL(expression, [optional message], [optional extra diagnostics, ...]);decltype(auto) ASSERT_VAL (expression, [optional message], [optional extra diagnostics, ...]);decltype(auto) ASSUME_VAL (expression, [optional message], [optional extra diagnostics, ...]);voidPANIC ([optional message], [optional extra diagnostics, ...]);voidUNREACHABLE([optional message], [optional extra diagnostics, ...]);
-DLIBASSERT_PREFIX_ASSERTIONS can be used to prefix these macros withLIBASSERT_. This is useful for wrappinglibassert assertions.
-DLIBASSERT_LOWERCASE can be used to enable thedebug_assert andassert aliases forDEBUG_ASSERT andASSERT.See:Replacing <cassert>.
Theexpression is automatically decomposed so diagnostic information can be provided. The resultant type must beconvertible to boolean.
The operation between left and right hand sides of the top-level operation in the expression tree is evaluated by afunction object.
Note: Boolean logical operators (&& and||) are not decomposed by default due to short circuiting.
Note: Because of limitations with C macros, expressions with template arguments will need to be templatized. E.g.ASSERT(foo<a, b>() == c) needs to be written asASSERT((foo<a, b>()) == c).
Note: Because libassert's expression decomposition system involves binding to references there can be problems whenasserting comparisons involving bit fields. A simple workaround is to rewrite an assertion likeASSERT(s.bit == 1) asASSERT(+s.bit == 1),ASSERT(s.bit * 1 == 1), or similar.
An optional assertion message may be provided. If the first argument following the assertion expression, or the firstargument in PANIC/UNREACHABLE, is any string type it will be used as the message (if you want the first parameter, whichhappens to be a string, to be an extra diagnostic value instead simply pass an empty string first, i.e.ASSERT(foo, "", str);).
Note: The assertion message expression is only evaluated in the failure path of the assertion.
An arbitrary number of extra diagnostic values may be provided. These are displayed below the expression diagnostics ifa check fails.
Note: Extra diagnostics are only evaluated in the failure path of the assertion.
There is special handling whenerrno is provided: The value ofstrerror is displayed automatically.
To facilitate ease of integration into code_VAL variants are provided which return a value from the assertexpression. The returned value is determined as follows:
- If there is no top-level binary operation (e.g. as in
ASSERT_VAL(foo());orASSERT_VAL(false);) in the assertionexpression, the value of the expression is simply returned. - Otherwise if the top-level binary operation is
==,!=,<,<=,>,>=,&&,||, or or any assignment orcompound assignment then the value of theleft-hand operand is returned. - Otherwise if the top-level binary operation is
&,|,^,<<,>>, or any binary operator with precedence abovebitshift then value of the whole expression is returned.
I.e.,ASSERT_VAL(foo() > 2); returns the computed result fromfoo() andASSERT_VAL(x & y); returns the computedresult ofx & y;
If the value from the assertion expression selected to be returned is an lvalue, the type of the assertion call will bean lvalue reference. If the value from the assertion expression is an rvalue then the type of the call will be anrvalue.
namespacelibassert { [[nodiscard]] std::stringstacktrace(int width =0,const color_scheme& scheme = get_color_scheme(), std::size_t skip = 0 ); [[nodiscard]] std::stringprint_stacktrace(const cpptrace::stacktrace& trace,int width =0,const color_scheme& scheme = get_color_scheme(), path_mode = get_path_mode() );template<typename T> [[nodiscard]] std::string_viewtype_name()noexcept;template<typename T> [[nodiscard]] std::stringpretty_type_name()noexcept;template<typename T> [[nodiscard]] std::stringstringify(const T& value); std::stringhighlight( std::string_view expression,const color_scheme& scheme = get_color_scheme() );template<typename T> [[nodiscard]] std::stringhighlight_stringify(const T& value,const color_scheme& scheme = get_color_scheme() );}
stacktrace: Generates a stack trace, formats to the given width (0 for no width formatting)print_stacktrace: Formats a provided stack trace with libassert's internal trace formattingtype_name: Returns the type name of Tpretty_type_name: Returns the prettified type name for Tstringify: Produces a debug stringification of a valuehighlight: Syntax-highlights a string using libassert's internal expression highlighterhighlight_stringify: Equivalent tolibassert::highlight(libassert::stringify(value))
namespacelibassert {voidenable_virtual_terminal_processing_if_needed();inlineconstexprint stdin_fileno =0;inlineconstexprint stdout_fileno =1;inlineconstexprint stderr_fileno =2;boolisatty(int fd); [[nodiscard]]intterminal_width(int fd);}
enable_virtual_terminal_processing_if_needed: Enable ANSI escape sequences for terminals on windows, needed forcolor output.isatty: Returns true if the file descriptor corresponds to a terminalterminal_width: Returns the width of the terminal represented by fd or 0 on error
namespacelibassert {// NOTE: string view underlying data should have static storage duration, or otherwise live as// long as the scheme is in usestructcolor_scheme { std::string_view string, escape, keyword, named_literal, number, punctuation, operator_token, call_identifier, scope_resolution_identifier, identifier, accent, unknown, highlight_delete, highlight_insert, highlight_replace, reset;staticconst color_scheme ansi_basic;staticconst color_scheme ansi_rgb;staticconst color_scheme blank; };voidset_color_scheme(const color_scheme&);const color_scheme&get_color_scheme();}
By defaultcolor_scheme::ansi_rgb is used. To disable colors, usecolor_scheme::blank.
set_color_scheme: Sets the color scheme for the default assertion handler when stderr is a terminal
Diff highlighting is opt-in withset_diff_highlighting:
namespacelibassert {voidset_diff_highlighting(bool);}
namespacelibassert {voidset_separator(std::string_view separator);}
set_separator: Sets the separator between expression and value in assertion diagnostic output. Default:=>. NOTE:Not thread-safe.
namespacelibassert {enumclassliteral_format_mode { infer,// infer literal formats based on the assertion condition no_variations,// don't do any literal format variations, just default fixed_variations// always use a fixed set of formats (in addition to the default format) };voidset_literal_format_mode(literal_format_mode);enumclassliteral_format :unsigned {// integers and floats are decimal by default, chars are of course chars, and everything// else only has one format that makes sense default_format =0, integer_hex =1, integer_octal =2, integer_binary =4, integer_character =8,// format integers as characters and characters as integers float_hex =16, }; [[nodiscard]]constexpr literal_formatoperator|(literal_format a, literal_format b);voidset_fixed_literal_format(literal_format);}
set_literal_format_mode: Sets whether the library should show literal variations or infer themset_fixed_literal_format: Set a fixed literal format configuration, automatically changes the literal_format_mode;note that the default format will always be used along with others
namespacelibassert {enumclasspath_mode { full,// full path is used disambiguated,// only enough folders needed to disambiguate are provided basename,// only the file name is used }; LIBASSERT_EXPORTvoidset_path_mode(path_mode mode); path_modeget_path_mode();}
set_path_mode: Sets the path shortening mode for assertion output. Default:path_mode::disambiguated.get_path_mode: Gets the path shortening mode for assertion output.
namespacelibassert {enumclassassert_type { debug_assertion, assertion, assumption, panic, unreachable };structLIBASSERT_EXPORT binary_diagnostics_descriptor { std::string left_expression; std::string right_expression; std::string left_stringification; std::string right_stringification; };structextra_diagnostic { std::string_view expression; std::string stringification; };structLIBASSERT_EXPORT assertion_info { std::string_view macro_name; assert_type type; std::string_view expression_string; std::string_view file_name; std::uint32_t line; std::string_view function; std::optional<std::string> message; std::optional<binary_diagnostics_descriptor> binary_diagnostics; std::vector<extra_diagnostic> extra_diagnostics;size_t n_args; std::string_viewaction()const;const cpptrace::raw_trace&get_raw_trace()const;const cpptrace::stacktrace&get_stacktrace()const; [[nodiscard]] std::stringheader(int width =0,const color_scheme& scheme = get_color_scheme())const; [[nodiscard]] std::stringtagline(const color_scheme& scheme = get_color_scheme())const; [[nodiscard]] std::stringlocation()const; [[nodiscard]] std::stringstatement(const color_scheme& scheme = get_color_scheme())const; [[nodiscard]] std::stringprint_binary_diagnostics(int width =0,const color_scheme& scheme = get_color_scheme())const; [[nodiscard]] std::stringprint_extra_diagnostics(int width =0,const color_scheme& scheme = get_color_scheme())const; [[nodiscard]] std::stringprint_stacktrace(int width =0,const color_scheme& scheme = get_color_scheme())const; [[nodiscard]] std::stringto_string(int width =0,const color_scheme& scheme = get_color_scheme())const; };}
Debug Assertion failed at demo.cpp:194: void foo::baz(): Internal error with foobars debug_assert(open(path, 0) >= 0, ...); Where: open(path, 0) => -1 Extra diagnostics: errno => 2 "No such file or directory" path => "/home/foobar/baz"Stack trace:#1 demo.cpp:194 foo::baz()#2 demo.cpp:172 void foo::bar<int>(std::pair<int, int>)#3 demo.cpp:396 mainDebug Assertion failed:assertion_info.action()demo.cpp:194:assertion_info.file_nameandassertion_info.linevoid foo::baz():assertion_info.pretty_functionInternal error with foobars:assertion_info.messagedebug_assert:assertion_info.macro_nameopen(path, 0) >= 0:assertion_info.expression_string...: determined byassertion_info.n_argswhich has the total number of arguments passed to the assertion macro- Where clause
open(path, 0):assertion_info.binary_diagnostics.left_expression-1:assertion_info.binary_diagnostics.left_stringification- Same for the right side (omitted in this case because
0 => 0isn't useful)
- Extra diagnostics
errno:assertion_info.extra_diagnostics[0].expression2 "No such file or directory":assertion_info.extra_diagnostics[0].stringification- ... etc.
- Stack trace
assertion_info.get_stacktrace(), orassertion_info.get_raw_trace()to get the trace without resolving it
Helpers:
assertion_info.header():
Debug Assertion failed at demo.cpp:194: void foo::baz(): Internal error with foobars debug_assert(open(path, 0) >= 0, ...); Where: open(path, 0) => -1 Extra diagnostics: errno => 2 "No such file or directory" path => "/home/foobar/baz"assertion_info.tagline():
Debug Assertion failed at demo.cpp:194: void foo::baz(): Internal error with foobarsassertion_info.location():
Note
Path processing will be performed according to the path mode
demo.cpp:194assertion_info.statement():
debug_assert(open(path, 0) >= 0, ...);assertion_info.print_binary_diagnostics():
Where: open(path, 0) => -1assertion_info.print_extra_diagnostics():
Extra diagnostics: errno => 2 "No such file or directory" path => "/home/foobar/baz"assertion_info.print_stacktrace():
Stack trace:#1 demo.cpp:194 foo::baz()#2 demo.cpp:172 void foo::bar<int>(std::pair<int, int>)#3 demo.cpp:396 mainLibassert provides a customization point for user-defined types:
template<>structlibassert::stringifier<MyObject> { std::stringstringify(const MyObject& type) {return ...; }};
By default any container-like user-defined types will be automatically stringifiable.
Additionally,LIBASSERT_USE_FMT can be used to allow libassert to usefmt::formatters.
Lastly, any types with an ostreamoperator<< overload can be stringified.
namespacelibassert {voidset_failure_handler(void (*handler)(const assertion_info&)); [[noreturn]]voiddefault_failure_handler(const assertion_info& info);}
set_failure_handler: Sets the assertion handler for the program.default_failure_handler: The default failure handler, provided for convenience.
Example: If you wanted to log to a file in addition to the default behavior you could do something along the lines of:
voidhandler(const assertion_info& info) {logger::error(info.to_string());libassert::default_failure_handler(info);}
For more complex custom handling you can modify the default handler's logic:
voiddefault_failure_handler(const assertion_info& info) {libassert::enable_virtual_terminal_processing_if_needed();// for terminal colors on windows std::string message = info.to_string(libassert::terminal_width(libassert::stderr_fileno),libassert::isatty(libassert::stderr_fileno) ?libassert::get_color_scheme() : libassert::color_scheme::blank ); std::cerr << message << std::endl;switch(info.type) {case libassert::assert_type::assertion:case libassert::assert_type::debug_assertion:case libassert::assert_type::assumption:case libassert::assert_type::panic:case libassert::assert_type::unreachable: (void)fflush(stderr);std::abort();// Breaking here as debug CRT allows aborts to be ignored, if someone wants to make a// debug build of this librarybreak;default: std::cerr <<"Critical error: Unknown libassert::assert_type" << std::endl;std::abort(1); }}
By default libassert aborts from all assertion types. However, it may be desirable to throw an exception from some orall assertion types instead of aborting.
Important
Failure handlers must not return forassert_type::panic andassert_type::unreachable.
Libassert supports programatic breakpoints on assertion failure to make assertions more debugger-friendly by breaking onthe assertion line as opposed to several layers deep in a callstack:
This functionality is currently opt-in and it can be enabled by definingLIBASSERT_BREAK_ON_FAIL. This is best done asa compiler flag:-DLIBASSERT_BREAK_ON_FAIL or/DLIBASSERT_BREAK_ON_FAIL.
Internally the library checks for the presense of a debugger before executing an instruction to breakpoint the debugger.By default the check is only performed once on the first assertion failure. In some scenarios it may be desirable toconfigure this check to always be performed, e.g. if you're using a custom assertion handler that throws an exceptioninstead of aborting and you may be able to recover from an assertion failure allowing additional failures later and youonly attach a debugger part-way through the run of your program. You can uselibassert::set_debugger_check_mode tocontrol how this check is performed:
namespacelibassert {enumclassdebugger_check_mode { check_once, check_every_time, };voidset_debugger_check_mode(debugger_check_mode mode)noexcept;}
The library also exposes its internal utilities for setting breakpoints and checking if the program is being debugged:
namespacelibassert {boolis_debugger_present()noexcept;}#defineLIBASSERT_BREAKPOINT() <...internals...>#defineLIBASSERT_BREAKPOINT_IF_DEBUGGING() <...internals...>
This API mimics the API ofP2514, which hasbeen accepted to C++26.
A note aboutconstexpr: For clang and msvc libassert can use compiler intrinsics, however, for gcc inline assembly isrequired. Inline assembly isn't allowed in constexpr functions pre-C++20, however, gcc supports it with a warning aftergcc 10 and the library can surpress that warning for gcc 12.
Defines:
LIBASSERT_USE_MAGIC_ENUM: Usemagic enum for stringifying enum valuesLIBASSERT_USE_ENCHANTUM: Useenchantum for stringifying enum valuesLIBASSERT_DECOMPOSE_BINARY_LOGICAL: Decompose&&and||LIBASSERT_SAFE_COMPARISONS: Enable safe signed-unsigned comparisons for decomposed expressionsLIBASSERT_PREFIX_ASSERTIONS: Prefixes all assertion macros withLIBASSERT_LIBASSERT_USE_FMT: Enables libfmt integrationLIBASSERT_NO_STRINGIFY_SMART_POINTER_OBJECTS: Disables stringification of smart pointer contents
CMake:
LIBASSERT_USE_EXTERNAL_CPPTRACE: Use an externalcpptrace instead ofacquiring the library with FetchContentLIBASSERT_USE_EXTERNAL_MAGIC_ENUM: Use an externalmagic enum instead ofacquiring the library with FetchContentLIBASSERT_USE_EXTERNAL_ENCHANTUM: Use an externalenchantum instead ofacquiring the library with FetchContent
<libassert/version.hpp> provides version macros for the library.
Note
Because of MSVC's non-conformant preprocessor there is no easy way to provide assertion wrappers. In order to use testlibrary integrations/Zc:preprocessor is required.
Libassert provides a catch2 integration inlibassert/assert-catch2.hpp:
#include<libassert/assert-catch2.hpp>TEST_CASE("1 + 1 is 2") {ASSERT(1 +1 ==3);}
Currently the only macro provided isASSERT, which will perform aREQUIRE internally.
Note: Before v3.6.0 ansi color codes interfere with Catch2's line wrapping so color is disabled on older versions.
Libassert provides a gtest integration inlibassert/assert-gtest.hpp:
#include<libassert/assert-gtest.hpp>TEST(Addition, Arithmetic) {ASSERT(1 +1 ==3);}
Currently libassert providesASSERT andEXPECT macros for gtest.
This isn't as pretty as I would like, however, it gets the job done.
Since libassert v2.2.0, the library uses an inline ABI versioning namespace and all symbols part of the public interfaceare secretly under the namespacelibassert::abiv2. This is done to allow for potential future library evolution in anABI-friendly manner. The namespace version is independent of the library major versions, and ABI changes are expected tobe extremely rare.
This library targets >=C++17 and supports all major compilers and all major platforms (linux, macos, windows, andmingw).
Note: The library does rely on some compiler extensions and compiler specific features so it is not compatible with-pedantic.
With CMake FetchContent:
include(FetchContent)FetchContent_Declare( libassert GIT_REPOSITORY https://github.com/jeremy-rifkin/libassert.git GIT_TAG v2.2.1# <HASH or TAG>)FetchContent_MakeAvailable(libassert)target_link_libraries(your_target libassert::assert)
Note: On windows and macos some extra work is recommended, seePlatform Logistics below.
Be sure to configure with-DCMAKE_BUILD_TYPE=Debug or-DDCMAKE_BUILD_TYPE=RelWithDebInfo for symbols and lineinformation.
git clone https://github.com/jeremy-rifkin/libassert.gitgit checkout v2.2.1mkdir libassert/buildcd libassert/buildcmake .. -DCMAKE_BUILD_TYPE=Releasemake -jsudo make installUsing through cmake:
find_package(libassert REQUIRED)target_link_libraries(<yourtarget> libassert::assert)
Be sure to configure with-DCMAKE_BUILD_TYPE=Debug or-DDCMAKE_BUILD_TYPE=RelWithDebInfo for symbols and lineinformation.
Or compile with-lassert:
g++ main.cpp -o main -g -Wall -lassert./main
If you get an error along the lines of
error while loading shared libraries: libassert.so: cannot open shared object file: No such file or directoryYou may have to runsudo /sbin/ldconfig to create any necessary links and update caches so the system can findlibcpptrace.so (I had to do this on Ubuntu). Only when installing system-wide. Usually your package manger does this foryou when installing new libraries.
System-wide install on windows
git clone https://github.com/jeremy-rifkin/libassert.gitgit checkout v2.2.1mkdir libassert/buildcd libassert/buildcmake ..-DCMAKE_BUILD_TYPE=Releasemsbuild libassert.slnmsbuild INSTALL.vcxproj
Note: You'll need to run as an administrator in a developer powershell, or use vcvarsall.bat distributed with visualstudio to get the correct environment variables set.
To install just for the local user (or any custom prefix):
git clone https://github.com/jeremy-rifkin/libassert.gitgit checkout v2.2.1mkdir libassert/buildcd libassert/buildcmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/wherevermake -jsudo make install
Using through cmake:
find_package(libassert REQUIREDPATHS$ENV{HOME}/wherever)target_link_libraries(<yourtarget> libassert::assert)
Using manually:
g++ main.cpp -o main -g -Wall -I$HOME/wherever/include -L$HOME/wherever/lib -lassertTo use the library without cmake first follow the installation instructions atSystem-Wide Installation,Local User Installation,orPackage Managers.
Use the following arguments to compile with libassert:
| Compiler | Platform | Dependencies |
|---|---|---|
| gcc, clang, intel, etc. | Linux/macos/unix | -libassert -I[path] [cpptrace args] |
| mingw | Windows | -libassert -I[path] [cpptrace args] |
| msvc | Windows | assert.lib /I[path] [cpptrace args] |
| clang | Windows | -libassert -I[path] [cpptrace args] |
For the[path] placeholder in-I[path] and/I[path], specify the path to the include folder containinglibassert/assert.hpp.
If you are linking statically, you will additionally need to specify-DLIBASSERT_STATIC_DEFINE.
For the[cpptrace args] placeholder refer to thecpptrace documentation.
Libassert is available through conan athttps://conan.io/center/recipes/libassert.
[requires]libassert/2.2.1[generators]CMakeDepsCMakeToolchain[layout]cmake_layout# ...find_package(libassert REQUIRED)# ...target_link_libraries(YOUR_TARGET libassert::assert)
vcpkg install libassertfind_package(libassert CONFIG REQUIRED)target_link_libraries(YOUR_TARGETPRIVATE libassert::assert)
Libassert supports C++20 modules:import libassert;. You'll need a modern toolchain in order to use C++20 modules (i.e.relatively new compilers, cmake, etc).
You will also have to#include headers with the macro definitions:
<libassert/assert-macros.hpp>: All the library assertion macros<libassert/assert-gtest-macros.hpp>: Macros for gtest integration<libassert/assert-catch2-macros.hpp>: Macros for catch2 integration
Windows and macos require a little extra work to get everything in the right place
Copying the library .dll on windows:
# Copy the assert.dll on windows to the same directory as the executable for your_target.# Not required if static linking.if(WIN32) add_custom_command(TARGET your_target POST_BUILDCOMMAND${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:libassert::assert> $<TARGET_FILE_DIR:your_target> )endif()
On macOS it's recommended to generate a dSYM file containing debug information for your program:
In xcode cmake this can be done with
set_target_properties(your_target PROPERTIESXCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT"dwarf-with-dsym")
And outside xcode this can be done withdsymutil yourbinary:
# Create a .dSYM file on macos. Currently required, but hopefully not for longif(APPLE) add_custom_command(TARGET your_target POST_BUILDCOMMAND dsymutil $<TARGET_FILE:your_target> )endif()
This library is not a drop-in replacement for<cassert>.-DLIBASSERT_LOWERCASE can be used to create lowercase aliasesfor the assertion macros but be aware that libassert'sASSERT is still checked in release. To replace<cassert> usewith libassert, replaceassert withDEBUG_ASSERT or create an alias along the following lines:
#defineassert(...) DEBUG_ASSERT(__VA_ARGS__)
One thing to be aware: Overriding cassert'sassert is technicallynot allowed by the standard, but thisshould not be an issue for any sane compiler.
No, not yet.
Even with constructs likeassert_eq, assertion diagnostics are often lacking. For example, in rust the left and rightvalues are displayed but not the expressions themselves:
fnmain(){let count =4;assert_eq!(count,2);}
thread 'main' panicked at 'assertion failed: `(left == right)` left: `4`, right: `2`', /app/example.rs:3:5note: run with `RUST_BACKTRACE=1` environment variable to display a backtraceThis is not as helpful as it could be.
Functionality other languages / their standard libraries provide:
| C/C++ | Rust | C# | Java | Python | JavaScript | Libassert | |
|---|---|---|---|---|---|---|---|
| Expression string | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ | ✔️ |
| Location | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| Stack trace | ❌ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| Assertion message | ❌** | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| Extra diagnostics | ❌ | ❌* | ❌* | ❌ | ❌* | ❌* | ✔️ |
| Binary specializations | ❌ | ✔️ | ❌ | ❌ | ❌ | ✔️ | ✔️ |
| Automatic expression decomposition | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✔️ |
| Sub-expression strings | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✔️ |
*: Possible through string formatting but that is sub-ideal.**:assert(expression && "message") is commonly used but this is sub-ideal and only allows string literal messages.
Extras:
| C/C++ | Rust | C# | Java | Python | JavaScript | Libassert | |
|---|---|---|---|---|---|---|---|
| Syntax highlighting | ❌ | ❌ | ❌ | ❌ | 🟡 | ❌ | ✔️ |
| Literal formatting consistency | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✔️ |
| Expression strings and expression values everywhere | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✔️ |
| Return values from the assert to allow asserts to be integrated into expressions inline | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✔️ |
About
The most over-engineered C++ assertion library
Topics
Resources
License
Contributing
Security policy
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.














