5

我目前面临一个我自己无法解决的问题。基本上我想要做的是在 C++ 中实现一些类似 linq 的行为。

我将从标题中的代码开始:

template<typename T, template<class = T> class A,
         template<class = T, template<class=T> class = A> class C>
class queryable
{
public:
    typedef T value_type;
    typedef A<value_type> allocator_type;
    typedef C<value_type, allocator_type> container_type;    // (1)
    typedef queryable<T, A, C> type;
    queryable(container_type const &) { }
    template<typename _Out> queryable<_Out, A, C> select(/* some delegate */);
    // more methods etc
}

这就是我希望它被实例化的方式:

std::vector<int> my_vec;
queryable<std::vector<int> > q(my_vec);

不用说这不起作用(否则我不会在这里:))

现在更奇怪的是,即使这样似乎也不起作用:

std::vector<int> my_vec;
queryable<int, std::allocator, std::vector> q(my_vec);

正如你所看到的(通过查看 select 函数),对我来说重要的是不要只使用这样的东西:

template<typename T> class queryable;

关于如何解决这个问题的任何建议?这甚至可能吗?

任何帮助,将不胜感激!

编辑:我得到的错误:

../entry.cpp:19:58: error: type/value mismatch at argument 3 in template parameter list for ‘template<class T, template<class> class A, template<class, template<class> class<template-parameter-2-2> > class C> class failproof::collections::queryable’
../entry.cpp:19:58: error:   expected a template of type ‘template<class, template<class> class<template-parameter-2-2> > class C’, got ‘template<class _Tp, class _Alloc> class std::vector’
../entry.cpp:19:61: error: invalid type in declaration before ‘;’ token

编辑2:

据我了解,编译器抱怨 C 不采用 2 个类参数,而是 1 个类参数和 1 个模板化类参数 (1),因为我将 C 定义为那样。有没有办法解决这个问题?

4

3 回答 3

7

有一种通用方法可以“分解”一个类型以测试它是否由模板创建,并提取传递给该模板的类型。如果您愿意,也可以访问模板本身并将其他参数传递给它。

vector是一个类模板。当你对它应用参数时,你会得到类似的东西vector<int>,它是一个模板类模板类是一种特定类型,就像任何其他类型一样,它只是恰好是通过类模板创建的。

目标是,给定一个类型T,测试它是否是模板类,如果是,则访问用于创建它的类模板,并访问传递给类模板的参数。在这个示例中,我只是测试某个东西是单参数模板还是双参数模板,但该技术可以轻松扩展。

(从技术上讲,vector是一个双参数模板。第二个参数有一个默认值,vector<int>实际上也是vector<int, allocator<int> >,但它基本上仍然是一个双参数模板,而不是一个单参数模板。)

最好的起点是我放在 ideone 上的这个示例代码。我将在此答案的末尾复制 Exploder 代码。

我从

typedef list<int> list_of_ints;

并继续使用Exploder模板访问所有上述信息。例如,Exploder<list_of_ints> :: type_1是传递给模板的第一个参数,在本例中为int. 第二个参数(这是默认参数)allocator<int>可以使用Exploder<list_of_ints> :: type_2.

typedef Exploder<list_of_ints> :: type_2  should_be_an_allocator_int;

给定第二种类型,我们知道它是由模板创建的,我们可以访问它的参数类型 , intExploder< should_be_an_allocator_int > :: type_1但更有趣的是实际访问allocator模板并将不同的参数传递给它。在本例中,下一行计算为allocator<double>.

typedef Exploder< should_be_an_allocator_int >
           :: rebind<double> :: type should_be_an_allocator_double;

因此,即使您的list<...,...>类型没有使用默认分配器,您也可以访问使用的分配器,以及用于创建分配器类型的任何类模板。

现在我们有了合适的分配器,我们可以回到原来的模板类 list<int>并替换intdouble

Exploder<list_of_ints> :: rebind<double, should_be_an_allocator_double> :: type

为了验证这一切是否有效,示例代码用于typeid(...).name()打印各种对象的实际类型,以及它应该是的正确类型。你可以看到它们匹配。

(另外,有些模板的参数不是类型,而是其他类模板,甚至其他模板模板。应该可以提取所有这些,但我不打算在这里研究。)

(最后一个有趣的技术说明。某些类型,例如allocator,有一些调用rebind来允许这种访问。但是上面使用的技术适用于所有模板类,即使是那些没有自己的模板类rebind

模板 Exploder 的完整代码

请参阅我在 ideone 上放置的示例代码以获取完整演示。

template <class>
struct Exploder;

template<class T, template<class> class Template>
struct Exploder< Template<T> > {
        static const char * description() { return " One-arg template. Arg 1 is a type "; }
        typedef T type_1;
        template <class V>
        struct rebind {
                typedef Template<V> type;
        };
};
template<class T, class U, template<class,class> class Template>
struct Exploder< Template<T,U> > {
        static const char * description() { return " Two-arg template. All args are types, as opposed to being (unapplied) templates. "; }
        typedef T type_1;
        typedef U type_2;
        template <class V,class W>
        struct rebind {
                typedef Template<V,W> type;
        };
};
template<class S, class T, class U, template<class,class,class> class Template>
struct Exploder< Template<S,T,U> > {
        static const char * description() { return " Three-arg template. All args are types, as opposed to being (unapplied) templates. "; }
        typedef S type_1;
        typedef T type_2;
        typedef U type_3;
};
于 2012-01-18T21:40:32.517 回答
6

标准容器(分配器)的第二个模板参数是type,而不是template,因此您需要将第三个参数更改为类似

template<typename, typename> class C

(请注意,模板参数规范中的默认参数没有任何用途,因此我在这里省略了它们。)

然后你应该能够将模板实例化为

queryable<int, std::allocator, std::vector>

您最好只对容器类型进行参数化,然后使用它的value_typeallocator_type定义:

template <typename C> class queryable
{
public:
    typedef typename C::value_type value_type;
    typedef typename C::allocator_type allocator_type;
    typedef C container_type;
};

(一个缺点是您不能直接访问分配器模板;但是,如果您需要为其他类型实例化该模板,则可以使用分配器类型的嵌套定义。)rebind

还有,typedef iterator const const_iterator;是错的;它声明了一个不可修改的迭代器,可以用来修改序列,而const_iterator应该是一个可修改的迭代器,不能用来修改序列。

于 2012-01-18T13:01:22.147 回答
1

(关于术语的注释。vector是一个类模板,即没有任何参数。并且vector<int>是一个模板类,即除了它是由模板创建的之外,几乎与任何其他类一样。)

可以根据需要使用它,其中 queryable 采用一个模板参数:

queryable< vector<int> > q;

这意味着这querable是一个只有一个参数的模板:

template <typename T>
struct queryable;

然后,您使用具有多个参数的特化:

template <typename ValueType, template<class T,class = allocator<T> > class ContainerTemplate>
struct queryable< ContainerTemplate<Contained> > {
        typedef ValueType value_type;
        typedef ContainerTemplate<ValueType> container_type;
        typedef typename container_type :: allocator_type A;
        // typedef ContainerTemplate <WhateverOtherValueTypeYouWish> ...
        // typedef A :: rebind <SomeOtherType> ...
};

注释掉的最后两行显示了如何将ContainerTemplate其用作类模板,并根据需要创建其他类型。ContainerTemplatevectorlistset或类似的东西。

正如@MikeSeymour 所指出的,使用rebind可能是访问分配器类模板的方式。

ideone上的示例代码

于 2012-01-18T13:20:39.597 回答