Dec 1, 2024

[C++] pack_indexing

Reference:
https://en.cppreference.com/w/cpp/language/pack_indexing
https://godbolt.org/z/9eP8qT64s
#include <type_traits>

template <typename... Ts>
using last_type_t = Ts...[sizeof...(Ts) - 1];
 
template<typename... T>
void run(T... t) {
    decltype(t...[1]) i = 0; // float
}

int main() {
 static_assert(std::is_same_v<last_type_t<int>, int>);
 static_assert(std::is_same_v<last_type_t<bool, char>, char>);
 static_assert(std::is_same_v<last_type_t<float, int, bool*>, bool*>);

 struct tmp{};

 run(1, 2.0f, tmp{});
}

[C++][cpponsea 2024] C++ Cache Friendly Data + Functional + Ranges summary

Reference:
https://www.youtube.com/watch?v=XJzs4kC9d-Y
https://github.com/rollbear/columnist

https://godbolt.org/z/a8P7oTjoh

#include <functional>
#include <iostream>
#include <tuple>
#include <vector>

template<typename... Ts>
class Table {
 public:
  using row = std::tuple<Ts&...>;
  row operator[](size_t i) {
    auto access = [&]<size_t... Is>(std::index_sequence<Is...>) {
      return row{std::get<Is>(data_)[i]...};
    };
    return std::invoke(access, indexes);
  }

  Table() {
    init(indexes);
  }

 private:
  static constexpr std::index_sequence_for<Ts...> indexes{};
  std::tuple<std::vector<Ts>...> data_;

 private:
  template<size_t... I>
  constexpr void init(std::index_sequence<I...>) {
    ((std::get<I>(data_).push_back({42}),...));
  }
};

struct Data1 {
 int value;
};

struct Data2 {
 int value;
};

int main() {

  Table<Data1, Data2> table;
  auto row = table[0];
  std::cout << std::get<0>(row).value << '\n'; // print 42
};


Iterator

Use sentinal pattern.
struct sentinel {}; // or using sentinal = void;

struct iterator {
  using value_type = row;
  using difference_type = ssize_t;

  value_type operator*() const {
    return std::invoke([&]<size_t... Is>(std::index_sequence<Is...>) {
      return value_type{std::get<Is>(t->data_)[offset]...};
    }, t->indexes);
  }

  iterator& operator++() { ++offset; return *this;}
  iterator operator++(int) { auto copy = *this; ++*this; return copy;}
  bool operator==(const iterator&) const = default;
  bool operator==(sentinel) const { return offset == t->size(); }
  table* t;
  size_t offset;
};

Plug in
template<typename... Ts>
class Table {
 public:
  friend class iterator;
  iterator begin() { return {this, 0}; }
  sentinel end() { return {}; }
...
};

Reference:
https://en.cppreference.com/w/cpp/utility/integer_sequence
template<typename...>
struct Table;

template<typename, typename>
struct Row;


template <typename... Ts, size_t... Cs>
struct Row<Table<Ts...>, std::index_sequence<Cs...>> {
  using row_id = typename Table<Ts...>::row_id;

  // Indirection to make std::index_sequence<0, 2, 4> -> I==1 means std::get<2>(...)
  template<size_t I>
  friend auto& get(const Row& r) {
    static constexpr std::array columns{Cs...};
    return std::get<columns[I]>(r.t->data_)[r.offset];
  }

  ...
  Table<Ts...>* t;
  size_t offset;
};


template<typename Table, size_t... Cs>
struct std::tuple_size<Row<Table, std::index_sequence<Cs...>>>
 : std::integral_constant<size_t, sizeof...(Cs)> {};


template <size_t I, typename... Ts, size_t... Cs>
struct std::tuple_element<I, Row<Table<Ts...>, std::index_sequence<Cs...>>> {
  static constexpr std::array indexes = { Cs... };
  using type = Ts...[indexes[I]]&;
};

Select:
template<size_t... Is, typename Table, size_t... Cs>
auto select(const Row<Table, std::index_sequence<Cs...>>& r) {
  static constexpr size_t columns[] { Cs... };
  return Row<Table, std::index_sequence<columns[Is]...>>(r);
}

Usage:
drop_if(values, [](auto r){ auto [x,z] = select<0, 2>(r); return x < z; });
template<typename R, size_t... Cs>
struct range_selector {
  using r_iterator = decltype(std::declval<R&>().begin());
  struct iterator : r_iterator {
    using difference_type = ssize_t;
    using value_type = decltype(select<Cs...>(*std::declval<r_iterator>()));
    
    iterator(const r_iterator& i) : r_iterator(i) {}
    auto operator*() const {
      const r_iterator& i = *this;
      return select<Cs...>(*i);
    }
  };

  iterator begin() { return iterator{ r.begin() }; }
  auto end() { return r.end(); }

  R& r;
};
template<size_t... Cs>
struct range_selector_maker {
  template<typename R>
  friend range_selector<R, Cs...> operator|(R& r, range_selector_maker) {
    return { r };
  }
};

template<size_t... Cs>
range_selector_maker<Cs...> select() { return {}; }

Usage:
for (auto [x, d] : values | select<0, 3>()) {
  std::println("d={} x={}", d, x);
}

Concepts:
template<typename>
inline constexpr bool row_type_v = false;

template<typename T, typename Cs>
inline constexpr bool row_type_v<Row<T, Cs>> = true;

template<typename T>
concept row_type = row_type_v<T>;

Select:
template<size_t... Cs, typename F>
auto select(F&& f) 
  requires (! row_type<std::remove_cvref_t<F>>)
{
  return [f = std::forward<F>(f)]<typename T, typename Is>(row<T, Is> r) {
    return f(select<Cs...>(r));
  };

}
template<typename F>
auto apply(F&& f) {
  return [f = std::forward<F>(f)]<row_type R>(R r) {
    return std::invoke([&]<size_t... Cs>(std::index_sequence<Cs...>) {
      return f(get<Cs>(r)...);
    }, std::make_index_sequence<std::tuple_size_v<R>>{});
  };
}


Usage:
drop_if(values, select<0, 2>(apply([](auto x, auto z) { return x < z; })));

or just:
drop_if(values, select<0, 2>(apply(std::less{})));

[C++][cpponsea] Keep C++ Binaries Small - summary

Reference:
https://youtu.be/7QNtiH5wTAs?si=c8I_prA9k9Hasfdr


Problem

  • Devices with limited storage
  • Devices with limited bandwith
  • Env impact

Parts of an executable

  • Header
  • Text
  • Data (init. vs uninit)
  • Read-only data
  • symbol table
  • Relocation info
  • (ref https://eli.thegreenplace.net/2013/07/09/library-order-in-static-linking)
  • Import table / Procedure linkage table
  • Export table
  • Exception handling info
  • Debugging info

Consider

  • cross platform?
  • linking/symbol resolution handling
  • PIC
  • Endianness
  • Architecture / extensibility

Tools


Coding part


Object init.

0(.zerofill) vs. other thing else; decrease size


Member ordering

Usually won't reduce the binary size due to compiler is able to optimize.


Special member functions

  • inline default special functions
  • inline empty special functions
  • following the rule of zero(RoZ)
  • with either virtual or non-virtual destructor


templates

  • minimal template code section; same as marco
  • Exact/promote to the upper level, which the code is not generated
    for every new type instance.

Compiler options

Nov 21, 2024

[C++] key function

Reference:
undefined reference to vtable for X
What is a C++ "Key Function" as described by gold?



Every polymorphic class requires a virtual table or vtable describing the type and its virtual functions. Whenever possible the vtable will only be generated and output in a single object file rather than generating the vtable in every object file which refers to the class (which might be hundreds of objects that include a header but don't actually use the class.)

The cross-platform C++ ABI states that the vtable will be output in the object file that contains the definition of the key function, which is defined as the first non-inline, non-pure, virtual function declared in the class.

If you do not provide a definition for the key function (or fail to link to the file providing the definition) then the linker will not be able to find the class' vtable.


class X {
public:
  virtual ~X() = 0;
  void f();
  virtual void g() { }
  virtual void h(); // defined inline out side of class.
  virtual void i(); // <-- Key fuction.
  virtual void j();
};

inline void X::h() { }