Skip to content

Instantly share code, notes, and snippets.

@Airtnp
Created November 28, 2017 07:40
Show Gist options
  • Save Airtnp/5b0e56cbf5b5cb45d565e2ac38c1bb1b to your computer and use it in GitHub Desktop.
Save Airtnp/5b0e56cbf5b5cb45d565e2ac38c1bb1b to your computer and use it in GitHub Desktop.
C++ WTF?

Cpp Quiz

Array is sure not pointer!

int arr[1]; 
int *p;
arr = xxx; // Error 
p = xxx; // OK 

int arr[10]; 
int *p = (int*)malloc(sizeof(int)*10); 
size_t as = sizeof(arr); // 40 
size_t ps = sizeof(p); // 4

Bad, bad VLA

// Only valid in C99 and optional C11 (GNU11)
int a = 100;
int v[a] = {0};
sizeof(v); // 400! runtime evaluation

aggr-init vs. delete function

struct Foo {
    Foo(const Foo &) = delete;
};

int main() { Foo bar{}, b2{ Foo{} }; } // OK

class Foo {
    Foo(const Foo &) = delete;
};
int main() { Foo bar{}, b2{ Foo{} }; } // OK

struct Foo {
    explicit Foo(const Foo &) = delete;
};
int main() { Foo bar{}, b2{ Foo{} }; } // Error

struct Foo {
    Foo() = delete;
};
int main() { Foo bar{}; } // OK

struct Foo {
    Foo() = delete;
    Foo(int);
};
int main() { Foo bar{}; } // Error

struct Foo {
private:
    Foo();
};
int main() { Foo bar{}; } // Error

return initializer_list?

static int a = 1;
static int b = 2;

initializer_list<int> foo() {
    return {a, b} // copy first, return (assign by pointer)
}

int main() {
    auto p = foo(); // Error: dangling pointer
}

void()

template <typename T>
T foo1() {
    return T();
}

template <typename T>
T foo2() {
    return T{};
}

template <typename T>
void foo3() {
    auto p = new T{};
    delete p;
}

void bar() {
    return void(); // OK
}

void baz() {
    return void{}; // Error
}
  • std::invoke with return void

Unicode support

char* \U0001f431 = "cat" // OK
char* 😺 = "cat" // Error in GCC

int abc = 1;
int ab\u200c = 1;
int a\u200bc = 1;
int abc\u200 = 1;

Lambda capture forwarding and universal reference

template<class T>
template<typename F, typename ...Args>
T Container<T>::addTask(F&& func, Args&&... args)
{
    container.emplace_back( [func = std::forward<F>(func),
                             args = std::make_tuple(std::forward<ARGS>(args)...)                                 ] 
                             ()  mutable // make mutable if you want to move the args in to func
                             {
                                 return std::apply(func, std::move(args));
                             });

    //.....
}

Delete element in Cpp

  • vector erase-remove
  • remove_if
  • dis
std::unordered_map<...> m;
auto it = m.begin();
while (it != m.end())
{
    if (pred(*it))
        it = m.erase(it);
    else
        ++it;
} // UB before Cpp14

std::unordered_map<...> mymap;
std::vector<decltype(mymap)::key_type> vec;
for (auto&& i : mymap)
    if (/*compare i*/)
        vec.emplace_back(i.first);
for (auto&& key : vec)
    mymap.erase(key);

Nested using

template <typename T>
template <typename U>
using P = std::is_same<T, U>; // Usable in gcc, failed in clang
                              // ill-formed

vector.resize()

tie and tie

int a = 1, b = 2, c = 3;
std::tie(a, b, c) = {c, a, b};
cout << a << ' ' << b; // 3 3 well-defined?

integer conversion

sizeof('a' + ' ') == sizeof(int) // true (C even char/bool literal = int)
-1L < 1U // long > int -> convert to long 1 | long == int -> convert to unsigned long 0

default function argument

  • ref: SuperNaiveCppLib/notes/CppCon2017/Notes

launder

  • make compiler remove const assumption
struct X {
    const int n;
    const double d;
};
X* p = new X{7, 8.8};
new (p) X{42, 9.9}; // place new value into p
int b = std::launder(p)->n; // OK, b is 42
int c = p->n; // undefined behavior!
double d = p->d; // undefined behavior!

copy=elision

struct foo {
    foo() = default;
    foo(cosnt foo&) = delete;
};

int main() {
    auto x = foo(); // prev-17: error! | 17: prvalue copy elision
}

copy-and-swap without customize swap

class A {
    A(const A&) = default;
    A& operator(const A& rhs) {
        delete();
        A temp(rhs);
        std::swap(*this, temp); // infinite loop! will call copy ctor
        return *this;
    }  
};

string literal is anonymous lvalue

const_cast<char*>("WTF")[0] = 'd'; // OK, though segmentation fault
true = false; // Failed, 1 is prvalue. <del>Python 2 will allow this</del>

func-macro and identifier

class A {};

#define A(x) _

int main() {
    A a{}; // OK
    A(a{}); // Error
}

Reference bind to different integer

char c = 'X';
int& i = c; // Error: promotion causes a prvalue
i = 'J';

void foo(long long int& r) {}
foo(i) // Error
foo(reinterpret_cast<long long int&>(i)) // Potential problem
// same as *reinterpret_cast<T*>(&(i))

rdbuf()

std::cout << std::cin.rdbuf(); // consume
std::stringstream ss{"TEST"};
std::cin.rdbuf(ss.rdbuf()); // shallow copy

sizeof?

  • discussion
  • sizeof(arr)[0] === sizeof(arr[0]) (when sizeof expression)
static uint32_t my_arr[2];
static_assert(sizeof(my_arr) == 8, "");
static_assert(sizeof(my_arr[0]) == 4, "");
static_assert(sizeof(my_arr)[0] == 4, "");
static_assert(sizeof(int)*p == p * 4, ""); // will not parse as size((int*)p);

std::string and SSO

  • FBString

Small strings (<= 23 chars) are stored in-situ without memory allocation. (align as 24 bytes data type) Medium strings (24 - 255 chars) are stored in malloc-allocated memory and copied eagerly. Large strings (> 255 chars) are stored in malloc-allocated memory and copied lazily.

auto and private

class Foo {
    struct Bar { int i; };
public:
    Bar Baz() { return Bar(); } // Your problem...
};

class A {
    class B {};
public:
    typedef B user_t; // Because you choose it
};

int main() {
    Foo f;
    // Foo::Bar b = f.Baz();  // error
    auto b = f.Baz();         // ok
    std::cout << b.i;
}

__has_trivial_copy

#include <type_traits>

using namespace std;

struct A {
    A(int v) : a(v) {}
    A(A&&) {a = 1;}
    // A(const A&) { a = 1; }
private:
    A(const A&) = delete;
    int a;
};

int main() {
    A a{1};
    // A b{a};
    cout << is_trivially_copyable<A>::value; // 0 user-defined move ctor
    cout << __has_trivial_copy(A); // 1
    cout << __is_pod(A); // 0
}
  • before CWG-1734, trivial = implicit + delete

ambiguous

1++1; // error
1+ +1 // 2

push_back self element?

std::vector<X> vec;
vec.push_back(v.front()); // reallocation may happen before construct new element?
// emplace_back -> new space -> new element -> move/copy

implicit function definition?

  • only c90 allows this
#include<stdio.h>

int main() {
    int a = sum(4,6);
    printf("%d",a);
    return 0;
}

int sum(int a,int b) {
    return a + b;
}

injected class name

  • The injected class name means that X is declared as a member of X, so that name lookup inside X always finds the current class, not another X that might be declared at the same enclosing scope.
class X { 
    using T = typename A::A; // However, this fails because A::A implies constructor // though constructor address should not be taken 
    // and constructor cannot be directly call by A::A(...))
};
X x1;
class X::X x2; // class X::X is equal to X
class X::X::X x3; // ...and so on...

Inheritance function

struct X{ void func(); };
struct Y : public X{};
decltype(&Y::func) // void (X::*)()

Dependent name

  • so
template <typename T>
class base {

protected:
    int x;
};

template <typename T>
class derived : public base<T> {

public:
    int f() { 
        return this->x; // base<T>::x || using base<T>::x
    }
};

most vexing parse

void f(double adouble) {
  int i(int(adouble)); // function declaration int i(int adouble);
}

Attribute parsing

void f() {
  int y[3];
  y[[] { return 0; }()] = 1;    // error
  int i [[cats::meow([[]])]]; // OK
}
[[attr1]] class [[attr2]] c {...} [[attr3]] x [[attr4]], y;
// attr1 applies to variables x and y
// attr2 applies to class c
// attr3 applies to x's and y's types
// attr4 applies to variable x

Operation associativity and precedence

a = 1, 2, 3; // Evaluated as (a = 1), 2, 3. Comma operator is left to right
if (c > b > a) // (c > b > a) is treated as ((c > b) > a), associativity of '>' is left to right.
a = b = c; // Evaluated as a = (b = c). = operator is right to left
int a;
(a = 1) = 2; // Builtin assignment returns T& lvalue

Universal ? Rvalue ? Class template argument deduction ?

struct A {
    template <typename T>
    A(T&& v) {}
};

template <typename T>
struct B {
    B(T&& v) {};
};

int main() {
    int i = 233;
    auto&& v = i; // OK auto -> int&
    int&& m = i; // Error
    auto p = A{i}; // OK T -> int&
    auto q = B<int&>{i}; // OK T -> int&
    auto r = B{i}; // Error T -> int
    T& & p = ... // Error not such grammar
    // difference between class template deduction and template argument deduction
}

Virtual memory exhausted

namespace std
{
  typedef long unsigned int size_t;
}

namespace std __attribute__ ((__visibility__ ("default")))
{
  template<typename _Tp, std::size_t _Nm>
    struct __array_traits
    {
      typedef _Tp _Type[_Nm];
    };

  template<typename _Tp, std::size_t _Nm>
    struct array
    {
      typedef std::__array_traits<_Tp, _Nm> _AT_Type;
      typename _AT_Type::_Type _M_elems;
    };
}

namespace std __attribute__ ((__visibility__ ("default")))
{
  template<size_t _Nw>
    struct _Base_bitset
    {
      typedef unsigned long _WordT;

      _WordT _M_w[_Nw];

      constexpr _Base_bitset() noexcept
      : _M_w() { }
    };

  template<size_t _Nb>
    class bitset
    : private _Base_bitset<((_Nb) / (8 * 8) + ((_Nb) % (8 * 8) == 0 ? 0 : 1))>
    {
    };
}

constexpr std::size_t N = 100000;
std::array<std::bitset<N>, N> elems; // The constexpr makes gcc to expand 100000 * 1563 wordT

int main() {}

Bad variant

#include <variant>
#include <iostream>

using namespace std;

struct A{};
struct B{};
struct C{};
/*
struct D{};
struct E{};
struct F{};
struct G{};
struct H{};
struct I{};
struct J{};
struct K{};
struct L{};
struct M{};
struct N{};
struct O{};
*/

int main() {
  using T = variant<A, B, C/*, D, E, F, G, H, I, J, K, L, M, N, O*/>;
  T a{A{}};
  
  auto p = std::visit( 
    [](auto&& a, auto&&... args) -> T { return a; },
    a, a, a, a
  );
}
  • another requirement: visitor of std::visit must be exhausive (some return type and can receive all kinds of input value)
    • g++ directly use get<0>, the hinting was soooooooooo sabi
  • variant<T, T> is ill-formed but without diagnose

Member specialized (such a dick)

template <class C> class X
{
public:
   template <class T> void get_as();
   template <> void get_as(); // Error! full-specialization only allowed in namespace scope
};

template <class C> template<>
void X<C>::get_as<double>() {} // Error! Explicitly specialized members need their surrounding class templates to be explicitly specialized as well.

template <> template<>
void X<int>::get_as<double>() {} // So why no just overload

Asso container?

std::set<T> val{...};
struct MyClass {
    MyClass& operator<<(T& val); // #1
    template <typename U>
    MyClass& operator<<(U& val); // #2
};
for (auto& i: val) {
    MyClass{} << *i; // call #2, i -> const iterator
}

Specialize namespace std

Multiple declaration ?

Conversion Overload

#define PRETTY(x) (std::cout << __PRETTY_FUNCTION__ << " : " << (x) << '\n')

struct D;

struct C {
    C() { PRETTY(this);}
    C(const C&) { PRETTY(this);}
    C(const D&) { PRETTY(this);}
};

struct D {
    D() { PRETTY(this);}
    operator C() { PRETTY(this); return C();}
};

D d;
C c(d); // conversion or exact match ?

Without struct/class

#include <iostream>
#include <vector>
#include <cstdint>
#include <functional>
#include <string>

using namespace std;

size_t hash(string s) {
    return hash<string>{}(s);  // ambiguous 
}

unordered_map before 14

  • The order of the elements that are not erased is preserved (this makes it possible to erase individual elements while iterating through the container) (since C++14)
  • discussion-and-solution
std::unordered_map<...> m;
auto it = m.begin();
while (it != m.end())
{
    if (pred(*it))
        it = m.erase(it); // UB before 14
    else
        ++it;
}

A real problem #1

What's wrong with this?

#include <iostream>
#include <vector>
#include <cstdint>
#include <string>
#include <unordered_map>
#include <functional>
#include <tuple>
#include <cassert>
#include <variant>
using namespace std;
template <std::size_t I, typename ...Args>
auto tuple_index_impl(size_t n, std::tuple<Args...>& tp) {
    // compile-time decide/check return type will not notice runtime branch end conditon
    // if you only write one if constexpr without else, still not notice
    // only if constexpr { ... } else { ... }
    // Also: the return type will deduce differently and failed
    if (I >= sizeof...(Args)) {
        throw std::out_of_range("Out of bound.");
        return {};
    } else {    
        if (n == I) {
            return std::get<I>(tp);
        }
        return tuple_index_impl<I + 1, Args...>(n, tp);
    }
}

template <typename ...Args>
auto tuple_index(size_t n, std::tuple<Args...>& tp) {
    return tuple_index_impl<0, Args...>(n, tp);
}


int main() {
    int i = 2;
    tuple<int, long, int> tp{1, 233, 2};
    /* Question 2: why variant failed (some type ill-formed)
    std::visit(
        [](long long&& c) { 
            std::cout << c << std::endl; 
        },
        tuple_index(i, tp)
    );
    */
}

Short circuit ensure side-effect

int main() {
    fork(); // (1 parent and 1 child) 
    fork() && fork() || fork(); // 4(both fork) + 4(both fork) + 2(only child do this fork)
    fork(); // x2 = 20
}

C/C++ about pointer

#include  <stdio.h>

int main(void)
{
    int* x, *y;
    x = &(y); // fail in C++, valid in C
    y = &(x);
    printf("Hello, world!\n");
    return 0;
}

Ranged-based-for-loop pitfall

class T {
  std::vector<int> data_;
public:
  std::vector<int>& items() { return data_; }
  // ...
};

{
  T thing = f();
  for (auto& x : thing.items()) {
    // Note: “for (auto& x : f().items())” is WRONG
    mutate(&x);
    log(x);
  }
}
  • The wrong is because
auto&& temp = f().items(); // This will not extend lifetime! Causes dangling reference

Non-dependent name

template <class T>
struct Outer
{
    template <class U>
    void f();

    void bar(Outer outer) {
        [outer](){ outer.f<int>(); };
    }
};

int main() { }

constexpr exchange (change in C++20 ABQ)

template<class T, class U = T>
constexpr T exchange(T& obj, U&& new_value) // without constexpr, won't work
{
    T old_value = std::move(obj);
    obj = std::forward<U>(new_value);
    return old_value;
}

struct S
{
  int* p;
  int n;
 
  constexpr S(S&& other)
    :p{std::exchange(other.p, nullptr)}
    ,n{std::exchange(other.n, 0)}
  {}
 
  constexpr S& operator=(S&& other) {
    p = std::exchange(other.p, nullptr); // move p, while leaving nullptr in other.p
    n = std::exchange(other.n, 0); // move n, while leaving zero in other.n
    return *this;
  }
};

enable_shared_from_this

struct S
{
  shared_ptr<S> dangerous()
  {
     return shared_ptr<S>(this);   // don't do this!
  }
};

int main()
{
   shared_ptr<S> sp1(new S); // They are seperate
   shared_ptr<S> sp2 = sp1->dangerous();
   return 0;
}

struct S : enable_shared_from_this<S>
{
    // enable_shared_from_this<T> adds a private weak_ptr<T> instance to T which holds the 'one true reference count' for the instance of T.
    shared_ptr<S> not_dangerous()
    {
         return shared_from_this();
    }
}

int main()
{
   S *p = new S;
   shared_ptr<S> sp2 = p->not_dangerous();     // also bad.
}

value with name is lvalue (except enumeration)

ternary operator -> (l/rvalue)

int a=5, b=3;
++(a > b ? a : b);

int a=5;
double b=3;
++( a > b ? a : sb); // Error
  • same type lvalue -> lvalue
  • otherwise -> do common_type -> rvalue

Abominable Function Types

  • proposal
  • using abominable = void() const volatile &&;
  • you cannot decay it! const/volatile decorates this

wtf is array type

void f(double x[volatile], const double y[volatile]);
void f(double * volatile x, const double * volatile y);
void f(double a[restrict static 3][5]); // the value of the actual parameter must be a valid pointer to the first element of an array with at least as many elements as specified by expression:
void fadd(double a[static restrict 10],
          const double b[static restrict 10])
{
    for (int i = 0; i < 10; i++) { // loop can be unrolled and reordered
        if (a[i] < 0.0) break;
        a[i] += b[i];
    }
}

Anonymous union with using

int main() {
    using T = union {
        int a;
    };
    // No valid a!
    union {
        int b;
    }
    b = 1; // OK
}

constexpr function parameter?

constexpr int incr(int& n) {
  return ++n;
}
constexpr int g(int k) {
  constexpr int x = incr(k); // error: incr(k) is not a core constant
                             // expression because lifetime of k
                             // began outside the expression incr(k)
  return x;
}
constexpr int h(int k) {
  int x = incr(k); // OK: x is not required to be initialized with a core
                   // constant expression
  return x;
}

deduce first

template <typename R = int, typename U>
R foo() {
    return -0.1;
}


int main() {
    foo<long long>(1); // return long long
}

template default parameter

template<typename T1, typename T2 = int> class A;
template<typename T1 = int, typename T2> class A;
// the above is the same as the following: (same as function default parameter)
template<typename T1 = int, typename T2 = int> class A;

// not same
template<typename T = int> class X;
template<typename T = int> class X {}; // error

Compare POD bytes

Union + friend

operator-> chaining

myClass->myValue;
//--->
(myClass.operator-> ())->myValue;

Lambda capture and compiler

#include <type_traits>

int main() {
    int x = 1;
    int& y = x;
    int&& z = static_cast<int&&>(x);

    [ ]{ static_assert(std::is_same<decltype( x ), int>::value, "A1"); };
    [=]{ static_assert(std::is_same<decltype( x ), int>::value, "A2"); };
    [&]{ static_assert(std::is_same<decltype( x ), int>::value, "A3"); };

    [ ]{ static_assert(std::is_same<decltype((x)), int&>::value, "B1"); };
    [=]{ static_assert(std::is_same<decltype((x)), const int&>::value, "B2"); };
    [&]{ static_assert(std::is_same<decltype((x)), int&>::value, "B3"); };

    [ ]{ static_assert(std::is_same<decltype( y ), int&>::value, "C1"); };
    [=]{ static_assert(std::is_same<decltype( y ), int&>::value, "C2"); };
    [&]{ static_assert(std::is_same<decltype( y ), int&>::value, "C3"); };

    [ ]{ static_assert(std::is_same<decltype((y)), int&>::value, "D1"); };
    [=]{ static_assert(std::is_same<decltype((y)), int&>::value, "D2"); };
    [&]{ static_assert(std::is_same<decltype((y)), int&>::value, "D3"); };

    [ ]{ static_assert(std::is_same<decltype( z ), int&&>::value, "E1"); };
    [=]{ static_assert(std::is_same<decltype( z ), int&&>::value, "E2"); };
    [&]{ static_assert(std::is_same<decltype( z ), int&&>::value, "E3"); };

    [ ]{ static_assert(std::is_same<decltype((z)), int&>::value, "F1"); };
    [=]{ static_assert(std::is_same<decltype((z)), int&>::value, "F2"); };
    [&]{ static_assert(std::is_same<decltype((z)), int&>::value, "F3"); };
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment