24

If I have a normal (weak) enumeration, I can use its enumerated values as non-type template parameters, like so:

enum { Cat, Dog, Horse };

template <int Val, typename T> bool magic(T &t)
{
    return magical_traits<Val>::invoke(t);
}

and call it as: magic<Cat>(t)

as far as I can see, if I have a strongly-typed enumeration and don't want to hard-code the enumeration type, I end up with:

enum class Animal { Cat, Dog, Horse };

template <typename EnumClass, EnumClass EnumVal, typename T> bool magic(T &t)
{
    return magical_traits<EnumVal>::invoke(t);
}

and now I have to write: magic<Animal, Animal::Cat>(t), which seems redundant.

Is there any way to avoid typing out both the enum class and the value, short of

#define MAGIC(E, T) (magic<decltype(E), E>(T));
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
Useless
  • 64,155
  • 6
  • 88
  • 132
  • Why not `template bool magic(T &t)`? Are you going to pass other types of enums to this function? I can't think you are, since `magical_traits<>` only takes one parameter itself, and it wouldn't make sense. – Mooing Duck Feb 22 '12 at 18:38
  • Hmm, I over-simplified the example then: yes, the idea is to handle multiple enum classes without duplication. It's a shame, because it could deduce the enum class fine if I passed the _value_ as a function argument, but not if it's a template parameter. – Useless Feb 22 '12 at 18:59
  • Fact is, this is exactly the point of `enum class`es, to be a different type each, and to be especially distinct from `int` and the likes. – Xeo Feb 22 '12 at 19:31
  • True; it's a shame that deduction won't work for non-type parameters the same as for type parameters here. I can deduce the containing class and member type from a single pointer-to-member argument, but there's no way to do the equivalent for a strongly-typed enumerated argument. – Useless Feb 22 '12 at 19:40
  • 1
    why not just `template bool magic(T &t)` ? – balki Feb 24 '12 at 08:30
  • That works, it's just limited to one enum class. I was originally hoping to make the enum class and value _both_ template parameters (and ideally deduce the class from the value), but because they're interdependent it's not straightforward. – Useless Feb 24 '12 at 09:01

4 Answers4

28

You can do it like this, if you can use C++17

#include <type_traits>

enum class Animal { Cat, Dog, Horse };

template <typename EnumClass, EnumClass EnumVal> 
void magic_impl()
{
    static_assert(std::is_same_v<EnumClass, Animal>);
    static_assert(EnumVal == Animal::Cat);
}

template <auto EnumVal>
void magic()
{
    magic_impl<decltype(EnumVal), EnumVal>();
}

int main()
{
    magic<Animal::Cat>();
}

demo: http://coliru.stacked-crooked.com/a/9ac5095e8434c9da

Sopel
  • 1,179
  • 1
  • 10
  • 15
  • 11
    And it only took 5 years from first asking the question! – Useless Nov 26 '17 at 13:33
  • 1
    @Useless hah, it popped up on the question list because someone else posted an answer and I didn't look at the question date ;D, but I guess some people may find it helpful. – Sopel Nov 26 '17 at 13:46
  • @Sopel: it's very helpful, I haven't even seen the `template ` syntax before! – Violet Giraffe Nov 03 '18 at 15:01
12

I'm sorry, I have to tell you that

It is not possible

Take the macro, put it into a scary named header and protect it from your colleague's cleanup script. Hope for the best.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • 2
    Time for some macro wrappers then ... or maybe I can survive a little repetition so long as I know it isn't just me. – Useless Feb 22 '12 at 18:31
  • 3
    Note for future reference: this answer is correct for C++11 (which is what I asked, and what is tagged). I moved the tick to the currently-accepted answer because it's great news and I wasn't previously aware of it. – Useless Nov 30 '17 at 22:48
4

If you're only interested in the enum's value, and not its type, you should be able to use a constexpr function to convert the value to an integer, avoiding repeating the type name.

enum class Animal { Cat, Dog, Horse };

template <typename T> constexpr int val(T t)
{
    return static_cast<int>(t);
}

template <int Val, typename T> bool magic(T &t)
{
    return magical_traits<Val>::invoke(t);
}

magic<val(Animal::Cat)>(t);

However, as pointed out already by others, if you want to make this depend on the type as well, it will not work.

2

This question has an accepted answer (upvoted).

While refactoring my own code, I figured out a more complete solution:

Step 1: using code I was writing:

template<typename V, typename EnumClass, EnumClass Discriminator>
class strong_type final // type-safe wrapper for input parameters
{
    V value;
public:
    constexpr explicit strong_type(V x): value{x} {}
    constexpr auto get() const { return value; }
};

Step 2: client code:

enum class color { red, green, blue, alpha };

// the part OP was asking about:
template<color C>
using color_channel = strong_type<std::uint8_t, color, C>;

using red = color_channel<color::red>; // single argument here
using green = color_channel<color::green>;
using blue = color_channel<color::blue>;
using alpha = color_channel<color::alpha>;
utnapistim
  • 26,809
  • 3
  • 46
  • 82