6

It seems that the C++ STL container requirements are that the provided allocator type's value_type be the same as the STL container's value_type

Requires:allocator_- type::value_type is the same as X::value_type.

However, the following code that uses a vector of strings but with an allocator for doubles works just fine on VS 2012 and g++ 4.4.7. On g++, valgrind does not produce any errors either.

int main()
{
  typedef vector<std::string, std::allocator<double> > StringList;
  StringList s;
  for(int i=0; i < 100; i++){
    stringstream ss;
    ss << i;
    s.push_back(ss.str());
  }
  for(StringList::iterator it = s.begin(); it != s.end(); ++it)
    {
      cout << *it << " ";
    }
  cout << endl;

  return 0;
}

I'm assuming the allocator is being rebound internally to an allocator of the value_type of the container (though maybe I'm wrong).

My question is am I misreading the C++ spec and in fact all containers will always "rebind" the allocator provided to use the type they want? Or is that just a common practice but not guaranteed.

Essentially can I count on this "feature" that containers will always take whatever allocator I provide (of any type) and make it work for the value_type of that container?

  • 1
    I haven't checked, but I'd guess that what you are doing is Undefined Behaviour (so, broken code). – Jesper Juhl May 22 '17 at 04:39
  • 1
    I don't know if it fully answers the question, but I did find this related question: http://stackoverflow.com/questions/42736112/mismatched-stdallocator-for-some-of-stl-containers?rq=1 – innocent_bystander May 22 '17 at 04:41
  • 1
    This is probably partially related to the fact that `std::list>` is how it has to be defined, but the `Allocator` would probably be used to allocate `ListNode`s – Justin May 22 '17 at 05:15
  • @Justin: That's the ["rebind"](http://stackoverflow.com/questions/12362363/why-is-allocatorrebind-necessary-when-we-have-template-template-parameters) mentioned in the question. – MSalters May 22 '17 at 07:00
  • This is like literal proof that the `std::allocator` design is broken – Passer By May 22 '17 at 07:14
  • 2
    Your program exhibits undefined behavior; "seems to work" is one possible manifestation of undefined behavior. "**[res.on.required]/1** Violation of the preconditions specified in a function’s *Requires:* paragraph results in undefined behavior unless the function’s *Throws:* paragraph specifies throwing an exception when the precondition is violated." – Igor Tandetnik May 22 '17 at 18:01

2 Answers2

6

If you try to build your code with clang/libc++ (adding the appropriate includes and using namespace std;, you get:

/Sources/LLVM/llvm/projects/libcxx/include/vector:474:5: error: static_assert failed "Allocator::value_type must be same type as value_type" static_assert((is_same::value),

Anyway, the standard is really clear on this (in c++11/14/1z - but not c++03):

*Requires:* `allocator_type::value_type` is the same as `X::value_type`

So if you try to instantiate vector<std::string, std::allocator<double> >, you get undefined behavior - and "seems to work fine" is a particularly onerous version of undefined behavior. But really, it is "seems to work fine for now"

Marshall Clow
  • 15,972
  • 2
  • 29
  • 45
  • Thanks. Yes I saw the standard but was then surprised that it compiled so I was curious if there was something I was missing about a requirement to always rebind. I didn't have access to clang so wasn't able to test with that. Thanks for running that. – innocent_bystander May 23 '17 at 01:36
  • 1
    "Requires: XXXX" means that you, the user are responsible for complying with that requirement. If you don't, you get undefined behavior - which can do *anything* (even "appear to work correctly") – Marshall Clow May 23 '17 at 03:18
  • It could even “actually work correctly”. Unfortunately, it could also “actually work correctly, until you recompile in release mode” or “actually work correctly, until you upgrade your compiler”. And those are only some of the more plausible behaviors. I would bet that some programs with undefined behavior compiled today have the behavior “actually works correctly, until [January 19, 2038](https://en.wikipedia.org/wiki/Year_2038_problem)” (although that in particular is unlikely to happen with allocators). – Daniel H May 23 '17 at 14:35
0

When you write your own allocator you can in principle use a different value_type in your allocator as in your type trait.

As of C++11 a type trait is provided to check whether a type T has an allocator_type which a passed allocator may be converted into. If this conversion is not supplied (or the compiler does not check it) you have undefined behaviour.

In MSVC 14.1 the conversion is done via simple rebinding

    template<class _Other>
    struct rebind
    {   // convert this type to allocator<_Other>
    typedef allocator<_Other> other;
    };

So in MSVC it does indeed do internal rebounding, as to your last question, I would not rely on this implementation staying this way, certainly not across different compilers. Also I would wonder why you would want to rely on it, instead of giving the correct type trait?

D.J. Klomp
  • 2,429
  • 1
  • 15
  • 30