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

1

10

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.

Kautz answered 26/4, 2020 at 19:15 Comment(0)
A
7

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).

Audsley answered 26/4, 2020 at 21:10 Comment(6)
great answer, only thing is that I thought I disabled all the warnings with -w, but I guess that is just me misunderstanding the compiler options. Also this seems to be something Richard Smith/WG21 is familiar with : mail-archive.com/…Kautz
@Kautz Yeah, Richard Smith is generally familiar with everything :-). I couldn't reproduce the issue exactly from the OP, but I went ahead and submitted a review to fix it.Audsley
mild offtopic but do you still prefer free == functions instead of member ones? I think foonathan suggests to make them member so you get less candidates in error message when you mess up. If this is too much offtopic I will make another question.Kautz
for posterity: merge request with examples expanded to reviews.llvm.org/D78938Kautz
"and even clang doesn't even consider this an error, just a warning" Actually it is error and option doesn't help: godbolt.org/z/jWYGddCalton
@Calton Depends on the case, it does here: godbolt.org/z/cM78hEAudsley

© 2022 - 2024 — McMap. All rights reserved.