Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it wrong if the standard container element type and std::allocator type are different?

Tags:

c++

Take from here (which is quite old):

It's also important that the type used for the allocator template parameter and the type used as the element type in a standard container agree. For instance:

std::list<int, std::allocator<long> >                    // Wrong!

won't work.

Question

Is the above statement correct (or was it ever correct)? Any tests I have done seem to work fine no matter what I put for T in std::allocator. For example, std::vector<int, std::allocator<std::string>> compiled and worked fine pushing back and erasing elements, etc. (From what I understand std::allocator<std::string>::rebind<int>::other is the magic that makes this work).

like image 580
Jesse Good Avatar asked Jul 30 '13 21:07

Jesse Good


2 Answers

EDIT: In [containers.requirements.general], the Allocator-aware container requirements indicate that the allocator_type::value_type is the same as Container::value_type.

So it's ill formed to pass in an allocator type with a different value_type, although at least one implementation simply uses allocator_traits<...>::rebind<value_type> to get the correct allocator.

like image 25
MSN Avatar answered Nov 03 '22 06:11

MSN


I'm adding an answer here to clarify the difference between ill-formed and undefined behavior.

[intro.compliance]/p1:

The set of diagnosable rules consists of all syntactic and semantic rules in this International Standard except for those rules containing an explicit notation that “no diagnostic is required” or which are described as resulting in “undefined behavior.”

[defns.ill.formed]:

program that is not well formed

[defns.well.formed]

C++ program constructed according to the syntax rules, diagnosable semantic rules, and the One Definition Rule (3.2).

In English: an ill-formed program shall have a diagnostic associated with it. Undefined behavior can do anything:

  1. It can compile and execute as you intended.
  2. It can issue a diagnostic.
  3. It can delete the code you have written.
  4. It can reformat the nearest disk.

(all but the 4th routinely happen in practice)

Undefined behavior is very bad, and imho, the C and C++ standards apply that specification much too liberally.

Technically, violating a Requires clause results in undefined behavior.

[res.on.required]/p1:

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.

As noted by MSN, allocator_type::value_type shall be the same as container::value_type as stated in Table 99 -- Allocator-aware container requirements.

allocator_type A       Requires:  allocator_type::value_type 
                                  is the same as X::value_type.

(X denotes an allocator-aware container class with a value_type of T using allocator of type A)

So a violation such as:

std::list<int, std::allocator<long> >  

is undefined behavior. So it:

  1. can compile and execute as you intended.
  2. can issue a diagnostic.
  3. can delete the code you have written.
  4. can reformat the nearest disk.

Just very recently (within weeks of me writing this), libc++ (http://libcxx.llvm.org) has started diagnosing this undefined behavior with static_assert so that you get the bad news asap.

We decided to go this direction, instead of allowing the behavior because the containers are not set up to allow conversions among closely related types. For example:

std::list<int, std::allocator<long>>  list1;
std::list<int>                        list2 = list1;  // is specified to not work

I.e. if you start treating list1 and list2 as equivalent types because the std::allocator gets rebind'd anyway, you're going to get disappointed down the road as you discover the two lists are really different types and not designed to interoperate anyway. So it is really best to get the bad news asap, instead of finding out 2 months, or 2 years later, when you try to use them as equivalent types.

Perhaps a future standard will treat list1 and list2 as equivalent types. It is mostly technically possible (std::is_same would likely not work). But there are no proposals that I've heard of in this direction. This direction does not seem likely to me. And with static_assert, the error is easily diagnosable. Instead I would like to see the standard move in the direction of making this code ill-formed instead of undefined. The hardest part in doing so would be word-smithing the standard, and not in the std::lib implementation.

like image 142
Howard Hinnant Avatar answered Nov 03 '22 04:11

Howard Hinnant