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::pairdoes 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_conceptidea. In order to instantiate that,reverse_iteratorneeds 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
*ion our iterator type. That's__gnu_cxx::__normal_iterator<std::pair<foo, foo>**, std::vector<std::pair<foo, foo>*> >, in this case. Now,*itriggers 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::pairis using Eric Fisellier's trick:And
is_copy_assignablerequires complete types and we don't have one.But really even if
pairused 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.