HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMajor

Conversion between enum and string in C++ class header

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
conversionheaderenumbetweenandclassstring

Problem

I have the following redundant-feeling design to convert between enums and strings regarding a class that stores enums. The approach doesn't scale if there are more enums and in any event, less-redundant code is also better.

Questions

-
If there will be more enums, would it be possible to avoid defining two explicit conversion functions per enum type and device a system where the caller sees just one (i.e. convert) or two different function names (i.e. convertto/convertfrom for all the enums, not just per enum type)? Perhaps using some kind deduction magic with auto and decltype? It looks like ambiguity sets in since only the return value can be used to separate the different functions overloads (even if done with function templates).

-
Is the following design of separating the conversion functions and putting them to an anonymous namespace good design (I've thought about putting the conversion functions to a file, say conversions.incl and including it)?

The idea would be make the multiple (i.e. more enums than the one presented here) conversions as implicit as possible

The conversions would be used like this:

random.cpp

string token_string = "none"; //In reality this will be externally, user, generated.
some_class_instance->set_type(enum_conversion(token_string));
token_string = enum_conversion(some_class_instance->get_type());


And to present one enum and related conversions (but there could be more):

some_class.h

```
class some_class
{
public:
enum class enum_type
{
none = 0,
type1 = 1,
type2 = 2
}

void set_type(enum_type);
enum_type get_type() const;

private:
enum_type type_;
};

namespace
{
std::array, 3> type_map;

bool initialize_map()
{
type_map[0] = std::make_pair("none", some_class::enum_type::none);
type_map[1] = std::make_pair("type1", some_class::enum_type::type1);
type_map[2] = std

Solution

I would use some template logic to achieve the affect in a more scalable way:

#include 
#include 
#include 
#include 

// This is the type that will hold all the strings.
// Each enumeration type will declare its own specialization.
// Any enum that does not have a specialization will generate a compiler error
// indicating that there is no definition of this variable (as there should be
// be no definition of a generic version).
template
struct enumStrings
{
    static char const* data[];
};

// This is a utility type.
// Created automatically. Should not be used directly.
template
struct enumRefHolder
{
    T& enumVal;
    enumRefHolder(T& enumVal): enumVal(enumVal) {}
};
template
struct enumConstRefHolder
{
    T const& enumVal;
    enumConstRefHolder(T const& enumVal): enumVal(enumVal) {}
};

// The next two functions do the actual work of reading/writing an
// enum as a string.
template
std::ostream& operator const& data)
{
   return str ::data[data.enumVal];
}

template
std::istream& operator>>(std::istream& str, enumRefHolder const& data)
{
    std::string value;
    str >> value;

    // These two can be made easier to read in C++11
    // using std::begin() and std::end()
    //  
    static auto begin  = std::begin(enumStrings::data);
    static auto end    = std::end(enumStrings::data);

    auto find   = std::find(begin, end, value);
    if (find != end)
    {   
        data.enumVal = static_cast(std::distance(begin, find));
    }   
    return str;
}

// This is the public interface:
// use the ability of function to deduce their template type without
// being explicitly told to create the correct type of enumRefHolder
template
enumConstRefHolder  enumToString(T const& e) {return enumConstRefHolder(e);}

template
enumRefHolder       enumFromString(T& e)     {return enumRefHolder(e);}


Then you can use it like this:

// Define Enum Like this
enum X {Hi, Lo};
// Then you just need to define their string values.
template<> char const* enumStrings::data[] = {"Hi", "Lo"};

int main()
{
    X   a=Hi;

    std::cout > enumFromString(a);

    std::cout << "A: " << a << " : " << enumToString(a) << "\n";
}

Code Snippets

#include <iostream>
#include <sstream>
#include <string>
#include <algorithm>

// This is the type that will hold all the strings.
// Each enumeration type will declare its own specialization.
// Any enum that does not have a specialization will generate a compiler error
// indicating that there is no definition of this variable (as there should be
// be no definition of a generic version).
template<typename T>
struct enumStrings
{
    static char const* data[];
};

// This is a utility type.
// Created automatically. Should not be used directly.
template<typename T>
struct enumRefHolder
{
    T& enumVal;
    enumRefHolder(T& enumVal): enumVal(enumVal) {}
};
template<typename T>
struct enumConstRefHolder
{
    T const& enumVal;
    enumConstRefHolder(T const& enumVal): enumVal(enumVal) {}
};

// The next two functions do the actual work of reading/writing an
// enum as a string.
template<typename T>
std::ostream& operator<<(std::ostream& str, enumConstRefHolder<T> const& data)
{
   return str << enumStrings<T>::data[data.enumVal];
}

template<typename T>
std::istream& operator>>(std::istream& str, enumRefHolder<T> const& data)
{
    std::string value;
    str >> value;

    // These two can be made easier to read in C++11
    // using std::begin() and std::end()
    //  
    static auto begin  = std::begin(enumStrings<T>::data);
    static auto end    = std::end(enumStrings<T>::data);

    auto find   = std::find(begin, end, value);
    if (find != end)
    {   
        data.enumVal = static_cast<T>(std::distance(begin, find));
    }   
    return str;
}


// This is the public interface:
// use the ability of function to deduce their template type without
// being explicitly told to create the correct type of enumRefHolder<T>
template<typename T>
enumConstRefHolder<T>  enumToString(T const& e) {return enumConstRefHolder<T>(e);}

template<typename T>
enumRefHolder<T>       enumFromString(T& e)     {return enumRefHolder<T>(e);}
// Define Enum Like this
enum X {Hi, Lo};
// Then you just need to define their string values.
template<> char const* enumStrings<X>::data[] = {"Hi", "Lo"};

int main()
{
    X   a=Hi;

    std::cout << enumToString(a) << "\n";

    std::stringstream line("Lo");
    line >> enumFromString(a);

    std::cout << "A: " << a << " : " << enumToString(a) << "\n";
}

Context

StackExchange Code Review Q#14309, answer score: 26

Revisions (0)

No revisions yet.