Copying objects using different allocators in C++

c++, c++11

Solution

There is a way to handle this, but you need to think outside of the box a bit. What you need is an intermediate type that is implicitly constructable from both `std::string` and your allocator string.

There is a proposal for such a thing before the C++ committee currently. It is based on a Google-built Apache-licensed implementation that already exists. It's called `basic_string_ref`; it's a template class that is basically a pointer to the first character in a string and a size, representing the length of the string. It's not a true container in the sense that it doesn't manage memory.

Which is exactly what you need.

`basic_string_ref` for a particular character type and traits type is implicitly constructable from a `std::basic_string` regardless of allocator.

All of the comparison operators can be defined in terms of `basic_string_ref`. Since it is implicitly constructable from `std::basic_string` (and virtually free to construct), it would work transparently for comparisons between differently allocated strings.

Doing assignment is rather trickier, but doable. It requires a series of conversions:

a = pstring{basic_string_ref{y}};

Not the prettiest code, of course. We would prefer to simply change the copy constructor and assignment operator of `std::basic_string` to be allocator agnostic. But since that's not doable, this is really the next best thing. You could even wrap it in a template function:

template<typename DestAllocator, typename SourceAllocator, typename charT, typename traits>
std::basic_string<charT, traits, DestAllocator> conv_str(const std::basic_string<charT, traits, SourceAllocator> &input)
{
  return std::basic_string<charT, traits, DestAllocator>{basic_string_ref<charT, traits>{y}};
}

Of course, if you can do that, you can just do this:

template<typename DestAllocator, typename SourceAllocator, typename charT, typename traits>
std::basic_string<charT, traits, DestAllocator> conv_str(const std::basic_string<charT, traits, SourceAllocator> &input)
{
  return std::basic_string<charT, traits, DestAllocator>{y.begin(), y.end()};
}

It'd be great if this were just part of `std::basic_string`, so that you wouldn't need the workarounds. But it isn't.

Problem

So I have a nice persistent allocator class `persistent_alloc<T>` that allows me to allocate C++ container objects and strings in persistent memory which is backed by an mmaped file that can persist from one run of my program to the next. My problem comes when I want to do anything that mixes persistent and non persistent objects. For example, I have ``` typedef std::basic_string<char, std::char_traits<char>, persistent_alloc<char>> pstring; pstring a, b, c; std::string x, y, z; ``` I want to be able to do things like: ``` if (a == x) a = y; c = z + b; ``` and so forth, but by default it does not work, as `pstring` and `std::string` are unrelated types. Now as far as the comparison is concerned, I can define: ``` template<typename Alloc1, typename Alloc2> inline bool operator==(const std::basic_string<char, std::char_traits<char>, Alloc1> &a, const std::basic_string<char, std::char_traits<char>, Alloc2> &b) { return strcmp(a.c_str(), b.c_str()) == 0; } ``` ...and now I can compare strings for equality. But adding these for every operation seems like a pain -- it seems like they SHOULD be provided by the standard library. Worse, assignment operators and copy constructors must be members and can't be defined as global inline functions like this. Is there a reasonable way of doing this? Or do I have to effectively rewrite the entire standard library to support allocators usefully?

Original source