Следующий код компилируется до C++20:
#include <algorithm>
#include <vector>
struct Foo
{
};
struct FooArray
{
std::vector<Foo> a;
};
bool operator<(const FooArray& a, const FooArray& b)
{
return std::lexicographical_compare(std::begin(a.a), std::end(a.a), std::begin(b.a), std::end(b.a));
}
bool operator<(const Foo& a, const Foo& b)
{
return false;
}
При включенном C++20 GCC 10 и 11 отклоняют код, но только тогда, когда включена оптимизация:
In file included from /opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/bits/stl_algobase.h:71,
from /opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/algorithm:61,
from <source>:1:
/opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/bits/predefined_ops.h: In instantiation of 'constexpr bool __gnu_cxx::__ops::_Iter_less_iter::operator()(_Iterator1, _Iterator2) const [with _Iterator1 = const Foo*; _Iterator2 = const Foo*]':
/opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/bits/stl_algobase.h:1240:14: required from 'constexpr bool std::__lexicographical_compare_impl(_II1, _II1, _II2, _II2, _Compare) [with _II1 = const Foo*; _II2 = const Foo*; _Compare = __gnu_cxx::__ops::_Iter_less_iter]'
/opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/bits/stl_algobase.h:1257:46: required from 'static constexpr bool std::__lexicographical_compare<_BoolType>::__lc(_II1, _II1, _II2, _II2) [with _II1 = const Foo*; _II2 = const Foo*; bool _BoolType = false]'
/opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/bits/stl_algobase.h:1302:60: required from 'constexpr bool std::__lexicographical_compare_aux(_II1, _II1, _II2, _II2) [with _II1 = const Foo*; _II2 = const Foo*]'
/opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/bits/stl_algobase.h:1605:48: required from 'constexpr bool std::lexicographical_compare(_II1, _II1, _II2, _II2) [with _II1 = __gnu_cxx::__normal_iterator<const Foo*, std::vector<Foo> >; _II2 = __gnu_cxx::__normal_iterator<const Foo*, std::vector<Foo> >]'
<source>:15:40: required from here
/opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/bits/predefined_ops.h:43:23: error: no match for 'operator<' (operand types are 'const Foo' and 'const Foo')
43 | { return *__it1 < *__it2; }
| ~~~~~~~^~~~~~~~
In file included from /opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/bits/stl_algobase.h:67,
from /opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/algorithm:61,
from <source>:1:
/opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/bits/stl_iterator.h:1097:5: note: candidate: 'template<class _IteratorL, class _IteratorR, class _Container> constexpr std::__detail::__synth3way_t<_IteratorR, _IteratorL> __gnu_cxx::operator<=>(const __gnu_cxx::__normal_iterator<_IteratorL, _Container>&, const __gnu_cxx::__normal_iterator<_IteratorR, _Container>&)' (reversed)
1097 | operator<=>(const __normal_iterator<_IteratorL, _Container>& __lhs,
| ^~~~~~~~
/opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/bits/stl_iterator.h:1097:5: note: template argument deduction/substitution failed:
In file included from /opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/bits/stl_algobase.h:71,
from /opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/algorithm:61,
from <source>:1:
/opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/bits/predefined_ops.h:43:23: note: 'const Foo' is not derived from 'const __gnu_cxx::__normal_iterator<_IteratorL, _Container>'
43 | { return *__it1 < *__it2; }
| ~~~~~~~^~~~~~~~
In file included from /opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/bits/stl_algobase.h:67,
from /opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/algorithm:61,
from <source>:1:
/opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/bits/stl_iterator.h:1114:5: note: candidate: 'template<class _Iterator, class _Container> constexpr std::__detail::__synth3way_t<_T1> __gnu_cxx::operator<=>(const __gnu_cxx::__normal_iterator<_Iterator, _Container>&, const __gnu_cxx::__normal_iterator<_Iterator, _Container>&)' (rewritten)
1114 | operator<=>(const __normal_iterator<_Iterator, _Container>& __lhs,
| ^~~~~~~~
/opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/bits/stl_iterator.h:1114:5: note: template argument deduction/substitution failed:
In file included from /opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/bits/stl_algobase.h:71,
from /opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/algorithm:61,
from <source>:1:
/opt/compiler-explorer/gcc-10.5.0/include/c++/10.5.0/bits/predefined_ops.h:43:23: note: 'const Foo' is not derived from 'const __gnu_cxx::__normal_iterator<_Iterator, _Container>'
43 | { return *__it1 < *__it2; }
| ~~~~~~~^~~~~~~~
<source>:13:6: note: candidate: 'bool operator<(const FooArray&, const FooArray&)'
13 | bool operator<(const FooArray& a, const FooArray& b)
| ^~~~~~~~
<source>:13:32: note: no known conversion for argument 1 from 'const Foo' to 'const FooArray&'
13 | bool operator<(const FooArray& a, const FooArray& b)
| ~~~~~~~~~~~~~~~~^
Compiler returned: 1
GCC 12 и более поздние версии скомпилируют код. MSVC всегда компилирует код, clang отклоняет его, когда включен C++20.
https://godbolt.org/z/9ra6hn7c9
Какой компилятор правильный? Нужно ли объявлять bool operator<(const Foo& a, const Foo& b)
перед вызовом std::lexicographical_compare
? Это изменение в C++20 или код просто работает в C++17 (я был удивлен, что код не вызывает проблем и в C++17)?
GCC и MSVC, похоже, задерживают фактическое создание экземпляра до тех пор, пока не станет доступно объявление operator<
. Но clang пытается создать экземпляр. Хотя, возможно, я здесь ошибаюсь.
Если порядок объявления именно такой, но определения выполняются отдельно, он компилируется, так что я так думаю.
@user12002570 user12002570 этот вопрос указывает на то, что код должен быть действительным, поэтому он не объясняет, почему он не компилируется с clang, когда включен C++20.
@AlanBirtles Как только вы поймете, что такое стандартное поведение, на ваш вопрос будет дан ответ. Другой вопрос, правильно ли это реализует компилятор или показывает регрессию.
Это неправильно сформированный запрос, согласно temp.param#7 диагностика не требуется.
Прежде всего обратите внимание, что существует две точки создания экземпляров (POI). Первый POI находится в области пространства имен сразу после operator<(const FooArray& a, const FooArray& b)
согласно temp.point:
Для специализации шаблона функции, специализации шаблона функции-члена или специализации для функции-члена или члена статических данных шаблона класса, если специализация создается неявно, поскольку на нее ссылаются из другой специализации шаблона и контекста, из которого она создается. ссылка зависит от параметра шаблона, точкой создания экземпляра специализации является точка создания охватывающей специализации. В противном случае точка создания экземпляра такой специализации следует сразу за объявлением или определением области пространства имен, которое ссылается на специализацию.
Второй POI находится в конце единицы перевода.
Теперь согласно temp.point#7:
Специализация шаблона класса имеет не более одной точки реализации в единице перевода. Специализация для любого шаблона может иметь точки реализации в нескольких единицах перевода. Если две разные точки создания экземпляра придают специализации шаблона разные значения в соответствии с правилом одного определения, программа является неправильной и никакой диагностики не требуется.
Это означает, что данная программа является IFNDR, поскольку она нарушает Basic.def.odr:
В каждом таком определении упомянутые перегруженные операторы, неявные вызовы функций преобразования, конструкторов, функций нового оператора и функций удаления оператора должны ссылаться на одну и ту же функцию.
Вышеупомянутое нарушается, поскольку в первом POI нет operator<
, на который мы могли бы сослаться, а во втором POI он есть. Таким образом, они не относятся к одной и той же функции.
Решение состоит в том, чтобы предоставить объявление перед первым POI, чтобы упомянутые функции были одинаковыми в обоих POI.
Я думаю, что это связано с концом файла и также считается проблемой точки создания экземпляра.