基于类型的标签分派:是否可以根据元素的标签对容器进行不同的标记?

时间:2014-03-21 16:56:33

标签: c++ templates typetraits

由于未使用的变量,请忽略警告行,算法是虚拟示例函数。

另外,对于冗长的帖子感到抱歉,我试图尽可能地缩短它。

给出了以下标记类型和标记结构:

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; 
    }; 

}

和用于算法参数的具体类:

class concrete_one {}; 
class concrete_two {}; 

implementation命名空间存储算法algorithm的实现,基于算法结果的类型,可以是任何类型,使用特定标记进行标记。结果的标记决定了所选择的算法:

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; 
    } 
};
...

并且算法也被标记为标记元素类型的集合,例如当Result被标记为普通类型的集合时:

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; 
    } 
};

来自implementation命名空间的算法由使用可变参数的函数模板调度:

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 ...);
}

某些类型的定义和标记方式不同:

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; 
    };
}

他们按预期完美地工作:

concrete_one c1; 
concrete_two c2; 

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

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

但问题在于tag的特化,要考虑具有分配器的任何容器,并根据容器元素类型的标记对其进行标记。这就是我试图做的事情:

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; 
    };
}

如果人名enable_if真的被标记为T,则期望Container<OrdinaryType, Allocator<OrdinaryType>>OrdinaryType ordinary_tag提供enable_if参数 - 这是is_same的布尔参数,应由first_type提供。我尝试使用以下列方式将 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); 标记为普通的STL容器:

first_type_list/vector

我没有将ordinary_collection_tag识别为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’ - ed类型,而是收到以下错误:

tag

现在,当我没有根据OrdinaryType的标记启用OrdinaryType专精时,我会专门针对任何// 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; //}; //}; 这样的专长:

std::vector<first_type>

然后,std::list<second_type>ordinary_collection_tag等类型都会被second_type标记,即使special_tag标有{{1}}。这是我的预期。

那么,我做错了什么?

我正在使用gcc 4.8.2。

可以找到完整的小程序here

1 个答案:

答案 0 :(得分:1)

由于还没有人回答,我找到了问题的可能解决方案,我决定发布它。

我没有像我在问题中那样尝试对tag部分专门化任何容器,而是假设拥有一个元素容器是一般情况。因此,tag<Type>模板将Type视为集合。如果Type不满足此条件,则模板推导选择适合的Type的另一个专门化:单个元素的专门化。通过引入collection结构来强加该条件。任何可用value_type的容器现在都被识别为标记元素的集合。

这是解决方案(我只是将类型的名称更改为水果名称,我想让它更容易阅读):

#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;
}

就是这样。这是输出:

apple collection
apple collection
apple collection
banana collection