【问题标题】:Type based tag dispatching: Is it possible to differently tag containers based on the tags of their elements?基于类型的标签调度:是否可以根据容器元素的标签对容器进行不同的标签?
【发布时间】:2014-03-21 16:56:32
【问题描述】:

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

另外,很抱歉这篇文章太长了,我尽量缩短它。

给出以下标签类型和标签结构:

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 参数,如果人名OrdinaryType 确实被标记为ordinary_tag - 这是enable_if 的布尔参数,应该由is_same。我尝试通过以下方式使用将first_type 标记为普通的 STL 容器:

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

我没有将first_type_list/vector 识别为ordinary_collection_tag-ed 类型,而是收到以下错误:

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’

现在,当我不启用基于OrdinaryType 标记的tag 专业化时,我将它专门用于任何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&lt;first_type&gt;std::list&lt;second_type&gt; 这样的类型都被标记为ordinary_collection_tag,即使second_type 被标记为special_tag。这是我所期待的。

那么,我做错了什么?

我使用的是 gcc 4.8.2。

完整的小程序可以在here找到。

【问题讨论】:

  • 编译器产生相当准确的诊断:struct tag 的这种“专业化”并没有真正使用它的模板参数。
  • @Constructor,是的,这就是我的想法,但问题仍然存在:我如何将tag 专门用于所有包含标记元素的容器?现在我正在尝试使用value_type 来做到这一点,并依靠模板推导机制,但我仍然失败了..

标签: c++ templates typetraits


【解决方案1】:

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

我没有像我在问题中那样尝试将tag 部分专门化为任何容器,而是假设拥有一个元素容器是一般情况。因此,tag&lt;Type&gt; 模板将 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多