My [rushed] notes from an amazing C++ talk by my company’s cpp guild.

There’s a lot in here, and I probably won’t use some of the features too often. So far, I think I’d take advantage of:

  • Structured binding! Although it’s not as nice as Javascript’s structured binding, I think this is a big step in QoL enhancements for C++.
  • initializers in if/switch, I’ve already used it a few times, it’s really handy
  • if constexpr, very nice for template programming
  • [[nodiscard]] attribute for API wrapper functions

Namespace nesting

// Pre C++17
A {
    B { }
}
// In C++17, you can write
A::B { }

_v suffix

// Pre C++17
static_assert(std::is_floating_point<T>::value, "error");
// In C++17, you can write
static_assert(std::is_floating_point_v<T>); // no error required

Auto deduction

auto a = 0  // int
auto b(0)   // int
auto c{0}   // pre17 deduced to int; cpp 17 uses initializer list
auto d = {0} // init list
auto e{0,1} // pre17 used to be list, cpp17 now compilation error

Adding attributes on namespaces/enums

namespace A {
    namespace [[deprecated("this is dep")]] deprec {
        // Foo
    }
}

enum class GoodLanguages {
    e_PYTHON,
    e_JAVASCRIPT [[deprecated("eww")]],
    e_CPP,
    e_RUST
}

init-statements in conditionals

Also works with switch statements

// Pre 17, you only had a scoping block.
{  // scope block
    const int rc = init();
    if (rc == 0) {
        std::cout << "rc visible outside of if block";
    }
}

// In C++17:
if (const int rc = init(); rc == 0) {
    std::cout << "rc visible only in if block"
}

auto non-type template params

A non-type parameter is defined as a type of parameter that doesn’t substitute for a type, but rather a value. Some common non-type params are: - Enums and integral values - pointer/reference to a class obj/function

Pre C++17, you would have to pass in the class name with a non-type parameter.

template <typename T, T ENUM_VALUE>
constexpr const char* enumToString();
constexpr auto s = enumToString<Dog, Dog::e_BARK>();

First have to take the type in the first param, then enum for second param. Increases verbosity.

// More pre c++17 examples, integral types have no type
std::integral_constant<int, 42>{};
std::integral_constant<long, 1204l>{};

In C++17, auto can be used to deduce the non-type param:

template <auto ENUM_VALUE>  // no longer needs a T
constexpre const char* enumToString();
constexpr auto s = enumToString<Dog::e_BARK>();

template <auto VALUE> struct Constant {};
Constant<42> {};  // decltype is int
Constant<'a'> {}; // decltype is char
Constant<50ul>{}; // decltype is unsigned long

std::string_view

In C++17, std::string_view: ref to a string, does not own contents.

[[nodiscard]] attribute

Let’s say we have some function:

int httpGetRequest(std::string* output);

Generally, we use this function as such:

std::string output;
httpGetRequest(&output);
foo(output); // might error!

The problem with this is that you dont know if output is populated, so you must check the int return value. However, this is not enforced anywhere, the programmer just has to know to check the return value.

With C++17, we can enforce checking return types with [[nodiscard]]. The nodiscard attribute means to always check the return value, and the return value is not checked, the program compiles with a warning (not an error).

[[nodiscard] int httpGetRequest(std::string* output);
// Compiler warning tells you to do this
std::string output;
int rc = httpGetRequest(&output); // check rc
if (rc == e_ERROR) {
    // handle err
}
foo(output);

[[fallthrough]] attribute

switch (requestStatus)
{
    case RequestStatus::e_VALID:
        handleRequest(sender, request);
        // BUG: no break here! this statement will fallthrough
    case RequestStatus::e_TIMEOUT:
        sendTimeoutResponse(sender);
        break;
    // Use fallthrough or break for every case
    case RequestStatus::e_FOO:
        [[fallthrough]]
    case RequestStatus::e_BAR:
        handleFooAndBar()
        break;
}

Always end switch cases with break; or [[fallthrough]]

std::optional

I think this existed in boost::optional already

std::optional<int> random("file") {
    return randomNumberIn("file") ? 42 : std::nullopt;
}

random returns either std::nullopt or int.

value_or with std::optional

This could be really handy. Right now I sometimes use dummy values like -1 or empty string to represent an invalid case, but with optional and value_or I no longer have to.

In C++17, we can write:

void greet(std::optional<std::string> const& name ) {
    std::cout << "Hello" << name.value_or("<name missing>") << std::endl;
}

std::variant

I already have an entire post about std::variant, so I’ll keep it short.

using MarketEvent = std::variant<Order, Execution, Price>;
void processMarketEvent(const MarketEvent& e) {
    match(e, [](const Order&) {/* processs Order */},
             [](const Execution&) {/* processs Execution */},
             [](const Price&) {/* processs Price */},
    )
}

optional and variant is used for:

  • representing failure cases or multiple outcomes
  • modelling optional data
  • controlling construction/destruction
  • representing choice between types
  • typesafe error handling
  • state machines
  • recursive data structures (JSON)

Structured Binding!!

Probably the most useful feature in this whole release.

std::unordered_map<std::string, std::string> emails;
emails.emplace("Taylor", "the64@bloomberg.net");
// C++17's structured binding allows you to write
// this nice for loop
for (const auto& [name, email]: emails) {
    std::cout << name << " [" << emails << "]\n";
}

Can destruct anything, as long as its public. Structs, tuples, etc.

struct vec3 { double x, y, z };
const auto [px, py, pz] = pos;

Customizing this is possible as well, but requires some work.

Just my own thoughts, but maybe in the future it can smartly destructure by key name in an unordered_map or something, similar to javascript where you can write

const data = {
    foo: 30
};
const { foo } = data;
console.log(foo); // prints "30"

if constexpr

Pre C++17, when you build a template, you write can use enable_if for type cases that overload the templates. It’s basically a compile-time switch for templates, but in my opinion it’s pretty hard to use. You’d probably have to write some convoluted metaprogramming statement that is hard to read. An example is something like:

template <class T>
typename std::enable_if<std::is_arithmetic<T>, bool>::type
foo(T x)
{
    // implementation
}
// or even worse, 
template <bool B, typename T = void>
using enable_if_t = typename enable_if<B, T>::type;
template <class T,
         typename std::enable_if_t<std::is_integral<T>::value>* = nullptr>
void do_stuff(T& t) {
    // an implementation for integral types (int, char, unsigned, etc.)
}

With if constexpr you can write simply:

// using _v suffix
template <typename T>
auto get_value(T t) {
    if constexpr (std::is_pointer_v<T>) {
        return *t;
    } else {
        return t;
    }
}

Another great example is:

struct S 
{
    int n;
    std::string s;
    float d;
};

// Pre c++17, you'd have to write
template <> auto& get<0>(S &s) { return s.n; }
template <> auto& get<1>(S &s) { return s.s; }
template <> auto& get<2>(S &s) { return s.d; }

// Now, it can all be matched in the same function.
template <std::size_t N>
auto& get(S& s)
{
    if constexpr (N == 0)
        return s.n;
    else if constexpr (N == 1)
        return s.s;
    else if constexpr (N == 2)
        return s.d;
}

For fun, let’s say we want to write a naive fibonacci solution that finds the solution at compile time. We can write:

// Pre 17, we'd have to write multiple templates
template<long N>
constexpr int fibonacci() {
    return fibonacci<N-1>() + fibonacci<N-2>();
} 
template<>
constexpr int fibonacci_old<1>() { return 1; }
template<>
constexpr int fibonacci_old<0>() { return 0; }


// Now, the three functions can be packaged into one familiar function
template<long N>
constexpr long fibonacci()
{
    if constexpr (N>=2)
        return fibonacci<N-1>() + fibonacci<N-2>();
    else
        return N;
}
int main() {
    std::cout << fibonacci<20>();
}