3

Is it possible to specialise a template function on an enum?

I've seen noted here a template function can be disabled if it isn't an enum, but is this possible whilst still allowing other types?

My example below shows specialisations for int, float, and enum (it doesn't compile because it tries to overload the enum version rather than specialising it). I feel I'm missing something obvious.

Note that I'm looking to specialise on any enum, not just a named one (EAnEnum in the example)

#include <iostream>

enum class EAnEnum
{
    Alpha,
    Beta,
};

template<typename T>
void MyFunc();

template<>
void MyFunc<int>()
{
    std::cout << "Int" << std::endl;
}

template<>
void MyFunc<float>()
{
    std::cout << "Float" << std::endl;
}

// MyFunc<Enum>
template<typename T>
typename std::enable_if<std::is_enum<T>::value, void>::type MyFunc()
{
    std::cout << "Enum" << std::endl;
}

int main()
{
    MyFunc<EAnEnum>();
    return 0;
}
Community
  • 1
  • 1
c z
  • 7,726
  • 3
  • 46
  • 59
  • You are trying to perform a partial specialization of a function template. There ain't no such thing - you can do full specialization, or overloading. One possible approach - put your enum-specific implementation into a primary template, perhaps with a `static_assert` that the type is in fact an enum. – Igor Tandetnik Aug 14 '16 at 19:29
  • 1
    What actual problem are you trying to solve. No, not this question, but the problem to which you think the answer is this kind of a specialization. – Sam Varshavchik Aug 14 '16 at 19:31
  • 1
    In general function specialization fails to be the best way to solve almost every problem that you try to solve with it. In short, there is going to be a better way. Solutions end up brittle and awkward. Describe real problem, solve it another way. – Yakk - Adam Nevraumont Aug 14 '16 at 20:05
  • I'm using the `>>` operator on a stream, I want to specialise it to read enums differently from ints in the file without having to break out from the `my_stream >> x >> y >> z;` pattern, as a `ReadInt()`, `ReadEnum()` pattern would require. I don't know the types of the enums in advance. I agree there is probably a better solution but the question still stands as a thought exercise :) – c z Aug 14 '16 at 21:03

2 Answers2

3

You cannot partially specialize a function, but you can use tag dispatching instead.
It follows a minimal, working example based on the OP's question:

#include <iostream>
#include<type_traits>

enum class EAnEnum
{
    Alpha,
    Beta,
};

template<typename>
struct tag {};

void MyFunc(tag<int>)
{
    std::cout << "Int" << std::endl;
}

void MyFunc(tag<float>)
{
    std::cout << "Float" << std::endl;
}

void MyFunc(tag<EAnEnum>)
{
    std::cout << "Enum" << std::endl;
}

template<typename T>
void MyFunc() {
    MyFunc(tag<std::decay_t<T>>{});
}

int main()
{
    MyFunc<EAnEnum>();
    return 0;
}

You can easily add a parameter pack to be forwarded to the right MyFunc and still use this technique to solve your problem.
Of course, you can now specialize for any enum.
You can also provide a fallback MyFunc as:

template<typename T>
void MyFunc(tag<T>)
{
    std::cout << "Fallback" << std::endl;
}

If you want a fallback for all the possible enum types, you can now rely on SFINAE, for these are different overloaded functions:

template<typename T>
std::enable_if_t<std::is_enum<T>::value>
MyFunc(tag<T>)
{
    std::cout << "Fallback for enums only" << std::endl;
}

Note that you should not use directly the implications of MyFunc that accept a tag specialization as an entry point.
Those are meant as internal functions.
Use instead the generic one, as shown in the example.

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • Doesn't this still leave me with needing to state the name of my enum explicitly, as in `tag)`? – c z Aug 14 '16 at 20:58
  • @cz For those are overloaded functions, you can now use _sfinae_ on your `fallbacks` if needed. Let me edit the question to clarify this point. – skypjack Aug 14 '16 at 21:05
2

You cannot partially specialize a function template, but can you just let it forward to a class template.

Since your function doesn't have arguments that's particularly easy:

#include <iostream>
#include <type_traits>

namespace impl {
    using namespace std;

    template< class Type, bool is_enum_ = is_enum<Type>::value >
    struct Foo;

    template< class Type >
    struct Foo<Type, true>
    { void func() { cout << "Enum" << endl; } };

    template<>
    struct Foo<int>
    { void func() { cout << "Int" << endl; } };

    template<>
    struct Foo<float>
    { void func() { cout << "Float" << endl; } };

}  // namespace impl

template< class Type >
void foo()
{ impl::Foo<Type>().func(); }

auto main()
    -> int
{
    enum class An_enum
    {
        alpha, beta,
    };

    foo<An_enum>();
    foo<int>();
    foo<float>();
    #ifdef TEST
        foo<char>();    //! Doesn't compile.
    #endif
}

With arguments you can use “perfect forwarding” (which isn't all that perfect, really, but usually good enough) via std::forward.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331