12

I have written a simple C++11 style stateful allocator type. Given

template<typename T> class my_allocator {
   // the usual stuff
};

template<typename T> using my_vector = std::vector<T, my_allocator<T>>;

my_vector<int> x;

std::vector<int> y = x; // error

What is the best way to allow conversions from a my_vector to a std::vector using the default allocator? GCC 4.7 (recent svn) says

error: conversion from 'my_vector<int> {aka std::vector<int, my_allocator<int>>}' to non-scalar type 'std::vector<int>' requested

Obviously this could be done with, say, a simple conversion function such as

template<typename T> std::vector<T> to_std_vec(const my_vector<T>& v)  {
   return std::vector<T>(&v[0], &v[v.size()]);
   }

but this seems pretty inelegant. Is there a better solution in C++11?

Move semantics are right out in this situation, of course, but I'd like copy construction and assignment to work without extra noise/typing.

Jack Lloyd
  • 8,215
  • 2
  • 37
  • 47

3 Answers3

15

You cannot possibly hope to get around the obvious copy: std::vector<int> y(x.begin(), x.end();. The elements in y will be allocated by the std::allocator, while the elements in x were allocated, and will be destroyed, by your own allocator. The two allocators could have entirely unrelated notions of memory and pointers! There's no reason that the memory storage that's used by one allocator is in any way related to that of the other.

What else then can you do but make a semantic element copy?

If you don't want the old container any more, you should do a move, though:

std::vector<int> y(std::make_move_iterator(x.begin()),
                   std::make_move_iterator(x.end()));

(This will help in case the elements have their own allocator which agrees. Not for int, of course.)

Update: To reinforce the point, please note that the internal data buffer of a vector<T, Alloc> is not of type T*, but rather of type Alloc::pointer. There's no reason that those are related types for two distinct allocators.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • @HowardHinnant: Thanks! I had literally no idea what it was called and was hoping to fire blindly and hit something :-) (In hindsight the name is sort of obvious...) Why aren't you advocating `make_move_iterator(begin(x))`, by the way? – Kerrek SB Nov 19 '11 at 00:53
  • I'm sorry, perhaps I communicated my problem poorly. Of course I can't do anything but a copy, but I'd like to do so in a way that is syntactically clean. For instance, a conversion function like the one I suggested is verbose. Is there a way to make the `std::vector y = x;` line in my example work? – Jack Lloyd Nov 19 '11 at 01:40
  • @JackLloyd: How much simpler that `std::vector y(x.begin(), x.end());` do you want it? That's fairly concise already! – Kerrek SB Nov 19 '11 at 01:47
  • @Kerrek: I thought I just **did** advocate `make_move_iterator(begin(x))`! :-) Although Jack says he needs a copy, not a move, so have to go without `move_iterator`s here. – Howard Hinnant Nov 19 '11 at 02:23
  • @HowardHinnant: No no, I was only referring to the free `begin` function -- I thought that's the Rigth Way to get iterators nowadays :-) – Kerrek SB Nov 19 '11 at 02:47
  • 1
    Oh, completely missed that! End of day - brain going to bed before body does... ;-) I can take or leave the namespace scope `begin()`. Though if you're in a generic context where a built-in array is possible, then it certainly is handy. – Howard Hinnant Nov 19 '11 at 03:15
  • @Kerrek consider for example a my_vector being returned from a function, and the caller wishes to assign it to a std::vector. They would first have to assign it to a my_vector, then make a copy. Perhaps I should have emphasized that I am looking for an *implicit* conversion. Otherwise I would just use the conversion function I mentioned in my original question. – Jack Lloyd Nov 19 '11 at 17:10
  • 2
    @JackLloyd: That's what lifetime extension of temporaries is for: `const auto & x = func(); std::vector y(x.begin(), x.end());` Standard containers don't have constructors from containers or ranges only from iterator *pairs*. At best I'd write a generic *range* class plus wrappers to encapsulate an iterator pair and let `v.all()` return `range(v.begin(), v.end())`. I don't think your original conversion functions adds any sort of convenience, though. It's overly restrictive and does nothing that the standard container interface doesn't already provide. – Kerrek SB Nov 19 '11 at 17:17
  • One can have two different allocator types that can allocate and deallocate the same memory and only differ in accessory functions, like construct. I think it should be allowed to copy (and more importantly) move from vectors with different allocators under some conditions. – alfC Oct 01 '17 at 09:16
  • 1
    @alfC: Sure, but that's not currently how the standard library is specified. That would be an extension proposal. – Kerrek SB Oct 01 '17 at 10:41
  • @KerrekSB I agree, vector should be more flexible and accept a constructor from other (vector) types whose behavior is dispatched to the allocator customizations. – alfC Oct 01 '17 at 20:53
2

There are 3 solutions to your problem:

I. If your vector 'y' does not exist yet, you could use :

std::vector<int> y(x.begin(), x.end());

II. If your vector 'y' already exist, you could use : (e.g. for a member of a class)

this->y.assign(x.begin(), x.end());

These 2 ways to fill a vector are not looking the container type. (and therefore the allocator type)

III. Another solution is to create a parent class which could be used as an allocator adapter between the std allocator and your own allocator. In this case, your 2 allocators are of the same type, and could be used with vector methods like operator=().

Axel Borja
  • 3,718
  • 7
  • 36
  • 50
  • Why is II better than `this->y = TypeOfY(x.begin(), x.end());` ? – David Doria Oct 26 '16 at 14:02
  • @DavidDoria it expresses intent and doesn't require naming the type of `y`. Other than that there could be implementation defined benefits with regards to resource reuse – sehe Jul 06 '17 at 20:44
2

Hmm, I think your only option here is full copy like:

std::vector<int> y( x.begin(), x.end());
Nikolai Fetissov
  • 82,306
  • 11
  • 110
  • 171