mlib is a fast, simple c++ metaprogramming library that uses c++20. That also has a tiny bit of reflection!
#include<tuple> // std::tuple
#include<iostream> // std::cout
#include<utility> // std::make_index_sequence
#include "for_each.hpp" // mlib::for_each
#include "tuple.hpp" // mlib::tuple
#include "transform.hpp" // mlib::transform
#include "select.hpp" // mlib::select
#include "find.hpp" // mlib::find
#include "tuple_reverse.hpp" // mlib::tuple_reverse
#include "constexpr_for.hpp" // mlib::constexpr_for
#include "constexpr_while.hpp" // mlib::constexpr_while
#include "refl_get.hpp" // mlib::refl_get
#include "get_nth_element.hpp" // mlib::get_nth_element
#include "pack.hpp" // mlib::value_pack
#include "constexpr_map.hpp" // mlib::constexpr_map
#include "count_if.hpp" // mlib::count_if
#include "pack_find.hpp" // mlib::pack_find
#include "parse_rules.hpp" // mlib::parse_rules
#include "constexpr_switch.hpp"// mlib::constexpr_switch
#include "constexpr_range.hpp" // mlib::constexpr_range
int main()
{
// mlib::for_each
std::tuple<int, char, bool, double> t{ 42, 'c', true, 3.142 };
mlib::for_each(t, [](auto& t) { std::cout << "val: " << t << "\n"; });
// mlib::transform
std::tuple<int, char, double> t2{ 42, 'c', 3.142 };
mlib::transform(t2, [](auto& t) {return static_cast<int>(t); });
// mlib::select
std::tuple<char, bool, double> t3{ 'c', true, 3.142 };
auto tuple = mlib::select(t3, std::index_sequence<1, 2>{});
mlib::for_each(tuple, [](auto& t) {std::cout << "val: " << t << "\n"; });
// note need to add to less than: --\/, will print one hello.
mlib::constexpr_while < 0, [](int t) {t++; return t < 3; }, [&]() {std::cout << "Hello\n"; }, [](int t) {return t + 1; } > ();
// highest number of recursion.
mlib::constexpr_while < 0, [](int t) {t++; return t < 501; }, [&]() {std::cout << "."; }, [](int t) {return t + 1; } > ();
struct foo { int a; double b; char c; };
foo f{42, 3.14, 'c'};
auto x = mlib::meta::refl_get<foo>(f);
std::cout << std::get<0>(x);
std::cout << mlib::get_nth_element<2>(42, 'c', 3.14, true) << "\n"; // returns 3.14
mlib::value_pack<42, 'c', 3.142, true> v{};
mlib::constexpr_map < mlib::member_map<42, 'c'>{}, mlib::member_map<true, 3.142>{} > map{};
std::cout << map[mlib::constexpr_parameter<true>{}];
constexpr auto z = map.lookup<42>();
std::cout << z;
std::tuple tuple_count{ 0, 31.4, 5, 7, 0 };
std::cout << mlib::count_if([](auto t) {return t > 0; }, tuple_count);
std::cout << mlib::pack_find<42, 'c', true, 3, 12, 'b', 42, false, 5, 'y'>();
std::cout << mlib::pack_find<'n', 'c', true, 3, 12, 'b', 42, false, 5, 'y'>();
constexpr auto fixed = mlib::fixed_string{ "hello this is me" };
constexpr auto result_of_branches =
mlib::parse_rules<fixed>{}.if_has_character<'c'>([&](auto x) {return mlib::fixed_string{ "ey i don't want a aije" }; })
.if_has_character<'a'>([&](auto x) {return mlib::fixed_string{ "I dont wnt n eiy" }; })
.if_has_character<'t'>([&](auto x) {return mlib::fixed_string{ "I dont think that this will happen\n" }; })
.if_has_character<' '>([&](auto x) {return mlib::fixed_string{ x.data }; });
mlib::constexpr_switch <
5, [](mlib::constexpr_parameter<0>) { std::cout << "it is 0\n"; },
[](mlib::constexpr_parameter<5>) { std::cout << "it is not 0\n"; },
[](auto) { std::cout << "I don't know what it is!\n"; } >
value{};
value();
constexpr auto value_index = mlib::constexpr_range<1, 2, 3, 4, 5, 6>{}
.add<5>()
.pop_front()
.map < [](auto x) {return x + 1; } > ()
.map_with_location < [](auto x, auto idx) {return x + idx; } > ()
.join_with<0, 1, 2, 3, 4>()
.add(mlib::Size{})
.remove<0>()
.at<10>();
}
mlib::for_each
is a constexpr
function that takes two arguements std::tuple
and a callable, for instance a lambda. mlib::for_each
's has the following outline:
template<typename T, typename... Ts>
constexpr auto for_each(std::tuple<T, Ts...>& tup, auto& callable)
{
// delegate to for_each_impl
}
An example use case is passing a lambda expression that outputs the elements in the std::tuple
:
std::tuple<int, char, bool, double> t{42, 'c', true, 3.142};
mlib::for_each(t, [&](auto& a){std::cout << t << ", ";})
And the output would be the following:
42, c, t, 1, 3.142,
The benchmark for this is as follows:
------------------------------------------------------
Benchmark Time CPU Iterations
------------------------------------------------------
BM_for_each 144 ns 82.6 ns 8173558
mlib::transform
is a constexpr
function takes a given tuple, t
and a given function f
that transforms one of the elements inside of the tuple. An example invocation of mlib::transfrom
looks like this:
std::tuple t{1, 4, 7, 'c', true, false};
mlib::transform(t, [](auto& t){return decltype(t)(static_cast<int>(t));});
This invokation, has a tuple t: std::tuple<int, char, bool>
and then transforms that tuple using the [](auto t) { return (static_cast<int>(t)) + 1; };
, and does so accordingly.
mlib::transform
's implementation looks like this:
template <typename... Ts, std::size_t... indexes>
constexpr auto transform_helper(std::tuple<Ts...> t, auto lambda,
std::index_sequence<indexes...> i_s) {
return std::make_tuple(lambda(std::get<indexes>(t))...);
}
template <typename T, typename... Ts>
constexpr auto transform(std::tuple<T, Ts...> t, auto lambda) {
return transform_helper(t, lambda,
std::make_index_sequence<sizeof...(Ts)>{});
}
It is a very simple implementation, and mlib::transform
is a great tool.
The benchmark is as follows:
-------------------------------------------------------
Benchmark Time CPU Iterations
-------------------------------------------------------
BM_Transform 307 ns 173 ns 3620400
mlib::transform
is a string that can be used at compile time, so the size needs to be known at compile time and needs to be passed as an NTTP or it can be infered by the constexpr
constructor. Here is how you would have a compile time string, using fixed_string
:
fixed_string<6> f{"hello"}; // note that `fixed_string` has a `constexpr` constructor taking an array.
The only hardship is specifying the NTTP. An example implementation of fixed_string
is like this:
template<auto I> // I is the size of the string
struct fixed_string
{
// notice the constexpr constructor.
constexpr fixed_string(char(&arr)[I])
{
// copies `arr` into `m_data`
}
char m_data[I] = {};
};
mlib::all_of
is a simple feature that checks if all of the booleans, bools
passed in are all true
. A simple example is as follows:
mlib::all_of(true, true, 6 == 6, true, 42 == 0, true); // false
Another example is as follows:
mlib::all_of(true, true, true, true); // true
So it is a very useful feature, and can be used in many different ways, and example implementation of mlib::all_of
is as follows:
namespace mlib
{
template<bool b, bool... bs>
struct all_of
{
static constexpr auto value = b && all_of<bs...>::value;
};
template<bool b>
struct all_of<b>
{
static constexpr auto value = b;
};
} // namespace mlib
mlib::amount
allows you do do things like you would do in a run time loop but at compile time. You can specify the number of recursions in the <>
template parameter part. It is a very easy think to use, and can happen at compile time, as long as the callable passed in can run at compile time. Here is an example usage:
amount<10>.times([](){std::cout << "hello" << "\n";}); // note that this doesn't happen at compile time
amount<10>.times([](){0 + 257;}); // happens at compile time, but the result isn't really observable.
So, as shown it is a very useful feature. An example implementation is as follows:
template<auto T>
struct amount
{
constexpr auto times(auto&& lambda) const;
};
template<auto T>
static constexpr auto amount_t = amount<T>{};
template<auto T>
constexpr auto amount<T>::times(auto&& lambda) const
{
lambda();
if constexpr (T - 1 != 0)
{
amount_t<T - 1>.times(lambda);
}
return true;
}
mlib::one_of
is a compile time or
. You pass in a bunch of bools, and if one of then is true
, the value that the class mlib::one_of
holds will be true
. An example use of mlib::one_of
is as follows:
bool b = mlib::one_of<false, false, false>::value; // b == false
bool c = mlib::one_of<false, false, true, false, false>::value; // c == true
So it is very easy to use. An example implementation of mlib::one_of
is as follows:
namespace mlib
{
template<bool b, bool... b>
struct one_of
{
static constexpr bool value = (b || b...);
};
template<bool b>
struct one_of
{
static constexpr bool value = b;
};
} // namespace mlib
And thats it for its implementation, note that it's implementation is not recursive!
mlib::constexpr_map
is a compile-time map that also allows compile time lookup using the operator[]
which takes a mlib::constexpr_parameter
as it's compile time arguement and a constexpr
lookup function lookup
which takes the value to be looked up as a non type template parameter. It has a very cool implementation here is a watered down version of what it looks like:
template<auto T> struct constexpr_parameter {using type = decltype(T);}
template<auto A, auto B> struct member_map {
constexpr auto operator[](constexpr_parameter<A>)
{
return B;
}
};
template<auto... members> struct constexpr_map : constexpr_parameter<members>::type...
{
using decltype(members)::operator[]...;
// note lookup is missed out here
};
But as you can see it is a very cool implementation. Now onto some examples using mlib::constexpr_map
:
mlib::constexpr_map<mlib::member_map<'c', 4>{}, mlib::member_map<true, 42>{}> map{};
std::cout << map[mlib::c_p<'c'>];
Note that here we are using mlib::c_p
which is just a mlib::constexpr_parameter
it looks like this:
template<auto T>
constexpr auto c_p = constant_parameter<T>{};
So in conclusion, mlib::constexpr_map
is a really cool feature, the only problem is that the map is constexpr
so therefore cannot add values, change values, etc.
mlib::meta::relf_get
is a useful reflection, that gets all of the members out of a struct
for example if you had a struct a
:
struct a
{
int a;
char c;
bool b;
};
and so if pass an instance of a
like this: a A{42, 'c', true};
to refl_get
we will get a tuple with the contents of std::tuple<int, char, bool>{42, 'c', true}
! It is as easy as that to reflect this!
It looks like this:
constexpr auto refl_get(auto& t)
{
// implementation
}
mlib::string_parse
is a struct
taking an mlib::fixed_string
, str
as a NTTP. It then has member functions:
- mlib::string_parse::substr
- mlib::string_parse::consume_until
This, mlib::string_parse
is a very basic example of compile-time string parsing. An example of how mlib::string_parse
looks like is as follows:
template<mlib::fixed_string str>
struct string_parse
{
template<auto Start, auto End>
constexpr auto substr() -> mlib::fixed_string<End - Start>;
template<char C>
constexpr auto consume_until() -> mlib::fixed_string;
template<char C>
constexpr auto blur_with() const noexcept;
template<char c, int X>
constexpr auto blur_until() const noexcept;
};