1 of 15

C++ is a reasonable language for reasonable people

Simon Brand

@TartanLlama

2 of 15

In C++, you can introduce UB by changing where you write =default

struct foo {� foo() = default;int a;};��struct bar {� bar();int b;};��bar::bar() = default;��int main() {� foo a{};� bar b{};std::cout << a.a //okay

<< b.b; //UB}

3 of 15

In C++, this is valid code

class A {public:virtual void foo() = 0;};��class B : public A {public:virtual void foo();};��void B::B::B::B::B::B::B::B::B::B::B::� B::B::B::B::B::B::B::B::B::B::� B::A::foo(){}

4 of 15

In C++, you get a cache miss on pretty much every lookup in the STL hash table

5 of 15

In C++, you can store state at compile time by defining friend functions

constexpr int flag (int);template<class Tag>struct writer {friend constexpr int flag (Tag) {return 0;}};template<bool B, class Tag = int>struct dependent_writer : writer<Tag> { };template<bool B = noexcept (flag (0)),int = sizeof (dependent_writer<B>)>�constexpr int f () {return B;}int main () {� constexpr int a = f ();� constexpr int b = f ();�� static_assert (a != b, "fail");}

6 of 15

In C++, sum types are footguns

std::variant<std::string, bool> lol = "oh no";std::visit([](auto&& x) { std::cout << x; }, lol);

7 of 15

In C++, non-allocating strings are footguns

std::string s = "Hellooooooooooooooo ";std::string_view sv = s + "World\n";std::cout << sv;

8 of 15

9 of 15

In C++, my std::expected assignment operator is hundreds of lines of template nonsense

10 of 15

// This class manages conditionally having a trivial copy assignment operatortemplate <class T, class E,bool = is_void_or<� T, conjunction<TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T),� TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T),� TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T)>>::value� &&TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(E)::value� &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value� &&TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(E)::value>struct expected_copy_assign_base : expected_move_base<T, E> {using expected_move_base<T, E>::expected_move_base;};��template <class T, class E>struct expected_copy_assign_base<T, E, false> : expected_move_base<T, E> {using expected_move_base<T, E>::expected_move_base;�� expected_copy_assign_base() = default;� expected_copy_assign_base(const expected_copy_assign_base &rhs) = default;�� expected_copy_assign_base(expected_copy_assign_base &&rhs) = default;� expected_copy_assign_base &operator=(const expected_copy_assign_base &rhs) {this->assign(rhs);return *this;}� expected_copy_assign_base &operator=(expected_copy_assign_base &&rhs) = default;};��// This class manages conditionally having a trivial move assignment operator// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it// doesn't implement an analogue to std::is_trivially_move_assignable. We have// to make do with a non-trivial move assignment operator even if T is trivially// move assignable

#ifndef TL_EXPECTED_GCC49template <class T, class E,bool =� is_void_or<T, conjunction<std::is_trivially_destructible<T>,std::is_trivially_move_constructible<T>,std::is_trivially_move_assignable<T>>>::� value &&std::is_trivially_destructible<E>::value� &&std::is_trivially_move_constructible<E>::value� &&std::is_trivially_move_assignable<E>::value>struct expected_move_assign_base : expected_copy_assign_base<T, E> {using expected_copy_assign_base<T, E>::expected_copy_assign_base;};

#elsetemplate <class T, class E, bool = false> struct expected_move_assign_base;#endif��template <class T, class E>struct expected_move_assign_base<T, E, false>: expected_copy_assign_base<T, E> {using expected_copy_assign_base<T, E>::expected_copy_assign_base;�� expected_move_assign_base() = default;� expected_move_assign_base(const expected_move_assign_base &rhs) = default;�� expected_move_assign_base(expected_move_assign_base &&rhs) = default;�� expected_move_assign_base &operator=(const expected_move_assign_base &rhs) = default;�� expected_move_assign_base &operator=(expected_move_assign_base &&rhs) noexcept(std::is_nothrow_move_constructible<T>::value� &&std::is_nothrow_move_assignable<T>::value) {this->assign(std::move(rhs));return *this;}};

11 of 15

// expected_delete_assign_base will conditionally delete copy and move// constructors depending on whether T and E are copy/move constructible +// assignabletemplate <class T, class E,bool EnableCopy = (std::is_copy_constructible<T>::value &&std::is_copy_constructible<E>::value &&std::is_copy_assignable<T>::value &&std::is_copy_assignable<E>::value),bool EnableMove = (std::is_move_constructible<T>::value &&std::is_move_constructible<E>::value &&std::is_move_assignable<T>::value &&std::is_move_assignable<E>::value)>struct expected_delete_assign_base {� expected_delete_assign_base() = default;� expected_delete_assign_base(const expected_delete_assign_base &) = default;� expected_delete_assign_base(expected_delete_assign_base &&) noexcept =default;� expected_delete_assign_base &operator=(const expected_delete_assign_base &) = default;� expected_delete_assign_base &operator=(expected_delete_assign_base &&) noexcept = default;};��template <class T, class E>struct expected_delete_assign_base<T, E, true, false> {� expected_delete_assign_base() = default;� expected_delete_assign_base(const expected_delete_assign_base &) = default;� expected_delete_assign_base(expected_delete_assign_base &&) noexcept =default;� expected_delete_assign_base &operator=(const expected_delete_assign_base &) = default;� expected_delete_assign_base &operator=(expected_delete_assign_base &&) noexcept = delete;};

template <class T, class E>struct expected_delete_assign_base<T, E, false, true> {� expected_delete_assign_base() = default;� expected_delete_assign_base(const expected_delete_assign_base &) = default;� expected_delete_assign_base(expected_delete_assign_base &&) noexcept =default;� expected_delete_assign_base &operator=(const expected_delete_assign_base &) = delete;� expected_delete_assign_base &operator=(expected_delete_assign_base &&) noexcept = default;};��template <class T, class E>struct expected_delete_assign_base<T, E, false, false> {� expected_delete_assign_base() = default;� expected_delete_assign_base(const expected_delete_assign_base &) = default;� expected_delete_assign_base(expected_delete_assign_base &&) noexcept =default;� expected_delete_assign_base &operator=(const expected_delete_assign_base &) = delete;� expected_delete_assign_base &operator=(expected_delete_assign_base &&) noexcept = delete;};��emplate <class U = T, class G = T,� detail::enable_if_t<std::is_nothrow_constructible<T, U &&>::value> * =nullptr,� detail::enable_if_t<!std::is_void<G>::value> * = nullptr,� detail::enable_if_t<(!std::is_same<expected<T, E>, detail::decay_t<U>>::value &&!detail::conjunction<std::is_scalar<T>,std::is_same<T, detail::decay_t<U>>>::value &&std::is_constructible<T, U>::value &&std::is_assignable<G &, U>::value &&std::is_nothrow_move_constructible<E>::value)> * = nullptr>� expected &operator=(U &&v) {if (has_value()) {� val() = std::forward<U>(v);} else {� err().~unexpected<E>();::new (valptr()) T(std::forward<U>(v));this->m_has_val = true;}�� return *this;}��

12 of 15

/// \excludetemplate <class U = T, class G = T,� detail::enable_if_t<!std::is_nothrow_constructible<T, U &&>::value> * =nullptr,� detail::enable_if_t<!std::is_void<U>::value> * = nullptr,� detail::enable_if_t<(!std::is_same<expected<T, E>, detail::decay_t<U>>::value &&!detail::conjunction<std::is_scalar<T>,std::is_same<T, detail::decay_t<U>>>::value &&std::is_constructible<T, U>::value &&std::is_assignable<G &, U>::value &&std::is_nothrow_move_constructible<E>::value)> * = nullptr>� expected &operator=(U &&v) {if (has_value()) {� val() = std::forward<U>(v);} else {auto tmp = std::move(err());� err().~unexpected<E>();try {::new (valptr()) T(std::move(v));this->m_has_val = true;} catch (...) {� err() = std::move(tmp);throw;}}�� return *this;}�� template <class G = E,� detail::enable_if_t<std::is_nothrow_copy_constructible<G>::value &&std::is_assignable<G &, G>::value> * = nullptr>� expected &operator=(const unexpected<G> &rhs) {if (!has_value()) {� err() = rhs;} else {� val().~T();::new (errptr()) unexpected<E>(rhs);this->m_has_val = false;}�� return *this;}���

template <class G = E,� detail::enable_if_t<std::is_nothrow_move_constructible<G>::value &&std::is_move_assignable<G>::value> * = nullptr>� expected &operator=(unexpected<G> &&rhs) noexcept {if (!has_value()) {� err() = std::move(rhs);} else {� val().~T();::new (errptr()) unexpected<E>(std::move(rhs));this->m_has_val = false;}�� return *this;}�� // These assign overloads ensure that the most efficient assignment// implementation is used while maintaining the strong exception guarantee.// The problematic case is where rhs has a value, but *this does not.//// This overload handles the case where we can just copy-construct `T`// directly into place without throwing.template <class U = T,� detail::enable_if_t<std::is_nothrow_copy_constructible<U>::value>* = nullptr>void assign(const expected_operations_base &rhs) noexcept {if (!this->m_has_val && rhs.m_has_val) {� geterr().~unexpected<E>();� construct(rhs.get());} else {� assign_common(rhs);}}

13 of 15

�� // This overload handles the case where we can attempt to create a copy of// `T`, then no-throw move it into place if the copy was successful.template <class U = T,� detail::enable_if_t<!std::is_nothrow_copy_constructible<U>::value &&std::is_nothrow_move_constructible<U>::value>* = nullptr>void assign(const expected_operations_base &rhs) noexcept {if (!this->m_has_val && rhs.m_has_val) {� T tmp = rhs.get();� geterr().~unexpected<E>();� construct(std::move(tmp));} else {� assign_common(rhs);}}�� // This overload is the worst-case, where we have to move-construct the// unexpected value into temporary storage, then try to copy the T into place.// If the construction succeeds, then everything is fine, but if it throws,// then we move the old unexpected value back into place before rethrowing the// exception.template <class U = T,� detail::enable_if_t<!std::is_nothrow_copy_constructible<U>::value &&!std::is_nothrow_move_constructible<U>::value>* = nullptr>void assign(const expected_operations_base &rhs) {if (!this->m_has_val && rhs.m_has_val) {auto tmp = std::move(geterr());� geterr().~unexpected<E>();�� try {� construct(rhs.get());} catch (...) {� geterr() = std::move(tmp);throw;}} else {� assign_common(rhs);}}��

// These overloads do the same as above, but for rvaluestemplate <class U = T,� detail::enable_if_t<std::is_nothrow_move_constructible<U>::value>* = nullptr>void assign(expected_operations_base &&rhs) noexcept {if (!this->m_has_val && rhs.m_has_val) {� geterr().~unexpected<E>();� construct(std::move(rhs).get());} else {� assign_common(rhs);}}�� template <class U = T,

detail::enable_if_t<!std::is_nothrow_move_constructible<U>::value>* = nullptr>void assign(expected_operations_base &&rhs) {if (!this->m_has_val && rhs.m_has_val) {auto tmp = std::move(geterr());� geterr().~unexpected<E>();try {� construct(std::move(rhs).get());} catch (...) {� geterr() = std::move(tmp);throw;}} else {� assign_common(rhs);}}�� // The common part of move/copy assigningtemplate <class Rhs> void assign_common(Rhs &&rhs) {if (this->m_has_val) {if (rhs.m_has_val) {� get() = std::forward<Rhs>(rhs).get();} else {� get().~T();� construct_error(std::forward<Rhs>(rhs).geterr());}} else {if (!rhs.m_has_val) {� geterr() = std::forward<Rhs>(rhs).geterr();}}}

14 of 15

IMPLICIT CONVERSIONS

15 of 15

C++ is a reasonable language for reasonable people

Simon Brand

@TartanLlama