Link | Description |
---|---|
Here | For implementation |
API and Examples | General examples |
API and Examples for enum_bitset |
std::bitset replacement |
API and Examples for conjure_type |
any type string extractor |
Building | How to build or include |
Notes | Notes on the implementation, limits, etc |
Compilers | Supported compilers |
Compiler issues | Workarounds for various compiler issues |
Review std::source_location |
For implementation specific source_location results |
Tip
Use the built-in table of contents to navigate this guide.
Based on the awesome work of magic_enum
1 and boost::desribe
,
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 need while enhancing and expanding them for a more efficient and expressive experience. We've also
added general purpose type reflection for any type.
conjure_enum
2 takes full advantage of the latest C++20 features. We've leveraged the convenience of std::source_location
and
unlocked the potential of constexpr
algorithms and concepts. This translates to:
- Improved Performance Optimized code for faster and smoother operation - get your compiler to do more!
- Enhanced Developer Experience Write cleaner, more concise, and more powerful C++ code.
- Lightweight: Designed for performance without unnecessary overhead.
- Single Header-Only: No external dependencies, simplifying integration into your project.
- Modern C++20: Entirely
constexpr
for compile-time safety, efficiency and performance. - Broad Support: Works with scoped and unscoped enums, enum aliases and even with gaps.
- Simple & Easy to Use: Class-based approach with intuitive syntax.
- Convenient:
enum_bitset
offers an enhancedstd::bitset
. - Useful:
conjure_type
lets you obtain the type string of any type! - Wide Compiler Compatibility: Supports GCC, Clang, MSVC and XCode/Clang;
x86_64
,AArch64
- Confidence in Quality: Includes comprehensive unit tests for reliable functionality.
- Expanded: Enhanced API such as
add_scope
,remove_scope
,unscoped_string_to_enum
, iterators and more!
All examples refer to the following enums:
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 };
static constexpr std::string_view enum_to_string(T value, bool noscope=false);
Returns a std::string_view
or empty if not found. Optionally passing true
will remove scope in result if present.
auto name { conjure_enum<component>::enum_to_string(component::path) };
auto name_trim { conjure_enum<component>::enum_to_string(component::path, true) }; // optionally remove scope in result
auto alias_name { conjure_enum<component>::enum_to_string(component::test) }; // alias
auto noscope_name { conjure_enum<component1>::enum_to_string(path) };
std::cout << name << '\n' << name_trim << '\n' << alias_name << '\n' << noscope_name << '\n';
output
component::path
path
component::path
path
Because all methods in conjure_enum
are within a class
instead of individual template functions in a namespace
, you can reduce your
typing with standard aliases:
using ec = conjure_enum<component>;
std::cout << std::format("\"{}\"\n", ec::enum_to_string(component::authority));
std::cout << std::format("\"{}\"\n", ec::enum_to_string(static_cast<component>(100)));
output
"component::authority"
""
Also supplied is a template version of enum_to_string
.
std::cout << std::format("\"{}\"\n", ec::enum_to_string<component::scheme>());
std::cout << std::format("\"{}\"\n", ec::enum_to_string<scheme>());
output
"component::scheme"
"scheme"
static constexpr std::optional<T> string_to_enum(std::string_view str);
Returns a std::optional<T>
. Empty if string was not valid. Use std::optional<T>::value_or()
to set an error value
and avoid throwing an exception.
int value { static_cast<int>(conjure_enum<component>::string_to_enum("component::path").value()) };
int noscope_value { static_cast<int>(conjure_enum<component1>::string_to_enum("path").value()) };
int bad_value { static_cast<int>(conjure_enum<component>::string_to_enum("bad_string").value_or(component(100))) };
std::cout << value << '\n' << noscope_value << '\n' << bad_value << '\n';
output
12
12
100 <-- invalid, error value
static constexpr std::optional<T> unscoped_string_to_enum(std::string_view str);
Same as string_to_enum
except works with unscoped strings. Returns a std::optional<T>
. Empty if string was not valid. Use std::optional<T>::value_or()
to set an error value
and avoid throwing an exception.
int value { static_cast<int>(conjure_enum<component>::unscoped_string_to_enum("path").value()) };
int noscope_value { static_cast<int>(conjure_enum<component1>::string_to_enum("path").value()) };
int bad_value { static_cast<int>(conjure_enum<component>::string_to_enum("bad_string").value_or(component(100))) };
std::cout << value << '\n' << noscope_value << '\n' << bad_value << '\n';
output
12
12
100 <-- invalid, error value
static constexpr std::optional<T> int_to_enum(int value);
Returns a std::optional<T>
. Empty if value was not valid. Use std::optional<T>::value_or()
to set an error value
and avoid throwing an exception.
int value { static_cast<int>(conjure_enum<component>::int_to_enum(12).value()) };
int noscope_value { static_cast<int>(conjure_enum<component1>::int_to_enum(12).value()) };
int bad_value { static_cast<int>(conjure_enum<component>::int_to_enum(100).value_or(component(100))) };
std::cout << value << '\n' << noscope_value << '\n' << bad_value << '\n';
output
12
12
100 <-- invalid, error value
static constexpr int enum_to_int(T value);
static constexpr std::underlying_type_t<T> enum_to_underlying(T value);
Returns an int
or the underlying
value for the given enum value.
std::cout << conjure_enum<component>::enum_to_int(component::path) << '\n';
std::cout << conjure_enum<component>::enum_to_underlying(component::path) << '\n';
std::cout << conjure_enum<component1>::enum_to_int(path) << '\n';
std::cout << conjure_enum<component1>::enum_to_underlying(path) << '\n';
output
12
12
12
12
static constexpr std::size_t count();
Returns number of enumerations.
std::cout << conjure_enum<component>::count() << '\n';
output
10
static constexpr std::array<std::string_view, std::size_t> names;
This static member is generated for your type. It is a std::array
of the std::string_view
strings in enum order.
for(const auto ev : conjure_enum<component>::names) // scoped enum
std::cout << ev << '\n';
for(const auto ev : conjure_enum<component1>::names) // unscoped enum
std::cout << ev << '\n';
output
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
static constexpr std::array<std::string_view, std::size_t> 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.
for(const auto ev : conjure_enum<component>::unscoped_names) // scoped enum
std::cout << ev << '\n';
std::cout << '\n';
for(const auto ev : conjure_enum<component1>::unscoped_names) // unscoped enum
std::cout << ev << '\n';
output
scheme
authority
userinfo
user
password
host
port
path
query
fragment
scheme
authority
userinfo
user
password
host
port
path
query
fragment
static constexpr std::array<T, std::size_t> values;
This static member is generated for your type. It is a std::array
of the T
values in enum order.
for(const auto ev : conjure_enum<component>::values) // scoped enum
std::cout << static_cast<int>(ev) << '\n';
output
0
1
2
3
4
5
6
12
13
14
static constexpr std::array<std::tuple<T, std::string_view>, std::size_t> entries;
static constexpr std::array<std::tuple<T, std::string_view>, 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.
using ec = conjure_enum<component>;
for(const auto [value, str] : ec::entries) // scoped enum
std::cout << std::format("{:<2} {}\n", static_cast<int>(value), str);
std::cout << '\n';
for(const auto [value, str] : ec::sorted_entries) // scoped enum
std::cout << std::format("{:<2} {}\n", static_cast<int>(value), str);
output
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
static constexpr std::array<std::tuple<std::string_view, std::string_view>, 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.
for(const auto [a, b] : conjure_enum<component>::scoped_entries)
std::cout << std::format("{:9} {}\n", a, b);
output
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
static constexpr std::array<std::tuple<std::string_view, std::string_view>, std::size_t> rev_scoped_entries;
Same as scoped_entries
except reversed, sorted by scoped name. Use to lookup unscoped name.
static constexpr bool contains(T value);
static constexpr bool contains(std::string_view str);
Returns true
if the enum contains the given value or string.
std::cout << std::format("{}\n", conjure_enum<component>::contains(component::path));
std::cout << std::format("{}\n", conjure_enum<component1>::contains("nothing"));
output
true
false
template<typename Fn, typename... Args>
requires std::invocable<Fn&&, T, Args...>
[[maybe_unused]] static constexpr auto for_each(Fn&& func, Args&&... args);
template<typename Fn, typename C, typename... Args> // specialisation for member function with object
requires std::invocable<Fn&&, C, T, Args...>
[[maybe_unused]] static constexpr auto for_each(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. Works with lambdas, member functions, functions etc. The second version 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<Fn>(func), std::placeholders::_1, std::forward<Args>(args)...)
or std::bind(std::forward<Fn>(func), obj, std::placeholders::_1, std::forward<Args>(args)...)
which can be stored or immediately invoked.
See enum_bitset::for_each
to iterate through a bitset.
conjure_enum<component>::for_each([](component val, int other)
{
std::cout << static_cast<int>(val) << ' ' << other << '\n';
}, 10);
output
0 10
1 10
2 10
3 10
4 10
5 10
6 10
12 10
13 10
14 10
Example using returned object and additional reference parameter:
int total{};
auto myfunc { conjure_enum<component>::for_each([](component val, int other, int& tot)
{
std::cout << static_cast<int>(val) << ' ' << other << '\n';
tot += static_cast<int>(val);
}, 10, std::ref(total)) };
myfunc(component::fragment);
std::cout << total << '\n';
output
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:
struct foo
{
void process(component val, int offset, int& tot)
{
tot += offset + static_cast<int>(val);
}
};
int total{};
foo bar;
conjure_enum<component>::for_each(&foo::process, &bar, 10, std::ref(total));
std::cout << total << '\n';
output
160
struct is_scoped : std::integral_constant<bool, requires
{ requires !std::is_convertible_v<T, std::underlying_type_t<T>>; }>{};
Returns true
if the specified enum type is scoped.
std::cout << std::format("{}\n", conjure_enum<component>::is_scoped());
std::cout << std::format("{}\n", conjure_enum<component1>::is_scoped());
output
true
false
template<T e>
static constexpr bool is_valid();
Returns true
if enum value is valid.
std::cout << std::format("{}\n", conjure_enum<component>::is_valid<component::password>());
std::cout << std::format("{}\n", conjure_enum<component1>::is_valid<static_cast<component>(16)>());
output
true
false
static constexpr std::string_view type_name();
Returns a std::string_view
of T
.
std::cout << conjure_enum<component>::type_name() << '\n';
std::cout << conjure_enum<component1>::type_name() << '\n';
output
component
component1
static constexpr std::string_view remove_scope(std::string_view what);
Returns a std::string_view
with scope removed; for unscoped returns unchanged
std::cout << conjure_enum<component>::remove_scope("component::path"sv) << '\n';
std::cout << conjure_enum<component>::remove_scope("path"sv) << '\n';
output
path
path
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
std::cout << conjure_enum<component>::add_scope("path"sv) << '\n';
std::cout << conjure_enum<component1>::add_scope("path"sv) << '\n';
output
component::path
path
static constexpr bool has_scope(std::string_view what);
Returns true
if the supplied string is scoped (and is valid).
std::cout << std::format("{}\n", conjure_enum<component>::has_scope("component::scheme"));
std::cout << std::format("{}\n", conjure_enum<component>::has_scope("scheme"));
std::cout << std::format("{}\n", conjure_enum<component1>::has_scope("scheme"));
output
true
false
false
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.
using en = conjure_enum<numbers>;
for (auto itr{en::cbegin()}; itr != en::cend(); ++itr)
std::cout << static_cast<int>(std::get<0>(*itr)) << ' ' << std::get<1>(*itr) << '\n';
output
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
template<valid_enum T>
struct iterator_adaptor;
This class wraps conjure_enum<T>::entries
allowing it to be used in range based for loops:
for (const auto pp : iterator_adaptor<numbers>())
std::cout << static_cast<int>(std::get<0>(pp)) << '\n';
output
0
1
2
3
4
5
6
7
8
9
static constexpr auto front();
static constexpr auto back();
These methods return *cbegin()
and *std::prev(cend())
respectively all from entries
defined above.
using en = conjure_enum<numbers>;
std::cout << static_cast<int>(std::get<0>(en::front())) << ' ' << std::get<1>(en::front()) << '\n';
std::cout << static_cast<int>(std::get<0>(en::back())) << ' ' << std::get<1>(en::back()) << '\n';
output
0 numbers::zero
9 numbers::nine
template<typename CharT, typename Traits=std::char_traits<CharT>, valid_enum T>
constexpr std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, T value);
Provides std::ostream
insertion for any enum. You just need to include
using ostream_enum_operator::operator<<;
Examples
using ostream_enum_operator::operator<<;
std::cout << '"' << component::host << '"' << '\n';
std::cout << '"' << component1::host << '"' << '\n';
std::cout << '"' << static_cast<component>(100) << '"' << '\n';
output
"component::host"
"host"
"100"
static consteval const char *tpeek();
template<T e>
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.
Tip
If you want to report an issue please include the output of these methods.
The following code:
std::cout << conjure_enum<component>::tpeek() << '\n';
std::cout << conjure_enum<component>::epeek<component::scheme>() << '\n';
Generates this output with gcc:
static consteval const char* FIX8::conjure_enum<T>::epeek() [with T e = component::path; T = component]
static consteval const char* FIX8::conjure_enum<T>::tpeek() [with T = component]
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 should be continuous. The last value must be less than the count of enumerations.
We decided on this restriction for both simplicity and practicality - bitsets only really make sense when represented in this manner.
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);
template<valid_enum... E>
constexpr enum_bitset(E... comp);
template<std::integral... I>
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:
enum_bitset<numbers> b(numbers::zero, numbers::one, numbers::two, numbers::three);
std::cout << b << '\n';
output
0000001111
You can use the underlying type as well:
enum_bitset<numbers> b(0,1,2,3);
std::cout << b << '\n';
output
0000001111
You can use an int
initialiser too:
enum_bitset<numbers> b(15);
std::cout << b << '\n';
output
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.
enum_bitset<numbers> b("numbers::zero|numbers::one|numbers::two|numbers::three");
std::cout << b << '\n';
enum_bitset<numbers> b1("zero,one ,two, three", true, ',');
std::cout << b1 << '\n';
enum_bitset<numbers> b2("zero|one|two|three", true);
std::cout << b2 << '\n';
output
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:
try
{
enum_bitset<numbers> b("zero,twenty,two,three", true, ',', false);
std::cout << b << '\n';
}
catch(const std::invalid_argument& e)
{
std::cerr << "exception: " << e.what() << '\n';
}
output
exception: twenty
All of the standard operators are supported. Assignment operators return a enum_bitset&
, non-assignment operators return a enum_bitset
.
Operator | Description |
---|---|
<<= |
right shift assign |
>>= |
left shift assign |
&= |
and assign |
|= |
or shift assign |
^= |
xor shift assign |
<< |
left shift |
>> |
right shift |
& |
and |
| |
or |
^ |
xor |
~ |
not |
Operators work with enum values or integers:
enum_bitset<numbers> 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
All of the standard accessors and mutators are supported.
Method | Description |
---|---|
test |
test for bit(s) |
set |
set bit(s) |
reset |
reset bits(s) |
flip |
flip bits(s) |
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[] |
test bit at position |
Additional methods
Method | Description |
---|---|
set_all |
set all specified bits |
reset_all |
reset all specified bits |
test_any |
test for one or more bits |
test_all |
test for all specified bits |
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:
enum_bitset<numbers> eb;
eb.set_all<numbers::zero,numbers::two,numbers::five,numbers::nine>();
std::cout << eb << '\n';
std::cout << std::boolalpha << eb.test_all<numbers::zero,numbers::two,numbers::five,numbers::nine>() << '\n';
eb.reset<numbers::five,numbers::two>();
std::cout << std::boolalpha << eb.test_all(0, 2, 5, 9) << '\n';
std::cout << std::boolalpha << eb.test_any(0, 2, 5, 9) << '\n';
std::cout << std::boolalpha << eb.test_all(numbers::zero,numbers::nine) << '\n';
std::cout << eb << '\n';
eb.reset(numbers::nine)
std::cout << ec << '\n';
output
1000100101
true
false
true
true
1000000001
0000000001
constexpr operator bool() const;
Return true if any bits are on.
if (enum_bitset<numbers> ec(15); ec)
std::cout << ec << '\n';
output
0001001111
friend constexpr std::ostream& operator<<(std::ostream& os, const enum_bitset& what);
constexpr std::string to_string(char zero='0', char one='1') 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
.
enum_bitset<numbers> ec(numbers::one,numbers::three,numbers::six);
std::cout << ec << '\n';
std::cout << ec.to_string('-', '+') << '\n';
output
0001001010
---+--+-+-
template<typename Fn, typename... Args>
requires std::invocable<Fn&&, T, Args...>
[[maybe_unused]] constexpr auto for_each(Fn&& func, Args&&... args);
template<typename C, typename Fn, typename... Args> // specialisation for member function with object
requires std::invocable<Fn&&, C, T, Args...>
[[maybe_unused]] constexpr auto for_each(Fn&& func, C *obj, Args&&... args);
Call supplied invocable for each 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. The second version 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<Fn>(func), std::placeholders::_1, std::forward<Args>(args)...)
or
std::bind(std::forward<Fn>(func), obj, std::placeholders::_1, std::forward<Args>(args)...)
which can be stored or immediately invoked.
To iterate over each bit regardless of whether it is on or not, use conjure_enum<T>::for_each
.
Example using member function:
struct foo
{
void printer(numbers val, std::ostream& ostr) const
{
ostr << conjure_enum<numbers>::enum_to_string(val) << '\n';
}
};
enum_bitset<numbers> ec(numbers::zero,numbers::two,numbers::five,numbers::nine);
const foo bar;
ec.for_each(&foo::printer, &bar, std::ref(std::cout));
output
numbers::zero
numbers::two
numbers::five
numbers::nine
conjure_type
is a general purpose class allowing you to extract a string representation of any type.
The string will be stored statically by the compiler, so use the statically generated value name
for your type.
template<typename T>
class conjure_type;
static constexpr fixed_string name;
This static member is generated for your type. It is a fixed_string
but has a built-in std::string_view
operator.
class foo;
std::cout << std::format("\"{}\"\n", conjure_type<foo>::name);
output
"foo"
Works with aliases:
using test = std::map<std::size_t, std::string_view>;
using test1 = std::map<std::size_t, foo>;
std::cout << conjure_type<test>::name << '\n';
std::cout << conjure_type<test1>::name << '\n';
std::cout << conjure_type<std::underlying_type_t<numbers>>::name << '\n';
output
std::map<long unsigned int, std::basic_string_view<char> >
std::map<long unsigned int, foo>
int
Works with its own types too:
std::cout << conjure_type<conjure_type<conjure_enum<numbers>>>::name << '\n';
output
FIX8::conjure_type<FIX8::conjure_enum<numbers> >
If you need to explicitly obtain a std::string_view
, use the get()
method on name
(not windows sorry):
auto fstrv { conjure_type<test>::name };
auto strv { conjure_type<test>::name.get() };
std::cout << conjure_type<decltype(fstrv)>::name << '\n';
std::cout << conjure_type<decltype(strv)>::name << '\n';
output
fixed_string<58>
std::basic_string_view<char>
Alternatively you can use the as_string_view()
method:
auto fstrv { conjure_type<test>::as_string_view() };
std::cout << conjure_type<decltype(fstrv)>::name << '\n';
output
std::basic_string_view<char>
This implementation is header only. Apart from standard C++20 includes there are no external dependencies needed in your application. Catch2 is used for the built-in unit tests.
To clone and default build the test app, unit tests and the benchmark:
git clone https://github.com/fix8mt/conjure_enum.git
cd conjure_enum
mkdir build
cd build
cmake ..
make -j4
make test (or ctest)
By default all warnings are enabled. To prevent this, pass the following to cmake:
cmake -DBUILD_ALL_WARNINGS=false ..
By default the unit tests are built (which will download Catch2). To prevent this, pass the following to cmake:
cmake -DBUILD_UNITTESTS=false ..
To disable certain C++20 features that cause issues with some builds:
cmake -DBUILD_CONSRVCPP20=true ..
Create a new console project. Add the repo https://github.com/fix8mt/conjure_enum.git
and clone the source.
Make sure you set the C++ language to C++20 in the project preferences. The project should build and run the unit tests
by default.
In CMakeLists.txt
set your include path to:
include_directories([conjure_enum directory]/include)
# e.g.
set(cjedir /home/dd/prog/conjure_enum)
include_directories(${cjedir}/include)
and just include:
#include <fix8/conjure_enum.hpp>
in your application. Everything in this class is within the namespace FIX8
, so you can add:
using namespace FIX8;
You can use cmake FetchContent to integrate conjure_enum
with your project.
If your project was called myproj
with the sourcefile myproj.cpp
then...
project(myproj)
add_executable (myproj myproj.cpp)
set_target_properties(myproj PROPERTIES CXX_STANDARD 20 CXX_STANDARD_REQUIRED true)
message(STATUS "Downloading conjure_enum...")
include(FetchContent)
FetchContent_Declare(conjure_enum GIT_REPOSITORY https://github.com/fix8mt/conjure_enum.git)
FetchContent_MakeAvailable(conjure_enum)
target_include_directories(myproj PRIVATE ${conjure_enum_SOURCE_DIR}/include)
These are set by default unless you override them by defining them in your application.
Note
If you want to define these values they must appear before you include conjure_enum.hpp
.
The following are the default settings:
#if not defined ENUM_MIN_VALUE
# define ENUM_MIN_VALUE -128
#endif
#if not defined ENUM_MAX_VALUE
# define ENUM_MAX_VALUE 127
#endif
These definitions set the minimum and maximum enum values that are supported. You can adjust them to suit your requirements but for most use cases the defaults are sufficient.
All methods in this class are static. You cannot instantiate an object of this type.
This library provides a workaround to current limitations of C++. There are proposals out there for future versions of the language that will provide proper reflection. See Reflection TS and Reflection for C++26 for examples of some of these.
All of the generated static strings and generated static tables obtained by std::source_location
use the library defined fixed_string
. No string copying is done at runtime, resulting in
a single static string in your application. All conjure_enum
methods that return strings only return std::string_view
.
To demonstrate this, lets look at the supplied test application statictest
:
#include <iostream>
#include <fix8/conjure_enum.hpp>
enum class component : int { scheme, authority, userinfo, user, password, host, port, path, query, fragment };
int main(void)
{
for(const auto& [a, b] : conjure_enum<component>::entries)
std::cout << conjure_enum<component>::enum_to_int(a) << ' ' << b << '\n';
for(const auto& [a, b] : conjure_enum<component>::unscoped_entries)
std::cout << conjure_enum<component>::enum_to_int(a) << ' ' << b << '\n';
for(const auto& a : conjure_enum<component>::names)
std::cout << a << '\n';
for(const auto& a : conjure_enum<component>::unscoped_names)
std::cout << a << '\n';
return 0;
}
output
$ ./statictest
0 component::scheme
1 component::authority
2 component::userinfo
3 component::user
4 component::password
5 component::host
6 component::port
7 component::path
8 component::query
9 component::fragment
1 authority
9 fragment
5 host
4 password
7 path
6 port
8 query
0 scheme
3 user
2 userinfo
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
$
The default build of statictest
performs a strip on the executable.
Then we run strings on the executable.
shell output
$ strings statictest
/lib64/ld-linux-x86-64.so.2
__gmon_start__
_ITM_deregisterTMCloneTable
_ITM_registerTMCloneTable
_ZNSolsEi
_ZSt21ios_base_library_initv
_ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
_ZNSo3putEc
_ZSt4cout
__stack_chk_fail
__libc_start_main
__cxa_finalize
libstdc++.so.6
libc.so.6
GLIBC_2.4
GLIBC_2.34
GLIBC_2.2.5
GLIBCXX_3.4.32
GLIBCXX_3.4.9
GLIBCXX_3.4
AVAUATUH
[]A\A]A^
PTE1
u+UH
component::fragment
component::query
component::path
component::port
component::host
component::password
component::user
component::userinfo
component::authority
component::scheme
9*3$"
GCC: (Ubuntu 14-20240412-0ubuntu1) 14.0.1 20240412 (experimental) [master r14-9935-g67e1433a94f]
.shstrtab
.interp
.note.gnu.property
.note.gnu.build-id
.note.ABI-tag
.gnu.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rela.dyn
.rela.plt
.init
.plt.got
.plt.sec
.text
.fini
.rodata
.eh_frame_hdr
.eh_frame
.init_array
.fini_array
.data.rel.ro
.dynamic
.data
.bss
.comment
$
It can be observed that there is only one copy of the scoped enum value string in the executable.
Compiler | Version(s) | Notes | Unsupported |
---|---|---|---|
gcc | 11 , 12 , 13 , 14 |
std::format not complete in 11 , 12 |
<= 10 |
clang | 15 , 16 , 17 , 18 |
Catch2 needs cxx_std_20 in 15 |
<= 14 |
msvc | 16 , 17 |
Visual Studio 2019,2022, latest 17.9.5 |
<= 16.9 |
xcode | 15 |
Some issues with constexpr , workarounds |
<= 14 |
Compiler | Version(s) | Issues | Workaround |
---|---|---|---|
clang | 16 , 17 , 18 |
Compiler reports integers outside valid range [x,y] | specify underlying type when declaring enum eg. enum class foo : int |