Reputation: 5477
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
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