An open API service indexing awesome lists of open source software.

https://github.com/fix8mt/conjure_enum

Lightweight header-only C++20 enum reflection
https://github.com/fix8mt/conjure_enum

bitset c-plus-plus cpp cpp20 cxx20 enum enum-to-string fix8 header-only magic-enum metaprogramming no-dependencies no-macros reflection source-location string-to-enum

Last synced: 2 months ago
JSON representation

Lightweight header-only C++20 enum reflection

Awesome Lists containing this project

README

        



Lightweight header-only C++20 enum and typename reflection

---

[![clang](https://github.com/fix8mt/conjure_enum/actions/workflows/Ubuntu-clang-latest.yml/badge.svg)](https://github.com/fix8mt/conjure_enum/actions/workflows/Ubuntu-clang-latest.yml)
[![gcc](https://github.com/fix8mt/conjure_enum/actions/workflows/Ubuntu-gcc-latest.yml/badge.svg)](https://github.com/fix8mt/conjure_enum/actions/workflows/Ubuntu-gcc-latest.yml)

# 1. Quick links
|1|[`conjure_enum`](#3-conjure_enum)| API and examples|
|:--|:--|:--|
|2|[`enum_bitset`](#4-enum_bitset)| Enhanced enum aware `std::bitset`|
|3|[`conjure_type`](#5-conjure_type)| Any type string extractor|
|4|[`fixed_string`](#6-fixed_string)| Statically stored null terminated fixed string|
|5|[Building](#7-building)| How to build or include|
|6|[vcpkg](https://vcpkg.io/en/package/conjure-enum)| For vcpkg package|
|7|[Notes](#8-notes)| Notes on the implementation, limits, etc|
|8|[Benchmarks](#9-benchmarks)| Benchmarking |
|9|[Compilers](#10-compiler-support)| Supported compilers|
|10|[Compiler issues](#11-compiler-issues)| Workarounds for various compiler issues|
|11|[Results of `std::source_location`](reference/source_location.md)| For implementation specific `std::source_location` results|
> [!TIP]
> Use the built-in [table of contents](https://github.blog/changelog/2021-04-13-table-of-contents-support-in-markdown-files/) to navigate this guide.
> Even better in [full read view](./README.md) of this page.
>
> For the latest cutting edge changes, see the [dev branch](https://github.com/fix8mt/conjure_enum/tree/dev).
> You can view the changes (if any) [here](https://github.com/fix8mt/conjure_enum/compare/master..dev).

---
# 2. Introduction
## a) Supercharge Your C++ Enums with This Lightweight Reflection Library!

Based on the awesome work in [`magic_enum`](https://github.com/Neargye/magic_enum)[^2] and [`boost::describe`](https://github.com/boostorg/describe),
this library offers a streamlined and powerful way to add reflection capabilities to your C++ enums and other types. We've optimized the core functionality,
focusing on the main features developers usually want. We've also added general purpose typename reflection for any type.

`conjure_enum`[^1] takes full advantage of recently added C++20 features. We've leveraged the convenience of [`std::source_location`](https://en.cppreference.com/w/cpp/utility/source_location) and
unlocked the potential of [`constexpr` algorithms](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0202r3.html) and [concepts](https://en.cppreference.com/w/cpp/language/constraints).

## b) Highlights

- ***Single Header-Only***: No external dependencies, simplifying integration into your project
- ***Modern C++20***: Entirely `constexpr` for compile-time safety, efficiency and performance; no macros
- ***Broad Support***: Works with:
- scoped and unscoped enums
- enums with **aliases** and **gaps**
- anonymous and named namespaced enums and types
- custom [enum ranges](#ii-using-enum_range)
- ***Easy to Use***: Class-based approach with intuitive syntax
- ***Convenient***: `enum_bitset` provides an enhanced enum aware `std::bitset` (see 2 above)
- ***Useful***: `conjure_type` gives you the type string of _any type!_ (see 3 above)
- ***Wide Compiler Compatibility***: Support for: (see 9 above)
- GCC
- Clang
- MSVC
- XCode/Apple Clang
- ***Confidence***: Includes comprehensive unit test suite for reliable functionality and profiling
- ***Expanded***: Enhanced API:
- `add_scope`
- `remove_scope`
- `unscoped_string_to_enum`
- `for_each_n`
- `dispatch`
- iterators and more!
- ***Transparency***: Compiler implementation variability fully documented, verifiable and reportable (see 11 above)

---
# 3. `conjure_enum`
All examples refer to the following enums:
```c++
enum class component { scheme, authority, userinfo, user, password, host, port, path=12, test=path, query, fragment };
enum component1 { scheme, authority, userinfo, user, password, host, port, path=12, query, fragment };
enum class numbers { zero, one, two, three, four, five, six, seven, eight, nine };
```

> [!IMPORTANT]
> Your type _must_ be an enum, satisfying:
> ```C++
>template
>concept valid_enum = requires(T)
>{
> requires std::same_as>;
> requires std::is_enum_v;
>};
>```

## a) `enum_to_string`
```c++
static constexpr std::string_view enum_to_string(T value, bool noscope=false);
template
static constexpr std::string_view enum_to_string();
```
Returns a `std::string_view` or empty if not found. Optionally passing `true` will remove scope in result if present.
`noscope` option ![](assets/notminimalred.svg).
```c++
auto name { conjure_enum::enum_to_string(component::path) };
auto name_trim { conjure_enum::enum_to_string(component::path, true) }; // optionally remove scope in result
auto alias_name { conjure_enum::enum_to_string(component::test) }; // alias
auto noscope_name { conjure_enum::enum_to_string(path) };
std::cout << name << '\n' << name_trim << '\n' << alias_name << '\n' << noscope_name << '\n';
std::cout << conjure_enum::enum_to_string() << '\n';
```
_output_
```CSV
component::path
path
component::path
path
numbers::two
```
### Aliases
Because all methods in `conjure_enum` are defined _within_ a `class` instead of individual template functions in a `namespace`, you can reduce your
typing with standard aliases:
```c++
using ec = FIX8::conjure_enum;
std::cout << std::format("\"{}\"\n", ec::enum_to_string(component::authority));
std::cout << std::format("\"{}\"\n", ec::enum_to_string(static_cast(100)));
```
_output_
```CSV
"component::authority"
""
```
Also supplied is a template version of `enum_to_string`.
```c++
std::cout << std::format("\"{}\"\n", ec::enum_to_string());
std::cout << std::format("\"{}\"\n", ec::enum_to_string());
```
_output_
```CSV
"component::scheme"
"scheme"
```

## b) `string_to_enum`
```c++
static constexpr std::optional string_to_enum(std::string_view str);
```
Returns a `std::optional`. Empty if string was not valid. Use `std::optional::value_or()` to set an error value
and avoid throwing an exception.
```c++
int value { static_cast(conjure_enum::string_to_enum("component::path").value()) };
int noscope_value { static_cast(conjure_enum::string_to_enum("path").value()) };
int bad_value { static_cast(conjure_enum::string_to_enum("bad_string").value_or(component(100))) };
std::cout << value << '\n' << noscope_value << '\n' << bad_value << '\n';
```
_output_
```CSV
12
12
100 <-- invalid, error value
```
## c) `unscoped_string_to_enum` ![](assets/notminimalred.svg)
```c++
static constexpr std::optional unscoped_string_to_enum(std::string_view str);
```
Same as `string_to_enum` except works with unscoped strings. Returns a `std::optional`. Empty if string was not valid. Use `std::optional::value_or()` to set an error value
and avoid throwing an exception.
```c++
int value { static_cast(conjure_enum::unscoped_string_to_enum("path").value()) };
int noscope_value { static_cast(conjure_enum::string_to_enum("path").value()) };
int bad_value { static_cast(conjure_enum::string_to_enum("bad_string").value_or(component(100))) };
std::cout << value << '\n' << noscope_value << '\n' << bad_value << '\n';
```
_output_
```CSV
12
12
100 <-- invalid, error value
```
## d) `int_to_enum`, `enum_cast`
```c++
static constexpr std::optional int_to_enum(int value);
static constexpr T enum_cast(int value);
```
Returns a `std::optional`. Empty if value was not valid. Use `std::optional::value_or()` to set an error value
and avoid throwing an exception. `enum_cast` will cast to the enum type regardless of whether the value is a valid enum.
```c++
int value { static_cast(conjure_enum::int_to_enum(12).value()) };
int noscope_value { static_cast(conjure_enum::int_to_enum(12).value()) };
int bad_value { static_cast(conjure_enum::int_to_enum(100).value_or(component(100))) };
std::cout << value << '\n' << noscope_value << '\n' << bad_value << '\n';
std::cout << static_cast(conjure_enum::enum_cast(150)) << '\n';
```
_output_
```CSV
12
12
100 <-- invalid, error value
150 <-- invalid, but still casted
```
## e) `enum_to_int`, `enum_to_underlying`
```c++
static constexpr int enum_to_int(T value);
static constexpr std::underlying_type_t enum_to_underlying(T value);
```
Returns an `int` or the `underlying` value for the given enum value. These are added for completeness. For unscoped enums
you can always just use the value like an int, or for scoped enums just `static_cast` it first.
```c++
std::cout << conjure_enum::enum_to_int(component::path) << '\n';
std::cout << conjure_enum::enum_to_underlying(component::path) << '\n';
std::cout << conjure_enum::enum_to_int(path) << '\n';
std::cout << conjure_enum::enum_to_underlying(path) << '\n';
```
_output_
```CSV
12
12
12
12
```
## f) `count`
```c++
static constexpr std::size_t count();
```
Returns number of enumerations.
```c++
std::cout << conjure_enum::count() << '\n';
```
_output_
```CSV
10
```
## g) `names` ![](assets/notminimalred.svg)
```c++
static constexpr std::array names;
```
This static member is generated for your type. It is a `std::array` of the `std::string_view` strings in enum order.
```c++
for(const auto ev : conjure_enum::names) // scoped enum
std::cout << ev << '\n';
for(const auto ev : conjure_enum::names) // unscoped enum
std::cout << ev << '\n';
```
_output_
```CSV
component::scheme
component::authority
component::userinfo
component::user
component::password
component::host
component::port
component::path
component::query
component::fragment
scheme
authority
userinfo
user
password
host
port
path
query
fragment
```
## h) `unscoped_names` ![](assets/notminimalred.svg)
```c++
static constexpr std::array unscoped_names;
```
This static member is generated for your type. It is a `std::array` of the `std::string_view` unscoped strings in enum order.
For unscoped enums is the same as `names` above.
```c++
for(const auto ev : conjure_enum::unscoped_names) // scoped enum
std::cout << ev << '\n';
std::cout << '\n';
for(const auto ev : conjure_enum::unscoped_names) // unscoped enum
std::cout << ev << '\n';
```
_output_
```CSV
scheme
authority
userinfo
user
password
host
port
path
query
fragment

scheme
authority
userinfo
user
password
host
port
path
query
fragment
```
## i) `values`
```c++
static constexpr std::array values;
```
This static member is generated for your type. It is a `std::array` of the `T` values in enum order.
```c++
for(const auto ev : conjure_enum::values) // scoped enum
std::cout << static_cast(ev) << '\n';
```
_output_
```CSV
0
1
2
3
4
5
6
12
13
14
```
## j) `entries`, `sorted_entries`
```c++
static constexpr std::array, std::size_t> entries;
static constexpr std::array, std::size_t> sorted_entries;
```
These static members are generated for your type. They are `std::array` of tuples of `T` and `std::string_view`.
`sorted_entries` is the same as `entries` except the array is sorted by the `std::string_view` name.
```c++
using ec = conjure_enum;
for(const auto [value, str] : ec::entries) // scoped enum
std::cout << std::format("{:<2} {}\n", static_cast(value), str);
std::cout << '\n';
for(const auto [value, str] : ec::sorted_entries) // scoped enum
std::cout << std::format("{:<2} {}\n", static_cast(value), str);
```
_output_
```CSV
0 component::scheme
1 component::authority
2 component::userinfo
3 component::user
4 component::password
5 component::host
6 component::port
12 component::path
13 component::query
14 component::fragment

1 component::authority
14 component::fragment
5 component::host
4 component::password
12 component::path
6 component::port
13 component::query
0 component::scheme
3 component::user
2 component::userinfo
```
## k) `scoped_entries`, `unscoped_entries` ![](assets/notminimalred.svg)
```c++
static constexpr std::array, std::size_t> scoped_entries;
```
This static member is generated for your type. It is a `std::array` of a tuple of `std::string_view` pairs in enum order.
It contains pairs of unscoped and their scoped string version. This array is sorted by unscoped name.
For unscoped enums, these are identical.

`unscoped_entries` is the same except the pair is reversed.
```c++
for(const auto [a, b] : conjure_enum::scoped_entries)
std::cout << std::format("{:9} {}\n", a, b);
```
_output_
```CSV
authority component::authority
fragment component::fragment
host component::host
password component::password
path component::path
port component::port
query component::query
scheme component::scheme
user component::user
userinfo component::userinfo
```
## l) `rev_scoped_entries` ![](assets/notminimalred.svg)
```c++
static constexpr std::array, std::size_t> rev_scoped_entries;
```
Same as `scoped_entries` except reversed, sorted by scoped name. Use to lookup unscoped name.
## m) `index`
```c++
static constexpr std::optional index(T value);
template
static constexpr std::optional index();
```
Returns the index (position in 0 based array of values) of the supplied enum value as an `std::optional`.
Empty if value was not valid. Use `std::optional::value_or()` to set an error value
and avoid throwing an exception.
```c++
std::cout << conjure_enum::index(component::password).value() << '\n';
std::cout << conjure_enum::index(component(100)).value_or(100) << '\n';
std::cout << conjure_enum::index().value() << '\n';
std::cout << conjure_enum::index().value_or(100) << '\n';
```
_output_
```CSV
4
100 <-- invalid, error value
4
100 <-- invalid, error value
```
## n) `contains`, `is_valid`
```c++
static constexpr bool contains(T value);
static constexpr bool contains(std::string_view str);
template
static constexpr bool contains();
template
static constexpr bool is_valid();
```
Returns `true` if the enum contains the given value or string.
```c++
std::cout << std::format("{}\n", conjure_enum::contains(component::path));
std::cout << std::format("{}\n", conjure_enum::contains("nothing"));
std::cout << std::format("{}\n", conjure_enum::contains());
std::cout << std::format("{}\n", conjure_enum::is_valid());
```
_output_
```CSV
true
false
true
true
```
## o) `for_each`, `for_each_n` ![](assets/notminimalred.svg)
```c++
template
requires std::invocable
[[maybe_unused]] static constexpr auto for_each(Fn&& func, Args&&... args);

template // specialisation for member function with object
requires std::invocable
[[maybe_unused]] static constexpr auto for_each(Fn&& func, C *obj, Args&&... args);

template
requires std::invocable
[[maybe_unused]] static constexpr auto for_each_n(int n, Fn&& func, Args&&... args);

template // specialisation for member function with object
requires std::invocable
[[maybe_unused]] static constexpr auto for_each_n(int n, Fn&& func, C *obj, Args&&... args);
```
Call supplied invocable for _each_ enum value. Similar to `std::for_each` except the first parameter of your invocable must accept an enum value (passed by `for_each`).
Optionally provide any additional parameters. You can limit the number of calls to your invocable by using the `for_each_n` version with the first parameter
being the maximum number to call. The second version of `for_each` and `for_each_n` is intended to be used
when using a member function - the _second_ parameter passed by your call must be the `this` pointer of the object.
If you wish to pass a `reference` parameter, you must wrap it in `std::ref`.

Works with lambdas, member functions, functions etc, compatible with `std::invoke`.

Returns
```c++
std::bind(std::forward(func), std::placeholders::_1, std::forward(args)...);
// or
std::bind(std::forward(func), obj, std::placeholders::_1, std::forward(args)...);
```
which can be stored or immediately invoked.

See `enum_bitset::for_each` to iterate through a bitset.
```c++
conjure_enum::for_each([](component val, int other)
{
std::cout << static_cast(val) << ' ' << other << '\n';
}, 10);
```
_output_
```CSV
0 10
1 10
2 10
3 10
4 10
5 10
6 10
12 10
13 10
14 10
```
Above example using `for_each_n`, limiting to 3:
```c++
conjure_enum::for_each_n(3, [](component val, int other)
{
std::cout << static_cast(val) << ' ' << other << '\n';
}, 10);
```
_output_
```CSV
0 10
1 10
2 10
```
Example using returned object and additional reference parameter:
```c++
int total{};
auto myfunc { conjure_enum::for_each([](component val, int other, int& tot)
{
std::cout << static_cast(val) << ' ' << other << '\n';
tot += static_cast(val);
}, 10, std::ref(total)) };
myfunc(component::fragment);
std::cout << total << '\n';
```
_output_
```CSV
0 10
1 10
2 10
3 10
4 10
5 10
6 10
12 10
13 10
14 10
14 10 <== invoked with returned object
74
```
Example with pointer to member function with additional parameters:
```c++
struct foo
{
void process(component val, int offset, int& tot)
{
tot += offset + static_cast(val);
}
};
int total{};
foo bar;
conjure_enum::for_each(&foo::process, &bar, 10, std::ref(total));
std::cout << total << '\n';
```
_output_
```CSV
160
```
## p) `dispatch` ![](assets/notminimalred.svg)
```c++
template
static constexpr bool tuple_comp(const std::tuple& pl, const std::tuple& pr);

template // with not found value(nval) for return
requires std::invocable
[[maybe_unused]] static constexpr R dispatch(T ev, R nval, const std::array, I>& disp, Args&&... args);

template // specialisation for member function with not found value(nval) for return
requires std::invocable
[[maybe_unused]] static constexpr R dispatch(T ev, R nval, const std::array, I>& disp, C *obj, Args&&... args);

template // void func with not found call to last element
requires (std::invocable && I > 0)
static constexpr void dispatch(T ev, const std::array, I>& disp, Args&&... args);

template // specialisation for void member function with not found call to last element
requires (std::invocable && I > 0)
static constexpr void dispatch(T ev, const std::array, I>& disp, C *obj, Args&&... args);
```
With a given enum, search and call user supplied invocable. A typical use case would be where you want to demux a complex event, allowing you to easily declare predefined invocable actions
for different enum values.

- Where invocable returns a value, return this value or a user supplied "not found" value.
- Where invocable is void, call user supplied invocable or "not found" invocable (last in supplied array).

The first parameter of your invocable must accept an enum value (passed by `dispatch`).
Optionally provide any additional parameters.

Works with lambdas, member functions, functions etc, compatible with `std::invoke`.

There are two versions of `dispatch` - the first takes an enum value, a 'not found' value, and a `std::array` of `std::tuple` of enum and invocable.
The second version takes an enum value, and a `std::array` of `std::tuple` of enum and invocable. The last element of the array is called if the enum is not found.
This version is intended for use with `void` return invocables.

The second version of each of the above is intended to be used when using a member function - the _first_ parameter passed after your array must be the `this` pointer of the object.
You can also use `std::bind` to bind the this pointer and any parameter placeholders when declaring your array.
If you wish to pass a `reference` parameter, you must wrap it in `std::ref`.

> [!TIP]
> If you wish to provide a `constexpr` array, you will need to use a simple function prototype, since `std::function` is not constexpr (see unit tests for examples).

> [!IMPORTANT]
> Your `std::array` of `std::tuple` should be sorted by enum.
> The `dispatch` method performs a binary search on the array. Complexity for a sorted array is at most  $2log_2(N)+O(1)$  comparisons.
> If the array is _not_ sorted, complexity is linear.

The following example uses a `static constexpr` array of pointers to functions. For brevity they all point to the same function except the last which is
a lambda.
```c++
enum class directions { left, right, up, down, forward, backward, notfound=-1 };
static constexpr auto prn([](directions ev) { std::cout << conjure_enum::enum_to_string(ev) << '\n'; });
static constexpr auto tarr
{
std::to_array>
({
{ directions::left, prn },
{ directions::right, prn },
{ directions::up, prn },
{ directions::down, prn },
{ directions::backward, prn },
{ directions::notfound, [](directions ev) { std::cout << "not found: "; prn(ev); } }, // not found func
})
};
conjure_enum::dispatch(directions::right, tarr);
conjure_enum::dispatch(directions::down, tarr);
conjure_enum::dispatch(directions::forward, tarr);
std::cout << conjure_enum::enum_to_int(directions::notfound) << '\n';
```
_output_
```CSV
directions::right
directions::down
not found: directions::forward
-1
```
This example uses lambdas:
```c++
const auto dd1
{
std::to_array>>
({
{ component::scheme, [](component ev, int a) { return a * 100 + conjure_enum::enum_to_int(ev); } },
{ component::port, [](component ev, int a) { return a * 200 + conjure_enum::enum_to_int(ev); } },
{ component::fragment, [](component ev, int a) { return a * 300 + conjure_enum::enum_to_int(ev); } },
})
};
std::cout << conjure_enum::dispatch(component::port, -1, dd1, 10) << '\n';
```
_output_
```CSV
2006
```
This example uses member functions:
```c++
struct foo
{
int process(component val, int aint)
{
return aint * static_cast(val);
}
int process1(component val, int aint)
{
return aint + static_cast(val);
}
};
constexpr auto tarr1
{
std::to_array>
({
{ component::scheme, &foo::process },
{ component::port, &foo::process },
{ component::fragment, &foo::process1 },
})
};
foo bar;
for (auto val : { component::scheme, component::path, component::port, component::fragment })
std::cout << conjure_enum::dispatch(val, -1, tarr1, &bar, 1000) << '\n';
```
_output_
```CSV
0
-1
6000
1015
```
## q) `is_scoped`
```c++
struct is_scoped : std::bool_constant>; }>{};
```
Returns `true` if the specified enum type is scoped.
```c++
std::cout << std::format("{}\n", conjure_enum::is_scoped());
std::cout << std::format("{}\n", conjure_enum::is_scoped());
```
_output_
```CSV
true
false
```
## r) `is_continuous`
```c++
static constexpr bool is_continuous();
```
Returns `true` if enum range is continuous (no gaps).
```c++
std::cout << std::format("{}\n", conjure_enum::is_continuous());
std::cout << std::format("{}\n", conjure_enum::is_continuous());
```
_output_
```CSV
true
false
```
## s) `type_name` ![](assets/notminimalred.svg)
```c++
static constexpr std::string_view type_name();
```
Returns a `std::string_view` of `T`.
```c++
std::cout << conjure_enum::type_name() << '\n';
std::cout << conjure_enum::type_name() << '\n';
```
_output_
```CSV
component
component1
```
## t) `remove_scope` ![](assets/notminimalred.svg)
```c++
static constexpr std::string_view remove_scope(std::string_view what);
```
Returns a `std::string_view` with scope removed; for unscoped returns unchanged
```c++
std::cout << conjure_enum::remove_scope("component::path"sv) << '\n';
std::cout << conjure_enum::remove_scope("path"sv) << '\n';
```
_output_
```CSV
path
path
```
## u) `add_scope` ![](assets/notminimalred.svg)
```c++
static constexpr std::string_view add_scope(std::string_view what);
```
Returns a `std::string_view` with scope added to the enum if the supplied enum string is valid but missing scope; for unscoped returns unchanged
```c++
std::cout << conjure_enum::add_scope("path"sv) << '\n';
std::cout << conjure_enum::add_scope("path"sv) << '\n';
```
_output_
```CSV
component::path
path
```
## v) `has_scope` ![](assets/notminimalred.svg)
```c++
static constexpr bool has_scope(std::string_view what);
```
Returns `true` if the supplied string representation is scoped (and is valid).
```c++
std::cout << std::format("{}\n", conjure_enum::has_scope("component::scheme"));
std::cout << std::format("{}\n", conjure_enum::has_scope("scheme"));
std::cout << std::format("{}\n", conjure_enum::has_scope("scheme"));
```
_output_
```CSV
true
false
false
```
## w) `iterators` ![](assets/notminimalred.svg)
```c++
static constexpr auto cbegin();
static constexpr auto cend();
static constexpr auto crbegin();
static constexpr auto crend();
```
These methods return `const_iterator` and `const_reverse_iterator` respectively all from `entries`
defined above.
```c++
using en = conjure_enum;
for (auto itr{en::cbegin()}; itr != en::cend(); ++itr)
std::cout << static_cast(std::get<0>(*itr)) << ' ' << std::get<1>(*itr) << '\n';
```
_output_
```CSV
0 numbers::zero
1 numbers::one
2 numbers::two
3 numbers::three
4 numbers::four
5 numbers::five
6 numbers::six
7 numbers::seven
8 numbers::eight
9 numbers::nine
```
## x) `iterator_adaptor` ![](assets/notminimalred.svg)
```c++
template
struct iterator_adaptor;
```
This class wraps `conjure_enum::entries` allowing it to be used in range based for loops:
```c++
for (const auto pp : iterator_adaptor())
std::cout << static_cast(std::get<0>(pp)) << '\n';
```
_output_
```CSV
0
1
2
3
4
5
6
7
8
9
```
## y) `front, back` ![](assets/notminimalred.svg)
```c++
static constexpr auto front();
static constexpr auto back();
```
These methods return `*cbegin()` and `*std::prev(cend())` respectively all from `entries`
defined above.
```c++
for (const auto& [ev,str] : {conjure_enum::front(), conjure_enum::back()})
std::cout << static_cast(ev) << ' ' << str << '\n';
```
_output_
```CSV
0 numbers::zero
9 numbers::nine
```
## z) `ostream_enum_operator` ![](assets/notminimalred.svg)
```c++
template, valid_enum T>
constexpr std::basic_ostream& operator<<(std::basic_ostream& os, T value);
```
Provides `std::ostream` insertion for any enum. You just need to include
```c++
using ostream_enum_operator::operator<<;
```
Examples
```c++
using ostream_enum_operator::operator<<;
std::cout << '"' << component::host << '"' << '\n';
std::cout << '"' << component1::host << '"' << '\n';
std::cout << '"' << static_cast(100) << '"' << '\n';
```
_output_
```CSV
"component::host"
"host"
"100"
```
## A) `epeek, tpeek`
```c++
static consteval const char *tpeek();
template
static consteval const char *epeek();
```
These functions return `std::source_location::current().function_name()` as `const char*` strings for the enum type or enum value.
The actual output is implementation dependent. See [Results of `source_location`](reference/source_location.md) for implementation specific `std::source_location` results.

The following code:
```c++
std::cout << conjure_enum::tpeek() << '\n';
std::cout << conjure_enum::epeek() << '\n';
```
Generates this output with gcc:
```CSV
static consteval const char* FIX8::conjure_enum::tpeek() [with T = component]
static consteval const char* FIX8::conjure_enum::epeek() [with T e = component::path; T = component]
```

## B) `get_enum_min_value`, `get_enum_max_value`, `get_actual_enum_min_value` and `get_actual_enum_max_value`
```c++
static constexpr int get_enum_min_value();
static constexpr int get_enum_max_value();
static constexpr int get_actual_enum_min_value();
static constexpr int get_actual_enum_max_value();
```
The first two functions return the min and max enum range for the specified enum. If you have specialised `enum_range` then these values
will be reported (see below).

The second two functions return the actual min and max enum values as ints for the specified enum.
```c++
std::cout << conjure_enum::get_enum_min_value() << '/' << conjure_enum::get_enum_min_value() << '\n';
std::cout << conjure_enum::get_actual_enum_min_value() << '/' << conjure_enum::get_actual_enum_min_value() << '\n';
```
_output_
```CSV
-128/127
0/14
```

## C) `in_range`
```c++
static constexpr bool in_range(T value);
```
Returns `true` if the given value is within the minimum and maximum defined values for this enum type.
```c++
std::cout << std::format("{}\n", conjure_enum::in_range(static_cast(100)));
```
_output_
```CSV
false
```

---
# 4. `enum_bitset`
`enum_bitset` is a convenient way of creating bitsets based on `std::bitset`. It uses your enum (scoped or unscoped)
for the bit positions (and names).
> [!NOTE]
> - Your enum sequence _must_ be 0 based
> - Continuous
> - The last value must be less than the count of enumerations
>
> We decided on these restrictions for both simplicity and practicality - bitsets only really make sense when represented in this manner; also...
>
> - This implementation is limited to 64 bits (arbitrary length impl. soon).

> [!IMPORTANT]
> You must include
> ```C++
> #include
> #include
> ```
> Your enum _must_ satisfy the following:
> ```C++
>template
>concept valid_bitset_enum = valid_enum and requires(T)
>{
> requires conjure_enum::is_continuous();
> requires conjure_enum::get_actual_enum_min_value() == 0;
> requires conjure_enum::get_actual_enum_max_value() < conjure_enum::count();
>};
>```

## a) Creating an `enum_bitset`
```c++
constexpr enum_bitset() = default;
constexpr enum_bitset(U bits);
constexpr enum_bitset(std::string_view from, bool anyscope=false,
char sep='|', bool ignore_errors=true);
constexpr enum_bitset(std::bitset from);

template
constexpr enum_bitset(E... comp);

template
constexpr enum_bitset(I... comp);
```
You can use the enum values directly in your constructor. _No_ need to `|` them - this is assumed. Just supply them comma separated:
```c++
enum class numbers { zero, one, two, three, four, five, six, seven, eight, nine };
enum_bitset b(numbers::zero, numbers::one, numbers::two, numbers::three);
std::cout << b << '\n';
```
_output_
```CSV
0000001111
```
You can use the underlying type as well:
```c++
enum_bitset b(0,1,2,3);
std::cout << b << '\n';
```
_output_
```CSV
0000001111
```
You can use an `int` initialiser too:
```c++
enum_bitset b(15);
std::cout << b << '\n';
```
_output_
```CSV
0000001111
```
You can even use a delimited string based on your enum names.
Optionally omit the scope and even specify your own delimiter (default is `|`).
Substrings are trimmed of whitespace before lookup.
```c++
enum_bitset b("numbers::zero|numbers::one|numbers::two|numbers::three");
std::cout << b << '\n';
enum_bitset b1("zero,one ,two, three", true, ',');
std::cout << b1 << '\n';
enum_bitset b2("zero|one|two|three", true);
std::cout << b2 << '\n';
```
_output_
```CSV
0000001111
0000001111
0000001111
```
A typical use of the above is for parsing configuration bitsets. Here you can tell the constructor to throw an `std::invalid_argument`
if a substring is invalid by specifying `false` for `ignore_errors`:
```c++
try
{
enum_bitset b("zero,twenty,two,three", true, ',', false);
std::cout << b << '\n';
}
catch(const std::invalid_argument& e)
{
std::cerr << "exception: " << e.what() << '\n';
}
```
_output_
```CSV
exception: twenty
```
You can also create an `enum_bitset` from a `std::bitset` of the same number of bits.
```c++
std::bitset<10> bs{1 << 1 | 1 << 3 | 1 << 6};
enum_bitset ed(bs);
std::cout << ed << '\n';
```
_output_
```CSV
0001001010
```

## b) Standard bit operators
All of the standard operators are supported. Assignment operators return a `enum_bitset&`, non-assignment operators return a `enum_bitset`.

| Operator | Description |
| :--- | :--- |
| `&` | binary AND |
| `\|` | binary OR |
| `^` | binary XOR |
| `~` | binary NOT (ones' complement)|
| `<<` | left shift |
| `>>` | right shift |
| `<<=` | left shift assign |
| `>>=` | right shift assign |
| `&=` | AND assign |
| `\|=` | OR assign |
| `^=` | XOR assign |

Operators work with enum values or integers:
```c++
enum_bitset b(numbers::zero, numbers::one, numbers::two, numbers::three);
std::cout << b << '\n';
std::cout << (b & 0b111) << '\n';
b ^= numbers::two;
std::cout << b << '\n';
```
_output_
```
0000001111
0000000111
0000001011
```
## c) Standard accessors and mutators
All of the standard accessors and mutators are supported.
| Method | Description |
| :--- | :--- |
| `test` | test for bit(s)|
| `set` | set all or 1 bit, optionally set to off|
| `reset` | reset bits(s)|
| `flip` | flip bits(s) (ones' complement)|
| `to_ulong` | convert to `unsigned long` |
| `to_ullong` | convert to `unsigned long long` |
| `count` | count of bits on |
| `size` | number of bits in bitset |
| `operator[]` | set or test bit at position or enum value |
| `any` | return `true` if any bit is on |
| `all` | return `true` if all bits are on |
| `none` | return `true` if no bits are on |

Additional methods
| Method | Description |
| :--- | :--- |
| `set` | set all specified bits, templated |
| `reset` | reset all specified bits, templated |
| `rotl` | rotate left specified times|
| `rotr` | rotate right specified times|
| `countl_zero` | counts number of consecutive `0` bits, starting from the most significant bit |
| `countl_one` | counts number of consecutive `1` bits, starting from the most significant bit |
| `countr_zero` | counts number of consecutive `0` bits, starting from the least significant bit |
| `countr_one` | counts number of consecutive `1` bits, starting from the least significant bit |
| `any_of` | test for one or more bits, templated, function, types and underlyings |
| `all_of` | test for all specified bits, templated, function, types and underlyings |
| `none_of` | test for all specified bits set to off, templated, function, types and underlyings |
| `not_count` | complement of count, count of off bits |
| `has_single_bit` | return true if bitset is an integral power of two|

> [!NOTE]
> `rotl`, `rotl`, `countl*` and `countr*` operate on the _used_ bits of the underlying type.

Take a look at the [implementation](include/fix8/conjure_enum_bitset.hpp) for more detail on the various API functions available.
You can also review the unit test cases for examples of use.

All accessors and mutators work with enum values or integers as with operators. They also work with multiple values, either as template parameters or
as variadic arguments:
```c++
enum_bitset eb;
eb.set();
std::cout << eb << '\n';
std::cout << std::boolalpha << eb.all_of() << '\n';
eb.reset();
std::cout << std::boolalpha << eb.all_of(0, 2, 5, 9) << '\n';
std::cout << std::boolalpha << eb.any_of(0, 2, 5, 9) << '\n';
std::cout << std::boolalpha << eb.all_of(numbers::zero,numbers::nine) << '\n';
std::cout << eb << '\n';
eb.reset(numbers::nine)
std::cout << ec << '\n';
eb.reset();
eb[2] = true;
eb[numbers::three] = true;
std::cout << eb << '\n';
std::cout << eb.rotr(1) << '\n';
```
_output_
```
1000100101
true
false
true
true
1000000001
0000000001
0000001100
0000011000
```
## d) Other functions
### i. `operator bool`
```c++
constexpr operator bool() const;
```
Return true if _any_ bits are on.

```c++
if (enum_bitset ec(15); ec)
std::cout << ec << '\n';
```
_output_
```CSV
0001001111
```

### ii. `operator std::bitset()`
```c++
constexpr operator std::bitset() const;
```
Cast an `enum_bitset` to a `std::bitset` with the same number of bits.

```c++
enum_bitset ec(numbers::one,numbers::three,numbers::six);
std::bitset<10> bs{ec};
std::cout << bs << '\n';
```
_output_
```CSV
0001001010
```

### iii. `std::ostream& operator<<`, `to_string`, `to_hex_string`
```c++
friend constexpr std::ostream& operator<<(std::ostream& os, const enum_bitset& what);
constexpr std::string to_string(char zero='0', char one='1') const;

template
constexpr std::string to_hex_string() const;

constexpr std::string to_hex_string() const;
```
Inserts default string representation into `std::ostream`.

Returns a `std::string` representation of the bitset. Optionally specify which characters to use for `0` and `1`.

Returns a `std::string` representation of the bitset in hex format. Optionally specify `showbase` which will prefix
the string with `0x` or `0X`; optionally specify `uppercase` which will set the case of the hex digits.

```c++
enum_bitset ec(numbers::one,numbers::three,numbers::six);
std::cout << ec << '\n';
std::cout << ec.to_string('-', '+') << '\n';
std::cout << ec.to_hex_string() << '\n';
std::cout << ec.to_hex_string() << '\n';
```
_output_
```CSV
0001001010
---+--+-+-
0x4a
0X4A
```
### iv. `for_each`, `for_each_n`
```c++
template
requires std::invocable
[[maybe_unused]] constexpr auto for_each(Fn&& func, Args&&... args);

template // specialisation for member function with object
requires std::invocable
[[maybe_unused]] constexpr auto for_each(Fn&& func, C *obj, Args&&... args);

template
requires std::invocable
[[maybe_unused]] constexpr auto for_each_n(int n, Fn&& func, Args&&... args);

template // specialisation for member function with object
requires std::invocable
[[maybe_unused]] constexpr auto for_each_n(int n, Fn&& func, C *obj, Args&&... args);
```
Call supplied invocable for _every bit that is on_. Similar to `std::for_each` except first parameter of your invocable must accept an enum value (passed by `for_each`).
Optionally provide any additional parameters. Works with lambdas, member functions, functions etc. You can limit the number of calls to your
invocable by using the `for_each_n` version with the first parameter being the maximum number to call. The second version of `for_each` and `for_each_n` is intended to be used
when using a member function - the _second_ parameter passed by your call must be the `this` pointer of the object.
If you wish to pass a `reference` parameter, you must wrap it in `std::ref`.

Returns `std::bind(std::forward(func), std::placeholders::_1, std::forward(args)...)` or
`std::bind(std::forward(func), obj, std::placeholders::_1, std::forward(args)...)` which can be stored or immediately invoked.

To iterate over every bit regardless of whether it is on or not, use `conjure_enum::for_each`.

Example using member function:
```c++
struct foo
{
void printer(numbers val, std::ostream& ostr) const
{
ostr << conjure_enum::enum_to_string(val) << '\n';
}
};
enum_bitset ec(numbers::zero,numbers::two,numbers::five,numbers::nine);
const foo bar;
ec.for_each(&foo::printer, &bar, std::ref(std::cout));
```
_output_
```CSV
numbers::zero
numbers::two
numbers::five
numbers::nine
```
Above example using `for_each_n`, limiting to 3:
```c++
ec.for_each_n(3, &foo::printer, &bar, std::ref(std::cout));
```
_output_
```CSV
numbers::zero
numbers::two
numbers::five
```

### v. Using `conjure_enum::dispatch` with `enum_bitset`
Using an `enum_bitset` wth `conjure_enum::dispatch` can be a convenient way of iterating through a set of bits to call specific functions using `for_each`. The following demonstrates this:
```c++
const auto dd3
{
std::to_array>>
({
{ numbers::one, [](numbers ev, int a)
{ std::cout << 1000 + a + conjure_enum::enum_to_int(ev) << '\n'; } },
{ numbers::two, [](numbers ev, int a)
{ std::cout << 2000 + a + conjure_enum::enum_to_int(ev) << '\n'; } },
{ numbers::three, [](numbers ev, int a)
{ std::cout << 3000 + a + conjure_enum::enum_to_int(ev) << '\n'; } },
{ static_cast(-1), [](numbers ev, [[maybe_unused]] int a)
{ std::cout << "not found: " << conjure_enum::enum_to_int(ev) << '\n'; } }, // not found func
})
};
enum_bitset(1,2,3,5).for_each([](numbers val, const auto& arr, int num)
{
conjure_enum::dispatch(val, arr, num);
}, dd3, 100);
```
_output_
```CSV
1101
2102
3103
not found: 5
```

### vi. `get_underlying`
```c++
constexpr U get_underlying() const;
```
Returns the underlying integral value.

### vii. `get_underlying_bit_size`
```c++
constexpr int get_underlying_bit_size() const
```
Returns the number of bits that the underlying integral contains. Will always be a power of 2 and an integral type. The number of bits may be larger
than the count of bits.

### viii. `get_bit_mask`,`get_unused_bit_mask`
```c++
constexpr U get_bit_mask() const;
constexpr U get_unused_bit_mask() const;
```
Returns a bit mask that would mask off the _unused_ bits of the underlying integral.

Returns a bit mask that would mask off the _used_ bits of the underlying integral.

### ix. `std::hash>`
```c++
template
struct std::hash>;
```
Provides a specialization of `std::hash` for `enum_bitset`.

---
# 5. `conjure_type`
`conjure_type` is a general purpose class allowing you to extract a string representation of any typename.
The string will be stored statically by the compiler, so you can use the statically generated value `name` to obtain your type.
> [!IMPORTANT]
> You must include
> ```C++
> #include
> #include
> ```

## a) `name`
This static member is generated for your type. It is a `fixed_string` but has a built-in `std::string_view` operator.
```c++
template
class conjure_type;
static constexpr fixed_string name;
```

```c++
class foo;
std::cout << std::format("\"{}\"\n", conjure_type::name);
```
_output_
```CSV
"foo"
```
Works with aliases:
```c++
using test = std::map;
using test1 = std::map;
std::cout << conjure_type::name << '\n';
std::cout << conjure_type::name << '\n';
std::cout << conjure_type>::name << '\n';
```
_output_
```CSV
std::map >
std::map
int
```
Works with its own types too:
```c++
std::cout << conjure_type>>::name << '\n';
```
_output_
```CSV
FIX8::conjure_type >
```
If you need to explicitly obtain a `std::string_view`, use the `get()` method on `name` (not windows sorry):
```c++
auto fstrv { conjure_type::name };
auto strv { conjure_type::name.get() };
std::cout << conjure_type::name << '\n';
std::cout << conjure_type::name << '\n';
```
_output_
```CSV
fixed_string<58>
std::basic_string_view
```
Alternatively you can use the `as_string_view()` method:
```c++
auto fstrv { conjure_type::as_string_view() };
std::cout << conjure_type::name << '\n';
```
_output_
```CSV
std::basic_string_view
```

## b) `as_string_view`
Return the name as a `std::string_view`.
```c++
static constexpr std::string_view as_string_view();
```

## c) `tpeek`
```c++
static consteval const char *tpeek();
```

These functions return `std::source_location::current().function_name()` as `const char*` strings for type.
The actual output is implementation dependent. See [Results of `source_location`](reference/source_location.md) for implementation specific `std::source_location` results.

The following code:
```c++
std::cout << conjure_type