Overview

This is a C++ library, created to include algorithms that can be worked out at compile time.
It uses template meta programming and functional programming techniques to execute many algorithms at compile time on one or more types.

The more work done at compile time, less time to complete the job at run time!

All algorithms/features are wrapped in namespace ctl

Features available to use:

Note:

  • Container algorithms and utility are influenced by Boost::MP11.
    This has been started as re-inventing wheel theory and taken personal approach to implement the APIs/Functions that MP11 library supports.
    Many of the algorithms are having the same name as mentioned in library MP11.

  • _t next to algorithm name is implemented to avoid using ::type keyword with algorithm

  • _c next to algorithm name is implemented to take constants which are known at compile time (ex: true/false or numbers)

  • _f next to algorithm name is implemented to take template types as template parameter

  • _p next to algorithm name is implemented to take predicate as template parameter (predicate is a template type). It is same as _f, but _p is used to make it more clear to read

  • _qmf next to algorithm name is implemented to take Quoted meta functions as template parameter

    • _qmf implementations are using its non _qmf counterparts

  • _v next to algorithm name is implemented to get the constant value (boolean or number). it is like using ::value on the integral_constant type

Container

There are 2 parts here.

  • List ⇒ the container which holds the type

  • Algorithms ⇒ used with list type. Not necessarily with ctl::list but any other compile time list as well. ex: std::tuple

List

It is a container which holds the types. It is an heterogenous container.

To use make sure to add #include <container/list.hpp> in source file.

There are 3 variants of list

  • clt::list<> ⇒ non-instantiable list

  • ctl::ilist<> ⇒ instantiable list

  • ctl::clist<> ⇒ instantiable and callable list.
    It has many operator over-loadings which can be called at runtime.
    Example for the same can be found in src/main.cpp file

Many algorithms can be applied on the created list type.
Even algorithms from MP11 can also be used with the list type.

Algorithms

Once the heterogenous list is created, algorithms are required to do some work with that list. like for example, to add new element to existing list, or to retrieve an element, or remove specific entry in the list like or even sorting the types in the list. Just like data structure std::vector<> (note that std::vector<> can be used to hold the values of homogenous type at runtime).

These algorithms can be used with any list of types like std::tuple<>. It is not restricted to ctl::list alone.

To use any algorithms make sure to add #include <container/algorithms.hpp> in source file.

All algorithms will result in new type. original type is never modified

Modifiers

Set of algorithms are used to get the new list from the original list by adding/removing one or more types.

General usage of the algorithm is:

using modified_type = _algo name_<L<Ts...>, U>::type

where,

  • _ algo name _ ⇒ name of the modifiable algorithms listed below

  • L<Ts…​> ⇒ list which has to be modified

  • U ⇒ type of the interest. It could be single type or another list type!
    This is optional parameter depending on algorithm.

  • ::type ⇒ to access the type from the result of the modified list.
    If algorithm name is postfixed with _t then there is no need of using ::type

Following are some of modifiable algorithms (replaced with _ algo name _):

rename

Usage:

using result = ctl::rename<L<Ts...>, Y>::type
if
  `L<Ts...> = ctl::list<T1, T2, T3>`
  `Y == std::tuple`
then
  `result = std::tuple<T2, T3, T1>`

Variants:

  • rename

  • rename_t ⇒ to avoid ::type

apply

Usage:

using result = ctl::apply<Y, L<Ts...>>::type
if
    `Y == std::tuple`
    `L<Ts...> = ctl::list<T1, T2, T3>`
then
    `result = std::tuple<T2, T3, T1>`

Variants:

  • apply ⇒ same as rename but template parameters reversed

  • apply_t ⇒ to avoid ::type

rotate left

Usage:

using result = rotate_left_c<L<Ts...>, _num_>::type
if
    `L<Ts...> = L<T1, T2, T3>`
    `_num_ = 1`
then
    `result = L<T2, T3, T1>`

Variants:

  • rotate_left_c

  • rotate_left_c_t ⇒ to avoid ::type

  • rotate_leftnum is passed as type (It should have ::value to access the member). ::type is needed to access the type

  • rotate_left_t ⇒ to avoid ::type

rotate right

Usage:

using result = rotate_right_c<L<Ts...>, _num_>::type
if
    `L<Ts...> = L<T1, T2, T3>`
    `_num_ = 1`
then
    `result = L<T3, T1, T2>`

Variants:

  • rotate_right_c

  • rotate_right_c_t ⇒ to avoid ::type

  • rotate_rightnum is passed as type (It should have ::value to access the member). ::type is needed to access the result type

  • rotate_right_t ⇒ to avoid ::type

sort

Usage:

using result = sort<L<Ts...>>::type
if
    `L<Ts...> = L<T3, T2, T1>`
then
    `result = L<T1, T2, T3>`

where `T1::value <= T2::value <= T3::value`.

Logic used here is Quick sort.

Variants:

  • sort

  • sort_t ⇒ to avoid ::type

  • sort_p ⇒ to provide comparator as predicate to compare 2 types. ::type is needed to access the result type

    • comparator should return true if the first template parameter should be considered before the second template parameter

  • sort_p_t ⇒ to avoid ::type

  • sort_qmf_p ⇒ to provide comparator predicate as quoted meta function. ::type is needed to access the result type

  • sort_qmf_p_t ⇒ to avoid ::type

reverse

Usage:

using result = ctl::reverse<L<Ts...>>::type
if
    `L<Ts...> = L<T1, T2, T3>`
then
    `result = L<T3, T2, T1>`

Variants:

  • reverse

  • reverse_t ⇒ to avoid ::type

replace

Usage:

using result = ctl::replace<L<Ts...>, TR, RW>::type
if
    `L<Ts...> = L<T1, T2, T3>`
    `TR = T2`
    `RW = T4`
then
    `result = L<T1, T4, T3>`

Variants:

  • replace

  • replace_t ⇒ to avoid ::type

  • replace_at_c ⇒ to replace type at given position (position is a constant). ::type is needed to access the result type

  • replace_at_c_t ⇒ to avoid ::type

  • replace_at ⇒ to replace type at given position (position is a type, ::value is used to access the constant). ::type is needed to access the result type

  • replace_at_t ⇒ to avoid ::type

  • replace_if ⇒ to replace all types which results in true when passed to given predicate. ::type is needed to access the result type

  • replace_if_t ⇒ to avoid ::type

  • replace_if_qmf ⇒ predicate passed as quoted meta function. ::type is needed to access the result type

  • replace_if_qmf_t ⇒ to avoid ::type

push_front

Usage:

using result = ctl::push_front<L<Ts...>, T>::type
if
    `L<Ts...> = L<T1, T2, T3>`
    `L<T4, T5, T6>`
then
    `result = L<T4, T5, T6, T1, T2, T3>`

Variants:

  • push_front ⇒ to push another type/list to front of given list

  • push_front_t ⇒ used to avoid ::type

push_back

Usage:

using result = ctl::push_back<L<Ts...>, T>::type
if
    `L<Ts...> = L<T1, T2, T3>`
    `L<T4, T5, T6>`
then
    `result = L<T1, T2, T3, T4, T5, T6>`

Variants:

  • push_back ⇒ to push another type/list to back of given list

  • push_back_t ⇒ to avoid ::type

append

Usage:

using result = ctl::append<L<Ts...>, T>::type
if
    `L<Ts...> = L<T1, T2, T3>`
    `T = `L<T4, T5, T6>`
then
    `result = L<T1, T2, T3, T4, T5, T6>`

Variants:

  • append ⇒ alias to push_back

  • append_t ⇒ alias to push_back_t

pop_front

Usage:

using result = ctl::pop_front<L<Ts...>>::type
if
    `L<Ts...> = L<T1, T2, T3>`
then
    `result = L<T2, T3>`

if list provided is empty, then it will result in error

Variants:

  • pop_front

  • pop_front_t ⇒ to avoid ::type

pop_back

Usage:

using result = ctl::pop_back<L<Ts...>>::type
if
    `L<Ts...> = L<T1, T2, T3>`
then
    `result = L<T1, T2>`

Variants:

  • pop_back

  • pop_back_t ⇒ to avoid ::type

insert

Usage:

using result = ctl::insert_c<L<Ts...>, _index_, Us...>::type
if
    `L<Ts...> = L<T1, T2, T3>`
    `_index_ = 1`
    `Us... = U1, U2, U3`
then
    `result = L<T1, U1, U2, U3, T2, T3>`

if index should be less than size of the L<Ts…​>. otherwise it will result in compiler error

Variants:

  • insert_c

  • insert_c_t ⇒ to avoid ::type

  • insert ⇒ when index passed as type (::value is used to get the index value). ::type is needed to access the result type

  • insert_t ⇒ to avoid ::type

repeat

Usage:

using result = ctl::repeat_c<L<Ts...>, _count_>::type
if
    `L<Ts...> = L<T1, T2, T3>`
    _count_ = 2
then
    `result = L<T1, T2, T3, T1, T2, T3>`

if count == 0, then result = L<>

Variants:

  • repeat_c

  • repeat_c_t ⇒ to avoid ::type

  • repeat ⇒ when count passed as type (::value is used to get the count value). ::type is needed to access the result type

  • repeat_t ⇒ to avoid ::type

clear

Usage:

using result = ctl::repeat_c<L<Ts...>>::type
if
    `L<Ts...> = L<T1, T2, T3>`
then
    `result = L<>`

Variants:

  • clear

  • clear_t ⇒ to avoid ::type

erase

Usage:

using result = ctl::erase_c<L<Ts...>, _pos1_, _pos2_>::type
if
    `L<Ts...> = L<T1, T2, T3>`
    _pos1_ == 0
    _pos2_ == 1
then
    `result = L<T2, T3>`

if condition pos1 < L<Ts…​>pos2 fails, then results in compiler error.

Variants:

  • erase_c

  • erase_c_t ⇒ to avoid ::type

  • erase ⇒ when pos1 and pos2 are passed a types. ::type is needed to access the result type

  • erase_t ⇒ to avoid ::type

remove

Usage:

using result = ctl::remove_type<L<Ts...>, U>::type
if
    `L<Ts...> = L<T1, T2, T3>`
    `U = T2`
then
    `result = L<T1, T2>`

Variants:

  • remove_type

  • remove_type_t ⇒ to avoid ::type

  • remove_if ⇒ when U is a predicate. if P<T> results in true then type is removed. ::type is needed to access the result type

  • remove_if_t ⇒ to avoid ::type

  • remove_if_qmf

  • remove_if_qmf_t

filter

Usage:

using result = ctl::filter_if<P, L1, L2, ..., Ln>::type
if
    `L1 = L1<T1, T2, T3>,
    `L2 = L2<T4, T5, T6>`
    ...
    `Ln<Tn, Tn+1, Tn+2>`

    `P = P<T2, T5, ..., Tn+1> = true`
then
    `result = L<T2>`

Variants:

  • filter_if

  • filter_if_t ⇒ to avoid ::type

  • filter_if_qmf ⇒ when predicate is passed as quoted meta function. ::type is needed to access the result type

  • filter_if_qmf_t ⇒ to avoid ::type

copy_if

Usage:

using result = ctl::copy_if<L<Ts...>, P>::type
if
    `L<Ts...> = L<T1, T2, T3>`

    `P<T2> = true`
then
    `result = L<T2>`

Variants:

  • copy_if ⇒ alias to filter_if

  • copy_if_t ⇒ to avoid ::type

  • copy_if_qmf ⇒ alias to filter_if_qmf

  • copy_if_qmf_t ⇒ to avoid ::type

drop

Usage:

using result = ctl::drop_c<L<Ts...>, _count_>::type
if
    `L<Ts...> = L<T1, T2, T3>`
    _count_ = 2
then
    `result = L<T3>`

if count >= L<Ts…​> size, then result = L<>

Variants:

  • drop_c

  • drop_c_t ⇒ to avoid ::type

  • drop ⇒ when count is a type. ::type is needed to access the result type

  • drop_t ⇒ to avoid ::type

remove_duplicates

Usage:

using result = ctl::remove_duplicates<L<Ts...>>::type
if
    `L<Ts...> = L<T1, T1, T2>`
    _count_ = 2
then
    `result = L<T1, T2>`

Variants:

  • remove_duplicates

  • remove_duplicates_t ⇒ to avoid ::type

unique

Usage:

using result = ctl::unique<L<Ts...>>::type
if
    `L<Ts...> = L<T1, T1, T2>`
    _count_ = 2
then
    `result = L<T1, T2>`

Variants:

  • unique ⇒ alias to remove_duplicates

  • unique_t ⇒ to avoid ::type

unique_if

Usage:

using result = ctl::unique_if<L<Ts...>, P>::type
if
    `L<Ts...> = L<T1, T2, T3>`
    `P<T> = T2`
then
    `result = L<T1, T2>`

Variants:

  • unique_if ⇒ alias to remove_if

  • unique_if_t ⇒ to avoid ::type

  • unique_if_qmf ⇒ alias to remove_if_qmf

  • unique_if_qmf_t ⇒ to avoid ::type

transform

Usage:

using result = ctl::transform<F, L1, L2, ..., Ln>::type
if
    `L1 = L1<T1, T2, T3>
    `L2 = L2<T4, T5, T6> `
    ...
    `Ln<Tn, Tn+1, Tn+2>`
then
    `result = L<F<T1, T4, ..., Tn>, F<T2, T5, ..., Tn+1>, F<T3, T6, ..., Tn+2>>`.
    where, F is templated type.

Variants:

  • transform

  • transform_t ⇒ to avoid ::type

  • transform_qmf ⇒ when F is provided as quoted meta function. ::type is needed to access the result type

  • transform_qmf_t ⇒ to avoid ::type

  • transform_if ⇒ when predicate P is passed as 3rd template argument. result will have F<T> only when P<T> is true. ::type is needed to access the result type

  • transform_if_t ⇒ to avoid ::type

  • transform_if_qmf ⇒ when F and predicate provided as quoted meta function

  • transform_if_qmf_t ⇒ to avoid ::type

flatten

Usage:

using result = ctl::flatten<L1, L2=clear_t<L1>>::type
if
    `L1 = L1<T1, T2, std::tuple<T3>>
    `L2 = std::tuple<>`
then
    `result = L1<T1, T2, T3>

L2 is optional. If L2 is not provided, then L2 will be assumed as L1<>

Variants:

  • flatten

  • flatten_t ⇒ to avoid ::type

Accessors

Set of algorithms are used to retrieve the one or more types from the original list. In some case conditional retrieval is possible. These algorithms will result in compiler error if the provided list is empty.

General usage of the algorithm is:

using result = _algo name_<L<Ts...>, P>::type

where,

  • _ algo name _ ⇒ name of the accessor algorithms listed below

  • L<Ts…​> ⇒ list from which one or more type is retrieved

  • P ⇒ predicate/function which is applied on each type to access/retrieve.
    It is optional, not every algorithm needs this parameter

  • ::type ⇒ to access the type from the result.
    If algorithm name is postfixed with _t then there is no need of using ::type

Following are some of accessor algorithms (replaced with _ algo name _):

at

Usage:

using result = ctl::at_c<L<Ts...>, _pos_>::type
if
    `L<Ts...> = L<T1, T2, T3>`
    _pos_ == 2
then
    `result = T3`

If condition pos < size of L<Ts…​> then it will result in compiler error

Variants:

  • at_c

  • at_c_t ⇒ to avoid ::type

  • at ⇒ when pos is passed as type. ::type is needed to access the result type

  • at_t ⇒ to avoid ::type

first

Usage:

using result = ctl::first<L<Ts...>>::type
if
    `L<Ts...> = L<T1, T2, T3>`
then
    `result = T1`

If list provided is empty, then it will result in compiler error

Variants:

  • first ⇒ to get the first type from the list

  • first_t ⇒ to avoid ::type

front

Usage:

using result = ctl::front<L<Ts...>>::type
if
    `L<Ts...> = L<T1, T2, T3>`
then
    `result = T1`

If list provided is empty, then it will result in compiler error

Variants:

  • front ⇒ alias to first

  • front_t ⇒ to avoid ::type

last

Usage:

using result = ctl::last<L<Ts...>>::type
if
    `L<Ts...> = L<T1, T2, T3>`
then
    `result = T3`

If list provided is empty, then it will result in compiler error

Variants:

  • last ⇒ to get the last type from the list

  • last_t ⇒ to avoid ::type

back

Usage:

using result = ctl::back<L<Ts...>>::type
if
    `L<Ts...> = L<T1, T2, T3>`
then
    `result = T3`

If list provided is empty, then it will result in compiler error

Variants:

  • back ⇒ alias to last

  • back_t ⇒ to avoid ::type

head

Usage:

using result = ctl::head<L<Ts...>>::type
if
    `L<Ts...> = L<T1, T2, T3>`
then
    `result = L<T1, T2>`

If list provided is empty, then it will result in compiler error.
If there is only one entry in the list, then result = L<>

Variants:

  • head

  • head_t ⇒ to avoid ::type

tail

Usage:

using result = ctl::tail<L<Ts...>>::type
if
    `L<Ts...> = L<T1, T2, T3>`
then
    `result = L<T2, T3>`

If list provided is empty, then it will result in compiler error.
If there is only one entry in the list, then result = L<>

Variants:

  • tail

  • tail_t ⇒ to avoid ::type

take

Usage:

using result = ctl::take<L<Ts...>, _count_>::type
if
    `L<Ts...> = L<T1, T2, T3>`
    _count_ = 2,
then
    `result = L<T1, T2>`

If count >= size of L<Ts…​> then result = L<Ts…​>

Variants:

  • take_c

  • take_c_t ⇒ to avoid ::type

  • take ⇒ when count is provided as type. ::type is needed to access the result type

  • take_t ⇒ to avoid ::type

Miscellaneous

Set of algorithms used for miscellaneous stuffs which are not listed above! like for ex, creating the integer sequence, getting the position of the type in a list, getting the size of the list, etc.

Following are some of algorithms:

size

Usage:

using result = ctl::size<L<Ts...>>::type
if
    `L<Ts...> = L<T1, T2, T3>`
then
    `result = std::integral_constant<uint32_t, 3>`

Variants:

  • size

  • size_t ⇒ to avoid ::type

  • size_v ⇒ to avoid ::value

count

Usage:

using result = ctl::count<L<Ts...>>::type
if
    `L<Ts...> = L<T1, T2, T3>`
then
    `result = std::integral_constant<uint32_t, 3>`

Variants:

  • count ⇒ alias to size

  • count_t ⇒ to avoid ::type

  • count_v ⇒ to avoid ::value

  • count_if ⇒ when predicate P is passed as second template argument. type will be counted only if P<T> is true. ::type is needed to access the result type

  • count_if_t ⇒ to avoid ::type

  • count_if_v ⇒ to avoid ::value

  • count_if_qmf ⇒ when predicate is passed as quoted meta function

  • count_if_qmf_t ⇒ to avoid ::type

  • count_if_qmf_v ⇒ to avoid ::value

empty

Usage:

using result = ctl::empty<L<Ts...>>::type
if
    `L<Ts...> = L<T1, T2, T3>`
then
    `result = std::false_type`

If L<Ts…​> = L<> then result = std::true_type

Variants:

  • empty

  • empty_t ⇒ to avoid ::type

  • empty_v ⇒ to avoid ::value

contains

Usage:

using result = ctl::contains<L<Ts...>, U>::type
if
    `L<Ts...> = L<T1, T2, T3>`
    `U == T2`
then
    `result = std::true_type`

If U == T4 then result = std::false_type

Variants:

  • contains

  • contains_t ⇒ to avoid ::type

  • contains_v ⇒ to avoid ::value

find

Usage:

using result = ctl::find<L<Ts...>, U>::type
if
    `L<Ts...> = L<T1, T2, T3>`
    `U == T2`
then
    `result = std::integral_constant<uint32_t, 1>`.

If U is not found in list, then result is size of the list

Variants:

  • find

  • find_t ⇒ to avoid ::type

  • find_v ⇒ to avoid ::value

  • find_if ⇒ when U is a predicate. result will have the first position for which P<T> will result in true. ::type is needed to access the result type

  • find_if_t ⇒ to avoid ::type

  • find_if_v ⇒ to avoid ::value

  • find_if_qmf ⇒ when predicate is passed as quoted meta function. ::type is needed to access the result type

  • find_if_qmf_t ⇒ to avoid ::type

  • find_if_qmf_v ⇒ to avoid ::value

all_of

Usage:

using result = ctl::all_of<L<Ts...>, P>::type
if
    `L<Ts...> = L<T1, T2, T3>`
    `P<T> == true` for all T1, T2, T3 types
then
    `result = std::true_type`,
    `result = std::false_type`, if P<T> == false for any one of the types

Variants:

  • all_of

  • all_of_t ⇒ to avoid ::type

  • all_of_v ⇒ to avoid ::value

  • all_of_qmf ⇒ when predicate is passed as quoted meta function. ::type is needed to access the result type

  • all_of_qmf_t ⇒ to avoid ::type

  • all_of_qmf_v ⇒ to avoid ::value

any_of

Usage:

using result = ctl::any_of<L<Ts...>, P>::type
if
    `L<Ts...> = L<T1, T2, T3>`
    `P<T> == true` for any T1, T2, T3 types
then
    `result = std::true_type`,
    `result = std::false_type`, if P<T> == false for all types

Variants:

  • any_of

  • any_of_t ⇒ to avoid ::type

  • any_of_v ⇒ to avoid ::value

  • any_of_qmf ⇒ when predicate is passed as quoted meta function. ::type is needed to access the result type

  • any_of_qmf_t ⇒ to avoid ::type

  • any_of_qmf_v ⇒ to avoid ::value

none_of

Usage:

using result = ctl::none_of<L<Ts...>, P>::type
if
    `L<Ts...> = L<T1, T2, T3>`
    `P<T> == false` for all T1, T2, T3 types
then
    `result = std::true_type`,
    `result = std::false_type`, if P<T> for any one of the types

Variants:

  • none_of

  • none_of_t ⇒ to avoid ::type

  • none_of_v ⇒ to avoid ::value

  • none_of_qmf ⇒ when predicate is passed as quoted meta function. ::type is needed to access the result type

  • none_of_qmf_t ⇒ to avoid ::type

  • none_of_qmf_v ⇒ to avoid ::value

from integer sequence

Usage:

using result = ctl::from_integer_sequence<sequence, RT>::type
if
    `sequence = std::integer_sequence<unsigned int, 9, 2, 5>`
    `RT == ctl::list`
then
    `result = ctl::list<std::integral_constant<unsigned int, 9>, std::integral_constant<unsigned int, 2>, std::integral_constant<unsigned int, 5> >`

RT default type is ctl::list.

Variants:

  • from_integer_sequence

  • from_integer_sequence_t ⇒ to avoid ::type

iota

Usage:

using result = ctl::iota_c<_count_, DT, RT>::type
if
    _count_ = 3,
    `DT = uint32_t`
    `RT == ctl::list`
then
    `result = ctl::list<std::integral_constant<uint32_t, 0>, std::integral_constant<uint32_t, 1>, std::integral_constant<uint32_t, 2> >`

DT default type is uint32_t.
RT default type is ctl::list.

  • iota_c

  • iota_c_t ⇒ to avoid ::type

  • iota ⇒ when count is provided as type. ::type is needed to access the result type

  • iota_t ⇒ to avoid ::type

is_similar

Usage:

using result = ctl::is_similar<T1, T2>::type
if
    T1 = std::tuple<Ts...>,
    T2 = std::tuple<Us...>
then
    `result = std::integral_constant<bool, true>

if T1 or T2 are not similar list, then result will be false type

  • is_similar

  • is_similar_t ⇒ to avoid ::type

  • is_similar_v ⇒ to avoid ::value

equal

Usage:

using result = ctl::equal<T1, T2>::type
if
    T1 = std::tuple<Ts...>,
    T2 = std::tuple<Ts...>
then
    `result = std::integral_constant<bool, true>

if T1 or T2 are not similar list or any entry in the list is not same or list are of different length, then result will be false type

  • equal

  • equal_t ⇒ to avoid ::type

  • equal_v ⇒ to avoid ::value

Utility

Following struct(s)/algorithm(s) can be applied on the types. To use algorithms make sure to add #include <utils.hpp> in source file.

quote

This helps to wrap template type inside a structure called quote.
Template type can be accessed with the help of nested type fn.

template <template <typename...> typename F>
struct quote {
    template <typename... Ts>
    using fn = F<Ts...>;
};

invert

This helps to invert the provided type/value.

template <bool C>
struct invert_c {
  ...
};

variants of invert are:

  • invert_c

  • invert_c_t ⇒ to avoid ::type

  • invert_c_v ⇒ to avoid ::value

  • invert ⇒ to pass typename as template parameter.
    boolean value is accessed with the help of C::value

  • invert_t ⇒ to avoid ::type

  • invert_v ⇒ to avoid ::value

is_same

This helps to Check if the given 2 types are same

template <typename T1, typename T2>
struct is_same {
  ...
};

It results in std::true_type if T1 and T2 are same. std::false_type otherwise.

variants of is_same are:

  • is_same

  • is_same_t ⇒ to avoid using ::type

  • is_same_v ⇒ to avoid using ::value

valid

This helps to validate if the provided template type is valid or not.

template <template <typename...> typename F, typename... Ts>
struct valid {
  ...
};

It results in std::true_type if F<Ts…​>::type is valid. std::false_type otherwise.

variants of valid are:

  • valid

  • valid_t ⇒ to avoid using ::type

  • valid_v ⇒ to avoid using ::value

  • valid_qmf ⇒ to pass template function as a quoted meta function

  • valid_qmf_t ⇒ to avoid using ::type

  • valid_qmf_v ⇒ to avoid using ::value

select

This is equivalent of ternary operator but for types.

template <bool C, typename T, typename F>
struct select_c {
  ...
};

If condition C is true, select_c<>::type will be alias to T, to F otherwise.

variants of select are:

  • select_c ⇒ when condition is true/false value. ::type required to retrieve the type

  • select_c_t ⇒ to avoid ::type

  • select ⇒ when condition is a type.
    boolean value is accessed with the help of C::value

  • select_t ⇒ to avoid ::type

Following variants of select is used to get the false type as templated function.
i.e. if condition results in false then selected type is F<Ts…​>.

template <bool C, typename T, template <typename...> typename F, typename... Ts>
using select_f_c_t {
  ...
};
  • select_f_c ⇒ when condition is true/false value. ::type required to retrieve the type

  • select_f_c_t ⇒ to avoid ::type

  • select_f ⇒ when condition is a type. boolean value is accessed with the help of C::value

  • select_f_t ⇒ to avoid ::type

  • select_qmf_c ⇒ when false type is captured with quoted meta function, and condition is true/false value

  • select_qmf_c_t ⇒ to avoid ::type

  • select_qmf ⇒ when false type is captured with quoted meta function, and condition is a type

  • select_qmf_t ⇒ to avoid ::type

is_function

This will check if the provided type is a function or not.

template <typename T>
struct is_function {
  ...
};

If the provided type is not a reference and adding const to type will not result in the const, then it is considered as function.

variants of is_function are:

  • is_function_t ⇒ to avoid using ::type

  • is_function_v ⇒ to avoid using ::value

is_invocable

This will check if the provided type is a function OR a class which implements operator()

template <typename T>
struct is_invocable {
  ...
};

It makes use of the CPP member detection idiom technique.

If the provided T is a function, then it will result in std::true_type
If the provided T is a class, if it implements operator() then it will result in std::true_type
otherwise it will result in std::false_type

variants of is_invocable are:

  • is_invocable_t ⇒ to avoid using ::type

  • is_invocable_v ⇒ to avoid using ::value

Logger

The Logger component provides compile-time logging functionality using template meta-programming.
It captures log messages as compile-time strings and generates unique tokens for each message at compile time.
This enables efficient, zero-overhead logging with minimal runtime overhead.

Overview

The logger system works by:

  1. Creating log messages using string_variadic_t - a compile-time string representation

  2. Automatically generating unique token identifiers at compile time via get_token<T>()

  3. Capturing undefined symbol information from object files during the build

  4. Generating token-to-message mappings at build time via Python script

  5. Linking the generated token implementations into the final executable

Features

  • Compile-Time Message Capture: Log messages are captured at compile time as template parameters

  • Zero-Runtime Overhead: Message content doesn’t need to be stored in the executable

  • Unique Token Generation: Each unique message gets a deterministic hash-based token ID

  • Build-Time Source Generation: The CMake build system automatically generates the required C++ source code

  • Multiple Output Formats: Generates C++, JSON, and XML representations of captured tokens

Components

Log Levels

The logger supports a hierarchical log level system that allows you to control which messages get logged at runtime.

Log Level Hierarchy

The log levels form an inheritance hierarchy from least severe (most verbose) to most severe (least verbose):

none_t (no logs)
 ↑
critical_t (most severe)
 ↑
error_t
 ↑
warning_t
 ↑
debug_t
 ↑
info_t (least severe, most verbose)

The inheritance relationship allows compile-time filtering: a message is logged if the configured
minimum log level is a base class of (or equal to) the message’s log level.

Available Log Levels
  • none_t - Disables all logging

  • critical_t - Only critical messages are logged

  • error_t - Error and critical messages are logged

  • warning_t - Warning, error, and critical messages are logged

  • debug_t - Debug and above messages are logged

  • info_t - All messages are logged (default)

Configuring the Minimum Log Level

You configure the minimum log level by updating the logLevelConfig value to the desired log level in log_config.hpp

An important feature of the logger is that the filtering happens at compile-time using if constexpr.
This means:

  • No runtime overhead: Disabled log messages are completely removed by the compiler

  • Zero-cost abstraction: Filtered logs don’t generate any executable code

  • Type-safe: Errors in log level configuration are caught at compile time

Example of compile-time optimization:

// With logLevelConfig set to error_t
LOG_DEBUG("This won't be in the binary");  // Compiled away completely
LOG_ERROR("This will be in the binary");   // Included in executable

Configuration

The logger behavior is configured via template specializations. Multiple loggers can be defined by
specializing template variables in log_config.hpp to customize logging behavior.

All loggers should be move assignable and move constructable and has to implement is following method:

struct console_logger_t {
    auto dispatch(std::uint32_t token) -> void {
        // Handle the log message identified by 'token'
        // e.g., print the token, broadcast over network, etc.
    }
};

All loggers configured with the help of template specialization like below:

// specialize the global dispatcher config variable
template <>
inline auto ctl::dispatcherConfig <> = log_destinations_t<
    console_logger_t /* Add any other loggers as a comma separated list */
    >{};

CMake Build Integration

The logger system integrates with CMake to automatically generate token source files.

cmake/GenerateGetTokenSource.cmake

A CMake module that handles the automatic generation of token implementations.

Function: extract_symbols_and_generate_source(TARGET_NAME)

This function orchestrates the entire token generation process:

  1. Compiles the target and captures undefined get_token<T>() symbols

  2. Parses the demangled symbols to extract template parameters

  3. Decodes compile-time strings from the template parameters

  4. Generates C++ source with template specializations

  5. Produces JSON and XML documentation of all captured tokens

Python Script: scripts/generate_get_token_source.py

Processes captured undefined symbols and generates the token source files.

Inputs:
* undefined_symbols_captured.txt - Demangled undefined symbols from object files

Outputs:
* log_tokens.cpp - C++ source with get_token<T>() specializations
* log_tokens.xml - XML documentation of all tokens (indented and readable)
* log_tokens.json - JSON representation of all tokens with message strings

Features:
* Extracts ASCII character codes from variadic template parameters
* Reconstructs original message strings
* Generates deterministic hash-based token IDs
* Provides both human-readable and machine-readable output formats

Usage

Quick Start: Setting up your CMakeLists.txt

To enable automatic token generation in your project, follow these simple steps:

Step 1: Include the CMake Module

At the beginning of your CMakeLists.txt file, include the logger generation module:

include(${PROJECT_SOURCE_DIR}/cmake/GenerateGetTokenSource.cmake)
Step 2: Create Your Executable Target

Define your main executable as usual:

set(MAIN my_app)
set(SRC_FILES ./main.cpp)

add_executable(${MAIN} ${SRC_FILES})
target_include_directories(${MAIN} PUBLIC ${PROJECT_SOURCE_DIR}/include)
target_link_libraries(${MAIN} PUBLIC ctl)
Step 3: Create temporary target to create object files

For the sources which need to be scanned for get_token<> calls, create an object library first:

set(OBJ_LIB my_app_objlib)
add_library(${OBJ_LIB} OBJECT ${SRC_FILES})
target_include_directories(${OBJ_LIB} PUBLIC ${PROJECT_SOURCE_DIR}/include)
Step 4: Enable Automatic Token Generation

Call the provided function to automatically handle all token generation:

# Automatically extract symbols and generate log_tokens source
extract_symbols_and_generate_source(${MAIN} ${OBJ_LIB})

The extract_symbols_and_generate_source() function handles everything:

  • Extracts undefined get_token<T>() symbols from your executable

  • Parses the demangled symbols to extract compile-time strings

  • Generates log_tokens.cpp with all required template specializations

  • Creates JSON and XML documentation files

  • Automatically manages all dependencies

Step 5: Generating Token Library

Now with the help of source files generated, create a library to link to main project:

set(TOKEN_LIB my_app_token_lib)
add_library(${TOKEN_LIB} STATIC ${CMAKE_CURRENT_BINARY_DIR}/log_tokens.cpp)
target_include_directories(${TOKEN_LIB} PUBLIC ${PROJECT_SOURCE_DIR}/include)
add_dependencies(${TOKEN_LIB} generate_source)

Finally, link the generated token library to your main executable:

target_link_libraries(${MAIN} PUBLIC ${TOKEN_LIB})
Complete Example

Here’s a complete CMakeLists.txt example showing all 6 steps:

cmake_minimum_required(VERSION 3.25.1)
project(MyLoggerApp)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Step 1: Include the logger generation module
include(${CMAKE_SOURCE_DIR}/cmake/GenerateGetTokenSource.cmake)

# Step 2: Define source files
set(MAIN my_app)
set(SRC_FILES src/main.cpp)

# Step 3: Create temporary object library to capture symbols
set(OBJ_LIB my_app_objlib)
add_library(${OBJ_LIB} OBJECT ${SRC_FILES})
target_include_directories(${OBJ_LIB} PUBLIC ${CMAKE_SOURCE_DIR}/include)

# Step 4: Enable automatic token generation from object files
extract_symbols_and_generate_source(${MAIN} ${OBJ_LIB})

# Step 5: Create library from generated log_tokens.cpp
set(TOKEN_LIB my_app_token_lib)
add_library(${TOKEN_LIB} STATIC ${CMAKE_CURRENT_BINARY_DIR}/log_tokens.cpp)
target_include_directories(${TOKEN_LIB} PUBLIC ${CMAKE_SOURCE_DIR}/include)
add_dependencies(${TOKEN_LIB} generate_source)

# Create main executable
add_executable(${MAIN} ${SRC_FILES})
target_include_directories(${MAIN} PUBLIC ${CMAKE_SOURCE_DIR}/include)
target_link_libraries(${MAIN} PUBLIC ctl)

# Step 6: Link the generated token library to main executable
target_link_libraries(${MAIN} PUBLIC ${TOKEN_LIB})

Writing Log Messages

Create compile-time string messages and log them:

#include "logger/log_dispatcher.hpp"

struct console_logger_t {
    auto dispatch(std::uint32_t token) -> void {
        std::cout << token << std::endl;
    }
};

// specialize the global dispatcher config variable
template <>
inline auto ctl::dispatcherConfig <> = log_destinations_t<console_logger_t>{};


int main() {
    LOG("Hello World");

    return 0;
}

Generated Output Files

After building, check the build directory for:

log_tokens.cpp

The generated C++ source file containing template specializations:

// Auto-generated file - Do not edit manually
#include <cstdint>
#include "logger/metadata.hpp"

// Message: "Hello World"
template <>
auto get_token<log_metadata_t<string_variadic_t<char, (char)72, (char)101, (char)108, (char)108, (char)111, (char)32, (char)87, (char)111, (char)114, (char)108, (char)100> >>() -> log_token_t {
    return 2323707410U;
}
log_tokens.json

Machine-readable format with complete token information:

{
  "tokens": [
    {
      "token": 2323707410,
      "in-hex": "0x8a80f612",
      "string": "Hello World"
    }
  ]
}
log_tokens.xml

Formatted XML documentation of all tokens:

<?xml version='1.0' encoding='utf-8'?>
<log_tokens>
  <log_token>
    <token>2323707410</token>
    <in-hex>0x8a80f612</in-hex>
    <string>Hello World</string>
  </log_token>
</log_tokens>

Build Process Flow

The logger system follows this build process:

  1. Compile Phase: Your source files are compiled, creating object files with undefined get_token<T>() references

  2. Symbol Extraction: The nm utility extracts demangled symbols from object files

  3. Symbol Parsing: The Python script parses the captured symbols and decodes message strings

  4. Code Generation: C++ source code is generated with all required template specializations

  5. Compilation: The generated log_tokens.cpp is compiled into a static library

  6. Linking: The token library is linked into the final executable, resolving all undefined symbols

Configuration Requirements

  • CMake >= 3.25.1

  • Python 3 (for the token generation script)

  • nm utility (standard with GNU binutils - used to extract symbols)

  • C++20 compiler (required for the library features)

Future Extensions

The current token capture system is designed to be extensible. Future enhancements could include:

  • Line Number Capture: Extract source file location information

  • Context Information: Capture additional compile-time context

  • Runtime Token Database: Automatic generation of a token lookup database

The token structure already supports this through the metadata system and can be extended
by modifying the string_variadic_t template parameters and the extraction logic in the Python script.

Troubleshooting

Build Fails: "missing and no known rule to make it"

Cause: The Python script path is incorrect in CMakeLists.txt
Solution: Verify the ${PROJECT_SOURCE_DIR}/cmake/GenerateGetTokenSource.cmake module exists and the scripts/generate_get_token_source.py path is correct

No Tokens Generated

Cause: No get_token<T>() calls found in the code
Solution: Ensure you have at least one call to get_token<>() in your source code

Python Script Not Found

Cause: Incorrect path to Python or the script
Solution: Ensure python3 is in your PATH and the script exists at the specified location

Function Call Fails: "extract_symbols_and_generate_source not found"

Cause: The CMake module was not included before calling the function
Solution: Make sure to call include(${PROJECT_SOURCE_DIR}/cmake/GenerateGetTokenSource.cmake) before calling extract_symbols_and_generate_source()