tags:

views:

179

answers:

3

Hello!

I am using templates to implement a range checked conversion from int to enum. It looks like this:

template<typename E>
E enum_cast(const int &source);

The template function placed more or less in the root-directoy of the project. When defining a new enum that is foreseen to be assigned values from a config file like this:

enum ConfigEnum {
    ConfigEnumOption1 = 'A'
  , ConfigEnumOption2 = 'B'
  , ConfigEnumInvalid };

ConfigEnum option = XmlNode.iAttribute("option");

I define a template specialization for this particular enum type in a .cpp-file for the module this enum is used in.

template<>
ConfigEnum enum_cast(const int &source) {
   switch(source) {
   case ConfigEnumOption1 : return ConfigEnumOption1;
   case ConfigEnumOption2 : return ConfigEnumOption2;
   default return ConfigEnumInvalid;
}

Now the assignment of an int to the enum becomes:

ConfigEnum option = enum_cast<ConfigEnum>(XmlNode.iAttribute("option"));

which makes sure that the enum is allways in valid range. Note that I have not allways control over these enums so this seems to a be reasonable and easily configureable solution.

Anyway, this works all very well (although I am not shure all code given here is correct because I just recall it from memory right now)

The problem stems from the fact that it might be desireable to use this "enum_cast" construct thorughout the code base whenever an in is assigned to a enum. After all this can be enforced by a simple search-and-replace operation. Of course I do not want to define these specializations for all and every enum around but only for those that need the range check at the moment. I would prefer to add template specializations for the enum types when the need arises and use the assignment operator when no specialization is defined.

Thus:

InternalEnum internal = enum_cast<InternalEnum>(internal_integer);

would effecively call internal = internal_integer. I figure that I need to tell the compiler to use a certain "default" implementation for all enum types that do not have a specialization.

My first bet was giving the original template function an implementation like this:

template<typename E>
E enum_cast(const int &source) {
  E copy = source;
  return copy;
};

Unfortunately now this is allways called instead of the specialiazations given in the .cpp-files deeper into the project directory-tree.

Any thoughts?

Thanks in advance Arne

A: 

This

#include <iostream>

enum e1 { a, b, c, e1_invalid };
enum e2 { x, y, z, e2_invalid };

template<typename E>
E enum_cast(int source)
{
    std::cout << "default implementation\n";
    return static_cast<E>(source);
}

template<>
e2 enum_cast<e2>(int source)
{
    std::cout << "specialization\n";
    switch(source) {
     case x: return x;
     case y: return y;
     case z: return z;
    }
    return e2_invalid;
}

int main(int /*argc*/, char* /*argv*/[])
{
    std::cout << static_cast<int>(enum_cast<e1>(1)) << '\n';
    std::cout << static_cast<int>(enum_cast<e2>(1)) << '\n';
    return 1;
}

Works On My Machine(TM). It prints

default implementation
1
specialization
1
sbi
that is probably because all specializations reside in the same file. In my case the enum definitions and specialiazations are usually in seperate files.I guess the comment by AProgrammer will remedy my problem, though
Arne
@Arne: I guess that's right.
sbi
+4  A: 

The explicit specializations have to be visible everywhere they are used. And as they are definitions, they can't be repeated in every compilation unit. So in the header file where you define an enum you want to check you say

#include "enum_cast.h"
enum Foo { Foo_A, Foo_B, Foo_C };
template<> Foo enum_cast<Foo>(int source);

and in the corresponding .cpp you give the definition.

AProgrammer
thanks for the hint - as usual it's easy once you know the answer ;-)
Arne
+1  A: 

Couldn't you use a traits class to describe each enum:

const int LARGE = 65536;

template<typename>
struct EnumTrait
{
    enum {LOW = -LARGE};
    enum {HIGH = LARGE};
};

template<typename ENUM>
static ENUM enum_cast (int i)
{
    if (i < EnumTrait<ENUM>::LOW || i > EnumTrait<ENUM>::HIGH)
        throw std::runtime_error ("Out of bounds");
    return static_cast<ENUM> (i);
}

enum Colour {RED = 0, GREEN, BLUE};

template<>
struct EnumTrait<Colour>
{
    enum {LOW = RED};
    enum {HIGH = BLUE};
};

enum Location {HERE = 0, THERE, NOWHERE};
// No EnumTraits class.

int main (int argc, char* argv[])
{
    int i = 2;

    Colour c = enum_cast<Colour> (i);
    std::cout << "c=" << c << std::endl;

    Location l = enum_cast<Location> (i);
    std::cout << "l=" << l << std::endl;

    return 0;
}

Normally the enum definition is accompanied by an EnumTraits specialisation. For any enums outside your control then the bounds checking just uses the default traits.

jon hanson
I will consider this approach for future projects but for now I need hit the deadline.. many thanks!
Arne