GCC with patches for OS216
Revision | e6321c4508b2a85c21246c1c06a8208e2a151e48 (tree) |
---|---|
Time | 2020-07-03 02:20:23 |
Author | Jason Merrill <jason@redh...> |
Commiter | Jason Merrill |
c++: Support C++20 virtual consteval functions. [PR88335]
Jakub's partial implementation of consteval virtual had trouble with the
current ABI requirement that we omit the vtable slot for a consteval virtual
function; it's difficult to use the normal code for constant evaluation and
also magically make the slots disappear if the vtables get written out. I
notice that Clang trunk also doesn't implement that requirement, and it
seems unnecessary to me; I expect consteval virtual functions to be
extremely rare, so it should be fine to just give them a vtable slot as
normal but put zero in it if the vtable gets emitted. I've commented as
much to the ABI committee.
One of Jakub's testcases points out that we weren't handling thunks in
our constexpr virtual handling; that is fixed here as well.
Incidentally, being able to use C++11 range-for definitely simplified
clear_consteval_vfns.
gcc/c-family/ChangeLog:
* c-cppbuiltin.c (c_cpp_builtins): Define cpp_consteval.
gcc/cp/ChangeLog:
* decl.c (grokfndecl): Allow consteval virtual.
* search.c (check_final_overrider): Check consteval mismatch.
* constexpr.c (cxx_eval_thunk_call): New.
(cxx_eval_call_expression): Call it.
* cvt.c (cp_get_fndecl_from_callee): Handle FDESC_EXPR.
* decl2.c (mark_vtable_entries): Track vtables with consteval.
(maybe_emit_vtables): Pass consteval_vtables through.
(clear_consteval_vfns): Replace consteval with nullptr.
(c_parse_final_cleanups): Call it.
gcc/testsuite/ChangeLog:
* g++.dg/cpp2a/consteval-virtual1.C: New test.
* g++.dg/cpp2a/consteval-virtual2.C: New test.
* g++.dg/cpp2a/consteval-virtual3.C: New test.
* g++.dg/cpp2a/consteval-virtual4.C: New test.
* g++.dg/cpp2a/consteval-virtual5.C: New test.
Co-authored-by: Jakub Jelinek <jakub@redhat.com>
@@ -995,7 +995,7 @@ c_cpp_builtins (cpp_reader *pfile) | ||
995 | 995 | cpp_define (pfile, "__cpp_constexpr=201907L"); |
996 | 996 | cpp_define (pfile, "__cpp_constexpr_in_decltype=201711L"); |
997 | 997 | cpp_define (pfile, "__cpp_conditional_explicit=201806L"); |
998 | - /* cpp_define (pfile, "__cpp_consteval=201811L"); */ | |
998 | + cpp_define (pfile, "__cpp_consteval=201811L"); | |
999 | 999 | cpp_define (pfile, "__cpp_constinit=201907L"); |
1000 | 1000 | cpp_define (pfile, "__cpp_deduction_guides=201907L"); |
1001 | 1001 | cpp_define (pfile, "__cpp_nontype_template_parameter_class=201806L"); |
@@ -2129,6 +2129,52 @@ replace_result_decl (tree *tp, tree decl, tree replacement) | ||
2129 | 2129 | return data.changed; |
2130 | 2130 | } |
2131 | 2131 | |
2132 | +/* Evaluate the call T to virtual function thunk THUNK_FNDECL. */ | |
2133 | + | |
2134 | +static tree | |
2135 | +cxx_eval_thunk_call (const constexpr_ctx *ctx, tree t, tree thunk_fndecl, | |
2136 | + bool lval, | |
2137 | + bool *non_constant_p, bool *overflow_p) | |
2138 | +{ | |
2139 | + tree function = THUNK_TARGET (thunk_fndecl); | |
2140 | + | |
2141 | + /* virtual_offset is only set in the presence of virtual bases, which make | |
2142 | + the class non-literal, so we don't need to handle it here. */ | |
2143 | + if (THUNK_VIRTUAL_OFFSET (thunk_fndecl)) | |
2144 | + { | |
2145 | + gcc_assert (!DECL_DECLARED_CONSTEXPR_P (function)); | |
2146 | + if (!ctx->quiet) | |
2147 | + { | |
2148 | + error ("call to non-%<constexpr%> function %qD", function); | |
2149 | + explain_invalid_constexpr_fn (function); | |
2150 | + } | |
2151 | + *non_constant_p = true; | |
2152 | + return t; | |
2153 | + } | |
2154 | + | |
2155 | + tree new_call = copy_node (t); | |
2156 | + CALL_EXPR_FN (new_call) = function; | |
2157 | + TREE_TYPE (new_call) = TREE_TYPE (TREE_TYPE (function)); | |
2158 | + | |
2159 | + tree offset = size_int (THUNK_FIXED_OFFSET (thunk_fndecl)); | |
2160 | + | |
2161 | + if (DECL_THIS_THUNK_P (thunk_fndecl)) | |
2162 | + { | |
2163 | + /* 'this'-adjusting thunk. */ | |
2164 | + tree this_arg = CALL_EXPR_ARG (t, 0); | |
2165 | + this_arg = build2 (POINTER_PLUS_EXPR, TREE_TYPE (this_arg), | |
2166 | + this_arg, offset); | |
2167 | + CALL_EXPR_ARG (new_call, 0) = this_arg; | |
2168 | + } | |
2169 | + else | |
2170 | + /* Return-adjusting thunk. */ | |
2171 | + new_call = build2 (POINTER_PLUS_EXPR, TREE_TYPE (new_call), | |
2172 | + new_call, offset); | |
2173 | + | |
2174 | + return cxx_eval_constant_expression (ctx, new_call, lval, | |
2175 | + non_constant_p, overflow_p); | |
2176 | +} | |
2177 | + | |
2132 | 2178 | /* Subroutine of cxx_eval_constant_expression. |
2133 | 2179 | Evaluate the call expression tree T in the context of OLD_CALL expression |
2134 | 2180 | evaluation. */ |
@@ -2209,6 +2255,8 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t, | ||
2209 | 2255 | if (fndecl_built_in_p (fun)) |
2210 | 2256 | return cxx_eval_builtin_function_call (ctx, t, fun, |
2211 | 2257 | lval, non_constant_p, overflow_p); |
2258 | + if (DECL_THUNK_P (fun)) | |
2259 | + return cxx_eval_thunk_call (ctx, t, fun, lval, non_constant_p, overflow_p); | |
2212 | 2260 | if (!DECL_DECLARED_CONSTEXPR_P (fun)) |
2213 | 2261 | { |
2214 | 2262 | if (TREE_CODE (t) == CALL_EXPR |
@@ -1000,12 +1000,11 @@ cp_get_fndecl_from_callee (tree fn, bool fold /* = true */) | ||
1000 | 1000 | if (fold) |
1001 | 1001 | fn = maybe_constant_init (fn); |
1002 | 1002 | STRIP_NOPS (fn); |
1003 | - if (TREE_CODE (fn) == ADDR_EXPR) | |
1004 | - { | |
1005 | - fn = TREE_OPERAND (fn, 0); | |
1006 | - if (TREE_CODE (fn) == FUNCTION_DECL) | |
1007 | - return fn; | |
1008 | - } | |
1003 | + if (TREE_CODE (fn) == ADDR_EXPR | |
1004 | + || TREE_CODE (fn) == FDESC_EXPR) | |
1005 | + fn = TREE_OPERAND (fn, 0); | |
1006 | + if (TREE_CODE (fn) == FUNCTION_DECL) | |
1007 | + return fn; | |
1009 | 1008 | return NULL_TREE; |
1010 | 1009 | } |
1011 | 1010 |
@@ -9560,15 +9560,6 @@ grokfndecl (tree ctype, | ||
9560 | 9560 | } |
9561 | 9561 | } |
9562 | 9562 | |
9563 | - /* FIXME: For now. */ | |
9564 | - if (virtualp && (inlinep & 8) != 0) | |
9565 | - { | |
9566 | - sorry_at (DECL_SOURCE_LOCATION (decl), | |
9567 | - "%<virtual%> %<consteval%> method %qD not supported yet", | |
9568 | - decl); | |
9569 | - inlinep &= ~8; | |
9570 | - } | |
9571 | - | |
9572 | 9563 | /* If this decl has namespace scope, set that up. */ |
9573 | 9564 | if (in_namespace) |
9574 | 9565 | set_decl_namespace (decl, in_namespace, friendp); |
@@ -65,8 +65,6 @@ typedef struct priority_info_s { | ||
65 | 65 | int destructions_p; |
66 | 66 | } *priority_info; |
67 | 67 | |
68 | -static void mark_vtable_entries (tree); | |
69 | -static bool maybe_emit_vtables (tree); | |
70 | 68 | static tree start_objects (int, int); |
71 | 69 | static void finish_objects (int, int, tree); |
72 | 70 | static tree start_static_storage_duration_function (unsigned); |
@@ -1879,7 +1877,7 @@ coerce_delete_type (tree decl, location_t loc) | ||
1879 | 1877 | and mark them as needed. */ |
1880 | 1878 | |
1881 | 1879 | static void |
1882 | -mark_vtable_entries (tree decl) | |
1880 | +mark_vtable_entries (tree decl, vec<tree> &consteval_vtables) | |
1883 | 1881 | { |
1884 | 1882 | tree fnaddr; |
1885 | 1883 | unsigned HOST_WIDE_INT idx; |
@@ -1887,6 +1885,8 @@ mark_vtable_entries (tree decl) | ||
1887 | 1885 | /* It's OK for the vtable to refer to deprecated virtual functions. */ |
1888 | 1886 | warning_sentinel w(warn_deprecated_decl); |
1889 | 1887 | |
1888 | + bool consteval_seen = false; | |
1889 | + | |
1890 | 1890 | FOR_EACH_CONSTRUCTOR_VALUE (CONSTRUCTOR_ELTS (DECL_INITIAL (decl)), |
1891 | 1891 | idx, fnaddr) |
1892 | 1892 | { |
@@ -1901,6 +1901,15 @@ mark_vtable_entries (tree decl) | ||
1901 | 1901 | continue; |
1902 | 1902 | |
1903 | 1903 | fn = TREE_OPERAND (fnaddr, 0); |
1904 | + if (TREE_CODE (fn) == FUNCTION_DECL && DECL_IMMEDIATE_FUNCTION_P (fn)) | |
1905 | + { | |
1906 | + if (!consteval_seen) | |
1907 | + { | |
1908 | + consteval_seen = true; | |
1909 | + consteval_vtables.safe_push (decl); | |
1910 | + } | |
1911 | + continue; | |
1912 | + } | |
1904 | 1913 | TREE_ADDRESSABLE (fn) = 1; |
1905 | 1914 | /* When we don't have vcall offsets, we output thunks whenever |
1906 | 1915 | we output the vtables that contain them. With vcall offsets, |
@@ -1917,6 +1926,20 @@ mark_vtable_entries (tree decl) | ||
1917 | 1926 | } |
1918 | 1927 | } |
1919 | 1928 | |
1929 | +/* Replace any consteval functions in vtables with null pointers. */ | |
1930 | + | |
1931 | +static void | |
1932 | +clear_consteval_vfns (vec<tree> &consteval_vtables) | |
1933 | +{ | |
1934 | + for (tree vtable : consteval_vtables) | |
1935 | + for (constructor_elt &elt : *CONSTRUCTOR_ELTS (DECL_INITIAL (vtable))) | |
1936 | + { | |
1937 | + tree fn = cp_get_fndecl_from_callee (elt.value, /*fold*/false); | |
1938 | + if (fn && DECL_IMMEDIATE_FUNCTION_P (fn)) | |
1939 | + elt.value = build_zero_cst (vtable_entry_type); | |
1940 | + } | |
1941 | +} | |
1942 | + | |
1920 | 1943 | /* Adjust the TLS model on variable DECL if need be, typically after |
1921 | 1944 | the linkage of DECL has been modified. */ |
1922 | 1945 |
@@ -2228,7 +2251,7 @@ decl_needed_p (tree decl) | ||
2228 | 2251 | Returns true if any vtables were emitted. */ |
2229 | 2252 | |
2230 | 2253 | static bool |
2231 | -maybe_emit_vtables (tree ctype) | |
2254 | +maybe_emit_vtables (tree ctype, vec<tree> &consteval_vtables) | |
2232 | 2255 | { |
2233 | 2256 | tree vtbl; |
2234 | 2257 | tree primary_vtbl; |
@@ -2273,7 +2296,7 @@ maybe_emit_vtables (tree ctype) | ||
2273 | 2296 | for (vtbl = CLASSTYPE_VTABLES (ctype); vtbl; vtbl = DECL_CHAIN (vtbl)) |
2274 | 2297 | { |
2275 | 2298 | /* Mark entities references from the virtual table as used. */ |
2276 | - mark_vtable_entries (vtbl); | |
2299 | + mark_vtable_entries (vtbl, consteval_vtables); | |
2277 | 2300 | |
2278 | 2301 | if (TREE_TYPE (DECL_INITIAL (vtbl)) == 0) |
2279 | 2302 | { |
@@ -4887,6 +4910,9 @@ c_parse_final_cleanups (void) | ||
4887 | 4910 | |
4888 | 4911 | emit_support_tinfos (); |
4889 | 4912 | |
4913 | + /* Track vtables we want to emit that refer to consteval functions. */ | |
4914 | + auto_vec<tree> consteval_vtables; | |
4915 | + | |
4890 | 4916 | do |
4891 | 4917 | { |
4892 | 4918 | tree t; |
@@ -4906,7 +4932,7 @@ c_parse_final_cleanups (void) | ||
4906 | 4932 | have to look at it again. */ |
4907 | 4933 | for (i = keyed_classes->length (); |
4908 | 4934 | keyed_classes->iterate (--i, &t);) |
4909 | - if (maybe_emit_vtables (t)) | |
4935 | + if (maybe_emit_vtables (t, consteval_vtables)) | |
4910 | 4936 | { |
4911 | 4937 | reconsider = true; |
4912 | 4938 | keyed_classes->unordered_remove (i); |
@@ -5177,6 +5203,7 @@ c_parse_final_cleanups (void) | ||
5177 | 5203 | perform_deferred_noexcept_checks (); |
5178 | 5204 | |
5179 | 5205 | fini_constexpr (); |
5206 | + clear_consteval_vfns (consteval_vtables); | |
5180 | 5207 | |
5181 | 5208 | /* The entire file is now complete. If requested, dump everything |
5182 | 5209 | to a file. */ |
@@ -1958,20 +1958,13 @@ check_final_overrider (tree overrider, tree basefn) | ||
1958 | 1958 | /* OK */; |
1959 | 1959 | else |
1960 | 1960 | { |
1961 | + auto_diagnostic_group d; | |
1961 | 1962 | if (fail == 1) |
1962 | - { | |
1963 | - auto_diagnostic_group d; | |
1964 | - error ("invalid covariant return type for %q+#D", overrider); | |
1965 | - inform (DECL_SOURCE_LOCATION (basefn), | |
1966 | - "overridden function is %q#D", basefn); | |
1967 | - } | |
1963 | + error ("invalid covariant return type for %q+#D", overrider); | |
1968 | 1964 | else |
1969 | - { | |
1970 | - auto_diagnostic_group d; | |
1971 | - error ("conflicting return type specified for %q+#D", overrider); | |
1972 | - inform (DECL_SOURCE_LOCATION (basefn), | |
1973 | - "overridden function is %q#D", basefn); | |
1974 | - } | |
1965 | + error ("conflicting return type specified for %q+#D", overrider); | |
1966 | + inform (DECL_SOURCE_LOCATION (basefn), | |
1967 | + "overridden function is %q#D", basefn); | |
1975 | 1968 | DECL_INVALID_OVERRIDER_P (overrider) = 1; |
1976 | 1969 | return 0; |
1977 | 1970 | } |
@@ -1993,6 +1986,25 @@ check_final_overrider (tree overrider, tree basefn) | ||
1993 | 1986 | return 0; |
1994 | 1987 | } |
1995 | 1988 | |
1989 | + /* A consteval virtual function shall not override a virtual function that is | |
1990 | + not consteval. A consteval virtual function shall not be overridden by a | |
1991 | + virtual function that is not consteval. */ | |
1992 | + if (DECL_IMMEDIATE_FUNCTION_P (overrider) | |
1993 | + != DECL_IMMEDIATE_FUNCTION_P (basefn)) | |
1994 | + { | |
1995 | + auto_diagnostic_group d; | |
1996 | + if (DECL_IMMEDIATE_FUNCTION_P (overrider)) | |
1997 | + error ("%<consteval%> function %q+D overriding non-%<consteval%> " | |
1998 | + "function", overrider); | |
1999 | + else | |
2000 | + error ("non-%<consteval%> function %q+D overriding %<consteval%> " | |
2001 | + "function", overrider); | |
2002 | + inform (DECL_SOURCE_LOCATION (basefn), | |
2003 | + "overridden function is %qD", basefn); | |
2004 | + DECL_INVALID_OVERRIDER_P (overrider) = 1; | |
2005 | + return 0; | |
2006 | + } | |
2007 | + | |
1996 | 2008 | /* A function declared transaction_safe_dynamic that overrides a function |
1997 | 2009 | declared transaction_safe (but not transaction_safe_dynamic) is |
1998 | 2010 | ill-formed. */ |
@@ -0,0 +1,12 @@ | ||
1 | +// { dg-do compile { target c++20 } } | |
2 | + | |
3 | +struct S { | |
4 | + virtual int foo () { return 42; } // { dg-message "overridden function is 'virtual int S::foo\\\(\\\)'" } | |
5 | + consteval virtual int bar () { return 43; } // { dg-message "overridden function is 'virtual consteval int S::bar\\\(\\\)'" } | |
6 | +}; | |
7 | +struct T : public S { | |
8 | + int bar () { return 44; } // { dg-error "non-'consteval' function 'virtual int T::bar\\\(\\\)' overriding 'consteval' function" } | |
9 | +}; | |
10 | +struct U : public S { | |
11 | + consteval virtual int foo () { return 45; } // { dg-error "'consteval' function 'virtual consteval int U::foo\\\(\\\)' overriding non-'consteval' function" } | |
12 | +}; |
@@ -0,0 +1,22 @@ | ||
1 | +// { dg-do compile { target c++20 } } | |
2 | + | |
3 | +struct A | |
4 | +{ | |
5 | + virtual consteval int f() const { return 1; }; | |
6 | +}; | |
7 | + | |
8 | +struct B: A | |
9 | +{ | |
10 | + virtual consteval int f() const { return 2; }; | |
11 | + virtual void g() { } | |
12 | +}; | |
13 | + | |
14 | +consteval int f() | |
15 | +{ | |
16 | + const A& ar = B(); | |
17 | + return ar.f(); | |
18 | +} | |
19 | + | |
20 | +static_assert (f() == 2); | |
21 | + | |
22 | +B b; |
@@ -0,0 +1,53 @@ | ||
1 | +// { dg-do compile { target c++20 } } | |
2 | + | |
3 | +struct S { | |
4 | + constexpr S () : s (0) {} | |
5 | + virtual int foo () const { return 42; } | |
6 | + consteval virtual int bar () const { return 43; } | |
7 | + consteval virtual int baz () const { return 44; } | |
8 | + consteval virtual int qux () const { return 47; } | |
9 | + int s; | |
10 | +}; | |
11 | +struct T : public S { | |
12 | + constexpr T () : t (0) {} | |
13 | + consteval int bar () const { return 45; } | |
14 | + consteval virtual int baz () const { return 46; } | |
15 | + consteval virtual int grault () const { return 48; } | |
16 | + int t; | |
17 | +}; | |
18 | + | |
19 | +consteval int | |
20 | +foo () | |
21 | +{ | |
22 | + S s; | |
23 | + T t; | |
24 | + S *u = (S *) &t; | |
25 | + T *v = &t; | |
26 | + if (s.bar () != 43) throw 1; | |
27 | + if (s.baz () != 44) throw 2; | |
28 | + if (t.bar () != 45) throw 3; | |
29 | + if (t.baz () != 46) throw 4; | |
30 | + if (u->bar () != 45) throw 5; | |
31 | + if (u->baz () != 46) throw 6; | |
32 | + if (s.qux () != 47) throw 7; | |
33 | + if (t.qux () != 47) throw 8; | |
34 | + if (u->qux () != 47) throw 9; | |
35 | + if (v->qux () != 47) throw 10; | |
36 | + if (v->grault () != 48) throw 11; | |
37 | + return 0; | |
38 | +} | |
39 | + | |
40 | +constexpr S s; | |
41 | +constexpr T t; | |
42 | + | |
43 | +constexpr const S * | |
44 | +bar (bool x) | |
45 | +{ | |
46 | + return x ? &s : (const S *) &t; | |
47 | +} | |
48 | + | |
49 | +int a = foo (); | |
50 | +int b = bar (false)->bar (); | |
51 | +int c = bar (true)->baz (); | |
52 | +static_assert (bar (false)->bar () == 45); | |
53 | +static_assert (bar (true)->baz () == 44); |
@@ -0,0 +1,48 @@ | ||
1 | +// { dg-do compile { target c++20 } } | |
2 | + | |
3 | +struct S { | |
4 | + constexpr S () : s (0) {} | |
5 | + virtual int foo () const { return 42; } | |
6 | + consteval virtual int bar () const { return 43; } | |
7 | + consteval virtual int baz () const { return 44; } | |
8 | + int s; | |
9 | +}; | |
10 | +struct T : public S { | |
11 | + constexpr T () : t (0) {} | |
12 | + consteval int bar () const { return 45; } | |
13 | + consteval virtual int baz () const { return 46; } | |
14 | + int t; | |
15 | +}; | |
16 | + | |
17 | +consteval int | |
18 | +foo () | |
19 | +{ | |
20 | + S s; | |
21 | + T t; | |
22 | + S *u = (S *) &t; | |
23 | + T *v = &t; | |
24 | + auto pmf1 = &S::bar; | |
25 | + auto pmf2 = &S::baz; | |
26 | + if ((s.*pmf1) () != 43) throw 1; | |
27 | + if ((s.*pmf2) () != 44) throw 2; | |
28 | + if ((t.*pmf1) () != 45) throw 3; | |
29 | + if ((t.*pmf2) () != 46) throw 4; | |
30 | + if ((u->*pmf1) () != 45) throw 5; | |
31 | + if ((u->*pmf2) () != 46) throw 6; | |
32 | + return 0; | |
33 | +} | |
34 | + | |
35 | +constexpr S s; | |
36 | +constexpr T t; | |
37 | + | |
38 | +constexpr const S * | |
39 | +bar (bool x) | |
40 | +{ | |
41 | + return x ? &s : (const S *) &t; | |
42 | +} | |
43 | + | |
44 | +int a = foo (); | |
45 | +int b = bar (false)->bar (); | |
46 | +int c = bar (true)->baz (); | |
47 | +static_assert (bar (false)->bar () == 45); | |
48 | +static_assert (bar (true)->baz () == 44); |
@@ -0,0 +1,61 @@ | ||
1 | +// { dg-do compile { target c++20 } } | |
2 | + | |
3 | +struct B1; | |
4 | +struct B2; | |
5 | +struct D; | |
6 | + | |
7 | +struct B1 | |
8 | +{ | |
9 | + virtual consteval const B1 *foo1 () const {return this;} | |
10 | + virtual consteval const B2 *foo2 (const D *) const; | |
11 | +}; | |
12 | +struct B2 | |
13 | +{ | |
14 | + virtual consteval const B2 *baz1 () const {return this;} | |
15 | + virtual consteval const B1 *baz2 (const D *) const; | |
16 | +}; | |
17 | + | |
18 | +struct D : public B1, B2 | |
19 | +{ | |
20 | + virtual consteval const D *foo1 () const {return this;} | |
21 | + virtual consteval const D *foo2 (const D *d) const {return d;} | |
22 | + virtual consteval const D *baz1 () const {return this;} | |
23 | + virtual consteval const D *baz2 (const D *d) const {return d;} | |
24 | +}; | |
25 | + | |
26 | +consteval const B2 *B1::foo2 (const D *d) const {return d;} | |
27 | +consteval const B1 *B2::baz2 (const D *d) const {return d;} | |
28 | + | |
29 | +consteval int | |
30 | +test (const B1 *b1, const B2 *b2, const D *d) | |
31 | +{ | |
32 | + if (b1->foo1 () != b1) | |
33 | + return 1; | |
34 | + if (b2->baz1 () != b2) | |
35 | + return 2; | |
36 | + if (b1->foo2 (d) != b2) | |
37 | + return 3; | |
38 | + if (b2->baz2 (d) != b1) | |
39 | + return 4; | |
40 | + return 0; | |
41 | +} | |
42 | + | |
43 | +consteval int | |
44 | +test (const D *d) | |
45 | +{ | |
46 | + if (d->foo2 (d) != d) | |
47 | + return 11; | |
48 | + if (d->baz2 (d) != d) | |
49 | + return 12; | |
50 | + if (d->foo1 () != d) | |
51 | + return 13; | |
52 | + if (d->baz1 () != d) | |
53 | + return 14; | |
54 | + return 0; | |
55 | +} | |
56 | + | |
57 | +constexpr D d; | |
58 | +constexpr auto e = test (&d, &d, &d); | |
59 | +constexpr auto f = test (&d); | |
60 | +static_assert (e == 0); | |
61 | +static_assert (f == 0); |