Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this clang code fail to compile with clang 10 with -std=c++20

Following program fails to compile with clang10 and -std=c++20

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

With -std=c++17 it works.

This is the compile attempt output(note that I am fine with linker error in C++17 since I did not give the required -l to the command line)

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)

Notes:

  • tagged this spaceship since I am not aware of the tag that relates to != == changes in C++20

  • could not reduce this example since DenseMap is a monster of a class, I found similar question, solution there was that operators are missing a const qualifier, that does not seem to be a problem here(I can see the const in the source), and when I tried to get the similar error for a simple case I failed to get an error.

like image 784
NoSenseEtAl Avatar asked Apr 26 '20 19:04

NoSenseEtAl


1 Answers

This LLVM example reduces to:

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{};

In C++17, this is fine: we only have one candidate and it's viable (iterator is convertible to const_iterator so that one works).

In C++20, we suddenly have three candidates. I'm going to write them out using non-member syntax so the parameters are more obvious:

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 is the reversed candidate for #1. There is no reversed candidate for #3 because only the primary comparison operators (== and <=>) get reversed candidates.

Now, the first step in overload resolution is doing conversion sequences. We have two arguments of type iterator: for #1, that's an exact match/conversion. For #2, that's conversion/exact match. For #3, that's exact match/conversion. The problem here is we have this "flip-flop" between #1 and #2: each is better in one parameter/argument pair and worse in the other. That's ambiguous. Even if #3 is the "better candidate" in some sense, we don't get that far - ambiguous conversion sequence means ambiguous overload resolution.


Now, gcc compiles this anyway (I'm not entirely sure what specific rules it implements here) and even clang doesn't even consider this an error, just a warning (which you can disable with -Wno-ambiguous-reversed-operator). There's some ongoing work in trying to resolve these situations more gracefully.


To be slightly more helpful, here is a more direct reduction of the LLVM example along with how we could fix it for 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
};

In C++20, we only need the one, homogeneous comparison operator.

This didn't work in C++17 because of wanting to support the iterator<false>{} == iterator<true>{} case: the only candidate there is iterator<false>::operator==(iterator<false>), and you can't convert a const_iterator to an iterator.

But in C++20 it's fine, because in this case now we have two candidates: iterator<false>'s equality operator and iterator<true>'s reversed equality operator. The former isn't viable, but the latter is and works fine.

We also only need the operator==. The operator!= we just get for free, since all we want is negated equality.

Alternatively, you could, still in C++17, write the comparison operators as hidden friends:

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

Which gets you the same behavior as C++20, by way of having candidates from both types participate (it's just having to write one extra function as compared to the C++20 version, and that function must be a hidden friend - it cannot be a member).

like image 86
Barry Avatar answered Sep 21 '22 08:09

Barry