better-enums/example/103-quine.cc
2015-06-05 13:01:28 -05:00

168 lines
5.2 KiB
C++

// This file was generated automatically.
// Semi-quine
//
// Let's make a Better Enum assemble its own definition in memory. It won't be
// literally as defined, since we will lose the exact initializer expressions,
// but we will be able to preserve the numeric values. We will reserve the
// memory buffer for the definition at compile time.
//
// Ok, so it's not really a quine, because we won't be writing all the code
// needed to generate the definition to the buffer as well. And, there are
// better ways to dump the definition than shown here. You could simply define a
// macro that expands to an ENUM declaration and also stringizes it.
//
// But that's not the point here. The point of this page is to show some of the
// reflective capabilities of Better Enums, so you can adapt them for cases
// where a macro is not sufficient :)
#include <cassert>
#include <cstdio>
#include <iostream>
// First, we will need full compile-time reflection, since we will be calling
// _to_string. Let's make sure it's enabled by defining
// BETTER_ENUMS_CONSTEXPR_TO_STRING before including enum.h:
#ifndef BETTER_ENUMS_CONSTEXPR_TO_STRING
#define BETTER_ENUMS_CONSTEXPR_TO_STRING
#endif
#include <enum.h>
// Now, let's declare some enums to dump later:
ENUM(Channel, int, Red, Green, Blue)
ENUM(Depth, int, TrueColor = 1, HighColor = 0)
// Computing the size of the buffer
//
// First, we need to be able to get the length of each declaration above. We
// will assume that the underlying type is always int, and that the spacing
// convention is followed as above.
//
// First, let's get the lengths of basic components:
// Returns the length of the string representation of the number n
constexpr size_t value_length(int n, int bound = 10, size_t digits = 1)
{
return
n < bound ? digits : value_length(n, bound * 10, digits + 1);
}
// Returns the length of s
constexpr size_t string_length(const char *s, size_t index = 0)
{
return s[index] == '\0' ? index : string_length(s, index + 1);
}
// Now, the length of the constant declaration. Here is where we lose
// information about initializers. We are going to format the constant
// declarations like this:
//
// Red = 0, Green = 1, Blue = 2
// TrueColor = 1, HighColor = 0
//
// This is because Better Enums doesn't provide a way to know what the exact
// initializer was or whether there even was one - just the numeric value of
// each constant. If we were trying to be clever, we could avoid formatting
// initializers for sequential values, but I won't go through this exercise
// here.
// Returns the length of the constants portion of the declaration of Enum,
// as described above.
template <typename Enum>
constexpr size_t constants_length(size_t index = 0, size_t accumulator = 0)
{
return
index >= Enum::_size ? accumulator :
constants_length<Enum>(
index + 1, accumulator
+ string_length(", ")
+ string_length(Enum::_names()[index])
+ string_length(" = ")
+ value_length(
Enum::_values()[index]._to_integral()));
}
// Finally, we can combine these to get the length of the formatted declaration
// of the whole enum:
// Returns the length of the whole declaration of Enum, assuming the
// underlying type is int, and the constants are initialized as assumed by
// constants_length() above.
template <typename Enum>
constexpr size_t declaration_length()
{
return
string_length("ENUM(")
+ string_length(Enum::_name())
+ string_length(", int")
+ constants_length<Enum>()
+ string_length(")");
}
// Formatting the enums
//
// Now, we can declare the buffers. The memory will be reserved at load time by
// the binary's loader. The extra one byte in each buffer is for the null
// terminator.
char channel_definition[declaration_length<Channel>() + 1];
char depth_definition[declaration_length<Depth>() + 1];
// Let's also create the formatting function. This is executed at run time, but
// we will be giving it pointers to our statically-allocated buffers. It will
// format the enum declaration and then return the number of bytes it wrote to
// the buffer, so that we can do a sanity check on it.
template <typename Enum>
size_t format(char *buffer)
{
size_t offset = 0;
offset += std::sprintf(buffer, "ENUM(%s, int", Enum::_name());
for (Enum value : Enum::_values()) {
offset +=
std::sprintf(buffer + offset,
", %s = %i",
value._to_string(), value._to_integral());
}
offset += std::sprintf(buffer + offset, ")");
return offset;
}
// Checking our work
//
// Now, we can write and run this code.
int main()
{
size_t channel_length = format<Channel>(channel_definition);
assert(channel_length + 1 == sizeof(channel_definition));
size_t depth_length = format<Depth>(depth_definition);
assert(depth_length + 1 == sizeof(depth_definition));
std::cout << channel_definition << std::endl;
std::cout << depth_definition << std::endl;
return 0;
}
// It prints:
//
// ENUM(Channel, int, Red = 0, Green = 1, Blue = 2)
// ENUM(Depth, int, TrueColor = 1, HighColor = 0)