cjdb created this revision. cjdb added a reviewer: aaron.ballman. Herald added a project: All. cjdb requested review of this revision. Herald added a project: clang. Herald added a subscriber: cfe-commits.
Per per [range.iter.ops]/2 and [algorithms.requirements]/2, functions declared in the namespace 'std::ranges' aren't found by ADL, and suppress ADL when they're called in an unqualified context (e.g. a using-directive). Libraries have been implementing these functions as function objects with varying rules (e.g. libc++ and Microsoft/STL both try their best to make the function objects appear as standard library function templates, while libstdc++ makes them plain function objects). Having a large number of types typically has a negative impact on both compile-times and progam size, and there are approximately 130 of these at present. Furthermore, the diagnostics can be marginally improved by switching to proper functions, which will make it clearer that the problem is at the user level, rather than the implementation level (see https://godbolt.org/z/1sYMsxdfM for an example). By making it possible to implement ranges functions as functions, it's hoped that the library will be incentivised to migrate over. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D129951 Files: clang/include/clang/AST/DeclBase.h clang/lib/AST/DeclBase.cpp clang/lib/Sema/SemaOverload.cpp clang/test/Sema/disable-adl.cpp
Index: clang/test/Sema/disable-adl.cpp =================================================================== --- /dev/null +++ clang/test/Sema/disable-adl.cpp @@ -0,0 +1,94 @@ +// RUN: %clang_cc1 %s -fsyntax-only -verify + +// expected-n...@disable-adl.cpp:* 2{{}} + +namespace std { + struct S1 {}; + S1 inhibited(S1); + + namespace ranges { + struct S2 {}; + void hidden(S2); + int inhibited(S1); + } +} + +void test_functions() { + hidden(std::ranges::S2{}); + // expected-error@-1{{use of undeclared identifier 'hidden'; did you mean 'std::ranges::hidden'?}} + + using namespace std::ranges; + int x = inhibited(std::S1{}); // no error +} + +namespace std { + template<typename T> + S1 inhibited_template(T); + + namespace ranges { + template<typename T> + void hidden_template(T); + + template<typename T> + int inhibited_template(T); + } +} + +void test_function_templates() { + hidden_template(std::ranges::S2{}); + // expected-error@-1{{use of undeclared identifier 'hidden_template'; did you mean 'std::ranges::hidden_template'?}} + + using namespace std::ranges; + int x = inhibited_template(std::S1{}); +} + +namespace std { + S1 inhibited_mixed(S1); + + namespace ranges { + template<typename T> + int inhibited_mixed(T); + } +} + +void test_mixed() { + using namespace std::ranges; + int x = inhibited_mixed(std::S1{}); +} + +// Should be covered by the hidden functions checks, but just to be sure. +void test_ranges_hidden() { + { + std::S1 a = inhibited(std::S1{}); + std::S1 b = inhibited_template(std::S1{}); + std::S1 c = inhibited_mixed(std::S1{}); + } + { + using namespace std; + std::S1 a = inhibited(std::S1{}); + std::S1 b = inhibited_template(std::S1{}); + std::S1 c = inhibited_mixed(std::S1{}); + } +} + +namespace std { + namespace ranges { + void operator-(S2); + + struct hidden_friend_operator { + friend void operator-(hidden_friend_operator i, int) {} + }; + + struct hidden_friend_swap { + friend void swap(hidden_friend_swap, hidden_friend_swap) {} + }; + } +} + +void test_friends_and_operators() { + -std::ranges::S2{}; // no error + std::ranges::hidden_friend_operator{} - 1; // no error + + swap(std::ranges::hidden_friend_swap{}, std::ranges::hidden_friend_swap{}); + // expected-error@-1{{use of undeclared identifier 'swap'}} +} Index: clang/lib/Sema/SemaOverload.cpp =================================================================== --- clang/lib/Sema/SemaOverload.cpp +++ clang/lib/Sema/SemaOverload.cpp @@ -9452,6 +9452,12 @@ for (ADLResult::iterator I = Fns.begin(), E = Fns.end(); I != E; ++I) { DeclAccessPair FoundDecl = DeclAccessPair::make(*I, AS_none); + // Functions in 'std::ranges' are hidden from ADL per [range.iter.ops]/2 and + // [algorithms.requirements]/2. + if ((*I)->isInStdRangesNamespace() && + Name.getNameKind() == DeclarationName::NameKind::Identifier) + continue; + if (FunctionDecl *FD = dyn_cast<FunctionDecl>(*I)) { if (ExplicitTemplateArgs) continue; @@ -9650,7 +9656,7 @@ const OverloadCandidate &Cand1, const OverloadCandidate &Cand2) { // FIXME: Per P2113R0 we also need to compare the template parameter lists - // when comparing template functions. + // when comparing template functions. if (Cand1.Function && Cand2.Function && Cand1.Function->hasPrototype() && Cand2.Function->hasPrototype()) { auto *PT1 = cast<FunctionProtoType>(Cand1.Function->getFunctionType()); @@ -12828,6 +12834,12 @@ CandidateSet, PartialOverloading, /*KnownValid*/ true); + // Functions in 'std::ranges' inhibit ADL per [range.iter.ops]/2 and + // [algorithms.requirements]/2. + if (!ULE->decls().empty() && ULE->decls_begin()->isInStdRangesNamespace() && + ULE->getName().getNameKind() == DeclarationName::NameKind::Identifier) + return; + if (ULE->requiresADL()) AddArgumentDependentLookupCandidates(ULE->getName(), ULE->getExprLoc(), Args, ExplicitTemplateArgs, Index: clang/lib/AST/DeclBase.cpp =================================================================== --- clang/lib/AST/DeclBase.cpp +++ clang/lib/AST/DeclBase.cpp @@ -396,6 +396,11 @@ return DC && DC->isStdNamespace(); } +bool Decl::isInStdRangesNamespace() const { + const DeclContext *DC = getDeclContext(); + return DC && DC->isStdRangesNamespace(); +} + TranslationUnitDecl *Decl::getTranslationUnitDecl() { if (auto *TUD = dyn_cast<TranslationUnitDecl>(this)) return TUD; @@ -1149,6 +1154,19 @@ return II && II->isStr("std"); } +bool DeclContext::isStdRangesNamespace() const { + if (!isNamespace()) + return false; + + const auto *ND = cast<NamespaceDecl>(this); + if (!ND->getParent()->isStdNamespace()) { + return false; + } + + const IdentifierInfo *II = ND->getIdentifier(); + return II && II->isStr("ranges"); +} + bool DeclContext::isDependentContext() const { if (isFileContext()) return false; Index: clang/include/clang/AST/DeclBase.h =================================================================== --- clang/include/clang/AST/DeclBase.h +++ clang/include/clang/AST/DeclBase.h @@ -465,6 +465,7 @@ bool isInAnonymousNamespace() const; bool isInStdNamespace() const; + bool isInStdRangesNamespace() const; ASTContext &getASTContext() const LLVM_READONLY; @@ -1986,6 +1987,7 @@ bool isNamespace() const { return getDeclKind() == Decl::Namespace; } bool isStdNamespace() const; + bool isStdRangesNamespace() const; bool isInlineNamespace() const;
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits