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. -
_tnext to algorithm name is implemented to avoid using::typekeyword with algorithm -
_cnext to algorithm name is implemented to take constants which are known at compile time (ex: true/false or numbers) -
_fnext to algorithm name is implemented to take template types as template parameter -
_pnext to algorithm name is implemented to take predicate as template parameter (predicate is a template type). It is same as_f, but_pis used to make it more clear to read -
_qmfnext to algorithm name is implemented to take Quoted meta functions as template parameter-
_qmfimplementations are using its non_qmfcounterparts
-
-
_vnext to algorithm name is implemented to get the constant value (boolean or number). it is like using::valueon the integral_constant type
Container
There are 2 parts here.
-
List ⇒ the container which holds the type
-
Algorithms ⇒ used with
listtype. Not necessarily withctl::listbut 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 insrc/main.cppfile
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_tthen 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 asrenamebut 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_left⇒numis passed as type (It should have::valueto access the member).::typeis 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_right⇒numis passed as type (It should have::valueto access the member).::typeis 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.::typeis needed to access the result type-
comparatorshould returntrueif 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.::typeis 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).::typeis 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,::valueis used to access the constant).::typeis needed to access the result type -
replace_at_t⇒ to avoid::type -
replace_if⇒ to replace all types which results intruewhen passed to given predicate.::typeis needed to access the result type -
replace_if_t⇒ to avoid::type -
replace_if_qmf⇒ predicate passed as quoted meta function.::typeis 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 anothertype/listto 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 anothertype/listto 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 topush_back -
append_t⇒ alias topush_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 (::valueis used to get the index value).::typeis 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 (::valueis used to get the count value).::typeis 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.::typeis 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⇒ whenUis a predicate. ifP<T>results intruethen type is removed.::typeis 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.::typeis 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 tofilter_if -
copy_if_t⇒ to avoid::type -
copy_if_qmf⇒ alias tofilter_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.::typeis 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 toremove_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 toremove_if -
unique_if_t⇒ to avoid::type -
unique_if_qmf⇒ alias toremove_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⇒ whenFis provided as quoted meta function.::typeis needed to access the result type -
transform_qmf_t⇒ to avoid::type -
transform_if⇒ when predicatePis passed as 3rd template argument.resultwill haveF<T>only whenP<T>istrue.::typeis needed to access the result type -
transform_if_t⇒ to avoid::type -
transform_if_qmf⇒ whenFand 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/functionwhich isapplied on each typeto access/retrieve.
It isoptional, not every algorithm needs this parameter -
::type⇒ to access the type from the result.
If algorithm name is postfixed with_tthen 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.::typeis 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 tofirst -
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 tolast -
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.::typeis 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 tosize -
count_t⇒ to avoid::type -
count_v⇒ to avoid::value -
count_if⇒ whenpredicate Pis passed as second template argument. type will be counted only ifP<T> is true.::typeis needed to access the result type -
count_if_t⇒ to avoid::type -
count_if_v⇒ to avoid::value -
count_if_qmf⇒ whenpredicateis 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⇒ whenUis a predicate.resultwill have the first position for whichP<T>will result intrue.::typeis 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.::typeis 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.::typeis 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.::typeis 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.::typeis 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.::typeis 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 ofC::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 istrue/falsevalue.::typerequired to retrieve the type -
select_c_t⇒ to avoid::type -
select⇒ when condition is a type.
boolean value is accessed with the help ofC::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 istrue/falsevalue.::typerequired 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 ofC::value -
select_f_t⇒ to avoid::type -
select_qmf_c⇒ whenfalsetype is captured with quoted meta function, and condition istrue/falsevalue -
select_qmf_c_t⇒ to avoid::type -
select_qmf⇒ whenfalsetype 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:
-
Creating log messages using
string_variadic_t- a compile-time string representation -
Automatically generating unique token identifiers at compile time via
get_token<T>() -
Capturing undefined symbol information from object files during the build
-
Generating token-to-message mappings at build time via Python script
-
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:
-
Compiles the target and captures undefined
get_token<T>()symbols -
Parses the demangled symbols to extract template parameters
-
Decodes compile-time strings from the template parameters
-
Generates C++ source with template specializations
-
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.cppwith 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)
Step 6: Link Generated Token Library
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:
-
Compile Phase: Your source files are compiled, creating object files with undefined
get_token<T>()references -
Symbol Extraction: The
nmutility extracts demangled symbols from object files -
Symbol Parsing: The Python script parses the captured symbols and decodes message strings
-
Code Generation: C++ source code is generated with all required template specializations
-
Compilation: The generated
log_tokens.cppis compiled into a static library -
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()