Skip to content

Commit dbe57a7

Browse files
decltype of template parameters in requires-expression parameter types [GH #462]
1 parent 7183cff commit dbe57a7

8 files changed

Lines changed: 149 additions & 1 deletion

File tree

‎src/declarator.c‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3329,6 +3329,13 @@ an error if a default argument expression is encountered.
33293329
*new_type_ptr, (a_routine_ptr)NULL);
33303330
scope_stack_top().decl_parse_state = state;
33313331
must_pop_function_prototype_scope = !state->for_requires_expr_params;
3332+
if (state->for_requires_expr_params && is_template_dependent_context()) {
3333+
/* Like its requirements (see scan_requires_expr), the parameter
3334+
declarations of a requires-expression in a template-dependent
3335+
context undergo substitution: Make sure the rescan information
3336+
needed for that is recorded. */
3337+
scope_stack_top().in_template_deduction_context = TRUE;
3338+
} /* if */
33323339
if (is_constructor && is_template_dependent_context()) {
33333340
/* A constructor in a template-dependent context may be used for
33343341
deduction even if it isn't a template itself, because of C++17

‎src/exprutil.c‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4917,6 +4917,7 @@ in *bound_function_selector.
49174917
an_expr_node_ptr expr_copy = NULL;
49184918
a_boolean rescanned_case = FALSE;
49194919
a_boolean is_template_param_object = FALSE;
4920+
a_boolean is_template_param_lvalue = FALSE;
49204921
a_constant_ptr constant = local_constant();
49214922
a_constant_ptr alloc_con = NULL;
49224923
an_expr_rescan_info_entry_ptr eriep;
@@ -4997,6 +4998,21 @@ in *bound_function_selector.
49974998
} else {
49984999
/* The result is a constant, so return a constant operand. */
49995000
make_constant_operand(cp, operand);
5001+
if (constant_is(cp, ck_template_param) &&
5002+
is_operation_node(orig_expr) &&
5003+
node_operator_is(orig_expr, eok_lvalue) &&
5004+
orig_expr->variant.operation.operands == expr) {
5005+
/* A reference to a C++20 nontype template parameter of class
5006+
type that is still a template parameter after substitution
5007+
(e.g., when an enclosing template is instantiated): Keep it
5008+
an lvalue, as it was originally (see make_sym_constant_operand),
5009+
so that it can later be identified as an id-expression
5010+
designating the template parameter object. */
5011+
change_template_param_constant_operand_to_lvalue(
5012+
operand,
5013+
!is_template_dependent_type(cp->type));
5014+
is_template_param_lvalue = TRUE;
5015+
} /* if */
50005016
} /* if */
50015017
} else {
50025018
/* Return an expression operand. */
@@ -5020,7 +5036,8 @@ in *bound_function_selector.
50205036
/* Normal case: restore everything. */
50215037
restore_operand_info_from_expr_rescan_info_entry(operand, eriep);
50225038
} /* if */
5023-
if (is_template_param_object && orig_expr->extra.rescan_info != NULL) {
5039+
if ((is_template_param_object || is_template_param_lvalue) &&
5040+
orig_expr->extra.rescan_info != NULL) {
50245041
/* The rescan information on the template parameter constant was
50255042
saved before the reference was identified as an id-expression
50265043
(possibly parenthesized). That is recorded on the node that makes
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[GH #462]
2+
decltype of template parameters in requires-expression parameter types
3+
4+
When a requires-expression appeared in a template other than a concept
5+
definition (e.g., in a variable template initializer or a function template
6+
body), decltype applied to a template parameter in the type of one of its
7+
parameters was not substituted correctly. The parameter then got an
8+
unusable type, so that any requirement using it in an operation (e.g., a
9+
call) was considered unsatisfied. Similarly, in a member template of a class
10+
template, decltype applied to a class-type template parameter of the class
11+
template produced the const type of the template parameter object (rather
12+
than the declared type of the template parameter) once the class template
13+
was instantiated. That is now fixed. For example, with --c++20:
14+
15+
struct A { int operator()() { return 0; } };
16+
template <auto F>
17+
constexpr bool b = requires(decltype(F) f) { f(); };
18+
static_assert(b<A{}>); // Previously failed.
19+
template <auto F> struct S {
20+
template <int I> static constexpr bool m() {
21+
return requires(decltype(F) f) { f(); };
22+
}
23+
};
24+
static_assert(S<A{}>::m<1>()); // Previously failed.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fe_only -DTEST_NUMBER=1 --c++20 Test_name.c
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fe_only -DTEST_NUMBER=2 --c++20 --g++ --gnu_version=150200 Test_name.c
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fe_only -DTEST_NUMBER=3 --c++20 --clang --clang_version=210000 Test_name.c
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fe_only -DTEST_NUMBER=4 --ms_c++20 --microsoft_version=1950 Test_name.c
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
//type:fp
2+
//options:--c++20:--c++20 --g++ --gnu_version=150200:--c++20 --clang --clang_version=210000:--ms_c++20 --microsoft_version=1950
3+
//remark:decltype of a template parameter in a requires-expression parameter
4+
5+
// The parameter declarations of a requires-expression appearing outside of a
6+
// concept definition did not record the information needed to substitute
7+
// decltype applied to a template parameter. The substituted parameter type
8+
// was therefore unusable, and any requirement using the parameter in an
9+
// operation was considered unsatisfied. In a member template of a class
10+
// template, decltype applied to a class-type template parameter of the
11+
// enclosing class template also lost its id-expression form when the class
12+
// template was instantiated, so the parameter got the const type of the
13+
// template parameter object.
14+
15+
template <typename T, typename U> constexpr bool is_same = false;
16+
template <typename T> constexpr bool is_same<T, T> = true;
17+
18+
struct NoConst {
19+
int operator()() { return 0; }
20+
};
21+
22+
// F is a const lvalue (a template parameter object), but decltype(F) is the
23+
// declared type of the template parameter, which is not const.
24+
template <auto F>
25+
inline constexpr bool const_callable_v = requires { F(); };
26+
template <auto F>
27+
inline constexpr bool non_const_callable_v =
28+
requires(decltype(F) f) { f(); };
29+
static_assert(!const_callable_v<NoConst{}>);
30+
static_assert(non_const_callable_v<NoConst{}>);
31+
32+
// A template parameter declared with the class type.
33+
template <NoConst F>
34+
constexpr bool typed_v = requires(decltype(F) f) { f(); };
35+
static_assert(typed_v<NoConst{}>);
36+
37+
// The parameter has exactly the declared type.
38+
template <auto F>
39+
constexpr bool exact_type_v =
40+
requires(decltype(F) f) { requires is_same<decltype(f), NoConst>; };
41+
static_assert(exact_type_v<NoConst{}>);
42+
43+
// Declarators built on the decltype type.
44+
template <auto F>
45+
constexpr bool ref_v = requires(decltype(F) &f) { f(); };
46+
static_assert(ref_v<NoConst{}>);
47+
template <auto F>
48+
constexpr bool ptr_v = requires(decltype(F) *f) { (*f)(); };
49+
static_assert(ptr_v<NoConst{}>);
50+
51+
// The parenthesized form denotes the const template parameter object.
52+
template <auto F>
53+
constexpr bool paren_v = requires(decltype((F)) f) { f(); };
54+
static_assert(!paren_v<NoConst{}>);
55+
56+
// A nontype template parameter of non-class type.
57+
template <auto N>
58+
constexpr bool int_v = requires(decltype(N) n) { n + 1; };
59+
static_assert(int_v<42>);
60+
61+
// A requires-expression in a function template body.
62+
template <auto F> constexpr bool in_function() {
63+
return requires(decltype(F) f) { f(); };
64+
}
65+
static_assert(in_function<NoConst{}>());
66+
67+
// A requires-expression in a member of a class template.
68+
template <auto F> struct S {
69+
static constexpr bool value = requires(decltype(F) f) { f(); };
70+
template <int I> static constexpr bool member_template() {
71+
return requires(decltype(F) f, decltype(I) i) { f() + i; };
72+
}
73+
template <int I> static constexpr bool member_exact_type() {
74+
return requires(decltype(F) f) {
75+
requires is_same<decltype(f), NoConst>;
76+
};
77+
}
78+
template <int I> static constexpr bool member_paren() {
79+
return requires(decltype((F)) f) { f(); };
80+
}
81+
template <int I> static constexpr bool out_of_class();
82+
};
83+
template <auto F> template <int I>
84+
constexpr bool S<F>::out_of_class() {
85+
return requires(decltype(F) f) { f(); };
86+
}
87+
static_assert(S<NoConst{}>::value);
88+
static_assert(S<NoConst{}>::member_template<1>());
89+
static_assert(S<NoConst{}>::member_exact_type<1>());
90+
static_assert(!S<NoConst{}>::member_paren<1>());
91+
static_assert(S<NoConst{}>::out_of_class<1>());
92+
93+
// This already worked in a concept definition.
94+
template <auto F>
95+
concept NonConstCallable = requires(decltype(F) f) { f(); };
96+
static_assert(NonConstCallable<NoConst{}>);

0 commit comments

Comments
 (0)