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:
- It can compile and execute as you intended.
- It can issue a diagnostic.
- It can delete the code you have written.
- 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:
- can compile and execute as you intended.
- can issue a diagnostic.
- can delete the code you have written.
- 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.