Template Metaprogramming - Difference Between Using Enum Hack and Static Const

Template Metaprogramming - Difference Between Using Enum Hack and Static Const

Enums aren't lvals, static member values are and if passed by reference the template will be instanciated:

void f(const int&);
f(TMPFib<1>::value);

If you want to do pure compile time calculations etc. this is an undesired side-effect.

The main historic difference is that enums also work for compilers where in-class-initialization of member values is not supported, this should be fixed in most compilers now.

There may also be differences in compilation speed between enum and static consts.

There are some details in the boost coding guidelines and an older thread in the boost archives regarding the subject.

Why enum instead of static bool?

The key reason is that a static bool is a variable after all and a enum is a type - hence in case of enum no variable is ever instantiated and it's thus guaranteed to be a compile-time evaluation.

Also see this question for more details.

How to define the different template structs with different enums

StrMyEnum identifies the name of the template.

You can have a general declaration for it, and then some specializations.

But you cannot have a second set of declaration + specializations (like you did with the set for XXX).

What you can do is have one template parametrized with both the enum type and the enum value.
Then you can have 2 levels of specializations:

  1. For a specific value of a specific enum.
  2. Default for a specific enum.

See the code below:

#include <iostream>

enum TTT { t = 2, t2 = 3 };
enum XXX { x = 2, x2 = 3 };

template <typename T, T val>
struct StrMyEnum {
static char const* name() { return "unknown"; }
};

// Default specialization for TTT:
template <TTT t>
struct StrMyEnum<TTT, t> {
static char const* name() { return "TTT"; }
};

// Specialization for TTT::t:
template <>
struct StrMyEnum<TTT, t> {
static char const* name() { return "TTT::t"; }
};

// Specialization for XXX::x:
template <>
struct StrMyEnum<XXX, x> {
static char const* name() { return "XXX::x"; }
};

int main()
{
std::cout << StrMyEnum<TTT, t2>().name() << std::endl;
std::cout << StrMyEnum<TTT, t>().name() << std::endl;
std::cout << StrMyEnum<XXX, x2>().name() << std::endl;
std::cout << StrMyEnum<XXX, x>().name() << std::endl;
return 0;
}

Output:

TTT
TTT::t
unknown
XXX::x

Demo: https://godbolt.org/z/zW547T9nf.

Calculate some values using template size_t param

The enum hack is the old way to provide compile-time computations. It was used when in class initialization is not supportted by some compilers, so static const cannot be used. Nowadays its fixed in all modern compilers.
So the preferred way is to use static const.

Check this answer for more info.

Why do people use enums in C++ as constants while they can use const?

An enumeration implies a set of related constants, so the added information about the relationship must be useful in their model of the problem at hand.

fill static templated arrays with metaprogramming and variadic templates

Using arrays:

Given a table with unique entries from 0 to dim^2-1, you can write constexpr lookup functions for the i and j of a given table entry:

constexpr unsigned get_x_comp(unsigned index, unsigned i=0, unsigned j=0) 
{ return table[i][j] == index ? i : get_x_comp(index, ((j+1)%dim ? i : i+1), (j+1)%dim); }

constexpr unsigned get_y_comp(unsigned index, unsigned i=0, unsigned j=0)
{ return table[i][j] == index ? j : get_y_comp(index, ((j+1)%dim ? i : i+1), (j+1)%dim); }

These will recursively call themselves, iterating through the table and looking for index. Recursion will eventually end when the given index is found and i/j of that index is returned.

Combine that with the C++14 std::integer_sequence mentioned by Jonathan to initialize the arrays:

template<unsigned... I>
constexpr auto make_x_comp(std::integer_sequence<unsigned, I...>) -> std::array<unsigned, sizeof...(I)> { return {get_x_comp(I)...}; }

Using metafunctions instead of arrays:

In some cicumstances, one might not even need arrays. I assume you want to the table to contain consecutive indices from 0 to dim^2-1. If that's the case, table, x_comp and y_comp are only simple compiletime functions with the following attributes:

  • table(i,j) := i*dim + j
  • x_comp(index) := index / dim (integer division)
  • y_comp(index) := index % dim

Depending on if you have C++11 features available, the implementation will be different, but both times without arrays.

Note: the following implementations will assume that the numbers stored in table are consecutive from 0 to dim^2-1. If that is not the case, you'll have to roll your own appropiate function for table and use the above get_x_comp and get_y_comp implementatio

C++11:

template <unsigned dim> //use unsigned to avoid negative numbers!
struct internal {
static constexpr unsigned table(unsigned i, unsigned j) { return i*dim+j; }
static constexpr unsigned x_comp(unsigned index) { return index/dim; }
static constexpr unsigned y_comp(unsigned index) { return index%dim; }
};

You can call these functions like normal functions anywhere, especially anywhere you need compiletime constants. Example: int a[internal<5>::table(2,4)];

C++03:

template <unsigned dim> //use unsigned to avoid negative numbers!
struct internal {
template<unsigned i, unsigned j>
struct table{ static const unsigned value = i*dim+j; };
template<unsigned index>
struct x_comp{ static const unsigned value = index/dim; };
template<unsigned index>
struct y_comp{ static const unsigned value = index%dim; };
};

Using these metafunctions is a bit more clumsy than in C++11, but works as usual with template metafunctions. Same example as above: int a[internal<5>::table<2,4>::value];

Note: This time you can put the (meta-)functions in the header, since they are not non-integral static member variables any more. Also you do not need to restrict the template to small dimensions, since everything will be calculated well for dimensions less than sqrt(numeric_limits<unsigned>::max()).

Template Metaprogramming - I still don't get it :(

Just as factorial is not a realistic example of recursion in non-functional languages, neither is it a realistic example of template metaprogramming. It's just the standard example people reach for when they want to show you recursion.

In writing templates for realistic purposes, such as in everyday libraries, often the template has to adapt what it does depending on the type parameters it is instantiated with. This can get quite complex, as the template effectively chooses what code to generate, conditionally. This is what template metaprogramming is; if the template has to loop (via recursion) and choose between alternatives, it is effectively like a small program that executes during compilation to generate the right code.

Here's a really nice tutorial from the boost documentation pages (actually excerpted from a brilliant book, well worth reading).

http://www.boost.org/doc/libs/1_39_0/libs/mpl/doc/tutorial/representing-dimensions.html

The usage of anonymous enums

Enums don't take up any space and are immutable.

If you used const int color = 1; then you would solve the mutability issue but if someone took the address of color (const int* p = &color;) then space for it would have to be allocated. This may not be a big deal but unless you explicitly want people to be able to take the address of color you might as well prevent it.

Also when declaring a constant field in a class then it will have to be static const (not true for modern C++) and not all compilers support inline initialization of static const members.


Disclaimer: This answer should not be taken as advice to use enum for all numeric constants. You should do what you (or your co-workers) think is more readable. The answer just lists some reasons one might prefer to use an enum.



Related Topics



Leave a reply



Submit