arz
arz

Reputation: 33

How to specialize template for containers and enums

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?

Solution:

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

Answers (1)

Sam Varshavchik
Sam Varshavchik

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

Related Questions