From f4b2be333b8c6c27f29d7049860c14dea9c01efd Mon Sep 17 00:00:00 2001 From: Dan Katz Date: Thu, 13 Jun 2024 13:46:27 -0400 Subject: [PATCH] Implement P3096R1. Closes issue #52. --- clang/lib/Sema/Metafunctions.cpp | 182 ++++++++++++++++-- libcxx/include/experimental/meta | 40 +++- .../reflection/p3096-fn-parameters.pass.cpp | 99 ++++++++-- 3 files changed, 284 insertions(+), 37 deletions(-) diff --git a/clang/lib/Sema/Metafunctions.cpp b/clang/lib/Sema/Metafunctions.cpp index c653f94260bc43..df5e0c02a72adf 100644 --- a/clang/lib/Sema/Metafunctions.cpp +++ b/clang/lib/Sema/Metafunctions.cpp @@ -317,10 +317,27 @@ static bool has_consistent_name(APValue &Result, Sema &S, EvalFn Evaluator, QualType ResultTy, SourceRange Range, ArrayRef Args); +static bool has_ellipsis_parameter(APValue &Result, Sema &S, EvalFn Evaluator, + QualType ResultTy, SourceRange Range, + ArrayRef Args); + static bool has_default_argument(APValue &Result, Sema &S, EvalFn Evaluator, QualType ResultTy, SourceRange Range, ArrayRef Args); +static bool is_explicit_object_parameter(APValue &Result, Sema &S, + EvalFn Evaluator, QualType ResultTy, + SourceRange Range, + ArrayRef Args); + +static bool is_function_parameter(APValue &Result, Sema &S, EvalFn Evaluator, + QualType ResultTy, SourceRange Range, + ArrayRef Args); + +static bool return_type_of(APValue &Result, Sema &S, EvalFn Evaluator, + QualType ResultTy, SourceRange Range, + ArrayRef Args); + // ----------------------------------------------------------------------------- // Metafunction table // @@ -342,7 +359,7 @@ static constexpr Metafunction Metafunctions[] = { { Metafunction::MFRK_metaInfo, 1, 1, map_decl_to_entity }, // exposed metafunctions - { Metafunction::MFRK_spliceFromArg, 3, 3, name_of }, + { Metafunction::MFRK_spliceFromArg, 4, 4, name_of }, { Metafunction::MFRK_spliceFromArg, 3, 3, display_name_of }, { Metafunction::MFRK_sourceLoc, 1, 1, source_location_of }, { Metafunction::MFRK_metaInfo, 1, 1, type_of }, @@ -404,7 +421,11 @@ static constexpr Metafunction Metafunctions[] = { // P3096 metafunction extensions { Metafunction::MFRK_metaInfo, 3, 3, get_ith_parameter_of }, { Metafunction::MFRK_bool, 1, 1, has_consistent_name }, + { Metafunction::MFRK_bool, 1, 1, has_ellipsis_parameter }, { Metafunction::MFRK_bool, 1, 1, has_default_argument }, + { Metafunction::MFRK_bool, 1, 1, is_explicit_object_parameter }, + { Metafunction::MFRK_bool, 1, 1, is_function_parameter }, + { Metafunction::MFRK_metaInfo, 1, 1, return_type_of }, }; constexpr const unsigned NumMetafunctions = sizeof(Metafunctions) / sizeof(Metafunction); @@ -532,6 +553,25 @@ static void getTypeName(std::string &Result, ASTContext &C, QualType QT, encodeName(Result, QT.getAsString(PP), BasicOnly); } +static bool parameterHasConsistentName(ParmVarDecl *PVD) { + StringRef FirstNameSeen = PVD->getName(); + unsigned ParamIdx = PVD->getFunctionScopeIndex(); + + while (PVD) { + FunctionDecl *FD = cast(PVD->getDeclContext()); + FD = FD->getPreviousDecl(); + if (!FD) + return true; + + PVD = FD->getParamDecl(ParamIdx); + assert(PVD); + if (StringRef Name = PVD->getName(); + Name.size() > 0 && Name != FirstNameSeen) + return false; + } + return true; +} + static void getDeclName(std::string &Result, ASTContext &C, Decl *D, bool BasicOnly) { PrintingPolicy PP = C.getPrintingPolicy(); @@ -1341,6 +1381,14 @@ bool name_of(APValue &Result, Sema &S, EvalFn Evaluator, QualType ResultTy, IsUtf8 = Scratch.getInt().getBoolValue(); } + bool EnforceConsistent; + { + APValue Scratch; + if (!Evaluator(Scratch, Args[3], true)) + return true; + EnforceConsistent = Scratch.getInt().getBoolValue(); + } + std::string Name; switch (R.getReflection().getKind()) { case ReflectionValue::RK_type: { @@ -1348,6 +1396,10 @@ bool name_of(APValue &Result, Sema &S, EvalFn Evaluator, QualType ResultTy, return !Evaluator(Result, makeCString(Name, S.Context, IsUtf8), true); } case ReflectionValue::RK_declaration: { + if (auto *PVD = dyn_cast(R.getReflectedDecl()); + EnforceConsistent && PVD && !parameterHasConsistentName(PVD)) + return true; + getDeclName(Name, S.Context, R.getReflectedDecl(), !IsUtf8); return !Evaluator(Result, makeCString(Name, S.Context, IsUtf8), true); } @@ -1486,8 +1538,11 @@ bool type_of(APValue &Result, Sema &S, EvalFn Evaluator, QualType ResultTy, } case ReflectionValue::RK_declaration: { ValueDecl *VD = cast(R.getReflectedDecl()); - QualType QT = desugarType(VD->getType(), /*UnwrapAliases=*/false, - /*DropCV=*/false, /*DropRefs=*/false); + + bool UnwrapAliases = isa(VD); + bool DropCV = isa(VD); + QualType QT = desugarType(VD->getType(), UnwrapAliases, DropCV, + /*DropRefs=*/false); return SetAndSucceed(Result, makeReflection(QT)); } case ReflectionValue::RK_base_specifier: { @@ -3947,24 +4002,43 @@ bool has_consistent_name(APValue &Result, Sema &S, EvalFn Evaluator, return true; case ReflectionValue::RK_declaration: { if (auto *PVD = dyn_cast(R.getReflectedDecl())) { - StringRef FirstNameSeen = PVD->getName(); - ParmVarDecl *FirstParmSeen = PVD; - - bool Unique = true; - while (PVD) { - FunctionDecl *FD = cast(PVD->getDeclContext()); - FD = FD->getPreviousDecl(); - if (!FD) - break; + bool Consistent = parameterHasConsistentName(PVD); + return SetAndSucceed(Result, makeBool(S.Context, Consistent)); + } + return true; + } + } + llvm_unreachable("unknown reflection kind"); +} - PVD = FD->getParamDecl(FirstParmSeen->getFunctionScopeIndex()); - assert(PVD); - if (PVD->getName() != FirstNameSeen) { - Unique = false; - break; - } - } - return SetAndSucceed(Result, makeBool(S.Context, Unique)); +bool has_ellipsis_parameter(APValue &Result, Sema &S, EvalFn Evaluator, + QualType ResultTy, SourceRange Range, + ArrayRef Args) { + assert(Args[0]->getType()->isReflectionType()); + assert(ResultTy == S.Context.BoolTy); + + APValue R; + if (!Evaluator(R, Args[0], true)) + return true; + + switch (R.getReflection().getKind()) { + case ReflectionValue::RK_null: + case ReflectionValue::RK_expr_result: + case ReflectionValue::RK_template: + case ReflectionValue::RK_namespace: + case ReflectionValue::RK_base_specifier: + case ReflectionValue::RK_data_member_spec: + return true; + case ReflectionValue::RK_type: + if (auto *FPT = dyn_cast(R.getReflectedType())) { + bool HasEllipsis = FPT->isVariadic(); + return SetAndSucceed(Result, makeBool(S.Context, HasEllipsis)); + } + return true; + case ReflectionValue::RK_declaration: { + if (auto *FD = dyn_cast(R.getReflectedDecl())) { + bool HasEllipsis = FD->getEllipsisLoc().isValid(); + return SetAndSucceed(Result, makeBool(S.Context, HasEllipsis)); } return true; } @@ -4001,5 +4075,73 @@ bool has_default_argument(APValue &Result, Sema &S, EvalFn Evaluator, llvm_unreachable("unknown reflection kind"); } +bool is_explicit_object_parameter(APValue &Result, Sema &S, EvalFn Evaluator, + QualType ResultTy, SourceRange Range, + ArrayRef Args) { + assert(Args[0]->getType()->isReflectionType()); + assert(ResultTy == S.Context.BoolTy); + + APValue R; + if (!Evaluator(R, Args[0], true)) + return true; + + bool result = false; + if (R.getReflection().getKind() == ReflectionValue::RK_declaration) { + if (auto *PVD = dyn_cast(R.getReflectedDecl())) + result = PVD->isExplicitObjectParameter(); + } + return SetAndSucceed(Result, makeBool(S.Context, result)); +} + +bool is_function_parameter(APValue &Result, Sema &S, EvalFn Evaluator, + QualType ResultTy, SourceRange Range, + ArrayRef Args) { + assert(Args[0]->getType()->isReflectionType()); + assert(ResultTy == S.Context.BoolTy); + + APValue R; + if (!Evaluator(R, Args[0], true)) + return true; + + bool result = false; + if (R.getReflection().getKind() == ReflectionValue::RK_declaration) { + result = isa(R.getReflectedDecl()); + } + return SetAndSucceed(Result, makeBool(S.Context, result)); +} + +bool return_type_of(APValue &Result, Sema &S, EvalFn Evaluator, + QualType ResultTy, SourceRange Range, + ArrayRef Args) { + assert(Args[0]->getType()->isReflectionType()); + assert(ResultTy == S.Context.MetaInfoTy); + + APValue R; + if (!Evaluator(R, Args[0], true)) + return true; + + switch (R.getReflection().getKind()) { + case ReflectionValue::RK_null: + case ReflectionValue::RK_expr_result: + case ReflectionValue::RK_template: + case ReflectionValue::RK_namespace: + case ReflectionValue::RK_base_specifier: + case ReflectionValue::RK_data_member_spec: + return true; + case ReflectionValue::RK_type: + if (auto *FPT = dyn_cast(R.getReflectedType())) + return SetAndSucceed(Result, makeReflection(FPT->getReturnType())); + + return true; + case ReflectionValue::RK_declaration: { + if (auto *FD = dyn_cast(R.getReflectedDecl()); + FD && !isa(FD) && !isa(FD)) + return SetAndSucceed(Result, makeReflection(FD->getReturnType())); + + return true; + } + } + llvm_unreachable("unknown reflection kind"); +} } // end namespace clang diff --git a/libcxx/include/experimental/meta b/libcxx/include/experimental/meta index 9862d515f5894c..1f69d8ae3368c8 100644 --- a/libcxx/include/experimental/meta +++ b/libcxx/include/experimental/meta @@ -143,7 +143,13 @@ consteval auto alignment_of(info) -> size_t; // function parameters (P3096) consteval auto parameters_of(info) -> vector; consteval auto has_consistent_name(info) -> bool; +template + consteval auto any_name_of(info) -> string_view; +consteval auto has_ellipsis_parameter(info) -> bool; consteval auto has_default_argument(info) -> bool; +consteval auto is_explicit_object_parameter(info) -> bool; +consteval auto is_function_parameter(info) -> bool; +consteval auto return_type_of(info) -> info; } // namespace reflection_v2 } // namespace meta @@ -249,7 +255,11 @@ enum : unsigned { // P3096 metafunctions __metafn_get_ith_parameter_of, __metafn_has_consistent_name, + __metafn_has_ellipsis_parameter, __metafn_has_default_argument, + __metafn_is_explicit_object_parameter, + __metafn_is_function_parameter, + __metafn_return_type_of, }; } // namespace detail @@ -484,7 +494,8 @@ consteval auto name_of(info r) -> T { return __metafunction(detail::__metafn_name_of, ^const typename T::value_type *, r, - ^T == dealias(^std::u8string_view)); + ^T == dealias(^std::u8string_view), + /*EnforceConsistent=*/true); } // Returns a name for the reflected entity. @@ -1040,10 +1051,37 @@ consteval auto has_consistent_name(info r) -> bool { return __metafunction(detail::__metafn_has_consistent_name, r); } +template +consteval auto any_name_of(info r) -> T { + static_assert(^T == dealias(^u8string_view) || ^T == dealias(^string_view), + "Name type must be 'string_view' or 'u8string_view'"); + + return __metafunction(detail::__metafn_name_of, + ^const typename T::value_type *, r, + ^T == dealias(^std::u8string_view), + /*EnforceConsistent=*/false); +} + +consteval auto has_ellipsis_parameter(info r) -> bool { + return __metafunction(detail::__metafn_has_ellipsis_parameter, r); +} + consteval auto has_default_argument(info r) -> bool { return __metafunction(detail::__metafn_has_default_argument, r); } +consteval auto is_explicit_object_parameter(info r) -> bool { + return __metafunction(detail::__metafn_is_explicit_object_parameter, r); +} + +consteval auto is_function_parameter(info r) -> bool { + return __metafunction(detail::__metafn_is_function_parameter, r); +} + +consteval auto return_type_of(info r) -> info { + return __metafunction(detail::__metafn_return_type_of, r); +} + #endif // __has_feature(parameter_reflection) template diff --git a/libcxx/test/std/experimental/reflection/p3096-fn-parameters.pass.cpp b/libcxx/test/std/experimental/reflection/p3096-fn-parameters.pass.cpp index c44196f288a94d..464bf2fbe0cdcf 100644 --- a/libcxx/test/std/experimental/reflection/p3096-fn-parameters.pass.cpp +++ b/libcxx/test/std/experimental/reflection/p3096-fn-parameters.pass.cpp @@ -31,6 +31,10 @@ void tfn(Ts...); static_assert(parameters_of(^fn).size() == 0); static_assert(parameters_of(^tfn<>).size() == 0); +static_assert(!has_ellipsis_parameter(^tfn<>)); +static_assert(!has_ellipsis_parameter(type_of(^tfn<>))); +static_assert(return_type_of(^fn) == ^void); +static_assert(return_type_of(^tfn<>) == ^void); } // namespace with_no_arguments // ==================== @@ -49,14 +53,20 @@ static_assert(type_of(parameters_of(^fn)[0]) == ^int); static_assert(name_of(parameters_of(^fn)[0]) == u8"a"); static_assert(has_consistent_name(parameters_of(^fn)[0])); static_assert(!has_default_argument(parameters_of(^fn)[0])); +static_assert(!is_explicit_object_parameter(parameters_of(^fn)[0])); static_assert(type_of(parameters_of(^fn)[1]) == ^bool); static_assert(name_of(parameters_of(^fn)[1]) == u8"b"); static_assert(has_consistent_name(parameters_of(^fn)[1])); static_assert(!has_default_argument(parameters_of(^fn)[1])); +static_assert(!is_explicit_object_parameter(parameters_of(^fn)[1])); static_assert(type_of(parameters_of(^fn)[2]) == ^const S &); static_assert(name_of(parameters_of(^fn)[2]) == u8"s"); static_assert(has_consistent_name(parameters_of(^fn)[2])); static_assert(!has_default_argument(parameters_of(^fn)[2])); +static_assert(!is_explicit_object_parameter(parameters_of(^fn)[2])); +static_assert(!has_ellipsis_parameter(^fn)); +static_assert(!has_ellipsis_parameter(type_of(^fn))); +static_assert(return_type_of(^fn) == ^void); } // namespace with_fixed_arguments // ===================== @@ -68,24 +78,23 @@ struct Cls { Cls(int a); ~Cls(); - void fn(int a, bool b = false); - void fn2(this Cls &self, int); - static void sfn(int a); + int fn(int a, bool b = false); + bool fn2(this Cls &self, int, ...); + static Cls &sfn(int a, ...); }; -constexpr auto ctor = [] { - return members_of(^Cls, std::meta::is_constructor)[0]; -}(); +constexpr auto ctor = members_of(^Cls, std::meta::is_constructor)[0]; static_assert(parameters_of(ctor).size() == 1); static_assert(type_of(parameters_of(ctor)[0]) == ^int); static_assert(name_of(parameters_of(ctor)[0]) == u8"a"); static_assert(has_consistent_name(parameters_of(ctor)[0])); static_assert(!has_default_argument(parameters_of(ctor)[0])); +static_assert(!is_explicit_object_parameter(parameters_of(ctor)[0])); +static_assert(!has_ellipsis_parameter(ctor)); -constexpr auto dtor = [] { - return members_of(^Cls, std::meta::is_destructor)[0]; -}(); +constexpr auto dtor = members_of(^Cls, std::meta::is_destructor)[0]; static_assert(parameters_of(dtor).size() == 0); +static_assert(!has_ellipsis_parameter(dtor)); static_assert(parameters_of(^Cls::fn).size() == 2); static_assert(parameters_of(type_of(^Cls::fn)) == std::vector {^int, ^bool}); @@ -93,10 +102,15 @@ static_assert(type_of(parameters_of(^Cls::fn)[0]) == ^int); static_assert(name_of(parameters_of(^Cls::fn)[0]) == u8"a"); static_assert(has_consistent_name(parameters_of(^Cls::fn)[0])); static_assert(!has_default_argument(parameters_of(^Cls::fn)[0])); +static_assert(!is_explicit_object_parameter(parameters_of(^Cls::fn)[0])); static_assert(type_of(parameters_of(^Cls::fn)[1]) == ^bool); static_assert(name_of(parameters_of(^Cls::fn)[1]) == u8"b"); static_assert(has_consistent_name(parameters_of(^Cls::fn)[1])); static_assert(has_default_argument(parameters_of(^Cls::fn)[1])); +static_assert(!is_explicit_object_parameter(parameters_of(^Cls::fn)[1])); +static_assert(!has_ellipsis_parameter(^Cls::fn)); +static_assert(!has_ellipsis_parameter(type_of(^Cls::fn))); +static_assert(return_type_of(^Cls::fn) == ^int); static_assert(parameters_of(^Cls::fn2).size() == 2); static_assert(parameters_of(type_of(^Cls::fn2)) == std::vector {^Cls &, ^int}); @@ -104,10 +118,15 @@ static_assert(type_of(parameters_of(^Cls::fn2)[0]) == ^Cls &); static_assert(name_of(parameters_of(^Cls::fn2)[0]) == u8"self"); static_assert(has_consistent_name(parameters_of(^Cls::fn2)[0])); static_assert(!has_default_argument(parameters_of(^Cls::fn2)[0])); +static_assert(is_explicit_object_parameter(parameters_of(^Cls::fn2)[0])); static_assert(type_of(parameters_of(^Cls::fn2)[1]) == ^int); static_assert(name_of(parameters_of(^Cls::fn2)[1]) == u8""); static_assert(has_consistent_name(parameters_of(^Cls::fn2)[1])); static_assert(!has_default_argument(parameters_of(^Cls::fn2)[1])); +static_assert(has_ellipsis_parameter(^Cls::fn2)); +static_assert(has_ellipsis_parameter(type_of(^Cls::fn2))); +static_assert(!is_explicit_object_parameter(parameters_of(^Cls::fn2)[1])); +static_assert(return_type_of(^Cls::fn2) == ^bool); static_assert(parameters_of(^Cls::sfn).size() == 1); static_assert(parameters_of(type_of(^Cls::sfn)) == std::vector {^int}); @@ -115,6 +134,10 @@ static_assert(type_of(parameters_of(^Cls::sfn)[0]) == ^int); static_assert(name_of(parameters_of(^Cls::sfn)[0]) == u8"a"); static_assert(has_consistent_name(parameters_of(^Cls::sfn)[0])); static_assert(!has_default_argument(parameters_of(^Cls::sfn)[0])); +static_assert(!is_explicit_object_parameter(parameters_of(^Cls::sfn)[0])); +static_assert(has_ellipsis_parameter(^Cls::sfn)); +static_assert(has_ellipsis_parameter(type_of(^Cls::sfn))); +static_assert(return_type_of(^Cls::sfn) == ^Cls&); } // namespace with_member_functions // =========================== @@ -123,7 +146,7 @@ static_assert(!has_default_argument(parameters_of(^Cls::sfn)[0])); namespace with_template_instantiation { template -void fn(Ts &&... ts); +std::tuple fn(Ts &&... ts); template // check with dependent names. consteval bool check() { @@ -135,14 +158,20 @@ consteval bool check() { static_assert(name_of(parameters_of(Fn)[0]) == u8"ts"); static_assert(has_consistent_name(parameters_of(Fn)[0])); static_assert(!has_default_argument(parameters_of(Fn)[0])); + static_assert(!is_explicit_object_parameter(parameters_of(Fn)[0])); static_assert(type_of(parameters_of(Fn)[1]) == ^char &&); static_assert(name_of(parameters_of(Fn)[1]) == u8"ts"); static_assert(has_consistent_name(parameters_of(Fn)[1])); static_assert(!has_default_argument(parameters_of(Fn)[1])); + static_assert(!is_explicit_object_parameter(parameters_of(Fn)[1])); static_assert(type_of(parameters_of(Fn)[2]) == ^bool &&); static_assert(name_of(parameters_of(Fn)[2]) == u8"ts"); static_assert(has_consistent_name(parameters_of(Fn)[2])); static_assert(!has_default_argument(parameters_of(Fn)[2])); + static_assert(!is_explicit_object_parameter(parameters_of(Fn)[2])); + static_assert(!has_ellipsis_parameter(Fn)); + static_assert(!has_ellipsis_parameter(type_of(Fn))); + static_assert(return_type_of(Fn) == ^std::tuple); return true; } @@ -165,10 +194,14 @@ static_assert(type_of(parameters_of(^fn)[0]) == ^int); static_assert(name_of(parameters_of(^fn)[0]) == u8"a"); static_assert(has_consistent_name(parameters_of(^fn)[0])); static_assert(!has_default_argument(parameters_of(^fn)[0])); +static_assert(!is_explicit_object_parameter(parameters_of(^fn)[0])); static_assert(type_of(parameters_of(^fn)[1]) == ^bool); static_assert(name_of(parameters_of(^fn)[1]) == u8"b"); static_assert(has_consistent_name(parameters_of(^fn)[1])); static_assert(has_default_argument(parameters_of(^fn)[1])); +static_assert(!is_explicit_object_parameter(parameters_of(^fn)[1])); +static_assert(!has_ellipsis_parameter(^fn)); +static_assert(!has_ellipsis_parameter(type_of(^fn))); } // namespace with_default_arguments // ==================== @@ -177,21 +210,55 @@ static_assert(has_default_argument(parameters_of(^fn)[1])); namespace with_ambiguous_names { void fn(int a1, bool b, char c1); -void fn(int a2, bool b, char c2); +static_assert(has_consistent_name(parameters_of(^fn)[2])); + +void fn(int a2, bool, char c2); void fn(int a3, bool b, char c1); static_assert(parameters_of(^fn).size() == 3); static_assert(parameters_of(type_of(^fn)) == std::vector {^int, ^bool, ^char}); -static_assert(name_of(parameters_of(^fn)[0]) == u8"a1" || - name_of(parameters_of(^fn)[0]) == u8"a2" || - name_of(parameters_of(^fn)[0]) == u8"a3"); +static_assert(any_name_of(parameters_of(^fn)[0]) == u8"a1" || + any_name_of(parameters_of(^fn)[0]) == u8"a2" || + any_name_of(parameters_of(^fn)[0]) == u8"a3"); static_assert(!has_consistent_name(parameters_of(^fn)[0])); +static_assert(any_name_of(parameters_of(^fn)[1]) == u8"b"); static_assert(name_of(parameters_of(^fn)[1]) == u8"b"); static_assert(has_consistent_name(parameters_of(^fn)[1])); -static_assert(name_of(parameters_of(^fn)[2]) == u8"c1" || - name_of(parameters_of(^fn)[2]) == u8"c2"); +static_assert(any_name_of(parameters_of(^fn)[2]) == u8"c1" || + any_name_of(parameters_of(^fn)[2]) == u8"c2"); static_assert(!has_consistent_name(parameters_of(^fn)[2])); } // namespace with_ambiguous_names + // ================= + // type_and_cv_decay + // ================= + +namespace with_ambiguous_types { +using Alias = int; +void fn(Alias, const bool, char [], char &); + +static_assert(type_of(parameters_of(^fn)[0]) == ^int); +static_assert(type_of(parameters_of(^fn)[1]) == ^bool); +static_assert(type_of(parameters_of(^fn)[2]) == ^char *); +static_assert(type_of(parameters_of(^fn)[3]) == ^char &); +} // namespace with_ambiguous_types + + // ============================ + // identify_function_parameters + // ============================ + +namespace identify_function_parameters { +void fn(int a, bool &b, std::string *, ...); + +static_assert(is_function_parameter(parameters_of(^fn)[0])); +static_assert(is_function_parameter(parameters_of(^fn)[1])); +static_assert(is_function_parameter(parameters_of(^fn)[2])); + +static_assert(!is_function_parameter(^::)); +static_assert(!is_function_parameter(^int)); +static_assert(!is_function_parameter(^fn)); +static_assert(!is_function_parameter(std::meta::reflect_value(3))); +static_assert(has_ellipsis_parameter(type_of(^fn))); +} // namespace identify_function_parameters int main() { }