Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/declarator.c
Original file line number Diff line number Diff line change
Expand Up @@ -3329,6 +3329,13 @@ an error if a default argument expression is encountered.
*new_type_ptr, (a_routine_ptr)NULL);
scope_stack_top().decl_parse_state = state;
must_pop_function_prototype_scope = !state->for_requires_expr_params;
if (state->for_requires_expr_params && is_template_dependent_context()) {
/* Like its requirements (see scan_requires_expr), the parameter
declarations of a requires-expression in a template-dependent
context undergo substitution: Make sure the rescan information
needed for that is recorded. */
scope_stack_top().in_template_deduction_context = TRUE;
} /* if */
if (is_constructor && is_template_dependent_context()) {
/* A constructor in a template-dependent context may be used for
deduction even if it isn't a template itself, because of C++17
Expand Down
19 changes: 18 additions & 1 deletion src/exprutil.c
Original file line number Diff line number Diff line change
Expand Up @@ -4917,6 +4917,7 @@ in *bound_function_selector.
an_expr_node_ptr expr_copy = NULL;
a_boolean rescanned_case = FALSE;
a_boolean is_template_param_object = FALSE;
a_boolean is_template_param_lvalue = FALSE;
a_constant_ptr constant = local_constant();
a_constant_ptr alloc_con = NULL;
an_expr_rescan_info_entry_ptr eriep;
Expand Down Expand Up @@ -4997,6 +4998,21 @@ in *bound_function_selector.
} else {
/* The result is a constant, so return a constant operand. */
make_constant_operand(cp, operand);
if (constant_is(cp, ck_template_param) &&
is_operation_node(orig_expr) &&
node_operator_is(orig_expr, eok_lvalue) &&
orig_expr->variant.operation.operands == expr) {
/* A reference to a C++20 nontype template parameter of class
type that is still a template parameter after substitution
(e.g., when an enclosing template is instantiated): Keep it
an lvalue, as it was originally (see make_sym_constant_operand),
so that it can later be identified as an id-expression
designating the template parameter object. */
change_template_param_constant_operand_to_lvalue(
operand,
!is_template_dependent_type(cp->type));
is_template_param_lvalue = TRUE;
} /* if */
} /* if */
} else {
/* Return an expression operand. */
Expand All @@ -5020,7 +5036,8 @@ in *bound_function_selector.
/* Normal case: restore everything. */
restore_operand_info_from_expr_rescan_info_entry(operand, eriep);
} /* if */
if (is_template_param_object && orig_expr->extra.rescan_info != NULL) {
if ((is_template_param_object || is_template_param_lvalue) &&
orig_expr->extra.rescan_info != NULL) {
/* The rescan information on the template parameter constant was
saved before the reference was identified as an id-expression
(possibly parenthesized). That is recorded on the node that makes
Expand Down
24 changes: 24 additions & 0 deletions src/pending_changes/gh-462-requires-expr-param-decltype.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[GH #462]
decltype of template parameters in requires-expression parameter types

When a requires-expression appeared in a template other than a concept
definition (e.g., in a variable template initializer or a function template
body), decltype applied to a template parameter in the type of one of its
parameters was not substituted correctly. The parameter then got an
unusable type, so that any requirement using it in an operation (e.g., a
call) was considered unsatisfied. Similarly, in a member template of a class
template, decltype applied to a class-type template parameter of the class
template produced the const type of the template parameter object (rather
than the declared type of the template parameter) once the class template
was instantiated. That is now fixed. For example, with --c++20:

struct A { int operator()() { return 0; } };
template <auto F>
constexpr bool b = requires(decltype(F) f) { f(); };
static_assert(b<A{}>); // Previously failed.
template <auto F> struct S {
template <int I> static constexpr bool m() {
return requires(decltype(F) f) { f(); };
}
};
static_assert(S<A{}>::m<1>()); // Previously failed.
1 change: 1 addition & 0 deletions tests/tests/regressions/.gh-462.rto/default.1.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fe_only -DTEST_NUMBER=1 --c++20 Test_name.c
1 change: 1 addition & 0 deletions tests/tests/regressions/.gh-462.rto/default.2.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fe_only -DTEST_NUMBER=2 --c++20 --g++ --gnu_version=150200 Test_name.c
1 change: 1 addition & 0 deletions tests/tests/regressions/.gh-462.rto/default.3.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fe_only -DTEST_NUMBER=3 --c++20 --clang --clang_version=210000 Test_name.c
1 change: 1 addition & 0 deletions tests/tests/regressions/.gh-462.rto/default.4.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fe_only -DTEST_NUMBER=4 --ms_c++20 --microsoft_version=1950 Test_name.c
96 changes: 96 additions & 0 deletions tests/tests/regressions/gh-462.sft.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//type:fp
//options:--c++20:--c++20 --g++ --gnu_version=150200:--c++20 --clang --clang_version=210000:--ms_c++20 --microsoft_version=1950
//remark:decltype of a template parameter in a requires-expression parameter

// The parameter declarations of a requires-expression appearing outside of a
// concept definition did not record the information needed to substitute
// decltype applied to a template parameter. The substituted parameter type
// was therefore unusable, and any requirement using the parameter in an
// operation was considered unsatisfied. In a member template of a class
// template, decltype applied to a class-type template parameter of the
// enclosing class template also lost its id-expression form when the class
// template was instantiated, so the parameter got the const type of the
// template parameter object.

template <typename T, typename U> constexpr bool is_same = false;
template <typename T> constexpr bool is_same<T, T> = true;

struct NoConst {
int operator()() { return 0; }
};

// F is a const lvalue (a template parameter object), but decltype(F) is the
// declared type of the template parameter, which is not const.
template <auto F>
inline constexpr bool const_callable_v = requires { F(); };
template <auto F>
inline constexpr bool non_const_callable_v =
requires(decltype(F) f) { f(); };
static_assert(!const_callable_v<NoConst{}>);
static_assert(non_const_callable_v<NoConst{}>);

// A template parameter declared with the class type.
template <NoConst F>
constexpr bool typed_v = requires(decltype(F) f) { f(); };
static_assert(typed_v<NoConst{}>);

// The parameter has exactly the declared type.
template <auto F>
constexpr bool exact_type_v =
requires(decltype(F) f) { requires is_same<decltype(f), NoConst>; };
static_assert(exact_type_v<NoConst{}>);

// Declarators built on the decltype type.
template <auto F>
constexpr bool ref_v = requires(decltype(F) &f) { f(); };
static_assert(ref_v<NoConst{}>);
template <auto F>
constexpr bool ptr_v = requires(decltype(F) *f) { (*f)(); };
static_assert(ptr_v<NoConst{}>);

// The parenthesized form denotes the const template parameter object.
template <auto F>
constexpr bool paren_v = requires(decltype((F)) f) { f(); };
static_assert(!paren_v<NoConst{}>);

// A nontype template parameter of non-class type.
template <auto N>
constexpr bool int_v = requires(decltype(N) n) { n + 1; };
static_assert(int_v<42>);

// A requires-expression in a function template body.
template <auto F> constexpr bool in_function() {
return requires(decltype(F) f) { f(); };
}
static_assert(in_function<NoConst{}>());

// A requires-expression in a member of a class template.
template <auto F> struct S {
static constexpr bool value = requires(decltype(F) f) { f(); };
template <int I> static constexpr bool member_template() {
return requires(decltype(F) f, decltype(I) i) { f() + i; };
}
template <int I> static constexpr bool member_exact_type() {
return requires(decltype(F) f) {
requires is_same<decltype(f), NoConst>;
};
}
template <int I> static constexpr bool member_paren() {
return requires(decltype((F)) f) { f(); };
}
template <int I> static constexpr bool out_of_class();
};
template <auto F> template <int I>
constexpr bool S<F>::out_of_class() {
return requires(decltype(F) f) { f(); };
}
static_assert(S<NoConst{}>::value);
static_assert(S<NoConst{}>::member_template<1>());
static_assert(S<NoConst{}>::member_exact_type<1>());
static_assert(!S<NoConst{}>::member_paren<1>());
static_assert(S<NoConst{}>::out_of_class<1>());

// This already worked in a concept definition.
template <auto F>
concept NonConstCallable = requires(decltype(F) f) { f(); };
static_assert(NonConstCallable<NoConst{}>);
Loading