Why do compilers allow an allocator of a different value type than the container used
Asked Answered
U

2

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?

Utah answered 22/5, 2017 at 4:30 Comment(6)
I haven't checked, but I'd guess that what you are doing is Undefined Behaviour (so, broken code).Hers
I don't know if it fully answers the question, but I did find this related question: #42736612Utah
This is probably partially related to the fact that std::list<T, Allocator<T>> is how it has to be defined, but the Allocator<T> would probably be used to allocate ListNode<T>sLythraceous
@Justin: That's the "rebind" mentioned in the question.Behlau
This is like literal proof that the std::allocator design is brokenBuntline
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."Mononuclear
A
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"

Assentation answered 23/5, 2017 at 1:1 Comment(3)
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.Utah
"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")Assentation
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” (although that in particular is unlikely to happen with allocators).Snaggy
P
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?

Principalities answered 22/5, 2017 at 22:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.