Reputation: 33
I am trying to specialize a simple functionality for enum types and stl container types. The SFINAE idea is working for enum using enable_if, however similar technique for stl container doesn't work (I know that relying on existence of value_type and assuming it container is not a very good idea, but that is not the point here).
template <typename T, typename = void>
struct wrapper {
static T getValue()
{
std::cout<<"\n WRAPPER DEFAULT VERSION IS CALLED.\n";
return T();
}
};
template<typename T>
struct wrapper<T,typename std::enable_if<std::is_enum<T>::value>::type>{
static T getValue()
{
std::cout<<"\n WRAPPER ENUM VERSION IS CALLED.\n";
return T();
}
};
template<typename Container>
struct wrapper<Container, typename Container::value_type> {
static Container getValue()
{
std::cout<<"\n WRAPPER CONTAINER VERSION IS CALLED.\n";
return Container();
}
};
int main()
{
//En is an enum type
En en = (En) wrapper<En>::getValue(); //Prints ENUM VERSION
std::vector<int> vec;
vec = wrapper<std::vector<int>>::getValue(); //Prints DEFAULT VERSION
}
Please let me know why second call goes to default implementation?
Thanks to Sam Varshavchik, I figured out I was missing the point that second parameter should resolve to void (as it does in case of enable_if::type), or else I would have to explicitly pass second parameter to make my call resolve to container version:
wrapper<std::vector<int>, int>::getValue();
In order for original version to work (prior to C++ 17 where void_t is not available), I am creating my own type trait relying on the fact that containers have iterator type defined for them:
template <typename T1, typename T2 = void>
struct container_trait
{};
template <typename T1>
struct container_trait<T1, typename T1::iterator> {
typedef void type;
};
and now my container version of wrapper becomes:
template<typename Container>
struct wrapper<Container, typename container_trait<Container, typename Container::iterator>::type> {
static Container getValue(const rapidjson::Value& rjv)
{
std::cout<<"\n WRAPPER CONTAINER VERSION IS CALLED.\n";
return Container();
}
};
And now the same call works perfectly fine:
vec = wrapper<std::vector<int>>::getValue(); //Prints CONTAINER VERSION
Upvotes: 2
Views: 210
Reputation: 118445
The reason that the second call to the default implementation is very simple.
Simply work out by hand how the parameters get deduced for the container version template's parameters:
template<typename Container>
struct wrapper<Container, typename Container::value_type>
You are instantiating the following template:
wrapper<std::vector<int>>
So:
1) Container
is a std::vector<int>
2) Container::value_type
is int
Therefore this specialization becomes:
struct wrapper<std::vector<int>, int>
However you are invoking only:
wrapper<std::vector<int>, void>
because void
is the default value for the second template parameter, so this matches the wrong specialization.
The solution is very simple, the container specialization should simply be:
#include <type_traits>
template<typename Container>
struct wrapper<Container, std::void_t<typename Container::value_type>> {
std::void_t
is C++17, there are other question on stackoverflow.com that explain how to implement it for earlier C++ standards. Complete example:
#include <vector>
#include <iostream>
#include <type_traits>
enum En {};
template <typename T, typename = void>
struct wrapper {
static T getValue()
{
std::cout<<"\n WRAPPER DEFAULT VERSION IS CALLED.\n";
return T();
}
};
template<typename T>
struct wrapper<T,typename std::enable_if<std::is_enum<T>::value>::type>{
static T getValue()
{
std::cout<<"\n WRAPPER ENUM VERSION IS CALLED.\n";
return T();
}
};
template<typename Container>
struct wrapper<Container, std::void_t<typename Container::value_type>> {
static Container getValue()
{
std::cout<<"\n WRAPPER CONTAINER VERSION IS CALLED.\n";
return Container();
}
};
int main()
{
//En is an enum type
En en = (En) wrapper<En>::getValue(); //Prints ENUM VERSION
std::vector<int> vec;
vec = wrapper<std::vector<int>>::getValue(); //Prints DEFAULT VERSION
}
Result:
WRAPPER ENUM VERSION IS CALLED.
WRAPPER CONTAINER VERSION IS CALLED.
Upvotes: 2