Trying to understand templates and name lookup - c++

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…

Related

Incomplete type works with gcc but not with clang and msvc

I have recently learnt about incomplete types and that under certain conditions they can be used as template arguments. In particular, like void, struct incomplete; are both incomplete types. Then I wrote the following program that works with gcc but not with msvc and clang. Live demo
struct incomplete;
template<typename T> struct C
{
static constexpr T t{};
};
template<class T>
struct myClass {
C<T> new_t() { return {}; }
};
int main() {
myClass<incomplete> d;
d.new_t();
}
As we can see the above program compiles with gcc but not with msvc and clang. So I want to know which is the correct technical behavior.
Clang says:
<source>:4:24: error: constexpr variable cannot have non-literal type 'const incomplete'
static constexpr T t{};
while msvc says:
<source>(4): error C2027: use of undefined type 'incomplete'
<source>(1): note: see declaration of 'incomplete'
while GCC accepts the code with both c++17 as well as c++20.
Which compiler is correct here?
The program is ill-formed and gcc is wrong in accepting the code because even though the definition of the static data member is not instantiated(because it is not odr-used), it's declaration will still be instantiated due to the implicit instantiation of C<incomplete> as per temp.inst#3. More importantly, if a declaration uses constexpr or inline (since C++17) specifier, the member must be declared to have complete type.
From temp.inst#3:
The implicit instantiation of a class template specialization causes:
the implicit instantiation of the declarations, but not of the definitions, of the non-deleted class member functions, member classes, scoped member enumerations, static data members, member templates, and friends; and
(emphasis mine)
This means that the implicit instantiation C<incomplete> will cause the implicit instantiation of the declaration of the static data member.
Further from static data member documentation:
However, if the declaration uses constexpr or inline (since C++17) specifier, the member must be declared to have complete type.
(emphasis mine)
This means that since the implicitly instantiated declaration, declares a member of an incomplete type, the program is ill-formed.
Here is the gcc bug report:
static constexpr incomplete (depedent) data member of a class template and in-member initialized incorrectly accepted

C++ concept checking in incomplete-class context

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.

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.

Why instantiation fails for local variable but not for static?

I am trying to find an easy way (with pre C++11, ie no decltype) to document what requirements a template has on a type to work properly. Maybe there are better ways to do this. However, this is my question:
#include <iostream>
template <typename T> struct Foo {
static const int test = sizeof(T::size);
};
template <typename T> struct DetectAndError {
DetectAndError() { int test = sizeof(T::size); }
};
struct Bar {};
int main() {
Foo<Bar> x; // NO ERROR ? :/
// std::cout << x.test << std::endl; // ERROR :)
// DetectAndError<Bar> y; // ERROR :)
}
Why is Foo<Bar> x; not an error?
For the other lines I get what I want:
error: 'size' is not a member of 'Bar'
This is because the standard mandates that test will be instantiated only when it is used. Member variables/member functions/static members of a template class aren't instantiated without them being used.
In your case the moment you try to do a x.test compiler tries to find test and subsequently cannot do it as x::size is missing.
The behaviour is pretty much accepted and common and ofcourse as per the standard.
The other answer is valid, but here's some standardese for it:
N4140 § 14.7.1 [temp.inst]/ 1 and 2
The implicit instantiation of a class template specialization causes
the implicit instantiation of the declarations, but not of the
definitions, default arguments, or exception-specification s of the
class member functions, member classes, scoped member enumerations,
static data members and member templates; and it causes the implicit
instantiation of the definitions of unscoped member enumerations and
member anonymous unions.
the specialization of the member is implicitly instantiated when the
specialization is referenced in a context that requires the member
definition to exist; in particular, the initialization (and any
associated side-effects) of a static data member does not occur unless
the static data member is itself used in a way that requires the
definition of the static data member to exist.
Thus, test is only declared in the first line, but yields an error when instantiation is attempted. As for DetectAndError, you're implicitly calling its default constructor. typedef DetectAndError<Foo> foo or DetectAndError<Foo>* ptr should compile with no problems