This isn’t an exhaustive C++ reference – see DevDocs for that – but an exploration of regularly-used features or those that have piqued my interest.
BTW, my other websites are here.
This code is mostly developed and formatted in Godbolt, which provides rapid
feedback and a deeper insight into your code. To add a new example, I
create a gtest to demonstrate it then simply add it to test.cxx
.
On commit, the examples are compiled and run in a GitLab CI pipe and the
C++ and test results are deployed as this HTML page.
See the build pipeline and Doxygen documentation for this repo.
const
–- it’s much
easier to reason about and parallelise if data are immutableauto
– and test your asserts about
what type it should be with static_assert
(you can
even use concepts: std::floating_point
)constexpr
– catch UB at compile time with static_assert
(most of the
Standard Library isn’t constexpr
yet but that doesn’t mean
you can’t write your own versions)Also see The Power of 10: Rules for Developing Safety-Critical Code for some related good practice.
#include "gtest/gtest.h"
#include <algorithm>
#include <any>
#include <deque>
#include <exception>
#include <execution>
#include <filesystem>
#include <future>
#include <iomanip>
#include <iostream>
#include <list>
#include <numeric>
#include <optional>
#include <ranges>
#include <string>
#include <string_view>
#include <thread>
#include <type_traits>
#include <utility>
#include <vector>
/**
Common string manipulations so you don't get tempted to use a third-party
library.
- trim_whitespace
- remove_control_characters
- to_lowercase
- to_uppercase
- to_snakecase
- get_file_name
- get_file_extension
- get_stem (the file name without the extension)
- starts_with
- ends_with
- split_string
- contains
*/
/// Remove leading and trailing whitespace
std::string_view trim_whitespace(const std::string_view path) {
const size_t start = path.find_first_not_of(" ");
const size_t end = path.find_last_not_of(" ");
const size_t diff = end - start;
return diff > 0uz ? path.substr(start, diff + 1) : "";
}
(cpp11, trim_whitespace) {
TEST(trim_whitespace(""), "");
EXPECT_EQ(trim_whitespace(" "), "");
EXPECT_EQ(trim_whitespace(" "), "");
EXPECT_EQ(trim_whitespace(" file.jpg "), "file.jpg");
EXPECT_EQ(trim_whitespace("file.jpg "), "file.jpg");
EXPECT_EQ(trim_whitespace(" file.jpg"), "file.jpg");
EXPECT_EQ(trim_whitespace(" one two "), "one two");
EXPECT_EQ(trim_whitespace(std::string{" one two "}), "one two");
EXPECT_EQ(trim_whitespace(std::string_view{" one two "}), "one two");
EXPECT_EQ}
/// Remove non-printable characters
std::string remove_control_characters(const std::string_view str) {
// Filter function
const auto func = [](const char c) { return std::isprint(c); };
// Filter input string
auto printable = str | std::views::filter(func);
return {printable.begin(), printable.end()};
}
(cpp20, remove_control_characters) {
TEST(remove_control_characters(""), "");
EXPECT_EQ(remove_control_characters(" "), " ");
EXPECT_EQ(remove_control_characters("hello"), "hello");
EXPECT_EQ(remove_control_characters("hel lo"), "hello");
EXPECT_EQ(remove_control_characters("hel lo"), "hello");
EXPECT_EQ(remove_control_characters(std::string{"hel lo"}), "hello");
EXPECT_EQ(remove_control_characters(std::string_view{"hel lo"}), "hello");
EXPECT_EQ(remove_control_characters("8=FIX.4.49=14835=D"),
EXPECT_EQ"8=FIX.4.49=14835=D");
}
/// Helper routine to transform a string with a function
constexpr std::string transform_view(const std::string str, const auto func) {
const auto transformed = str | std::views::transform(func);
return {transformed.begin(), transformed.end()};
}
/// Toggle case
constexpr char toggle_case(const char c, const bool to_upper) {
constexpr std::string_view the_lower_case = "abcdefghijklmnopqrstuvwxyz";
constexpr std::string_view the_upper_case = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
assert(std::size(the_lower_case) == std::size(the_upper_case));
for (auto i = 0uz; i < std::size(the_upper_case); ++i) {
if (to_upper) {
if (c == the_lower_case[i])
return the_upper_case[i];
} else {
if (c == the_upper_case[i])
return the_lower_case[i];
}
}
return c;
}
/// constexpr version of std::toupper
constexpr char toupper(const char c) { return toggle_case(c, true); }
/// constexpr version of std::tolower
constexpr char tolower(const char c) { return toggle_case(c, false); }
(cpp11, case_tests_common) {
TESTconstexpr auto do_nothing = [](const char c) { return c; };
static_assert(transform_view("", do_nothing) == "");
static_assert(transform_view("hello", do_nothing) == "hello");
static_assert(transform_view(std::string{"hello"}, do_nothing) == "hello");
static_assert(tolower('a') == 'a');
static_assert(tolower('C') == 'c');
static_assert(tolower('Z') == 'z');
static_assert(tolower('0') == '0');
static_assert(toupper('a') == 'A');
static_assert(toupper('C') == 'C');
static_assert(toupper('z') == 'Z');
static_assert(toupper('0') == '0');
static_assert(toggle_case('a', true) == 'A');
static_assert(toggle_case('C', true) == 'C');
static_assert(toggle_case('z', true) == 'Z');
static_assert(toggle_case('0', true) == '0');
static_assert(toggle_case('a', false) == 'a');
static_assert(toggle_case('C', false) == 'c');
static_assert(toggle_case('z', false) == 'z');
static_assert(toggle_case('0', false) == '0');
}
/// Use the lower case
constexpr std::string to_lowercase(const std::string str) {
return transform_view(str, [](const char c) { return tolower(c); });
}
/// Use the upper case
constexpr std::string to_uppercase(const std::string str) {
return transform_view(str, [](const char c) { return toupper(c); });
}
/// Use the Python case
constexpr std::string to_snakecase(const std::string str) {
constexpr auto func = [](const char c) { return c == ' ' ? '_' : c; };
return transform_view(str, func);
}
(cpp11, case_tests1) {
TESTstatic_assert(to_lowercase("") == "");
static_assert(to_lowercase("hello") == "hello");
static_assert(to_lowercase("Hello") == "hello");
static_assert(to_lowercase(" HeLlO0 THERE") == " hello0 there");
static_assert(to_lowercase(std::string{" HeLlO0 THERE"}) ==
" hello0 there");
static_assert(to_uppercase("") == "");
static_assert(to_uppercase("hello") == "HELLO");
static_assert(to_uppercase("Hello") == "HELLO");
static_assert(to_uppercase(" HeLlO0 THERE") == " HELLO0 THERE");
static_assert(to_uppercase(std::string{" HeLlO0 THERE"}) ==
" HELLO0 THERE");
}
(cpp11, case_tests2) {
TESTstatic_assert(to_snakecase("") == "");
static_assert(to_snakecase(" ") == "___");
static_assert(to_snakecase("hello there") == "hello_there");
static_assert(to_snakecase(std::string{"hello there"}) == "hello_there");
}
/// You can aslo break down a path using C++17's std::filesystem
std::string get_file_name(const std::string_view path) {
return std::filesystem::path{path}.filename();
};
/// Note string_view makes the data immutable, but prefixing const means you
/// also can't reassign path
std::string get_file_extension(const std::string_view path) {
return std::filesystem::path{path}.extension();
};
std::string get_stem(const std::string_view path) {
return std::filesystem::path{path}.stem();
};
(cpp11, timing_events) {
TEST/**
timing an event
Consider using Google Benchmark for any serious profiling but you can also
time micorsecond and above durations with std::crono
*/
// Run main routine
const auto start_time = std::chrono::high_resolution_clock::now();
// Do the thing
const auto end_time = std::chrono::high_resolution_clock::now();
// Calculate the elapsed time
const auto elapsed_time =
std::chrono::duration_cast<std::chrono::nanoseconds>(end_time -
)
start_time.count();
(elapsed_time, 0);
EXPECT_GT}
(cpp17, filename_operations) {
TEST(get_file_name(""), "");
EXPECT_EQ(get_file_name(" "), " ");
EXPECT_EQ(get_file_name("a b c"), "a b c");
EXPECT_EQ(get_file_name("/etc/hello/file.jpg"), "file.jpg");
EXPECT_EQ(get_file_name("file.jpg"), "file.jpg");
EXPECT_EQ(get_file_name("file.jpg/"), "");
EXPECT_EQ(get_file_name("blah/file.jpg"), "file.jpg");
EXPECT_EQ(get_file_name(std::string{"blah/file.jpg"}), "file.jpg");
EXPECT_EQ(get_file_name(std::string_view{"blah/file.jpg"}), "file.jpg");
EXPECT_EQ
(get_file_extension(""), "");
EXPECT_EQ(get_file_extension(" "), "");
EXPECT_EQ(get_file_extension(" .wav"), ".wav");
EXPECT_EQ(get_file_extension("blah/file.jpg"), ".jpg");
EXPECT_EQ(get_file_extension("/etc/hello/file.SVG"), ".SVG");
EXPECT_EQ(get_file_extension("file.123"), ".123");
EXPECT_EQ(get_file_extension("file.jpg/"), "");
EXPECT_EQ(get_file_extension(std::string{"file.jpg/"}), "");
EXPECT_EQ(get_file_extension(std::string_view{"file.jpg/"}), "");
EXPECT_EQ
(get_stem(""), "");
EXPECT_EQ(get_stem(" "), " ");
EXPECT_EQ(get_stem("blah/file.jpg"), "file");
EXPECT_EQ(get_stem("/etc/hello/file.SVG"), "file");
EXPECT_EQ(get_stem("yeah.jpg/"), "");
EXPECT_EQ(get_stem("blah.123"), "blah");
EXPECT_EQ(get_stem(std::string{"blah.123"}), "blah");
EXPECT_EQ(get_stem(std::string_view{"blah.123"}), "blah");
EXPECT_EQ}
/// Something that has eluded computer science for decades, and now C++20 has
/// solved it
std::vector<std::string> split_string(std::string_view sentence) {
constexpr std::string_view delim{" "};
std::vector<std::string> words;
std::ranges::transform(std::views::split(sentence, delim),
std::back_inserter(words), [](const auto word) {
return std::string{word.begin(), word.end()};
});
return words;
}
(cpp20, split_string) {
TESTconstexpr std::string_view sentence{
"A SYSTEM IS NO BETTER THAN ITS SENSORY ORGANS"};
const auto words = split_string(sentence);
(std::size(words), 9uz);
EXPECT_EQ(split_string("").empty());
EXPECT_TRUE(split_string(std::string{}).empty());
EXPECT_TRUE(split_string(std::string_view{"1 2"}).empty());
EXPECT_FALSE}
(cpp20, check_substrings) {
TEST// Avoid clumsy escape characters with raw strings, very useful with regular
// expressions
constexpr std::string_view quotation{
R"(It's a little like wrestling a gorilla:
you don't quit when you're tired, you quit when the gorilla is tired
- Robert Strauss)"};
// C++20
(quotation.starts_with("It's"));
EXPECT_TRUE(quotation.ends_with("Strauss"));
EXPECT_TRUE
// contains() for strings - C++23
(quotation.contains("gorilla"));
EXPECT_TRUE(quotation.contains("mandrill"));
EXPECT_FALSE}
(cpp11, range_based_for_loops) {
TEST/**
I really find it painful to go back to old style for-loops. All those
clumsy explicit iterator declarations can be cleaned up beautifully with
`auto`; in fact, we can drop the iterators altogether and avoid that weird
*i dereferencing idiom. Note you don't have access to the current index
(until C++2a), which isn't necessarily a bad thing if you have aspirations
of parallelisation.
*/
std::list v1{1, 2, 3, 4, 5};
// Eek!
for (std::list<int>::iterator i = v1.begin(); i != v1.end(); ++i)
++*i;
(v1.front(), 2);
EXPECT_EQ
// Better...
for (auto i = v1.begin(); i != v1.end(); ++i)
++*i;
(v1.front(), 3);
EXPECT_EQ
// Now we're talking!
for (auto &i : v1)
++i;
(v1.front(), 4);
EXPECT_EQ
// Wait...
const auto increment = [](auto &i) { ++i; };
std::for_each(v1.begin(), v1.end(), increment);
(v1.front(), 5);
EXPECT_EQ
// OMG - C++20
std::ranges::for_each(v1, increment);
(v1.front(), 6);
EXPECT_EQ
/**
You can also pass a function object where you would a lambda to
std::for_each, and do more complex things like store state between
element But this does, of course, make parallelising the operation more
complicated.
*/
struct count_positive_elements {
void operator()(const int i) {
if (i > 0)
++count_;
}
size_t count_;
};
// Run the function object over the container
const auto function_object =
std::for_each(cbegin(v1), cend(v1), count_positive_elements{});
// Get the result
const auto count = function_object.count_;
(count, 5uz);
EXPECT_EQ}
class base {
/**
Initialise class members in the header, I prefer the trailing underscore
rather than "m_member".
*/
// Note the implicit private scope for a class
// private:
int member_ = 0;
public:
// Pure virtual: you cannot create an instance of the base class
virtual void func3() = 0;
/**
Rule of 5: If a class requires a user-defined destructor, a user-defined
copy constructor, or a user-defined copy assignment operator, it almost
certainly requires all five.
*/
// Require one constructor is used via "explicit", no others allowed
explicit base() = default;
(const base &) = delete;
base(base &&) noexcept = delete;
base&operator=(const base &) = delete;
base &operator=(base &&) noexcept = delete;
base
// User defined destructors are noexcept by default
virtual ~base() = default;
};
class derived : public base {
/**
Use virtual at the top level and then override in derived classes
It stops you accidentally changing the signature or somebody else
removing the base method. Mark methods as final once you've fixed all
the bugs.
*/
// We cannot change the signature because of override
void func3() override final{};
// Create type-safe typedefs with "using"
using parent = base;
void func4() { parent::func3(); }
};
class base2 {
// Oops, we've forgotten to add a virtual destructor, see static_assert below
};
(cpp11, classes_and_type_traits) {
TEST/**
Not a modern feature, of course, but you should: *make everything constant*.
You ought to be prefixing const as a matter of course and then removing it
when you have to: it’s much easier to reason about code when the data are
immutable. In an ideal world everything would be constant -- like Haskell --
but it’s a balance of reason and getting things done.
*/
struct S {
size_t a{0};
size_t b{0};
size_t c{0};
};
const S s1;
;
S s2
(std::is_const_v<const int>);
EXPECT_TRUE(std::is_const_v<int>);
EXPECT_FALSE(std::is_const<decltype(s1)>::value);
EXPECT_TRUE(std::is_const<decltype(s2)>::value);
EXPECT_FALSE
/**
Knowning that classes sometimes need a virtual destructor always
seemed a bit nuanced and error prone. Now you can test it.
Not my rules but a common opinion: a struct holds data; but if there's any
funtionality, then it should probably be a class.
*/
(std::has_virtual_destructor<base>::value);
EXPECT_TRUE(std::has_virtual_destructor<base2>::value);
EXPECT_FALSE}
(cpp11, auto_type) {
TEST/**
Type inference is a game changer. You can simplify complicated
(or unknown) type declarations with auto, but it can be a balance of
convenience over readability.
*/
// And there are a few gotchas. Let's create a variable and
// a reference to it, updating y2 (below) also updates y1 as expected.
int y1 = 1;
int &y2 = y1;
= 2;
y2
(y1, 2);
EXPECT_EQ
// But how does auto deal with references? Do you get another reference or a
// copy? (Hint: auto "decays" to the base type -- no consts, no refs).
int z1 = 1;
int &z2 = z1;
auto z3 = z2;
auto &z4 = z2;
--z3;
++z4;
// These assertions took ages to get right... don't write confusing code!
(z1, 2);
EXPECT_EQ(z2, 2);
EXPECT_EQ(z3, 0);
EXPECT_EQ(z4, 2);
EXPECT_EQ
// What's the underlying type of x2? int
int x1 = 5;
auto x2 = x1;
(x1, x2);
EXPECT_EQstatic_assert(std::is_same_v<decltype(x2), int>);
// What's the underlying type? Still int (const ignored)
const int x3 = 5;
auto x4 = x3;
static_assert(std::is_same_v<decltype(x4), int>);
// What's the underlying type? _Now_ it's const int
decltype(auto) x6 = x3;
static_assert(std::is_same_v<decltype(x6), const int>);
// Here I don't know (or care) what the type is
const std::vector moon{"Don't", "look", "at", "the", "finger"};
const auto finger = moon.front();
(finger, "Don't");
EXPECT_EQ
// Something to ponder: if you declare everything "auto" then you cannot leave
// anything uninitialised.
[[maybe_unused]] const auto d = 3uz;
}
(cpp11, lambda_expressions) {
TEST/**
Lambda expressions are like function pointers but with a much friendlier
implementation. Call them like a regular function or pass them as a
parameter; you can also define them in-place so you don't have to go hunting
for the implementation.
*/
// Let's create a lambda expression and call it
constexpr auto sentence = [] { return "I am a first-class citizen"; };
std::string_view s1 = sentence();
(s1.front(), 'I');
EXPECT_EQ
// You can even call them directly, note you don't need the brackets here
constexpr std::string_view s2 = [] {
return "I am a second-class citizen";
}();
(s2.back(), 'n');
EXPECT_EQ
// And an in-place definition
std::vector d{0.0, 0.1, 0.2};
std::ranges::for_each(d, [](auto &i) { ++i; });
(d.front(), 1.0);
EXPECT_EQ}
(cpp11, exceptions) {
TEST/**
A key feature of the language but I eschew adding exceptions to my own
code; it's much easier to reason about errors where they occur.
*/
const auto throw_it = []() { throw "cya!"; };
(throw_it());
EXPECT_ANY_THROW}
(cpp11, brace_initialisers) {
TEST/**
There are many more ways to initialise and append to a container.
*/
using container_t = std::vector<std::pair<int, int>>;
container_t c{
{1.1, 1},
{2, 2},
};
.front() = std::make_pair(1.3, 2);
c
.push_back({1.1, 1.2});
c.emplace_back(1.1, 1.2);
c.emplace_back(std::make_pair(1.1, 1.3));
c
(c.size(), 5);
EXPECT_EQ
// Initialise more complex types
const struct S2 {
int x;
struct {
int u;
int v;
std::vector<int> a;
} b;
} s1 = {1, {2, 3, {4, 5, 6}}};
(s1.x, 1);
EXPECT_EQ(s1.b.a.at(0), 4);
EXPECT_EQ
/**
In C++17 the type of vector can be inferred from the init list.
*/
const std::vector v1{1, 2, 3, 4, 5};
(not v1.empty());
EXPECT_TRUE
const std::vector v2{'a', 'b', 'c', 'd', 'e'};
(not v2.empty());
EXPECT_TRUE}
(cpp11, narrowing) {
TEST/**
Initialising a small type with a large type will generate an error if you
use braces. You don't seem to be able to downgrade this a warning.
*/
// Braces with the same size type
const int d{1};
(d, 1);
EXPECT_EQ
// Brackets hiding the narrowing
const int e(1.1);
(e, 1);
EXPECT_EQ
// This would throw an error
// const int f{1.0};
// EXPECT_EQ(f, 1);
}
(cpp11, learn_the_standard_library) {
TEST/**
The standard library often expresses intention much more eloquently than a
regular for-loop. Note the namespace is omitted for the `count_if`
parameters, see Koenig/argument-dependent lookup (ADL).
*/
const std::vector vec{1, 2, 3, 4, 5, 6};
const auto count = std::count_if(cbegin(vec), cend(vec),
[](const auto &a) { return a < 3; });
(count, 2);
EXPECT_EQ}
(cpp11, threading) {
TEST/**
Threading gives you the promise of speed at the expense of
reasoning, complexity and debugging. But they are much more intuitive
now than the old POSIX library.
std::futures are particularly interesting and let you return the
stuff you're interested in much more easily: define a routine as a lambda
and run it in the background while the main routine gets on with something
else; and when we're ready, we block to get the value.
std::async runs the function asynchronously (potentially in a separate thread
which might be a part of a thread pool) and returns a std::future that will
eventually hold the result of that function call.
std::launch::async: the task is executed on a different thread, potentially
by creating and launching it first
std::launch::deferred: the task is executed on the calling thread the first
time its result is requested (lazy evaluation)
*/
// Spawn a task in the background, note we've declared a return type with ->
const auto background_task = []() -> int { return 1; };
auto f = std::async(std::launch::async, background_task);
// C++20 also introduced `std::jthread` which automatically rejoins on
// destruction.
// Do things and then block
const auto f1 = f.get();
(f1, 1);
EXPECT_EQ
/**
Listen very carefully, I shall say this only once... if you get this reference
then you are a senior dev. You can call things only once using statics and
lambdas (IIFE); or, use the well-named Standard Library function.
*/
auto i = 0uz;
std::once_flag flag;
std::vector<std::thread> threads;
for ([[maybe_unused]] const auto _ : {0, 1, 2, 3, 4}) {
.emplace_back(
threadsstd::thread([&]() { std::call_once(flag, [&i]() { ++i; }); }));
}
// Auto-joining jthreads are C++20
for (auto &t : threads)
.join();
t
(i, 1);
EXPECT_EQ}
size_t bird_count = 0;
(cpp14, return_value_optimisation) {
TEST/**
The destructor is called only once despite the nested constructors: see copy
elision.
*/
struct bird {
~bird() { ++bird_count; }
};
// Check count whilst b is still in scope
{
= bird(bird(bird(bird(bird()))));
bird b
(bird_count, 0);
EXPECT_EQ}
// Check again when it's gone out of scope and the destructor is called
(bird_count, 1);
EXPECT_EQ}
(cpp14, digit_separators) {
TEST/**
If you're defining hardware interfaces then you'll probably have register
maps defined as hexadecimals; using digit separators can help improve
readability in some cases. You can even define things in binary if you like.
*/
const auto reg1 = 0x5692a5b6;
const auto reg2 = 0x5692'a5b6;
(reg1, reg2);
EXPECT_EQ
const auto reg3 = 1'000.000'01;
(reg3, 1000.00001);
EXPECT_EQ
// Using brace init to check for narrowing
const uint32_t netmask{0b11111111'11111111'11111111'00000000};
(netmask, 0xffffff00);
EXPECT_EQ}
(cpp17, optional_types) {
TEST/**
Optional types overcome the problem of defining a "not initialised" value
-- say, -1 -- which will inevitably used to index an array and cause an
explosion. Your functions can now effectively return a "no result".
*/
// Let's initialise a container with these data
std::deque<std::optional<int>> options{0, 1, 2, 3, 4};
// Then make the one at the back undefined
.back() = {};
options
// And count the valid entries with the help of a lambda expression
// Note the const iterators
const auto c = std::count_if(cbegin(options), cend(options),
[](const auto &o) { return o; });
(c, 4);
EXPECT_EQ}
(cpp17, heterogeneous_types) {
TEST/**
std::tuple is like a pair but better, and offers arbitrary collections of
heterogeneous types. You can retrieve values by index (which looks a bit
odd) or even by type! I think it makes for quite strange code but you can
hide much of it with auto.
*/
const auto t = std::make_tuple("one", 2.0, 3);
(std::get<0>(t), "one");
EXPECT_EQ
/**
`std::any` is a little better thought out.
*/
std::vector<std::any> martini(10);
.at(5) = 1.0;
martini.at(6) = "time";
martini.at(7) = "place";
martini.at(8) = "where";
martini
(martini.size(), 10);
EXPECT_EQ}
(cpp17, filesystem) {
TEST/**
C++17 has a perfectly good interface to the filesystem so you don't need to
use something like Qt's `QFile`.
*/
std::vector<std::string> files;
const std::filesystem::path p{"."};
std::ranges::for_each(
std::filesystem::directory_iterator{p},
[&files](const auto &file) { files.push_back(file.path()); });
(not files.empty());
EXPECT_TRUE}
(cpp17, parallel_execution_policy) {
TEST/**
Quite an exciting prospect in C++17 is parallelising _existing_ for-loops
simply by adding an execution policy parameter.
*/
std::vector vec{1, 23, 4, 5, 6, 7, 8};
const auto sum =
std::reduce(std::execution::seq, vec.cbegin(), vec.cend(), int{});
(sum, 54);
EXPECT_EQ
std::for_each(std::execution::unseq, vec.begin(), vec.end(),
[](auto &x) { ++x; });
(vec.front(), 2);
EXPECT_EQ}
(cpp17, clamp_values) {
TEST/**
This would've been really useful in a previous life but I've yet to use of
it since! Documented as a reminder.
*/
// uz is C++23
const auto a{19uz};
// Using decltype to refer to the type of another thing
const decltype(a) b = std::clamp(a, 0uz, 16uz);
const auto c = std::clamp(0, 4, 10);
(b, 16uz);
EXPECT_EQ(c, 4);
EXPECT_EQ}
(cpp17, compiler_attributes) {
TEST/**
Tell the compiler you meant to follow through.
*/
const size_t in{0};
size_t out{0};
switch (in) {
case 0:
// Do the same thing as below
[[fallthrough]];
case 1:
// Do a thing
++out;
break;
default:
break;
}
(out, 1);
EXPECT_EQ
// Guide the compiler as to well-trodden path
if (true) {
[[likely]];
} else {
[[unlikely]];
}
}
(cpp17, maybe_unused_attribute) {
TEST/** If a variable is not used in all cases, mark with an attribute. Often
happens where things aren't compiled in release.
*/
[[maybe_unused]] std::string str;
#ifdef THIS_IS_NOT_DEFINED_IN_THIS_BUILD_BUT_I_DO_NOT_WANT_WARNING
= "special";
str #endif
// Use empty rather than size
(str.empty());
EXPECT_TRUE}
(cpp20, ranges_and_views) {
TEST/**
An opportunity to simplify all the begin/end code that's been written since
C++11.
*/
const std::vector vec1{1, 2, 3, 4, 5, 6, 7, 8};
std::vector<int> vec2(vec1.size());
std::ranges::copy(vec1, begin(vec2));
(vec1, vec2);
EXPECT_EQ}
(cpp20, reduce_and_accumulate) {
TEST
const std::vector vec{1, 2, 3, 4, 5, 66};
// Note the two ways to specify start and end iterators
// accumulate is a left fold where the zero at the end is the accumulator
// You can perform a right fold by using the reverse iterators
const auto sum1 = std::accumulate(vec.cbegin(), vec.cend(), 0);
// reduce has an implicit accumulator start value (the first element)
// but using the parallel version has additional considerations
// https://blog.tartanllama.xyz/accumulate-vs-reduce/
const auto sum2 = std::reduce(cbegin(vec), cend(vec));
(sum1, sum2);
EXPECT_EQ}
(cpp20, deprecated_with_comment) {
TEST/**
Mark things as deprecated before you remove them.
*/
struct A {
std::string_view member_{"cya"};
// Do not throw the result of the away
[[nodiscard("I have no side-effects")]] auto func1() { return member_; }
// Do not use this at all
[[deprecated("This is neither efficient nor thread-safe")]] auto
() const {
func2return member_;
}
} a;
[[maybe_unused]] const auto b = a.func1();
.func1(); // Throwing away the result of a const method
a
(b.empty());
EXPECT_FALSE
[[maybe_unused]] const auto c = a.func2(); // this is deprecated
}
(cpp20, contains_for_associative_containers) {
TEST/**
Check a key exists without find or count. Feels much more natural than
checking the end stop.
*/
const std::map<std::string_view, int> m{
{"one", 1},
{"two", 2},
{"three", 3},
};
(m.contains("one"));
EXPECT_TRUE(m.contains("four"));
EXPECT_FALSE}
-std=c++23 –all-warnings –extra-warnings –pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -Warray-bounds -Wattribute-alias -Wformat-overflow -Wformat-truncation -Wmissing-attributes -Wstringop-truncation -Wdeprecated-copy -Wclass-conversion -Wno-deprecated-declarations -Wno-float-conversion -Wno-unused-result -Og -lgtest -lgtest_main
Running main() from ./googletest/src/gtest_main.cc
[==========] Running 31 tests from 4 test suites.
[----------] Global test environment set-up.
[----------] 14 tests from cpp11
[ RUN ] cpp11.trim_whitespace
[ OK ] cpp11.trim_whitespace (0 ms)
[ RUN ] cpp11.case_tests_common
[ OK ] cpp11.case_tests_common (0 ms)
[ RUN ] cpp11.case_tests1
[ OK ] cpp11.case_tests1 (0 ms)
[ RUN ] cpp11.case_tests2
[ OK ] cpp11.case_tests2 (0 ms)
[ RUN ] cpp11.timing_events
[ OK ] cpp11.timing_events (0 ms)
[ RUN ] cpp11.range_based_for_loops
[ OK ] cpp11.range_based_for_loops (0 ms)
[ RUN ] cpp11.classes_and_type_traits
[ OK ] cpp11.classes_and_type_traits (0 ms)
[ RUN ] cpp11.auto_type
[ OK ] cpp11.auto_type (0 ms)
[ RUN ] cpp11.lambda_expressions
[ OK ] cpp11.lambda_expressions (0 ms)
[ RUN ] cpp11.exceptions
[ OK ] cpp11.exceptions (0 ms)
[ RUN ] cpp11.brace_initialisers
[ OK ] cpp11.brace_initialisers (0 ms)
[ RUN ] cpp11.narrowing
[ OK ] cpp11.narrowing (0 ms)
[ RUN ] cpp11.learn_the_standard_library
[ OK ] cpp11.learn_the_standard_library (0 ms)
[ RUN ] cpp11.threading
[ OK ] cpp11.threading (0 ms)
[----------] 14 tests from cpp11 (0 ms total)
[----------] 7 tests from cpp20
[ RUN ] cpp20.remove_control_characters
[ OK ] cpp20.remove_control_characters (0 ms)
[ RUN ] cpp20.split_string
[ OK ] cpp20.split_string (0 ms)
[ RUN ] cpp20.check_substrings
[ OK ] cpp20.check_substrings (0 ms)
[ RUN ] cpp20.ranges_and_views
[ OK ] cpp20.ranges_and_views (0 ms)
[ RUN ] cpp20.reduce_and_accumulate
[ OK ] cpp20.reduce_and_accumulate (0 ms)
[ RUN ] cpp20.deprecated_with_comment
[ OK ] cpp20.deprecated_with_comment (0 ms)
[ RUN ] cpp20.contains_for_associative_containers
[ OK ] cpp20.contains_for_associative_containers (0 ms)
[----------] 7 tests from cpp20 (0 ms total)
[----------] 8 tests from cpp17
[ RUN ] cpp17.filename_operations
[ OK ] cpp17.filename_operations (0 ms)
[ RUN ] cpp17.optional_types
[ OK ] cpp17.optional_types (0 ms)
[ RUN ] cpp17.heterogeneous_types
[ OK ] cpp17.heterogeneous_types (0 ms)
[ RUN ] cpp17.filesystem
[ OK ] cpp17.filesystem (0 ms)
[ RUN ] cpp17.parallel_execution_policy
[ OK ] cpp17.parallel_execution_policy (0 ms)
[ RUN ] cpp17.clamp_values
[ OK ] cpp17.clamp_values (0 ms)
[ RUN ] cpp17.compiler_attributes
[ OK ] cpp17.compiler_attributes (0 ms)
[ RUN ] cpp17.maybe_unused_attribute
[ OK ] cpp17.maybe_unused_attribute (0 ms)
[----------] 8 tests from cpp17 (0 ms total)
[----------] 2 tests from cpp14
[ RUN ] cpp14.return_value_optimisation
[ OK ] cpp14.return_value_optimisation (0 ms)
[ RUN ] cpp14.digit_separators
[ OK ] cpp14.digit_separators (0 ms)
[----------] 2 tests from cpp14 (0 ms total)
[----------] Global test environment tear-down
[==========] 31 tests from 4 test suites ran. (1 ms total)
[ PASSED ] 31 tests.
PRETTY_NAME="Ubuntu Oracular Oriole (development branch)"
NAME="Ubuntu"
VERSION_ID="24.10"
VERSION="24.10 (Oracular Oriole)"