[libcxx-commits] [clang] [libcxx] [Clang] Add __common_type builtin (PR #99473)
Nikolas Klauser via libcxx-commits
libcxx-commits at lists.llvm.org
Fri Jul 19 07:43:13 PDT 2024
================
@@ -3058,6 +3058,141 @@ void Sema::NoteAllFoundTemplates(TemplateName Name) {
}
}
+static std::optional<QualType> commonTypeImpl(Sema &S,
+ TemplateName BaseTemplate,
+ SourceLocation TemplateLoc,
+ ArrayRef<TemplateArgument> Ts) {
+ auto lookUpCommonType = [&](TemplateArgument T1,
+ TemplateArgument T2) -> std::optional<QualType> {
+ // Don't bother looking for other specializations if both types are
+ // builtins - users aren't allowed to specialize for them
+ if (T1.getAsType()->isBuiltinType() && T2.getAsType()->isBuiltinType())
+ return commonTypeImpl(S, BaseTemplate, TemplateLoc, {T1, T2});
+
+ TemplateArgumentListInfo Args;
+ Args.addArgument(TemplateArgumentLoc(
+ T1, S.Context.getTrivialTypeSourceInfo(T1.getAsType())));
+ Args.addArgument(TemplateArgumentLoc(
+ T2, S.Context.getTrivialTypeSourceInfo(T2.getAsType())));
+ QualType BaseTemplateInst =
+ S.CheckTemplateIdType(BaseTemplate, TemplateLoc, Args);
+ if (S.RequireCompleteType(TemplateLoc, BaseTemplateInst,
+ diag::err_incomplete_type))
+ return std::nullopt;
+ if (QualType Type = S.getTypeMember("type", BaseTemplateInst);
+ !Type.isNull()) {
+ return Type;
+ }
+ return std::nullopt;
+ };
+
+ // Note A: For the common_type trait applied to a template parameter pack T of
+ // types, the member type shall be either defined or not present as follows:
+ switch (Ts.size()) {
+
+ // If sizeof...(T) is zero, there shall be no member type.
+ case 0:
+ return std::nullopt;
+
+ // If sizeof...(T) is one, let T0 denote the sole type constituting the
+ // pack T. The member typedef-name type shall denote the same type, if any, as
+ // common_type_t<T0, T0>; otherwise there shall be no member type.
+ case 1:
+ return lookUpCommonType(Ts[0], Ts[0]);
+
+ // If sizeof...(T) is two, let the first and second types constituting T be
+ // denoted by T1 and T2, respectively, and let D1 and D2 denote the same types
+ // as decay_t<T1> and decay_t<T2>, respectively.
+ case 2: {
+ QualType T1 = Ts[0].getAsType();
+ QualType T2 = Ts[1].getAsType();
+ QualType D1 = S.BuiltinDecay(T1, {});
+ QualType D2 = S.BuiltinDecay(T2, {});
+
+ // If is_same_v<T1, D1> is false or is_same_v<T2, D2> is false, let C denote
+ // the same type, if any, as common_type_t<D1, D2>.
+ if (!S.Context.hasSameType(T1, D1) || !S.Context.hasSameType(T2, D2)) {
+ return lookUpCommonType(D1, D2);
+ }
+
+ // Otherwise, if decay_t<decltype(false ? declval<D1>() : declval<D2>())>
+ // denotes a valid type, let C denote that type.
+ {
+ auto CheckConditionalOperands =
+ [&](bool ConstRefQual) -> std::optional<QualType> {
+ EnterExpressionEvaluationContext UnevaluatedContext(
+ S, Sema::ExpressionEvaluationContext::Unevaluated);
+ Sema::SFINAETrap SFINAE(S, /*AccessCheckingSFINAE=*/true);
+ Sema::ContextRAII TUContext(S, S.Context.getTranslationUnitDecl());
+
+ // false
+ OpaqueValueExpr CondExpr({}, S.Context.BoolTy,
+ ExprValueKind::VK_PRValue);
+ ExprResult Cond = &CondExpr;
+
+ auto EVK =
+ ConstRefQual ? ExprValueKind::VK_LValue : ExprValueKind::VK_PRValue;
+ if (ConstRefQual) {
+ D1.addConst();
+ D2.addConst();
+ }
+
+ // declval<D1>()
+ OpaqueValueExpr LHSExpr(TemplateLoc, D1, EVK);
+ ExprResult LHS = &LHSExpr;
+
+ // declval<D2>()
+ OpaqueValueExpr RHSExpr(TemplateLoc, D2, EVK);
+ ExprResult RHS = &RHSExpr;
+
+ ExprValueKind VK = VK_PRValue;
+ ExprObjectKind OK = OK_Ordinary;
+
+ // decltype(false ? declval<D1>() : declval<D2>())
+ QualType Result =
+ S.CheckConditionalOperands(Cond, LHS, RHS, VK, OK, TemplateLoc);
+
+ if (Result.isNull() || SFINAE.hasErrorOccurred())
+ return std::nullopt;
+
+ // decay_t<decltype(false ? declval<D1>() : declval<D2>())>
+ return S.BuiltinDecay(Result, TemplateLoc);
+ };
+
+ if (auto Res = CheckConditionalOperands(false))
+ return Res;
+
+ // Let:
+ // CREF(A) be add_lvalue_reference_t<const remove_reference_t<A>>,
+ // COND-RES(X, Y) be
+ // decltype(false ? declval<X(&)()>()() : declval<Y(&)()>()()).
+
+ // C++20 only
+ // Otherwise, if COND-RES(CREF(D1), CREF(D2)) denotes a type, let C denote
+ // the type decay_t<COND-RES(CREF(D1), CREF(D2))>.
+ if (!S.Context.getLangOpts().CPlusPlus20)
+ return std::nullopt;
+ return CheckConditionalOperands(true);
+ }
+ }
+
+ // If sizeof...(T) is greater than two, let T1, T2, and R, respectively,
+ // denote the first, second, and (pack of) remaining types constituting T. Let
+ // C denote the same type, if any, as common_type_t<T1, T2>. If there is such
+ // a type C, the member typedef-name type shall denote the same type, if any,
+ // as common_type_t<C, R...>. Otherwise, there shall be no member type.
+ default: {
+ std::optional<QualType> Result = Ts[Ts.size() - 1].getAsType();
+ for (size_t i = Ts.size() - 1; i != 0; --i) {
+ Result = lookUpCommonType(Ts[i - 1].getAsType(), *Result);
----------------
philnik777 wrote:
I'm not sure I understand your question here. `lookUpCommonType` will either call `commonTypeImpl` directly if it's known to be safe, or it will instantiate `common_type<T1, T2>`, which will either be a user specialization or the base template, which then goes into this function again. So in the end we are calling this recursively if there isn't a user specialization of `common_type`.
https://github.com/llvm/llvm-project/pull/99473
More information about the libcxx-commits
mailing list