7

I have a problem where I need to detect whether a given type is an instance of a known nested type such as std::vector::iterator at compile time. I'd like to create the type trait is_std_vector_iterator:

#include <type_traits>
#include <vector>

template<typename T> struct is_std_vector_iterator : std::false_type {};

template<typename T, typename Allocator>
  struct is_std_vector_iterator<typename std::vector<T,Allocator>::iterator>
    : std::true_type
{};

int main()
{
  return 0;
}

But I receive the compiler error:

$ g++ -std=c++0x test.cpp 
test.cpp:7: error: template parameters not used in partial specialization:
test.cpp:7: error:         ‘T’
test.cpp:7: error:         ‘Allocator’

Is it possible to check for a dependent type like std::vector<T,Allocator>::iterator?


Here's a motivating use case of such a trait:

template<typename Iterator>
Iterator my_copy(Iterator first, Iterator last, Iterator result, std::true_type)
{
  // iterators are just pointer wrappers; call memcpy
  memcpy(&*result, &*first, sizeof(typename Iterator::value_type) * last - first);
  return result + last - first;
}

template<typename Iterator>
Iterator my_copy(Iterator first, Iterator last, Iterator result, std::false_type)
{
  // use a general copy
  return std::copy(first, last, result);
}

template<typename Iterator>
Iterator my_copy(Iterator first, Iterator last, Iterator result)
{
  // dispatch based on the type of Iterator
  return my_copy(first, last, result, typename is_std_vector_iterator<Iterator1>::type())
}
Jared Hoberock
  • 10,654
  • 2
  • 33
  • 74
  • A std::vector iterator can in principle be the same type as iterators for some other collection type, and it can in principle be a different type for each concrete std::vector instantiation. Thus, in spite of all the perpetuum mobile answers that you've got, there is no way in the formal, and not even in the in-practice. It is not surprising that optimization is a motivating case. It is probably premature optimization. But if you really want it, then you just have to *pass the knowledge* that's needed, to the operations. – Cheers and hth. - Alf Nov 12 '11 at 01:13
  • @Alf Thanks for your observation. I disagree that this is premature optimization; gcc performs a similar lowering inside its STL. – Jared Hoberock Nov 12 '11 at 01:26
  • @JaredHoberock: You are right, gcc does. It does it by checking `random_iterator_tag` as in my example (`template struct __copy_move<_ismove random_access_iterator_tag="" true="">`.. in stl_algobase.h of gcc 4.6.1 that I have). –  Nov 12 '11 at 01:42
  • @Jared: the observation that the implementation already does the optimization is not an argument against the idea that it's a premature optimization. it's an argument in favor of it's being premature optimization (when the programmer does it). at least for that implementation. – Cheers and hth. - Alf Nov 12 '11 at 01:48

3 Answers3

3

Well, in the simplest case scenario it could look something like this:

#include <type_traits>
#include <vector>
#include <list>
#include <cstdio>

template <typename T>
typename std::enable_if<
    std::is_same<typename std::vector<typename T::value_type>::iterator, T>::value
    , void>::type
do_something (T begin, T end)
{
    std::printf ("Got vector iterators!\n");
}

template <typename T>
typename std::enable_if<
    !std::is_same<typename std::vector<typename T::value_type>::iterator, T>::value
    , void>::type
do_something (T begin, T end)
{
    std::printf ("Got something other than vector iterators!\n");
}

template <typename T>
typename std::enable_if<std::is_pod<T>::value, void>::type
do_something (T begin, T end)
{
    std::printf ("Got some POD iterators!\n");
}

int main()
{
    std::vector<int> ivec;
    std::list<int> ilist;
    char cdata[64];

    do_something (ivec.begin (), ivec.end ());
    do_something (ilist.begin (), ilist.end ());
    do_something (&cdata[0], cdata + 32);

    return 0;
}

But the real problem comes when someone decides to use allocator different from default one. Since you want to check iterator against some well known type, not a well known template, then you can basically use this and possibly extend it with some allocators that you are aware of. Otherwise, a template instantiated with different types is a different type, and I am not sure if there is a way to test if a type is an instance of template specialized with some arbitrary parameter, there is probably no such way.

On the other hand, you may solve this problem differently. For example, what difference it makes whether this is std::vector<...> iterator or not? It might make sense to check whether it is random access or not, etc.

UPDATE:

For continuously laid out memory, I'd say the best bet is to use iterator traits and check for random access tag. For example:

#include <type_traits>
#include <functional>
#include <vector>
#include <list>
#include <cstdio>

template <typename T>
struct is_random_access_iterator : std::is_same<
    typename std::iterator_traits<T>::iterator_category
    , std::random_access_iterator_tag>
{};

template <typename T>
typename std::enable_if<is_random_access_iterator<T>::value>::type
do_something (T begin, T end)
{
    std::printf ("Random access granted!\n");
}

template <typename T>
typename std::enable_if<!is_random_access_iterator<T>::value>::type
do_something (T begin, T end)
{
    std::printf ("No random access for us today!\n");
}

int main()
{
    std::vector<int> ivec;
    std::list<int> ilist;
    char cdata[32];

    do_something (ivec.begin (), ivec.end ());
    do_something (ilist.begin (), ilist.end ());
    do_something (&cdata[0], cdata + sizeof (cdata) / sizeof (cdata[0]));

    return 0;
}

This one will be definitely simpler and even more solid than checking against std::vector with allocators. However, even in this case someone can fool you if they really want, buy providing you random access iterator that provides seamless access to different chunks of memory, and you will have big problems once you convert that into a pointer use pointer arithmetics rather than iterator's overloaded operators. You can protect yourself against that only by comparing memory addresses while changing both raw pointer and iterator, but there is no juice.

Hope it helps.

  • Thanks. You are correct, what I'm really interested in is whether the iterator is a pointer to a contiguous range of memory. ```std::vector::iterator``` with the default allocator is a good approximation. – Jared Hoberock Nov 12 '11 at 01:00
  • 1
    But `is_same` isn't going to do the trick. Imagine this: `struct A { typedef int foo; }; struct B { typedef int foo; };` Now you're getting an `int`. Can you tell whether we have an `A::foo` or a `B::foo`? – Kerrek SB Nov 12 '11 at 01:07
  • @JaredHoberock: I've updated my answer accordingly. But be careful with this thing. After all, you can never know for sure if memory is continuous. Will give you a hardcore cases - OS can use virtual memory to map it into many chunks of physical memory, splitting by page size for example. So you never know. –  Nov 12 '11 at 01:17
  • @KerrekSB: In this case you cannot but on the other hand you don't really need to. As long as iterator type matches, their behavior match. And thus if there is some other template providing exactly the same iterator as std::vector, it would be random access one. The thing is that there is a little connection between an iterator and a container, and it is like you said - int can be a member of any class, so you can never tell whether it comes from A or B, because there is not enough information to make a judgement. –  Nov 12 '11 at 01:20
  • @VladLazarenko: Wait, what? "As long as the iterator type matches, the behavior matches"?? How do you know that? That's a totally wild assumption! The question was "how do I know that this type is the typedef from a `std::vector`, and not "is this type the same as some other type". I'm afraid that the very point of this is that you simply cannot know! (And thus you cannot draw any of the conclusions the OP wants to.) – Kerrek SB Nov 12 '11 at 01:23
  • @KerrekSB: Do you think two types, that are the same, could have different behavior? Hm.. could you give an example? I was always under impression that two types that are exactly the same cannot have a different behavior. Now, exclude polymorphism from this statement, there it get a bit more complicated. –  Nov 12 '11 at 01:27
  • @VladLazarenko: But the types just carry some information about their own content, not about where they come from. So you could have two integers, but one is a phone number and one is your ex-wife's bank account number. It's OK to transmit money to the former (perhaps with no effect), but not to the latter. The integer type itself doesn't know this, though. Same with the iterators; they don't *know* that the elements to which they dereference are contiguously laid out in memory. Even if both iterator types are raw pointers! – Kerrek SB Nov 12 '11 at 01:36
  • @Vlad - A std::deque has random access iterators, but the underlying memory is not continuous. – Bo Persson Nov 12 '11 at 09:57
  • @BoPersson: Good point. It seems like OP wants to provide API that seamlessly copies elements pointed by iterators into GPU memory and optimize it with memcpy-like GPU call. So dequeue, say, won't work. On the other hands, why would want to copy deque into GPU? I'm a bit low level guy and will certainly prefer using `cudeMemcpy` myself, for example. IDK... –  Nov 12 '11 at 15:44
1

You should look at the is_container_helper typetrait from the pretty printer. In the more refined public version of that library, I call the typetrait has_const_iterator (e.g. here):

template<typename T>
struct has_const_iterator
{
private:
    typedef char                      yes;
    typedef struct { char array[2]; } no;

    template<typename C> static yes test(typename C::const_iterator*);
    template<typename C> static no  test(...);
public:
    static const bool value = sizeof(test<T>(0)) == sizeof(yes);
    typedef T type;
};
Community
  • 1
  • 1
Kerrek SB
  • 428,875
  • 83
  • 813
  • 1,025
  • Thanks for the pointer. I believe my use case is a bit different, though. I don't have access to the name of the container from whence the iterator came; only the iterator itself. I've updated the OP to make it more clear what I'm after. – Jared Hoberock Nov 12 '11 at 00:40
  • Could you please write a one-line example of how you want your final solution to be used? (I.e. pretend that the solution existed, and write a usage example.) – Kerrek SB Nov 12 '11 at 00:42
  • Independent of the trait question, your proposition is insane: using `memcpy` on non-POD types produces flat-out undefined behaviour. What good would such a thing be? – Kerrek SB Nov 12 '11 at 00:52
  • I'd guard on ```is_pod``` as well; I left it out for brevity. – Jared Hoberock Nov 12 '11 at 00:53
  • OK. First off, check out how the standard library handles this (e.g. `bits/stl_algobase.h` in GCC). Iterators famously don't know about their origin *container* type. However, the do have a *category* tag. The standard library uses `memmove` when the iterator category is "random access" and the types are memmovable. – Kerrek SB Nov 12 '11 at 00:57
  • @KerrekSB: really? Relying on random access category wouldn't work, because nothing guarantees the alignment of the objects in memory. Eg. `std::deque`'s iterators are random access, yet you cannot memove the data between two iterators. – jpalecek Nov 12 '11 at 01:09
  • @jpalecek: Hm. Random access is just the first step. There's a significantly greater amount of branching in the code, which you're strongly encouraged to read. I think it may be a minimum necessary requirement, though. – Kerrek SB Nov 12 '11 at 01:13
  • The standard iterator categories are famously insufficient for divining this kind of information. ```counting_iterator``` is a simple example of a random access iterator which is not memmovable. – Jared Hoberock Nov 12 '11 at 01:13
  • @JaredHoberock: I'm curious myself now how the GCC library figures out when it's OK (look for `__builtin_memmove`), but there are multiple template boolean parameters which I haven't traced yet. Maybe someone else can shed some light on the "proper" way of ascertaining that such an optimization is permissible. – Kerrek SB Nov 12 '11 at 01:15
  • @Kerrek. Good question. I believe they rely on implementation-specific knowledge and detect something like an instance of ```__gcc_normal_iterator``` which is known to be a pointer wrapper. – Jared Hoberock Nov 12 '11 at 01:17
  • @JaredHoberock: Yes, that would be it. They unwrap `__gnu_cxx::__normal_iterator` and if they get a raw pointer, they `memmove`. – jpalecek Nov 12 '11 at 12:06
1

AFAIK, you can get the value type of an iterator from iterator_traits<Iter>::value_type. Then, you can check that std::vector<that_value_type, Alloc>::iterator is really it (eg. with boost::is_same)

BTW from your motivating example, I can see you'll probably have troubles guessing Alloc - if you don't plan on using custom allocators, you'll just leave it as default. There is no general solution that will work on all Allocs.

jpalecek
  • 44,865
  • 7
  • 95
  • 139