Question

I'm currently facing a problem I haven't been able to solve myself. Basically what I'm trying to do is implement some linq-like behaviour in C++.

I'll start off with the code in my header:

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
}

And this is how I'd like it to be instantiated:

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

Needless to say this doesn't work (otherwist I wouldn't be here :) )

Now the even stranger part is that even this doesn't seem to work:

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

As you can see (by looking at the select function), it is important to me to not just use something like this:

template<typename T> class queryable;

Any suggestions on how to solve this? And is this even possible?

Any help would be appreciated!

EDIT: the errors I'm getting:

../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

EDIT 2:

As far as I understand the compiler is complaining about C not taking 2 class arguments, but 1 class argument and 1 templated class argument (1), because I defined C to be that way. Is there any way to resolve this issue?

Was it helpful?

Solution

There is a general method to 'explode' a type to test if it was created by a template, and to extract the types that were passed to that template. It is also possible to access the template itself and pass other parameters to it if you desire.

vector is a class template. When you apply parameters to it, you get something like vector<int>, which is a template class. A template class is a specific type, like any other type, it just happens to have been created via a class template.

The goal is, given a type T, to test if it is a template class, and if so to gain access to the class template that was used to create it, and also to access the parameters that were passed to the class template. In this sample, I just test for whether something is a one-arg or two-arg template, but the technique can easily be extended.

(Technically, vector is a two-arg template. There is a default for the second parameter, so vector<int> is actually vector<int, allocator<int> >, but it's still basically a two-arg template, not a one-arg template.)

The best place to start is with this sample code I've put on ideone. I'll copy the Exploder code at the end of this answer.

I begin with

typedef list<int> list_of_ints;

and proceed to use the Exploder template to access all the above information. For example, Exploder<list_of_ints> :: type_1 is the first parameter that was passed to the template, in this case int. The second parameter (this is the defaulted parameter) is allocator<int> and is accessible with Exploder<list_of_ints> :: type_2.

typedef Exploder<list_of_ints> :: type_2  should_be_an_allocator_int;

Given this second type, which we know was created by a template, we can access its parameter type, int, with Exploder< should_be_an_allocator_int > :: type_1, but it's more interesting to actually access the allocator template instead and pass a different parameter to it. This next line evaluates, in this example, to an allocator<double>.

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

So, even if your list<...,...> type did not use the default allocator, you can access the allocator that was used, and also any class template that was used to create the allocator type.

Now that we have a suitable allocator, we can go back to our original template class list<int> and replace int with double:

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

To verify all this has worked, the sample code uses typeid(...).name() to print the actual type of the various objects, along with the correct type that it should be. You can see that they match.

(Also, some templates are such that their parameters are not types, but other class templates, or even other template templates. It should be possible to extract all that, but I'm not going to look into that here.)

(One last interesting technical note. Some types, such as allocator, have something called rebind to allow this sort of access. But the technique used above works for all template classes, even those without their own rebind)

The full code for the template Exploder

See sample code I've put on ideone for a full demo.

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

OTHER TIPS

The second template parameter of the standard containers (the allocator) is a type, not a template, so you need to change your third parameter to something like

template<typename, typename> class C

(Note that the default arguments in your template parameter specifications don't serve any purpose, so I omitted them here.)

Then you should be able to instantiate the template as

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

You may be better off just parametrising over the container type, and then using its value_type and allocator_type definitions:

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

(One downside is that you can't directly access the allocator template; however, you can use the allocator type's nested rebind definition if you need to instantiate that template for other types.)

Also, typedef iterator const const_iterator; is wrong; that declares an unmodifiable iterator that can be used to modify the sequence, while const_iterator is supposed to be a modifiable iterator that can't be used to modify the sequence.

(A note on terminology. vector is a class template, i.e. without any parameters. And vector<int> is a template class, i.e. a class almost like any other except that it was created by a template.)

It is possible to use it as you wish, where queryable takes one template parameter:

queryable< vector<int> > q;

This means that querable is a template with just one parameter:

template <typename T>
struct queryable;

You then use a specialization that has more than one parameter:

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

The last two lines, commented out, show how you can use ContainerTemplate as a class template, creating other types as required. ContainerTemplate is vector or list or set or something like that.

As @MikeSeymour has pointed out, the use of rebind might be the way to access the allocator class template.

Sample code on ideone

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top