This is a missing SFINAE issue when verifying the accessibility of a static data member.
The reason is that check_accessibility_of_qualified_id unconditionally passes tf_warning_or_error to perform_or_defer_access_check, even when called from tsubst_qualified_id(..., complain=tf_none). This patch fixes this by plumbing 'complain' from tsubst_qualified_id through check_accessibility_of_qualified_id to reach perform_or_defer_access_check, and by giving check_accessibility_of_qualified_id the appropriate return value. Bootstrapped and regtested on x64_64-pc-linux-gnu, does this look OK to commit to trunk? gcc/cp/ChangeLog: PR c++/90880 * cp-tree.h (check_accessibility_of_qualified_id): Add tsubst_flags_t parameter and change return type to bool. * parser.c (cp_parser_lookup_name): Pass tf_warning_to_error to check_accessibility_of_qualified_id. * pt.c (tsubst_qualified_id): Return error_mark_node if check_accessibility_of_qualified_id returns false. * semantics.c (check_accessibility_of_qualified_id): Add complain parameter. Pass complain instead of tf_warning_or_error to perform_or_defer_access_check. Return true unless perform_or_defer_access_check returns false. gcc/testsuite/ChangeLog: PR c++/90880 * g++.dg/template/sfinae29.C: New test. --- gcc/cp/cp-tree.h | 2 +- gcc/cp/parser.c | 3 ++- gcc/cp/pt.c | 5 +++-- gcc/cp/semantics.c | 18 ++++++++++------- gcc/testsuite/g++.dg/template/sfinae29.C | 25 ++++++++++++++++++++++++ 5 files changed, 42 insertions(+), 11 deletions(-) create mode 100644 gcc/testsuite/g++.dg/template/sfinae29.C diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index e115a8a1d25..c4b81428e14 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -7258,7 +7258,7 @@ extern bool expand_or_defer_fn_1 (tree); extern void expand_or_defer_fn (tree); extern void add_typedef_to_current_template_for_access_check (tree, tree, location_t); -extern void check_accessibility_of_qualified_id (tree, tree, tree); +extern bool check_accessibility_of_qualified_id (tree, tree, tree, tsubst_flags_t); extern tree finish_qualified_id_expr (tree, tree, bool, bool, bool, bool, tsubst_flags_t); extern void simplify_aggr_init_expr (tree *); diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c index e1f9786893a..337f22d2784 100644 --- a/gcc/cp/parser.c +++ b/gcc/cp/parser.c @@ -28394,7 +28394,8 @@ cp_parser_lookup_name (cp_parser *parser, tree name, During an explicit instantiation, access is not checked at all, as per [temp.explicit]. */ if (DECL_P (decl)) - check_accessibility_of_qualified_id (decl, object_type, parser->scope); + check_accessibility_of_qualified_id (decl, object_type, parser->scope, + tf_warning_or_error); maybe_record_typedef_use (decl); diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c index d28585efd17..e48d3f791ac 100644 --- a/gcc/cp/pt.c +++ b/gcc/cp/pt.c @@ -16171,8 +16171,9 @@ tsubst_qualified_id (tree qualified_id, tree args, if (DECL_P (expr)) { - check_accessibility_of_qualified_id (expr, /*object_type=*/NULL_TREE, - scope); + if (!check_accessibility_of_qualified_id (expr, /*object_type=*/NULL_TREE, + scope, complain)) + return error_mark_node; /* Remember that there was a reference to this entity. */ if (!mark_used (expr, complain) && !(complain & tf_error)) return error_mark_node; diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c index 64a7b76d437..4d1592ab0d2 100644 --- a/gcc/cp/semantics.c +++ b/gcc/cp/semantics.c @@ -2028,12 +2028,14 @@ add_typedef_to_current_template_for_access_check (tree typedef_decl, an error message if it is not accessible. If OBJECT_TYPE is non-NULL, we have just seen `x->' or `x.' and OBJECT_TYPE is the type of `*x', or `x', respectively. If the DECL was named as - `A::B' then NESTED_NAME_SPECIFIER is `A'. */ + `A::B' then NESTED_NAME_SPECIFIER is `A'. Return value is like + perform_access_checks above. */ -void +bool check_accessibility_of_qualified_id (tree decl, tree object_type, - tree nested_name_specifier) + tree nested_name_specifier, + tsubst_flags_t complain) { tree scope; tree qualifying_type = NULL_TREE; @@ -2050,13 +2052,13 @@ check_accessibility_of_qualified_id (tree decl, /* If we're not checking, return immediately. */ if (deferred_access_no_check) - return; + return true; /* Determine the SCOPE of DECL. */ scope = context_for_name_lookup (decl); /* If the SCOPE is not a type, then DECL is not a member. */ if (!TYPE_P (scope)) - return; + return true; /* Compute the scope through which DECL is being accessed. */ if (object_type /* OBJECT_TYPE might not be a class type; consider: @@ -2097,8 +2099,10 @@ check_accessibility_of_qualified_id (tree decl, or similar in a default argument value. */ && CLASS_TYPE_P (qualifying_type) && !dependent_type_p (qualifying_type)) - perform_or_defer_access_check (TYPE_BINFO (qualifying_type), decl, - decl, tf_warning_or_error); + return perform_or_defer_access_check (TYPE_BINFO (qualifying_type), decl, + decl, complain); + + return true; } /* EXPR is the result of a qualified-id. The QUALIFYING_CLASS was the diff --git a/gcc/testsuite/g++.dg/template/sfinae29.C b/gcc/testsuite/g++.dg/template/sfinae29.C new file mode 100644 index 00000000000..0ccaea8593d --- /dev/null +++ b/gcc/testsuite/g++.dg/template/sfinae29.C @@ -0,0 +1,25 @@ +// PR c++/90880 +// { dg-do compile { target c++11 } } + +template <typename T, typename = void> +struct status +{ static const bool value = false; }; + +template <typename T> +struct status<T, decltype((void)T::member)> +{ static const bool value = true; }; + +struct s1{int member;}; +struct s2{int _member;}; + +class c1{int member;}; +class c2{int _member;}; + +void +foo() +{ + static_assert(status<s1>::value, "has member"); + static_assert(!status<s2>::value, "has no member"); + static_assert(!status<c1>::value, "has inaccessible member"); + static_assert(!status<c2>::value, "has no member"); +} -- 2.26.2.447.gd61d20c9b4