diff --git a/src/exprutil.c b/src/exprutil.c index db22dd4b7b..2181e28d1f 100644 --- a/src/exprutil.c +++ b/src/exprutil.c @@ -73,6 +73,9 @@ static a_boolean is_unmodifiable_initonly_field_operand( static void mark_init_component_as_permanently_allocated( an_init_component *icp); static void prep_generic_init_component_list(an_arg_list_elem_ptr list); +static void make_template_param_object_operand(a_constant_ptr cp, + an_operand *operand, + a_boolean copy_constant); /* @@ -4913,6 +4916,7 @@ in *bound_function_selector. an_expr_node_ptr orig_expr = expr; an_expr_node_ptr expr_copy = NULL; a_boolean rescanned_case = FALSE; + a_boolean is_template_param_object = FALSE; a_constant_ptr constant = local_constant(); a_constant_ptr alloc_con = NULL; an_expr_rescan_info_entry_ptr eriep; @@ -4964,11 +4968,35 @@ in *bound_function_selector. /* The operand was already built above. */ } else { if (expr_copy == NULL) { - /* The result is a constant, so return a constant operand. */ - if (alloc_con != NULL) { - make_constant_operand(alloc_con, operand); + a_constant_ptr cp = alloc_con != NULL ? alloc_con : constant; + if (is_constant_node(expr) && + constant_is(node_constant(expr), ck_template_param) && + tpck_is(node_constant(expr), tpck_param) && + constant_is(cp, ck_aggregate)) { + /* A C++20 nontype template parameter of class type has been + replaced by its argument value. As when such a parameter is + referenced in an instantiation (see make_sym_constant_operand), + the result is an lvalue designating the template parameter + object. That object is identified by the argument value + itself, i.e., without the "const" that substitution adds to + the value. */ + if (cp != constant) { + /* cp may be the template argument itself (if the argument value + was produced by an earlier substitution), so it must not be + modified. */ + copy_constant(cp, constant); + cp = constant; + } /* if */ + if (cp->variant.aggregate.added_const_for_template_param) { + cp->type = remove_qualifiers(cp->type, TQ_CONST); + cp->variant.aggregate.added_const_for_template_param = FALSE; + } /* if */ + make_template_param_object_operand(cp, operand, + /*copy_constant=*/TRUE); + is_template_param_object = TRUE; } else { - make_constant_operand(constant, operand); + /* The result is a constant, so return a constant operand. */ + make_constant_operand(cp, operand); } /* if */ } else { /* Return an expression operand. */ @@ -4992,6 +5020,15 @@ 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) { + /* 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 + the reference an lvalue (see make_sym_constant_operand), and it + matters, e.g., for decltype. */ + restore_operand_id_details(operand, + &orig_expr->extra.rescan_info->saved_operand); + } /* if */ } /* if */ release_local_constant(&constant); } /* make_rescan_operand_full */ @@ -8467,11 +8504,15 @@ STATIC_THREAD a_template_param_object_map object. */ static void make_template_param_object_operand(a_constant_ptr cp, - an_operand *operand) + an_operand *operand, + a_boolean copy_constant) /* Make operand an lvalue expression operand for the template parameter object associated with the constant cp. If needed, create that object (as a variable -entry). +entry). copy_constant is TRUE if cp is a local constant (rather than an +allocated IL constant): If the object is created, its value is then a copy of +cp, including its subconstants (which may be shared with other constants), +allocated in the file scope memory region. */ { a_constant_handle ch = { cp }; @@ -8480,6 +8521,15 @@ entry). if (vp == NULL) { a_symbol_ptr sym = make_template_param_object_sym(&error_position); a_type_ptr vtp = cp->type; + if (copy_constant) { + a_memory_region_number region_to_switch_back_to; + switch_to_file_scope_region(®ion_to_switch_back_to); + cp = copy_constant_full(cp, (a_constant *)NULL, + CE_SRC_CONSTANT_IS_NOT_ALLOC_IN_IL); + switch_back_to_original_region(region_to_switch_back_to); + break_constant_source_corresp(cp); + ch.ptr = cp; + } /* if */ vp = make_variable(vtp, sc_unspecified, DEPTH_OF_FILE_SCOPE); vp->is_template_param_object = TRUE; vp->is_constexpr = TRUE; @@ -8541,7 +8591,8 @@ The position of the current token will be used as the operand position. /* A C++20 nontype template parameter instantiated with a class type constant. Create a variable operand referring to the underlying template parameter object. */ - make_template_param_object_operand(con_ptr, operand); + make_template_param_object_operand(con_ptr, operand, + /*copy_constant=*/FALSE); } else { /* Normal (non-reference) case. */ make_constant_operand(&constant, operand); diff --git a/src/pending_changes/gh-414-class-type-template-param-object.chng.txt b/src/pending_changes/gh-414-class-type-template-param-object.chng.txt new file mode 100644 index 0000000000..4cf8b2eab2 --- /dev/null +++ b/src/pending_changes/gh-414-class-type-template-param-object.chng.txt @@ -0,0 +1,21 @@ +[GH #414] +Class-type template parameter objects in function template signatures + +A nontype template parameter of class type denotes a template parameter +object, which is a const lvalue. When such a parameter was used in the +signature of a function template, substituting template arguments treated +it as a prvalue instead. As a result, decltype applied to the +parenthesized parameter name produced the class type rather than a const +lvalue reference to it (causing a spurious "unexpected function type" +error), member accesses through the parameter had the wrong value category, +and taking the address of the parameter caused substitution to fail. That +is now fixed. For example, with --c++20: + + struct A { }; + template void f(decltype((V)) v) { } + template auto g() -> decltype(&V); + void h() { + constexpr A a; + f(a); // Previously an "unexpected function type" error. + g(); // Previously, no instance of g matched. + } diff --git a/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/default.1.1.txt b/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/default.1.1.txt index 35a69da7f7..1f7a1172ed 100644 --- a/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/default.1.1.txt +++ b/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/default.1.1.txt @@ -749,23 +749,6 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers static_assert( requires {{ &f } -> C;} ); // expected-error {{reference to overloaded function could not be resolved;}} ^ -"Test_name.c", line 1692: error: the deduced placeholder type "GH170856::absolute_zero" failed the type constraint - consteval PointOriginFor auto default_point_origin(R) { - ^ -"Test_name.c", line 1681: note: concept is false for arguments ", GH170856::{{{}}}>" - concept PointOriginFor = QuantitySpecOf; - ^ -"Test_name.c", line 1679: note: concept is false for arguments ">" - concept QuantitySpecOf = QuantitySpec; - ^ -"Test_name.c", line 1677: note: atomic constraint evaluates to false - concept QuantitySpec = __is_convertible(T*, quantity_spec_interface_base*); - ^ - detected during: - instantiation of "GH170856::PointOriginFor<...> auto GH170856::default_point_origin(R) [with R=struct GH170856::]" at line 1697 - instantiation of class "GH170856::point_ [with R=struct GH170856::]" at line 1705 - instantiation of "GH170856::point_)> GH170856::point [with R=GH170856::{{}}]" at line 1705 - "Test_name.c", line 41: error: a requires-clause is not allowed here (not a templated function) ([] () requires C {} ()); // expected-error {{expression contains unexpanded parameter pack 'Ts'}} ^ @@ -1000,4 +983,4 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers F* f = l; // expected-error {{no viable conversion from}} ^ -145 errors detected in the compilation of "Test_name.c". +144 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/default.2.1.txt b/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/default.2.1.txt index 177141856e..98c3adff53 100644 --- a/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/default.2.1.txt +++ b/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/default.2.1.txt @@ -741,23 +741,6 @@ fe_only -DTEST_NUMBER=2 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers static_assert( requires {{ &f } -> C;} ); // expected-error {{reference to overloaded function could not be resolved;}} ^ -"Test_name.c", line 1692: error: the deduced placeholder type "GH170856::absolute_zero" failed the type constraint - consteval PointOriginFor auto default_point_origin(R) { - ^ -"Test_name.c", line 1681: note: concept is false for arguments ", GH170856::{{{}}}>" - concept PointOriginFor = QuantitySpecOf; - ^ -"Test_name.c", line 1679: note: concept is false for arguments ">" - concept QuantitySpecOf = QuantitySpec; - ^ -"Test_name.c", line 1677: note: atomic constraint evaluates to false - concept QuantitySpec = __is_convertible(T*, quantity_spec_interface_base*); - ^ - detected during: - instantiation of "GH170856::PointOriginFor<...> auto GH170856::default_point_origin(R) [with R=struct GH170856::]" at line 1697 - instantiation of class "GH170856::point_ [with R=struct GH170856::]" at line 1705 - instantiation of "GH170856::point_)> GH170856::point [with R=GH170856::{{}}]" at line 1705 - "Test_name.c", line 41: error: a requires-clause is not allowed here (not a templated function) ([] () requires C {} ()); // expected-error {{expression contains unexpanded parameter pack 'Ts'}} ^ @@ -992,4 +975,4 @@ fe_only -DTEST_NUMBER=2 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers F* f = l; // expected-error {{no viable conversion from}} ^ -145 errors detected in the compilation of "Test_name.c". +144 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/edg_x86_32_cp.1.1.txt b/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/edg_x86_32_cp.1.1.txt index b4854b78cf..155259c307 100644 --- a/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/edg_x86_32_cp.1.1.txt +++ b/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/edg_x86_32_cp.1.1.txt @@ -846,23 +846,6 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers static_assert( requires {{ &f } -> C;} ); // expected-error {{reference to overloaded function could not be resolved;}} ^ -"Test_name.c", line 1692: error: the deduced placeholder type "GH170856::absolute_zero" failed the type constraint - consteval PointOriginFor auto default_point_origin(R) { - ^ -"Test_name.c", line 1681: note: concept is false for arguments ", GH170856::{{{}}}>" - concept PointOriginFor = QuantitySpecOf; - ^ -"Test_name.c", line 1679: note: concept is false for arguments ">" - concept QuantitySpecOf = QuantitySpec; - ^ -"Test_name.c", line 1677: note: atomic constraint evaluates to false - concept QuantitySpec = __is_convertible(T*, quantity_spec_interface_base*); - ^ - detected during: - instantiation of "GH170856::PointOriginFor<...> auto GH170856::default_point_origin(R) [with R=struct GH170856::]" at line 1697 - instantiation of class "GH170856::point_ [with R=struct GH170856::]" at line 1705 - instantiation of "GH170856::point_)> GH170856::point [with R=GH170856::{{}}]" at line 1705 - "Test_name.c", line 46: error: no instance of function template "lambda []()->auto" matches the argument list object type is: lambda []()->auto ([] requires C () {} (), ...); @@ -1014,4 +997,4 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers ^ detected during instantiation of "void DeferredInstantiationInstScope::LocalStructMemberVar(T) [with T=int]" at line 590 -151 errors detected in the compilation of "Test_name.c". +150 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/edg_x86_32_cp.2.1.txt b/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/edg_x86_32_cp.2.1.txt index 0addde9c02..468346a8cd 100644 --- a/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/edg_x86_32_cp.2.1.txt +++ b/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/edg_x86_32_cp.2.1.txt @@ -838,23 +838,6 @@ fe_only -DTEST_NUMBER=2 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers static_assert( requires {{ &f } -> C;} ); // expected-error {{reference to overloaded function could not be resolved;}} ^ -"Test_name.c", line 1692: error: the deduced placeholder type "GH170856::absolute_zero" failed the type constraint - consteval PointOriginFor auto default_point_origin(R) { - ^ -"Test_name.c", line 1681: note: concept is false for arguments ", GH170856::{{{}}}>" - concept PointOriginFor = QuantitySpecOf; - ^ -"Test_name.c", line 1679: note: concept is false for arguments ">" - concept QuantitySpecOf = QuantitySpec; - ^ -"Test_name.c", line 1677: note: atomic constraint evaluates to false - concept QuantitySpec = __is_convertible(T*, quantity_spec_interface_base*); - ^ - detected during: - instantiation of "GH170856::PointOriginFor<...> auto GH170856::default_point_origin(R) [with R=struct GH170856::]" at line 1697 - instantiation of class "GH170856::point_ [with R=struct GH170856::]" at line 1705 - instantiation of "GH170856::point_)> GH170856::point [with R=GH170856::{{}}]" at line 1705 - "Test_name.c", line 46: error: no instance of function template "lambda []()->auto" matches the argument list object type is: lambda []()->auto ([] requires C () {} (), ...); @@ -1006,4 +989,4 @@ fe_only -DTEST_NUMBER=2 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers ^ detected during instantiation of "void DeferredInstantiationInstScope::LocalStructMemberVar(T) [with T=int]" at line 590 -151 errors detected in the compilation of "Test_name.c". +150 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/edg_x86_64_cp.1.1.txt b/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/edg_x86_64_cp.1.1.txt index b4854b78cf..155259c307 100644 --- a/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/edg_x86_64_cp.1.1.txt +++ b/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/edg_x86_64_cp.1.1.txt @@ -846,23 +846,6 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers static_assert( requires {{ &f } -> C;} ); // expected-error {{reference to overloaded function could not be resolved;}} ^ -"Test_name.c", line 1692: error: the deduced placeholder type "GH170856::absolute_zero" failed the type constraint - consteval PointOriginFor auto default_point_origin(R) { - ^ -"Test_name.c", line 1681: note: concept is false for arguments ", GH170856::{{{}}}>" - concept PointOriginFor = QuantitySpecOf; - ^ -"Test_name.c", line 1679: note: concept is false for arguments ">" - concept QuantitySpecOf = QuantitySpec; - ^ -"Test_name.c", line 1677: note: atomic constraint evaluates to false - concept QuantitySpec = __is_convertible(T*, quantity_spec_interface_base*); - ^ - detected during: - instantiation of "GH170856::PointOriginFor<...> auto GH170856::default_point_origin(R) [with R=struct GH170856::]" at line 1697 - instantiation of class "GH170856::point_ [with R=struct GH170856::]" at line 1705 - instantiation of "GH170856::point_)> GH170856::point [with R=GH170856::{{}}]" at line 1705 - "Test_name.c", line 46: error: no instance of function template "lambda []()->auto" matches the argument list object type is: lambda []()->auto ([] requires C () {} (), ...); @@ -1014,4 +997,4 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers ^ detected during instantiation of "void DeferredInstantiationInstScope::LocalStructMemberVar(T) [with T=int]" at line 590 -151 errors detected in the compilation of "Test_name.c". +150 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/edg_x86_64_cp.2.1.txt b/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/edg_x86_64_cp.2.1.txt index 0addde9c02..468346a8cd 100644 --- a/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/edg_x86_64_cp.2.1.txt +++ b/tests/tests/imported/clang/cpp/SemaTemplate/.concepts.rto/edg_x86_64_cp.2.1.txt @@ -838,23 +838,6 @@ fe_only -DTEST_NUMBER=2 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers static_assert( requires {{ &f } -> C;} ); // expected-error {{reference to overloaded function could not be resolved;}} ^ -"Test_name.c", line 1692: error: the deduced placeholder type "GH170856::absolute_zero" failed the type constraint - consteval PointOriginFor auto default_point_origin(R) { - ^ -"Test_name.c", line 1681: note: concept is false for arguments ", GH170856::{{{}}}>" - concept PointOriginFor = QuantitySpecOf; - ^ -"Test_name.c", line 1679: note: concept is false for arguments ">" - concept QuantitySpecOf = QuantitySpec; - ^ -"Test_name.c", line 1677: note: atomic constraint evaluates to false - concept QuantitySpec = __is_convertible(T*, quantity_spec_interface_base*); - ^ - detected during: - instantiation of "GH170856::PointOriginFor<...> auto GH170856::default_point_origin(R) [with R=struct GH170856::]" at line 1697 - instantiation of class "GH170856::point_ [with R=struct GH170856::]" at line 1705 - instantiation of "GH170856::point_)> GH170856::point [with R=GH170856::{{}}]" at line 1705 - "Test_name.c", line 46: error: no instance of function template "lambda []()->auto" matches the argument list object type is: lambda []()->auto ([] requires C () {} (), ...); @@ -1006,4 +989,4 @@ fe_only -DTEST_NUMBER=2 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers ^ detected during instantiation of "void DeferredInstantiationInstScope::LocalStructMemberVar(T) [with T=int]" at line 590 -151 errors detected in the compilation of "Test_name.c". +150 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/regressions/.gh-414.rto/default.1.1.txt b/tests/tests/regressions/.gh-414.rto/default.1.1.txt new file mode 100644 index 0000000000..32fc82e916 --- /dev/null +++ b/tests/tests/regressions/.gh-414.rto/default.1.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=1 --c++20 Test_name.c diff --git a/tests/tests/regressions/.gh-414.rto/default.2.1.txt b/tests/tests/regressions/.gh-414.rto/default.2.1.txt new file mode 100644 index 0000000000..2f4f598fac --- /dev/null +++ b/tests/tests/regressions/.gh-414.rto/default.2.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=2 --c++20 --g++ --gnu_version=150100 Test_name.c diff --git a/tests/tests/regressions/.gh-414.rto/default.3.1.txt b/tests/tests/regressions/.gh-414.rto/default.3.1.txt new file mode 100644 index 0000000000..2a35d8f546 --- /dev/null +++ b/tests/tests/regressions/.gh-414.rto/default.3.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=3 --c++20 --clang --clang_version=190000 Test_name.c diff --git a/tests/tests/regressions/.gh-414.rto/default.4.1.txt b/tests/tests/regressions/.gh-414.rto/default.4.1.txt new file mode 100644 index 0000000000..6e33f463c5 --- /dev/null +++ b/tests/tests/regressions/.gh-414.rto/default.4.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=4 --ms_c++20 --microsoft_version=1950 Test_name.c diff --git a/tests/tests/regressions/gh-414.sft.cpp b/tests/tests/regressions/gh-414.sft.cpp new file mode 100644 index 0000000000..a59478fff6 --- /dev/null +++ b/tests/tests/regressions/gh-414.sft.cpp @@ -0,0 +1,113 @@ +//type:fp +//options:--c++20:--c++20 --g++ --gnu_version=150100:--c++20 --clang --clang_version=190000:--ms_c++20 --microsoft_version=1950 + +// A nontype template parameter of class type denotes a template parameter +// object, which is a const lvalue. When such a parameter was used in a +// function template signature, substitution treated it as a prvalue: The +// decltype of the parenthesized name became the class type rather than a +// const reference to it (resulting in a spurious "unexpected function type" +// error), and taking its address caused substitution to fail. + +template constexpr bool is_same = false; +template constexpr bool is_same = true; + +struct A { int i = 0; }; +constexpr A a1{1}, a2{2}; + +template void test1(decltype((V)) v) { } +#if __GNUC__ +template void test2(__decltype((V)) v) { } +#endif /* __GNUC__ */ + +void g() { + constexpr A a; + test1(a); +#if __GNUC__ + test2(a); +#endif /* __GNUC__ */ +} + +template auto paren() -> decltype((V)); +static_assert(is_same()), const A&>); + +template auto no_paren() -> decltype(V); +static_assert(is_same()), A>); + +template auto member() -> decltype((V.i)); +static_assert(is_same()), const int&>); + +template void dependent(decltype((V))) { } +void h() { dependent(a1); } + +template struct S { }; +template auto deduced(S) -> decltype((V)); +static_assert(is_same{})), const A&>); + +// The object designated in a rescanned signature is the same one designated +// in the instantiated function body. +template constexpr const A* addr() { return &V; } +template constexpr auto addr_in_sig() -> decltype(&V) { return &V; } +static_assert(addr() == addr_in_sig()); +static_assert(addr_in_sig() != addr_in_sig()); +static_assert(addr_in_sig()->i == 2); + +template requires (&V != nullptr) constexpr int constrained() { + return V.i; +} +static_assert(constrained() == 2); + +template struct X { + template static auto f() -> decltype((V)); +}; +static_assert(is_same::f()), const A&>); + +template struct Y { + template static auto f(T) -> decltype(&V); +}; +static_assert(is_same::f(0)), const A*>); + +// A closure-type argument that is itself the result of an earlier +// substitution (into the concept's template argument list). +template concept Callable = (F() == 1); +template constexpr int call() requires Callable { return 1; } +template constexpr int call() { return 0; } +static_assert(call<[]{ return 1; }>() == 1); +static_assert(call<[]{ return 2; }>() == 0); + +// Template parameter objects with subaggregates (whose values must not be +// shared with the template argument value), designated in a template argument +// list in the signature. +namespace subaggregates { + struct B { int x[3]; }; + struct C { B b; int y; }; + struct D { B bs[2]; }; + + template struct P { }; + template auto f() -> P<&V>; + using R = decltype(f()); + + template struct Q { + static constexpr const B* ptr = p; + static constexpr int value = p->x[2]; + }; + template constexpr auto g() -> Q<&V> { return {}; } + template constexpr auto g2() -> Q<&V> { return {}; } + template constexpr const B* addr() { return &V; } + static_assert(decltype(g())::value == 3); + static_assert(decltype(g())::ptr == addr()); + static_assert(is_same()), + decltype(g2())>); + static_assert(decltype(g())::ptr != + decltype(g())::ptr); + static_assert(g().value == 9); + + template struct QC { static constexpr int value = p->b.x[1]; }; + template auto h() -> QC<&V>; + static_assert(decltype(h())::value == 2); + + template struct QD { + static constexpr int value = p->bs[1].x[0]; + }; + template auto k() -> QD<&V>; + static_assert(decltype(k())::value == 4); +} // namespace subaggregates