tmaric
tmaric

Reputation: 5477

Type based tag dispatching: Is it possible to differently tag containers based on the tags of their elements?

Pleases disregard the row of warnings because of unused variables, the algorithms are dummy example functions.

Also, sorry for the lengthy post, I tried to shorten it as much as possible.

The following tag types and the tag structure are given:

namespace tags {

    struct ordinary_tag{}; 
    struct special_tag {}; 
    struct extra_special_tag {};

    struct ordinary_collection_tag {}; 
    struct special_collection_tag {}; 

    template<typename Type>
    struct tag 
    {
        typedef void type; 
    }; 

}

and the concrete classes used for algorithm arguments:

class concrete_one {}; 
class concrete_two {}; 

The implementation namespace stores implementations of an algorithm algorithm, based on the type of the algorithm result, which can be any type, that is tagged with a specific tag. The tag of the results determines the algorithm chosen:

namespace implementation {

template<typename Result, typename Tag>
struct algorithm {};

template<typename Result> 
struct algorithm<Result, tags::ordinary_tag>
{
    static Result apply(concrete_one const & a1, concrete_two const & a2)
    {
        Result r; 

        std::cout << "ordinary" << std::endl;
        // Modify r using a1, a2. 

        return r; 
    } 

    // Commutative algorithm. 
    static Result apply(concrete_two const & a1, concrete_one const & a2)
    {
        return apply(a2, a1); 
    }
};

template<typename Result> 
struct algorithm<Result, tags::special_tag>
{
    static Result apply(concrete_one const & a1, concrete_two const & a2)
    {
        Result r; 

        std::cout << "special" << std::endl;
        // Modify r using a1, a2.

        return r; 
    } 
};
...

and the algorithms are also tagged for collections of tagged element types, like for example when the Result is tagged as a collection of ordinary types:

template<typename Result>
struct algorithm<Result, tags::ordinary_collection_tag>
{
    static Result apply(concrete_one const & a1, concrete_two const & a2)
    {
        Result r; 

        std::cout << "ordinary collection" << std::endl;

        // Modify r using a1, a2.

        return r; 
    } 
};

The algorithms from the implementation namespace are dispatched by a function template that uses variadic arguments:

template<typename Result, typename ... Arguments>
Result algorithm(Arguments ... args)
{
    // Dispatch to the appropriate algorithm based on the result tag
    // and overload within the algorithm structure for the variadic arguments
    return implementation::algorithm<Result, typename tags::tag<Result>::type>::apply(args ...);
}

Some types are defined and tagged differently:

struct first_type {}; 

namespace tags {
    // Make first_type behave as ordinary type.
    template<>
    struct tag<first_type>
    {
        typedef ordinary_tag type; 
    };
}

struct second_type {};  

namespace tags {
    // Make second_type behave as a special type.
    template<>
    struct tag<second_type>
    {
        typedef special_tag type; 
    };
}

and they work perfectly fine as expected:

concrete_one c1; 
concrete_two c2; 

first_type f1 = algorithm<first_type>(c1, c2); 

second_type f2 = algorithm<second_type>(c1, c2); 

but the problem is in a specialization of tag to take into account any container with an allocator, and tag it based on the tag of the container element type. This is what I have tried to do:

namespace tags 
{
    // An attempt to tag all Containers with Allocator of ordinary tagged types using ordinary_collection_tag.
    template 
    <
        typename OrdinaryType,  
        template <typename, typename> class Container, 
        template <typename> class Allocator 
    >
    struct tag
    <
        typename std::enable_if 
        <
            std::is_same<typename tags::tag<OrdinaryType>::type, tags::ordinary_tag>::value, // true if OrdinaryType is tagged with ordinary_tag
            Container<OrdinaryType, Allocator<OrdinaryType>> // Use this as the T argument of enable_if
        >::type // in enable_if specialized for "true" :: typename T type; 
    > 
    {
        typedef ordinary_collection_tag type; 
    };
}

Expecting that enable_if will provide as it's T argument the Container<OrdinaryType, Allocator<OrdinaryType>> if the human-named OrdinaryType really is tagged with ordinary_tag - this is the boolean argument to enable_if that should be provided by is_same. I tried to use the STL containers that hold the first_type tagged as ordinary in the following way:

    typedef std::list<first_type> first_type_list; 
    typedef std::vector<first_type> first_type_vector; 

    first_type_list fl = algorithm<first_type_list>(c1, c2); 
    first_type_vector fv = algorithm<first_type_vector>(c1, c2); 

Instead of recognizing first_type_list/vector as ordinary_collection_tag-ed types, I get the following error:

test-other.cpp:158:12: error: template parameters not used in partial specialization:
     struct tag
test-other.cpp:158:12: error:         ‘OrdinaryType’
test-other.cpp:158:12: error:         ‘template<class, class> class Container’
test-other.cpp:158:12: error:         ‘template<class> class Allocator’

Now, when I don't enable the tag specialization based on the tag of the OrdinaryType, and I specialize it for any OrdinaryType like this:

// Works but doesn't see that OrdinaryType should be tagged with ordinary_tag, 
// std::list<first_type> and std::vector<second_type> are both tagged ordinary_collection_tag. 
//namespace tags 
//{
    //template 
    //<
        //typename OrdinaryType,  
        //template <typename, typename> class Container, 
        //template <typename> class Allocator 
    //>
    //struct tag
    //<
        //Container<OrdinaryType, Allocator<OrdinaryType>>
    //> 
    //{
        //typedef ordinary_collection_tag type; 
    //};
//}; 

then types like std::vector<first_type> and std::list<second_type> get both tagged with ordinary_collection_tag even though second_type is tagged with the special_tag. This is what I have expected.

So, what did I do wrong?

I'm using gcc 4.8.2.

The complete small program can be found here.

Upvotes: 2

Views: 287

Answers (1)

tmaric
tmaric

Reputation: 5477

Since no one answered yet and I have found a possible solution to the problem, I've decided to post it.

Instead of trying to partially specialize tag for any container like I did in the question, I assumed that having a container of elements is a general case. As a result, the tag<Type> template expexts Type to be a collection. If Type does not satisfy this condition, the template deduction chooses another specialization of Type that fits: the one for a single element. The condition is imposed by introducing a collection structure. Any container that has value_type available is now recognized as a collection of tagged elements.

Here is the solution (I just changed the names of types into fruit names, I guess to make it more easy to read):

#include <type_traits>
#include <iostream>
#include <list>
#include <vector>
#include <map>

namespace tags {

    struct apple_tag {}; 
    struct banana_tag {}; 

    struct apple_collection_tag {}; 
    struct banana_collection_tag {}; 

    template<typename Tag>
    struct collection {}; 

    template<>
    struct collection<apple_tag>
    {
        typedef apple_collection_tag type; 
    };

    template<>
    struct collection<banana_tag>
    {
        typedef banana_collection_tag type; 
    };


    template<typename Type>
    struct tag 
    {
        typedef typename collection<typename tag<typename Type::value_type>::type>::type type; 
    }; 

    // Select tags of pairs based on the second type. Used for maps (key, value) pairs.   
    template
    <
        typename First,
        typename Second 
    >
    struct tag<std::pair<First, Second>>
    {
        typedef typename tag<Second>::type type; 
    };
}

struct apple {}; 

namespace tags {
    template<>
    struct tag<apple>
    {
        typedef apple_tag type; 
    };
}

struct banana {}; 

namespace tags {
    template<>
    struct tag<banana>
    {
        typedef banana_tag type; 
    };
}

template<typename Type> 
struct my_container
{
    typedef Type value_type; 
};

namespace implementation {

    template<typename Type, typename Tag>
    struct function {}; 

    template<typename Type>
    struct function<Type, tags::apple_tag> 
    {
        static void apply(Type const& t)
        {
            std::cout << "apple" << std::endl;
        }
    };

    template<typename Type>
    struct function<Type, tags::banana_tag> 
    {
        static void apply(Type const& t)
        {
            std::cout << "banana" << std::endl;
        }
    };

    template<typename Type>
    struct function<Type, tags::apple_collection_tag> 
    {
        static void apply(Type const& t)
        {
            std::cout << "apple collection" << std::endl;
        }
    };

    template<typename Type>
    struct function<Type, tags::banana_collection_tag> 
    {
        static void apply(Type const& t)
        {
            std::cout << "banana collection" << std::endl;
        }
    };
}

// Value tag Dispatcher
template<typename Type>
void function(Type const & t)
{
    implementation::function<Type, typename tags::tag<Type>::type>::apply(t); 
}

int main(int argc, const char *argv[])
{
    typedef std::list<apple> apple_bag; 

    apple_bag abag; 

    function(abag); 

    typedef std::vector<apple> apple_box; 

    apple_box abox; 

    function(abox); 

    typedef std::map<int, apple> apple_orchard; 

    apple_orchard ao; 

    function (ao);

    // my_container has value_type, so it can be used as well. 
    typedef my_container<banana> banana_bag; 

    banana_bag bo; 

    function(bo); 

    return 0;
}

That's it. Here's the output:

apple collection
apple collection
apple collection
banana collection

Upvotes: 2

Related Questions