C++ concept checking in incomplete-class context - c++

Please consider a C++20 concept program:
struct A {};
template<typename T>
concept DerivedOnceFromA = requires(T t) { { static_cast<const A&>(t) }; };
template<DerivedOnceFromA T>
struct B {};
struct C : A
{
B<C> foo();
};
Here the concept DerivedOnceFromA checks that T can be statically cast to A. B is template struct following this concept. And struct C is derived from A (so the concept is satisfied) and defines a function that returns B<C>.
This code is accepted by GCC and MSVC, but rejected by Clang with the error:
constraints not satisfied for class template 'B' [with T = C]
Demo: https://gcc.godbolt.org/z/7Tc7xdbeq
Is it legal to use class as concepted template parameter inside class body (so what compiler is right)?

Legal? Yes, it's in scope. But you can't glean much of anything useful about it.
[class.mem.general]
6 A complete-class context of a class is a
function body ([dcl.fct.def.general]),
default argument,
noexcept-specifier ([except.spec]), or
default member initializer within the member-specification of the class.
7 A class is considered a completely-defined object type
([basic.types]) (or complete type) at the closing } of the
class-specifier. The class is regarded as complete within its
complete-class contexts; otherwise it is regarded as incomplete within
its own class member-specification.
So you are using it at a point it's still considered incomplete, just a type you forward declared at most. Consequentially you can't know if it's castable to some other reference, no matter the mechanism enabling the cast (inheritance or user defined conversion operator). Nor is it possible to know if an incomplete type is derived from A even with the help of a standard concept like std::derived_from. An incomplete type's observable properties are very limited.

In the body of a class, that class's type is incomplete.
So you cannot have a member signature that relies on the type being complete.
In order for your code to work, you need to defer the checking of the concept until after the class is complete. One way is via a template method:
template<std::same_as<C> Y=C>
B<Y> foo()
{
return {};
}
you can even implement the only valid specialization of Y=C within a cpp file.
This is a bit of a stupid workaround, and it does block virtual methods.

Related

Concept is satisfied by a type that would seemingly produce an invalid expression

In the following code, the can_foo concept tests whether or not a foo() member function can be called on an instance of a type. I will use it to test instances of two templates: base conditionally enables the foo member function, and derived overrides foo to call into its base's implementation:
template <typename T>
concept can_foo = requires(T v) {
v.foo();
};
template <bool enable_foo>
struct base {
void foo()
requires enable_foo
{}
};
template <typename T>
struct derived : T {
void foo()
{
static_cast<T&>(*this).foo();
}
};
If I test whether instances of the base template satisfy the concept, it does what I would expect:
static_assert(can_foo<base<true>>); //okay
static_assert(not can_foo<base<false>>); //okay
When I wrap those types in derived, I see:
static_assert(can_foo<derived<base<true>>>); //okay
static_assert(not can_foo<derived<base<false>>>); //error: static assertion failed
This is surprising! I expected that derived<base<false>> would not satisfy can_foo - its definition of foo uses an expression that isn't valid given T = base<false>, and using the same expression tested by the concept in an evaluated context results in an error that says as much:
int main()
{
derived<base<false>> v{};
v.foo(); //error
}
The error message isn't at the call site, which is probably relevant; it references the body of derived<>::foo. From clang:
<source>:18:32: error: invalid reference to function 'foo': constraints not satisfied
static_cast<T&>(*this).foo();
^
<source>:31:7: note: in instantiation of member function 'derived<base<false>>::foo' requested here
v.foo(); //"invalid reference to function 'foo'"
^
<source>:10:18: note: because 'false' evaluated to false
requires enable_foo
^
clang: https://godbolt.org/z/vh58TTPxo
gcc: https://godbolt.org/z/qMPrzznar
Both compilers produce the same results, so I assume the problem is that there's a subtlety in the standard that I'm missing. Adding a can_foo<T> constraint to derived<T> or derived<T>::foo "fixes" this (that is, derived<base<false>> will no longer satisfy can_foo), and in a code review I would argue that this constraint should be present - but this is surprising behaviour nonetheless and I'd like to understand what's going on.
So: why does derived<false> satisfy can_foo?
A requires-expression can only detect invalid constructs in the "immediate context" of the expression that is tested. In particular
requires(T v) {
v.foo();
};
will not check whether it is actually well-formed to have the call v.foo(). If v.foo() would be ill-formed due to an ill-formed construct inside the body of the foo function, this will not be detected by the requires-expression because the body is not in the immediate context.
The question is, what should happen next? Should the requires-expression go and instantiate the body of foo and give a hard error, or should it return true and give you a hard error later when you attempt to call v.foo()? The answer is the second one: instantiation is not performed because it is not required. See [temp.inst]/5
Unless a function template specialization is a declared specialization, the function template specialization is implicitly instantiated when the specialization is referenced in a context that requires a function definition to exist or if the existence of the definition affects the semantics of the program. [...]
[temp.inst]/11 additionally implies that the implementation is not permitted to instantiate the definition unless it is required.
In an unevaluated context, calling v.foo() does not require the definition of foo to exist, because it is not odr-used unless it is potentially evaluated and it is normally the ODR that requires a definition to exist. (However, there are two situations where referencing a function requires its definition to exist even in an unevaluated context: when the function has a deduced return type or is needed for constant evaluation ([temp.inst]/8)). Since the definition is not required to exist, the definition is not instantiated.
You might want to modify derived::foo so that it propagates the constraint from T::foo, and thus the ill-formedness can be detected by can_foo<derived<T>>:
void foo() requires can_foo<T> {
static_cast<T&>(*this).foo();
}
derived has a foo for every template parameter T, it just cannot always be instantiated. When trying to instantiate derived<base<false>>::foo where you try to to call it in main, you get the error message that the instantiation is not valid since foo cannot be called for the type base<false>.
Rules regarding when instantiation is optional and/or required in the standard would be [temp.inst].
I'm however not able to point to a conclusive passage that says that
checking whether derived<base<false>>::foo exists in the context of the static_assert does not require instantiation. Is it the same as overload resolution checking? "If the function selected by overload resolution can be determined without instantiating a class template definition, it is unspecified whether that instantiation actually takes place."
If you want derived::foo to only be available if it can be instantiated, you could require can_foo on its T.
template <typename T>
struct derived : T {
void foo() requires can_foo<T>
{
static_cast<T&>(*this).foo();
}
};
https://godbolt.org/z/EqrMq4Moq

One static_assert makes another static_assert fail (is_default_constructible for inner class) [duplicate]

I have those classes:
#include <type_traits>
template <typename T>
class A {
public:
static_assert(std::is_default_constructible_v<T>);
};
struct B {
struct C {
int i = 0;
};
A<C> a_m;
};
int main() {
A<B::C> a;
}
When compiling, a_m is not default constructible but a is.
When changing C to:
struct C {
int i;
};
everything is fine.
Tested with Clang 9.0.0.
This is disallowed both by the text of the standard and by several major implementations as noted in the comments, but for completely unrelated reasons.
First, the "by the book" reason: the point of instantiation of A<C> is, according to the standard, immediately before the definition of B, and the point of instantiation of std::is_default_constructible<C> is immediately before that:
For a class template specialization, [...] if the specialization is
implicitly instantiated because it is referenced from within another
template specialization, if the context from which the specialization
is referenced depends on a template parameter, and if the
specialization is not instantiated previous to the instantiation of
the enclosing template, the point of instantiation is immediately
before the point of instantiation of the enclosing template.
Otherwise, the point of instantiation for such a specialization
immediately precedes the namespace scope declaration or definition
that refers to the specialization.
Since C is clearly incomplete at that point, the behavior of instantiating std::is_default_constructible<C> is undefined. However, see core issue 287, which would change this rule.
In reality, this has to do with the NSDMI.
NSDMIs are weird because they get delayed parsing - or in standard parlance they are a "complete-class context".
Thus, that = 0 could in principle refer to things in B not yet declared, so the implementation can't really try to parse it until it has finished with B.
Completing a class necessitates the implicit declaration of special member functions, in particular the default constructor, as C doesn't have a constructor declared.
Parts of that declaration (constexpr-ness, noexcept-ness) depend on the properties of the NSDMI.
Thus, if the compiler can't parse the NSDMI, it can't complete the class.
As a result, at the point when it instantiates A<C>, it thinks that C is incomplete.
This whole area dealing with delayed-parsed regions is woefully underspecified, with accompanying implementation divergence. It may take a while before it gets cleaned up.
Undefined behavior it is:
If an instantiation of a template above depends, directly or
indirectly, on an incomplete type, and that instantiation could yield
a different result if that type were hypothetically completed, the
behavior is undefined.

Why is my class non default-constructible?

I have those classes:
#include <type_traits>
template <typename T>
class A {
public:
static_assert(std::is_default_constructible_v<T>);
};
struct B {
struct C {
int i = 0;
};
A<C> a_m;
};
int main() {
A<B::C> a;
}
When compiling, a_m is not default constructible but a is.
When changing C to:
struct C {
int i;
};
everything is fine.
Tested with Clang 9.0.0.
This is disallowed both by the text of the standard and by several major implementations as noted in the comments, but for completely unrelated reasons.
First, the "by the book" reason: the point of instantiation of A<C> is, according to the standard, immediately before the definition of B, and the point of instantiation of std::is_default_constructible<C> is immediately before that:
For a class template specialization, [...] if the specialization is
implicitly instantiated because it is referenced from within another
template specialization, if the context from which the specialization
is referenced depends on a template parameter, and if the
specialization is not instantiated previous to the instantiation of
the enclosing template, the point of instantiation is immediately
before the point of instantiation of the enclosing template.
Otherwise, the point of instantiation for such a specialization
immediately precedes the namespace scope declaration or definition
that refers to the specialization.
Since C is clearly incomplete at that point, the behavior of instantiating std::is_default_constructible<C> is undefined. However, see core issue 287, which would change this rule.
In reality, this has to do with the NSDMI.
NSDMIs are weird because they get delayed parsing - or in standard parlance they are a "complete-class context".
Thus, that = 0 could in principle refer to things in B not yet declared, so the implementation can't really try to parse it until it has finished with B.
Completing a class necessitates the implicit declaration of special member functions, in particular the default constructor, as C doesn't have a constructor declared.
Parts of that declaration (constexpr-ness, noexcept-ness) depend on the properties of the NSDMI.
Thus, if the compiler can't parse the NSDMI, it can't complete the class.
As a result, at the point when it instantiates A<C>, it thinks that C is incomplete.
This whole area dealing with delayed-parsed regions is woefully underspecified, with accompanying implementation divergence. It may take a while before it gets cleaned up.
Undefined behavior it is:
If an instantiation of a template above depends, directly or
indirectly, on an incomplete type, and that instantiation could yield
a different result if that type were hypothetically completed, the
behavior is undefined.

Trying to understand templates and name lookup

I am trying to understand the following code snippets
Snippet #1
template <typename T>
struct A
{
static constexpr int VB = T::VD;
};
struct B : A<B>
{
};
Neither gcc9 nor clang9 throw an error here.
Q. Why does this code compile? Aren't we instantiating A<B> when inheriting from B? There is no VD in B, so shouldn't the compiler throw an error here?
Snippet #2
template <typename T>
struct A
{
static constexpr auto AB = T::AD; // <- No member named AD in B
};
struct B : A<B>
{
static constexpr auto AD = 0xD;
};
In this case, gcc9 compiles fine but clang9 throws an error saying "No member named AD in B".
Q. Why does it compile with gcc9/why doesn't it compile with clang9?
Snippet #3
template <typename T>
struct A
{
using TB = typename T::TD;
};
struct B : A<B>
{
using TD = int;
};
Here both clang9 and gcc9 throw an error. gcc9 says "invalid use of incomplete type 'struct B'".
Q. If struct B is incomplete here then why is it not incomplete in snippet #2?
Compiler flags used: -std=c++17 -O3 -Wall -Werror. Thanks in Advance!!!
I believe these essentially boil down to [temp.inst]/2 (emphasis mine):
The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions, default arguments, or noexcept-specifiers of the class member functions, member classes, scoped member enumerations, static data members, member templates, and friends; […]
and [temp.inst]/9
An implementation shall not implicitly instantiate […] a static data member of a class template […] unless such instantiation is required.
The wording in the standard concerning implicit template instantiation leaves many details open to interpretation. In general, it would seem to me that you simply cannot rely on parts of a template not being instantiated unless the specification explicitly says so. Thus:
Snippet #1
Q. Why does this code compile? Aren't we instantiating A when inheriting from B? There is no VD in B, so shouldn't the compiler throw an error here?
You are instantiating A<B>. But instantiating A<B> only instantiates the declarations, not the definitions of its static data members. VB is never used in a way that would require a definition to exist. The compiler should accept this code.
Snippet #2
Q. Why does it compile with gcc9/why doesn't it compile with clang9?
As pointed out by Jarod42, the declaration of AB contains a placeholder type. It would seem to me that the wording of the standard is not really clear on what is supposed to happen here. Does the instantiation of the declaration of a static data member that contains a placeholder type trigger placeholder type deduction and, thus, constitute a use that requires the definition of the static data member? I can't find wording in the standard that would clearly say either yes or no to that. Thus, I would say that both interpretations are equally valid here and, thus, GCC and clang are both right…
Snippet #3
Q. If struct B is incomplete here then why is it not incomplete in snippet #2?
A class type is only complete at the point where you reach the closing } of the class-specifier [class.mem]/6. Thus, B is incomplete during the implicit instantiation of A<B> in all your snippets. It's just that this was irrelevant for Snippet #1. In Snippet #2, clang did give you an error No member named AD in B as a result. Similar to the case of Snippet #2, I can't find wording on when exactly member alias declarations would be instantiated. However, unlike for the definition of static data members, there is no wording in place to explicitly prevent the instantiation of member alias declarations during implicit instantiation of a class template. Thus, I would say that the behavior of both GCC and clang is a valid interpretation of the standard in this case…

Function returning T<A> doesn't compile in certain typedef situation

I don't understand why func3() can't compile, when func2() and func4() do.
g++ 4.1.2: error: 'B<T>::my_t' has incomplete type
VS2008: error C2079: 'B<T>::my_t' uses undefined class 'A'
template <typename T>
struct C {
T mt_t;
};
template <typename T>
struct B {
typedef C<T> C_type;
T my_t;
};
struct Other {};
struct A {
B<A> func2();
B<A>::C_type func3(); // error: 'B<T>::my_t' has incomplete type
B<Other>::C_type func4();
};
int main() {}
Well, when you try to get B<A>::C_type, you have to instantiate template <typename> B, but you cannot instantiate the template on an incomplete type because it contains a member object T my_t which mustn't be incomplete -- the compiler doesn't know that you only want to get at the member typedef of B<A>.
In func2, you only use B<A> as a return type, which is allowed to be incomplete -- we don't need to instantiate B<A> in order to allow it as a return type. But to access the member (typedef) we do need to instantiate B<A>. Furhtermore, func4 is fine because Other is a complete type.
The solution is simple, just resolve the typedef by hand and make the return type of func3 into C<A>.
There are a few factors at play here.
A is incomplete
First, in all the function declarations:
B<A> func2();
B<A>::C_type func3();
B<Other>::C_type func4();
A is an incomplete type:
[2003: 9.2/2]: A class is considered a completely-defined object type (3.9) (or
complete type) at the closing } of the class-specifier. Within the
class member-specification, the class is regarded as complete within
function bodies, default arguments, and constructor ctor-initializers (including such things in nested classes). Otherwise it is regarded as
incomplete within its own class member-specification.
B<A> needs to be instantiated
func2 and func4 are OK
Second, the function return type may be incomplete. That means that the return types of func2 and func4 are fine just the way they are.
[2003: 8.3.5/6]: [..] The type of a parameter or the return type for
a function definition shall not be an incomplete class type (possibly
cv-qualified) unless the function definition is nested within the
member-specification for that class (including definitions in nested
classes defined within the class).
func3 is not OK
However, in the more complex example of func3, in order to use the type B<A>::C_type, B<A> must be complete.*
And so it must also be instantiated:
[2003: 14.7.1/1]: Unless a class template specialization has been
explicitly instantiated (14.7.2) or explicitly specialized (14.7.3),
the class template specialization is implicitly instantiated when the
specialization is referenced in a context that requires a
completely-defined object type or when the completeness of the class
type affects the semantics of the program. [..]
But, because B<A> contains a member of type A, and A is not a complete type, and [8.3.5/6] requires it to be, the instantiation is invalid, B<A> remains incomplete... and the program is ill-formed.
* I haven't yet found a citation to back this up, though it seems obvious.
I think the relevant paragraph comes from the classes chapter 2:
A class is considered a completely-defined object type (3.9) (or
complete type) at the closing } of the class-specifier. Within the
class member-specification, the class is regarded as complete within
function bodies, default arguments, exception-specifications, and
brace-or-equal-initializers for non-static data members (including
such things in nested classes). Otherwise it is regarded as incomplete
within its own class member-specification.
B<A> func2() //A is incomplete here, but B<A> isn't instantiated
{
return B<A>(); //A is complete here
}
B<A>::C_type func3() //A is incomplete, B<A> needs to be instantiated for C_type
{
return B<A>::C_type(); //OK, A is complete
}
It is a circular reference:
In order for the compiler to understand what the structure A is, it must be able to understand what B<A>::C_type (since it contains a member that returns an object of this type). So, in order to understand what B<A>::C_type is, the compiler must understand what B<A> is. But in order to understand what B<A> is, the compiler needs to know what A is.
Essentially, you've given the compiler a paradox: in order to understand what A is, you need to first know what A is.
You can store the member my_t in template <typename T> struct B as pointer and handle its construction, destruction and copying in struct B, like so:
template <typename T>
struct B
{
typedef C<T> C_type;
T* my_t;
};