The following code compiles in C++11, C++14, and C++17, but does not compile in C++20. What change to the standard broke this code?
#include <vector>
#include <utility>
template<typename T>
struct bar
{
typename T::reverse_iterator x;
};
struct foo
{
bar<std::vector<std::pair<foo, foo>*>> x;
};
int main()
{
foo f;
}
The error is quite long, but can be summarized as:
template argument must be a complete class
This was always undefined. [res.on.functions]/2.5 says:
std::pair
does not (and cannot) support incomplete types. You were just relying on order of instantiation to kind of get around that. Something changed in the library that slightly changed the evaluation order, leading to the error. But undefined behavior is undefined - it happened to work before and it happens to not work now.As to why it's specifically C++20 that is causing this to fail. In C++20, iterators changed to have this new
iterator_concept
idea. In order to instantiate that,reverse_iterator
needs to determine what the concept should be. That looks like this:Now, in the process of checking
random_access_iterator
, the root of iterator concept hierarchy is wonderfully namedinput_or_output_iterator
, specified in [iterator.concept.iterator]:So, we have to do
*i
on our iterator type. That's__gnu_cxx::__normal_iterator<std::pair<foo, foo>**, std::vector<std::pair<foo, foo>*> >
, in this case. Now,*i
triggers ADL - because of course it does. And ADL requires instantiation of all the associated types - because those associated types might have injected friends that could be candidates!This, in turn, requires instantiating
pair<foo, foo>
- because, we have to check. And then that ultimately fails in this specific case because instantiating a type requires instantiating all of the type's special member functions, and the way that libstdc++ implements conditional assignment forstd::pair
is using Eric Fisellier's trick:And
is_copy_assignable
requires complete types and we don't have one.But really even if
pair
used concepts to check in this case, that would still involve instantiating the same type traits, so we'd ultimately end up in the same position.Moral of the story is, undefined behavior is undefined.