Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I Implement templated functions whose signatures are only different based on a type id?

Here are some enum classes:

enum class Race : char {AINU, ELF, DWARF, MAN, EAGLE, HOBBIT, ENT, ORC, WIZARD};
enum class Color: char {RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE};
enum class Direction: char{UP, DOWN, LEFT, RIGHT};

I would like to implement an enum_to_string function and a string_to_enum function for each.
It's no problem converting enums into strings, because I can overload the same function name.

std::string to_string(Race const& enum_value);
std::string to_string(Color const& enum_value);
std::string to_string(Direction const& enum_value);

However, you can't overload the same way when converting to enums, because only the return-type would be different. (nor would I want to, because different enums may be represented by the same string.)


Are one of the following ways possible for converting strings into enums?

Race race = to_enum<Race>("elf");
Color color = to_enum<Color>("green");

std::string blah{"up"};
Direction dir{to_enum<Direction>(blah)};

or possibly:

Race race = to_enum(Race,"elf");
Color color = to_enum(Color,"green");

std::string blah{"up"};
Direction dir{to_enum(Direction,blah)};

Can C++ support one or both of these behaviours?


I'm trying to avoid different function names like this:

Race to_race(std::string const& str);
Color to_color(std::string const& str);
Direction to_direction(std::string const& str);  

Here is the closest thing I could come up with,

template <typename T>struct to_enum{};
template <>
struct to_enum<Color>{

    static Color convert(std::string const& str){
        //match the string with a color enum, and return that color enum
        //(or like a default color enum if the string is garbage or something)
    }
};

and then you call it like this:

Color color = to_enum<Color>::convert("red");

can we get rid of the convert? or possibly implement this one?

Color color = to_enum(Color,"red");
like image 534
Trevor Hickey Avatar asked Jan 14 '13 15:01

Trevor Hickey


3 Answers

Use function template and specialization. These are rare situations where I think function specialization really helps.

template<typename TEnum>
TEnum enum_cast(std::string const & s);

then specialize it for each enum type. I changed the name, so you can read it like cast:

Color color = enum_cast<Color>("blue");

Or parse is also a good name. Choose whatever you think is good. I would personally choose either parse or enum_cast.

Now for to_string, you can define overloads (which you already have):

std::string to_string(Color c);
std::string to_string(Race r);
std::string to_string(Direction d);

Don't use template and specialization for to_string. Overloads are indeed good in general.

like image 126
Nawaz Avatar answered Nov 19 '22 02:11

Nawaz


and then you call it like this:

Color color = to_enum<Color>::convert("red");

can we get rid of the convert?

Sure thing! Simply encapsulate the call:

template <typename Enum>
Enum to_enum(std::string const& from) {
    return to_enum_helper<Enum>::convert(from);
}

Where to_enum_helper is the struct you defined previously. (EDIT: Or you specialise the function directly as shown in Nawaz’ answer.)

Alternatively, you can use overloading instead of specialisation if that’s your cup of tea:

Race to_enum_helper(std::string const& from, Race) {
    // Implement for enum class Race
}

Color to_enum_helper(std::string const& from, Color) {
    // Implement for enum class Color
}

template <typename Enum>
Enum to_enum(std::string const& from) {
    return to_enum_helper(from, Enum());
}

Here, the second argument to to_enum_helper is simply used to distinguish the different overloads.

like image 29
Konrad Rudolph Avatar answered Nov 19 '22 03:11

Konrad Rudolph


However, you can't overload the same way when converting to enums, because only the return-type would be different.

You could overload conversion operators to write overload functions that differ only by return type:

#include <string>
#include <iostream>

enum Race {};
enum Color {};

struct to_one_of_them {
    operator Race  () const { std::cout << "to Race()\n"; return Race(); }
    operator Color () const { std::cout << "to Color()\n"; return Color(); }

    to_one_of_them() = delete;

private:
    std::string str;
    to_one_of_them(std::string const &str) : str(str) {}

    friend to_one_of_them to_enum(std::string const&);
};

to_one_of_them to_enum(std::string const &str) {
    return {str};
}


int main () {
    Race  r = to_enum("meh");
    Color c = to_enum("meh");
}

However, this technique is virtually unused and therefore not a C++ idiom and thus probably not worth to learn.

like image 1
Sebastian Mach Avatar answered Nov 19 '22 01:11

Sebastian Mach