Почему этот код Clang не компилируется с Clang 10 с -std = c ++ 20 - PullRequest
7 голосов
/ 26 апреля 2020

Следующая программа не может быть скомпилирована с помощью clang10 и -std = c ++ 20

#include "clang/AST/ASTContext.h"
int main(){}

С -std = c ++ 17 она работает.

Это вывод попытки компиляции (обратите внимание, что я в порядке с ошибкой компоновщика в C ++ 17, поскольку я не дал требуемый ключ -l в командной строке)

clang++-10  toy.cc -I/usr/lib/llvm-10/include -std=c++20 -w
In file included from toy.cc:1:
In file included from /usr/lib/llvm-10/include/clang/AST/ASTContext.h:28:
In file included from /usr/lib/llvm-10/include/clang/AST/RawCommentList.h:14:
/usr/lib/llvm-10/include/clang/Basic/SourceManager.h:953:59: error: use of overloaded operator '!=' is ambiguous (with operand types 'llvm::DenseMapBase<llvm::DenseMap<const clang::FileEntry *, const clang::FileEntry *, llvm::DenseMapInfo<const clang::FileEntry *>, llvm::detail::DenseMapPair<const clang::FileEntry *, const clang::FileEntry *> >, const clang::FileEntry *, const clang::FileEntry *, llvm::DenseMapInfo<const clang::FileEntry *>, llvm::detail::DenseMapPair<const clang::FileEntry *, const clang::FileEntry *> >::iterator' (aka 'DenseMapIterator<const clang::FileEntry *, const clang::FileEntry *, llvm::DenseMapInfo<const clang::FileEntry *>, llvm::detail::DenseMapPair<const clang::FileEntry *, const clang::FileEntry *> >') and 'llvm::DenseMapBase<llvm::DenseMap<const clang::FileEntry *, const clang::FileEntry *, llvm::DenseMapInfo<const clang::FileEntry *>, llvm::detail::DenseMapPair<const clang::FileEntry *, const clang::FileEntry *> >, const clang::FileEntry *, const clang::FileEntry *, llvm::DenseMapInfo<const clang::FileEntry *>, llvm::detail::DenseMapPair<const clang::FileEntry *, const clang::FileEntry *> >::iterator')
      if (OverriddenFilesInfo->OverriddenFiles.find(File) !=
          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^
/usr/lib/llvm-10/include/llvm/ADT/DenseMap.h:1222:8: note: candidate function
  bool operator!=(const ConstIterator &RHS) const {
       ^
/usr/lib/llvm-10/include/llvm/ADT/DenseMap.h:1215:8: note: candidate function
  bool operator==(const ConstIterator &RHS) const {
       ^
/usr/lib/llvm-10/include/llvm/ADT/DenseMap.h:1215:8: note: candidate function (with reversed parameter order)
1 error generated.
clang++-10  toy.cc -I/usr/lib/llvm-10/include -std=c++17 -w
/usr/bin/ld: /tmp/toy-4396eb.o:(.data+0x0): undefined reference to `llvm::DisableABIBreakingChecks'
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Примечания:

  • пометил этот космический корабль, так как я не знаю тега, который относится к изменениям! = == в C ++ 20

  • не смог уменьшить этот пример, так как DenseMap - монстр класса , Я нашел похожий вопрос, решение там было: операторы не имеют спецификатора const, что не является проблемой здесь (я вижу const в источнике), и когда я пытался получить аналогичную ошибку для простого случая Мне не удалось получить ошибку.

1 Ответ

6 голосов
/ 27 апреля 2020

Этот пример LLVM сокращается до:

struct iterator;

struct const_iterator {
    const_iterator(iterator const&);
};

struct iterator {
    bool operator==(const_iterator const&) const;
    bool operator!=(const_iterator const&) const;
};

bool b = iterator{} != iterator{};

В C ++ 17 это нормально: у нас есть только один кандидат, и он жизнеспособен (iterator конвертируется в const_iterator, так что один работает ).

В C ++ 20 у нас неожиданно появилось три кандидата. Я собираюсь выписать их с использованием синтаксиса, не являющегося членом, поэтому параметры более очевидны:

bool operator==(iterator const&, const_iterator const&); // #1
bool operator==(const_iterator const&, iterator const&); // #2 (reversed #1)
bool operator!=(iterator const&, const_iterator const&); // #3

#2 - обратный кандидат для #1. Обратного кандидата для #3 нет, поскольку только основные операторы сравнения (== и <=>) получают обращенных кандидатов.

Теперь первым шагом в разрешении перегрузки является выполнение последовательностей преобразования. У нас есть два аргумента типа iterator: для #1 это точное совпадение / преобразование. Для #2 это конверсия / точное совпадение. Для #3 это точное совпадение / конверсия. Проблема здесь в том, что у нас есть «триггер» между #1 и #2: каждый лучше в одной паре параметр / аргумент и хуже в другой. Это неоднозначно. Даже если в каком-то смысле #3 является «лучшим кандидатом», мы не добьемся столь далеко - неоднозначная последовательность преобразования означает неоднозначное разрешение перегрузки.

Теперь, g cc компилирует это в любом случае (я не совсем уверен, какие именно правила c он здесь реализует), и даже clang даже не считает это ошибкой, просто предупреждением (который вы можете отключить с помощью -Wno-ambiguous-reversed-operator). В настоящее время ведется работа по более изящному разрешению этих ситуаций.

Чтобы быть немного более полезным, вот более прямое сокращение примера LLVM вместе с тем, как мы могли бы исправить это для C ++ 20:

template <bool Const>
struct iterator {
    using const_iterator = iterator<true>;

    iterator();

    template <bool B, std::enable_if_t<(Const && !B), int> = 0>
    iterator(iterator<B> const&);

#if __cpp_impl_three_way_comparison >= 201902
    bool operator==(iterator const&) const;
#else
    bool operator==(const_iterator const&) const;
    bool operator!=(const_iterator const&) const;
#endif
};

В C ++ 20 мы только нужен один, однородный оператор сравнения.

Это не сработало в C ++ 17 из-за желания поддержать случай iterator<false>{} == iterator<true>{}: единственный кандидат там iterator<false>::operator==(iterator<false>), и Вы не можете конвертировать const_iterator в iterator.

Но в C ++ 20 это нормально, потому что в этом случае теперь у нас есть два кандидата: оператор равенства iterator<false> и iterator<true> оператор обратного равенства. Первый не жизнеспособен, но последний работает и работает нормально.

Нам также нужен только operator==. operator!= мы просто получаем бесплатно, так как все, что нам нужно, это отрицание равенства.

В качестве альтернативы, вы все еще в C ++ 17 можете написать операторы сравнения как скрытые друзья:

friend bool operator==(iterator const&, iterator const&);
friend bool operator!=(iterator const&, iterator const&);

Что дает вам то же поведение, что и в C ++ 20, благодаря участию кандидатов от обоих типов (просто нужно написать одну дополнительную функцию по сравнению с версией C ++ 20, и эта функция должна быть скрытой друг - он не может быть членом).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...