In the following code class A
has a private member function f
. I want to write a static assertion that will check whether this function is accessible from the current context (as was suggested in the comment to this question).
There are 3 similar cases all based on requires
-expression:
class A{ void f(); };
// #1: accepted by all
static_assert( { return requires(decltype(x) a){ a.f(); }; }(A{}) );
// #2: rejected by Clang:
static_assert( { return requires(A a){ a.f(); }; }(nullptr) );
// #3: rejected by all
static_assert( { return requires(A a){ a.f(); }; }(nullptr) );
The case #3 (and #2 in Clang) is rejected with the error:
error: 'f' is a private member of 'A'
Demo: https://gcc.godbolt.org/z/Mxs4P7x8s
Why does requires-expression behave differently in template and not-template (cases #1 and #3)? Which compiler is right in #2?
Whilst https://eel.is/c++draft/expr.prim.req.general#1 describes that
A requires-expression provides a concise way to express requirements on template arguments
the grammar of a requires-expression
requires-expression: requires requirement-parameter-list(opt) requirement-body requirement-body: { requirement-seq } requirement-seq: requirement requirement requirement-seq requirement: simple-requirement [...] simple-requirement: expression ; // A simple-requirement asserts the validity of an expression.
allows requires-expressions containing arbitrary expressions that do not necessarily depend on template arguments, meaning the following is well-formed:
constexpr bool f() { return requires(int a) { a; }; }
constexpr bool g() { return requires { 0; }; }
static_assert(f());
static_assert(g());
Thus, for a requires-expression that is declared outside of a templated entity, the same rules as for any expression applies, and #3
is thus correctly rejected by all compilers (access checking control is not waived in the context of expressions in requires-expressions).
#1
is also correctly implemented by all compilers, as per https://eel.is/c++draft/expr.prim.req.general#5.sentence-2:
The substitution of template arguments into a requires-expression may result in the formation of invalid types or expressions in its requirements or the violation of the semantic constraints of those requirements. In such cases, the requires-expression evaluates to false; it does not cause the program to be ill-formed.
... as a.f();
in the constraint expression for the generic a
substituted as being of type A
results in an invalid expression, as f()
is private and not visible.
Finally, #2
is IFNDR as per https://eel.is/c++draft/expr.prim.req.general#5.sentence-6:
If the substitution of template arguments into a requirement would always result in a substitution failure, the program is ill-formed; no diagnostic required.
We could likewise argue that it is IFNDR as per https://eel.is/c++draft/temp.res.general#6.4:
The program is ill-formed, no diagnostic required, if:
- [...]
- a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter, or
Thus, #2
is correctly implemented by all compilers, but it's arguably always nice when compilers actually do diagnose IFNDR violations, so a usability star for Clang.