Reputation: 2684
Is it possible to overload or specialize class member functions based on given enum values?
enum class Type {
TypeA,
TypeB,
TypeC
};
class Foo {
public:
template <Type t, typename R = std::enable_if_t<t==Type::TypeA, int>>
R get() {
return 1;
}
template <Type t, typename R = std::enable_if_t<t==Type::TypeB, double>>
R get() {
return 2;
}
template <Type t, typename R= std::enable_if_t<t==Type::TypeC, float>>
R get() {
return 3;
}
};
Foo foo;
std::cout << foo.get<Type::TypeA>() << std::endl;
std::cout << foo.get<Type::TypeB>() << std::endl;
std::cout << foo.get<Type::TypeC>() << std::endl;
The compile complain about overloading on above code snippet.
Upvotes: 5
Views: 572
Reputation: 17704
Your example doesn't compile because defaulted type templated parameters are not part of the function signature. So as far as the compiler is concerned, you are defining the same function multiple times, which is illegal. Instead, you need to use a defaulted non-type template parameter.
class Foo {
public:
template <Type t, std::enable_if_t<t==Type::TypeA, int> = 0>
int get() {
return 1;
}
template <Type t, std::enable_if_t<t==Type::TypeB, int> = 0>
double get() {
return 2;
}
template <Type t, std::enable_if_t<t==Type::TypeC, int> = 0>
float get() {
return 3;
}
};
Two working solutions are already posted; so why did I post this one? Well, I like it better. To be concrete:
REQUIRE
or similar; this makes it extremely clear what's going on. Neither of the other two answers posted have this property.max66's approach of mapping the enum to a type is nice and it's something you should be aware of. It's more appropriate though when you can write the body generically; if you have to write out every implementation body separately anyhow, it only adds boilerplate (IMHO). Also you should be aware that a solution based on specialization is fragile; it doesn't work if there's a second template parameter nor if the class Foo
is a class template, because of the restrictions on specialization function templates.
With the macro I mentioned above you could write it this way:
template <Type t, REQUIRE(t==Type::TypeA)>
int get() {
return 1;
}
// ...
Which I think is as good as it gets.
Upvotes: 0
Reputation: 16204
One pretty standard way to fix it is to put the std::enable_if
clause in the return type of the function, rather than in the template parameters like that.
This compiles for me at c++11 standard.
#include <iostream>
#include <type_traits>
enum class Type {
TypeA,
TypeB,
TypeC
};
class Foo {
public:
template <Type t>
typename std::enable_if<t==Type::TypeA, int>::type get() {
return 1;
}
template <Type t>
typename std::enable_if<t==Type::TypeB, double>::type get() {
return 2;
}
template <Type t>
typename std::enable_if<t==Type::TypeC, float>::type get() {
return 3;
}
};
static_assert(std::is_same<int, decltype( std::declval<Foo>().get<Type::TypeA>())>::value, "");
static_assert(std::is_same<double, decltype( std::declval<Foo>().get<Type::TypeB>())>::value, "");
static_assert(std::is_same<float, decltype( std::declval<Foo>().get<Type::TypeC>())>::value, "");
int main() {
Foo foo;
std::cout << foo.get<Type::TypeA>() << std::endl;
std::cout << foo.get<Type::TypeB>() << std::endl;
std::cout << foo.get<Type::TypeC>() << std::endl;
}
I'm not sure if I can explain in detail why this change makes such a difference for the compiler.
However, consider the following. With your version, although you are never actually instantiating get
with two explicit template parameters, technically all three of those member functions templates can "collide" and produce functions with exactly the same name. Because, if you did instantiate get<Type::TypeB, int>
, then it would have the same return type, input parameters, and name as get<Type::TypeA>
. C++ does not support function template specialization, it would make overload resolution rules very complicated. So having function templates with the potential to collide like this can make the compiler very upset.
When you do it the way I showed, there is no possibility that the templates can collide and produce a function with the same name and signature.
Upvotes: 6
Reputation: 66210
You can avoid the use of std::enable_if
mapping the Type
and return types specializing a simple struct (Bar
, in the following example)
#include <iostream>
enum class Type {
TypeA,
TypeB,
TypeC
};
template <Type t>
struct Bar;
template <>
struct Bar<Type::TypeA>
{ using type = int; };
template <>
struct Bar<Type::TypeB>
{ using type = double; };
template <>
struct Bar<Type::TypeC>
{ using type = float; };
class Foo {
public:
template <Type t>
typename Bar<t>::type get();
};
template <>
Bar<Type::TypeA>::type Foo::get<Type::TypeA> ()
{ return 1; }
template <>
Bar<Type::TypeB>::type Foo::get<Type::TypeB> ()
{ return 2.2; }
template <>
Bar<Type::TypeC>::type Foo::get<Type::TypeC> ()
{ return 3.5f; }
int main ()
{
Foo foo;
std::cout << foo.get<Type::TypeA>() << std::endl;
std::cout << foo.get<Type::TypeB>() << std::endl;
std::cout << foo.get<Type::TypeC>() << std::endl;
return 0;
}
Upvotes: 3