Skip to content

Commit 0a517fd

Browse files
committed
Implement GCC's CWG 2369 heuristic
1 parent 671976d commit 0a517fd

File tree

4 files changed

+240
-13
lines changed

4 files changed

+240
-13
lines changed

clang/include/clang/Sema/Sema.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10452,7 +10452,8 @@ class Sema final : public SemaBase {
1045210452
FunctionTemplateDecl *FunctionTemplate, ArrayRef<QualType> ParamTypes,
1045310453
ArrayRef<Expr *> Args, OverloadCandidateSet &CandidateSet,
1045410454
ConversionSequenceList &Conversions, bool SuppressUserConversions,
10455-
CXXRecordDecl *ActingContext = nullptr, QualType ObjectType = QualType(),
10455+
bool NonInstOnly, CXXRecordDecl *ActingContext = nullptr,
10456+
QualType ObjectType = QualType(),
1045610457
Expr::Classification ObjectClassification = {},
1045710458
OverloadCandidateParamOrder PO = {});
1045810459

@@ -12497,7 +12498,9 @@ class Sema final : public SemaBase {
1249712498
sema::TemplateDeductionInfo &Info,
1249812499
SmallVectorImpl<OriginalCallArg> const *OriginalCallArgs,
1249912500
bool PartialOverloading, bool PartialOrdering,
12500-
llvm::function_ref<bool()> CheckNonDependent = [] { return false; });
12501+
llvm::function_ref<bool(bool)> CheckNonDependent = [](bool) {
12502+
return false;
12503+
});
1250112504

1250212505
/// Perform template argument deduction from a function call
1250312506
/// (C++ [temp.deduct.call]).
@@ -12532,7 +12535,7 @@ class Sema final : public SemaBase {
1253212535
bool PartialOverloading, bool AggregateDeductionCandidate,
1253312536
bool PartialOrdering, QualType ObjectType,
1253412537
Expr::Classification ObjectClassification,
12535-
llvm::function_ref<bool(ArrayRef<QualType>)> CheckNonDependent);
12538+
llvm::function_ref<bool(ArrayRef<QualType>, bool)> CheckNonDependent);
1253612539

1253712540
/// Deduce template arguments when taking the address of a function
1253812541
/// template (C++ [temp.deduct.funcaddr]) or matching a specialization to

clang/lib/Sema/SemaOverload.cpp

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7829,10 +7829,10 @@ static void AddMethodTemplateCandidateImmediately(
78297829
MethodTmpl, ExplicitTemplateArgs, Args, Specialization, Info,
78307830
PartialOverloading, /*AggregateDeductionCandidate=*/false,
78317831
/*PartialOrdering=*/false, ObjectType, ObjectClassification,
7832-
[&](ArrayRef<QualType> ParamTypes) {
7832+
[&](ArrayRef<QualType> ParamTypes, bool NonInstOnly) {
78337833
return S.CheckNonDependentConversions(
78347834
MethodTmpl, ParamTypes, Args, CandidateSet, Conversions,
7835-
SuppressUserConversions, ActingContext, ObjectType,
7835+
SuppressUserConversions, NonInstOnly, ActingContext, ObjectType,
78367836
ObjectClassification, PO);
78377837
});
78387838
Result != TemplateDeductionResult::Success) {
@@ -7943,10 +7943,11 @@ static void AddTemplateOverloadCandidateImmediately(
79437943
/*PartialOrdering=*/false,
79447944
/*ObjectType=*/QualType(),
79457945
/*ObjectClassification=*/Expr::Classification(),
7946-
[&](ArrayRef<QualType> ParamTypes) {
7946+
[&](ArrayRef<QualType> ParamTypes, bool NonInstOnly) {
79477947
return S.CheckNonDependentConversions(
79487948
FunctionTemplate, ParamTypes, Args, CandidateSet, Conversions,
7949-
SuppressUserConversions, nullptr, QualType(), {}, PO);
7949+
SuppressUserConversions, NonInstOnly, nullptr, QualType(), {},
7950+
PO);
79507951
});
79517952
Result != TemplateDeductionResult::Success) {
79527953
OverloadCandidate &Candidate =
@@ -8021,7 +8022,7 @@ bool Sema::CheckNonDependentConversions(
80218022
FunctionTemplateDecl *FunctionTemplate, ArrayRef<QualType> ParamTypes,
80228023
ArrayRef<Expr *> Args, OverloadCandidateSet &CandidateSet,
80238024
ConversionSequenceList &Conversions, bool SuppressUserConversions,
8024-
CXXRecordDecl *ActingContext, QualType ObjectType,
8025+
bool NonInstOnly, CXXRecordDecl *ActingContext, QualType ObjectType,
80258026
Expr::Classification ObjectClassification, OverloadCandidateParamOrder PO) {
80268027
// FIXME: The cases in which we allow explicit conversions for constructor
80278028
// arguments never consider calling a constructor template. It's not clear
@@ -8058,6 +8059,54 @@ bool Sema::CheckNonDependentConversions(
80588059
}
80598060
}
80608061

8062+
// A heuristic & speculative workaround for bug
8063+
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99599 that manifests after
8064+
// CWG2369.
8065+
auto ConversionMightInduceInstantiation = [&](QualType ParmType,
8066+
QualType ArgType) {
8067+
ParmType = ParmType.getNonReferenceType();
8068+
ArgType = ArgType.getNonReferenceType();
8069+
bool PointerConv = ParmType->isPointerType() && ArgType->isPointerType();
8070+
if (PointerConv) {
8071+
ParmType = ParmType->getPointeeType();
8072+
ArgType = ArgType->getPointeeType();
8073+
}
8074+
8075+
auto IsInstantiation = [&](QualType T) {
8076+
if (auto *RT = T->getAs<RecordType>()) {
8077+
if (auto *RD = dyn_cast<CXXRecordDecl>(RT->getDecl())) {
8078+
if (auto *ClassTemplateSpec =
8079+
dyn_cast<ClassTemplateSpecializationDecl>(RD))
8080+
return ClassTemplateSpec->getSpecializationKind() == TSK_Undeclared;
8081+
if (RD->getInstantiatedFromMemberClass())
8082+
return RD->getMemberSpecializationInfo()
8083+
->getTemplateSpecializationKind() !=
8084+
TemplateSpecializationKind::TSK_ExplicitSpecialization;
8085+
}
8086+
}
8087+
return false;
8088+
};
8089+
if (IsInstantiation(ParmType) || IsInstantiation(ArgType))
8090+
return true;
8091+
8092+
if (PointerConv || CompareReferenceRelationship(SourceLocation(), ParmType,
8093+
ArgType) == Ref_Related)
8094+
return false;
8095+
8096+
if (auto *RT = ParmType->getAs<RecordType>())
8097+
if (auto *RD = dyn_cast<CXXRecordDecl>(RT->getDecl());
8098+
RD && RD->hasDefinition() && !RD->isAggregate())
8099+
return false;
8100+
8101+
if (auto *RT = ArgType->getAs<RecordType>())
8102+
if (auto *RD = dyn_cast<CXXRecordDecl>(RT->getDecl());
8103+
RD && RD->hasDefinition() &&
8104+
!RD->getVisibleConversionFunctions().empty())
8105+
return true;
8106+
8107+
return false;
8108+
};
8109+
80618110
unsigned Offset =
80628111
Method && Method->hasCXXExplicitFunctionObjectParameter() ? 1 : 0;
80638112

@@ -8078,6 +8127,9 @@ bool Sema::CheckNonDependentConversions(
80788127
// For members, 'this' got ConvIdx = 0 previously.
80798128
ConvIdx = ThisConversions + I;
80808129
}
8130+
if (NonInstOnly &&
8131+
ConversionMightInduceInstantiation(ParamType, Args[I]->getType()))
8132+
continue;
80818133
Conversions[ConvIdx]
80828134
= TryCopyInitialization(*this, Args[I], ParamType,
80838135
SuppressUserConversions,

clang/lib/Sema/SemaTemplateDeduction.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3962,7 +3962,7 @@ TemplateDeductionResult Sema::FinishTemplateArgumentDeduction(
39623962
TemplateDeductionInfo &Info,
39633963
SmallVectorImpl<OriginalCallArg> const *OriginalCallArgs,
39643964
bool PartialOverloading, bool PartialOrdering,
3965-
llvm::function_ref<bool()> CheckNonDependent) {
3965+
llvm::function_ref<bool(bool)> CheckNonDependent) {
39663966
// Unevaluated SFINAE context.
39673967
EnterExpressionEvaluationContext Unevaluated(
39683968
*this, Sema::ExpressionEvaluationContext::Unevaluated);
@@ -4005,6 +4005,9 @@ TemplateDeductionResult Sema::FinishTemplateArgumentDeduction(
40054005
Owner = FunctionTemplate->getLexicalDeclContext();
40064006
FunctionDecl *FD = FunctionTemplate->getTemplatedDecl();
40074007

4008+
if (CheckNonDependent(/*NonInstOnly=*/true))
4009+
return TemplateDeductionResult::NonDependentConversionFailure;
4010+
40084011
// C++20 [temp.deduct.general]p5: [CWG2369]
40094012
// If the function template has associated constraints, those constraints
40104013
// are checked for satisfaction. If the constraints are not satisfied, type
@@ -4035,7 +4038,7 @@ TemplateDeductionResult Sema::FinishTemplateArgumentDeduction(
40354038
// P with a type that was non-dependent before substitution of any
40364039
// explicitly-specified template arguments, if the corresponding argument
40374040
// A cannot be implicitly converted to P, deduction fails.
4038-
if (CheckNonDependent())
4041+
if (CheckNonDependent(/*NonInstOnly=*/false))
40394042
return TemplateDeductionResult::NonDependentConversionFailure;
40404043

40414044
MultiLevelTemplateArgumentList SubstArgs(
@@ -4532,7 +4535,7 @@ TemplateDeductionResult Sema::DeduceTemplateArguments(
45324535
bool PartialOverloading, bool AggregateDeductionCandidate,
45334536
bool PartialOrdering, QualType ObjectType,
45344537
Expr::Classification ObjectClassification,
4535-
llvm::function_ref<bool(ArrayRef<QualType>)> CheckNonDependent) {
4538+
llvm::function_ref<bool(ArrayRef<QualType>, bool)> CheckNonDependent) {
45364539
if (FunctionTemplate->isInvalidDecl())
45374540
return TemplateDeductionResult::Invalid;
45384541

@@ -4746,9 +4749,9 @@ TemplateDeductionResult Sema::DeduceTemplateArguments(
47464749
Result = FinishTemplateArgumentDeduction(
47474750
FunctionTemplate, Deduced, NumExplicitlySpecified, Specialization, Info,
47484751
&OriginalCallArgs, PartialOverloading, PartialOrdering,
4749-
[&, CallingCtx]() {
4752+
[&, CallingCtx](bool NonInstOnly) {
47504753
ContextRAII SavedContext(*this, CallingCtx);
4751-
return CheckNonDependent(ParamTypesForArgChecking);
4754+
return CheckNonDependent(ParamTypesForArgChecking, NonInstOnly);
47524755
});
47534756
});
47544757
return Result;

clang/test/SemaTemplate/concepts-recursive-inst.cpp

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,172 @@ namespace GH60323 {
143143
Size().sizeparens(i);
144144
}
145145
}
146+
147+
namespace CWG2369_Regressions {
148+
149+
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109397
150+
namespace GCC_103997 {
151+
152+
template<typename _type, typename _stream>
153+
concept streamable = requires(_stream &s, _type &&v) {
154+
s << static_cast<_type &&>(v);
155+
};
156+
157+
struct type_a {
158+
template<typename _arg>
159+
type_a &operator<<(_arg &&) {
160+
// std::clog << "type_a" << std::endl;
161+
return *this;
162+
}
163+
};
164+
165+
struct type_b {
166+
type_b &operator<<(type_a const &) {
167+
// std::clog << "type_b" << std::endl;
168+
return *this;
169+
}
170+
};
171+
172+
struct type_c {
173+
type_b b;
174+
template<typename _arg>
175+
requires streamable<_arg, type_b>
176+
friend type_c &operator<<(type_c &c, _arg &&a) {
177+
// std::clog << "type_c" << std::endl;
178+
c.b << static_cast<_arg &&>(a);
179+
return c;
180+
}
181+
};
182+
183+
void foo() {
184+
type_a a;
185+
type_c c;
186+
a << c; // "type_a\n" (gcc gives error here)
187+
c << a; // "type_c\ntype_b\n"
188+
}
189+
190+
}
191+
192+
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108393
193+
namespace GCC_108393 {
194+
195+
template<class>
196+
struct iterator_traits
197+
{};
198+
199+
template<class T>
200+
requires requires(T __t, T __u) { __t == __u; }
201+
struct iterator_traits<T>
202+
{};
203+
204+
template<class T>
205+
concept C = requires { typename iterator_traits<T>::A; };
206+
207+
struct unreachable_sentinel_t
208+
{
209+
template<C _Iter>
210+
friend constexpr bool operator==(unreachable_sentinel_t, const _Iter&) noexcept;
211+
};
212+
213+
template<class T>
214+
struct S
215+
{};
216+
217+
static_assert(!C<S<unreachable_sentinel_t>>);
218+
219+
}
220+
221+
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107429
222+
namespace GCC_107429 {
223+
224+
struct tag_foo { } inline constexpr foo;
225+
struct tag_bar { } inline constexpr bar;
226+
227+
template<typename... T>
228+
auto f(tag_foo, T... x)
229+
{
230+
return (x + ...);
231+
}
232+
233+
template<typename... T>
234+
concept fooable = requires (T... x) { f(foo, x...); };
235+
236+
template<typename... T> requires (fooable<T...>)
237+
auto f(tag_bar, T... x)
238+
{
239+
return f(foo, x...);
240+
}
241+
242+
auto test()
243+
{
244+
return f(bar, 1, 2, 3);
245+
}
246+
247+
}
248+
249+
namespace GCC_99599 {
250+
251+
struct foo_tag {};
252+
struct bar_tag {};
253+
254+
template <class T>
255+
concept fooable = requires(T it) {
256+
invoke_tag(foo_tag{}, it); // <-- here
257+
};
258+
259+
template <class T> auto invoke_tag(foo_tag, T in) { return in; }
260+
261+
template <fooable T> auto invoke_tag(bar_tag, T it) { return it; }
262+
263+
int main() {
264+
// Neither line below compiles in GCC 11, independently of the other
265+
return invoke_tag(foo_tag{}, 2) + invoke_tag(bar_tag{}, 2);
266+
}
267+
268+
}
269+
270+
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99599#c22
271+
namespace GCC_99599_2 {
272+
273+
template<typename T> class indirect {
274+
public:
275+
template<typename U> requires
276+
requires (const T& t, const U& u) { t == u; }
277+
friend constexpr bool operator==(const indirect&, const U&) { return false; }
278+
279+
private:
280+
T* _M_ptr{};
281+
};
282+
283+
indirect<int> i;
284+
bool b = i == 1;
285+
286+
}
287+
288+
namespace FAILED_GCC_110160 {
289+
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110160
290+
// Current heuristic FAILED; GCC trunk also failed
291+
// https://godbolt.org/z/r3Pz9Tehz
292+
#if 0
293+
#include <sstream>
294+
#include <string>
295+
296+
template <class T>
297+
concept StreamCanReceiveString = requires(T& t, std::string s) {
298+
{ t << s };
299+
};
300+
301+
struct NotAStream {};
302+
struct UnrelatedType {};
303+
304+
template <StreamCanReceiveString S>
305+
S& operator<<(S& s, UnrelatedType) {
306+
return s;
307+
}
308+
309+
static_assert(!StreamCanReceiveString<NotAStream>);
310+
311+
static_assert(StreamCanReceiveString<std::stringstream>);
312+
#endif
313+
}
314+
}

0 commit comments

Comments
 (0)