How to check that a C++ class is incomplete (only declared)? - c++

I would like to write a C++ function that will check that its template parameter class is incomplete, so only class declaration is available but not full definition with all class members.
My function incomplete() looks as follows together with some demo program:
#include <type_traits>
#include <iostream>
template <typename T, typename V = void> constexpr bool is_incomplete = true;
template <typename T> constexpr bool is_incomplete<T, std::enable_if_t<sizeof(T)>> = false;
template <typename T> constexpr bool incomplete() { return is_incomplete<T>; }
struct A;
void print() { std::cout << incomplete<A>(); }
struct A {}; //this line affects GCC
int main()
{
print();
}
It works well in Clang printing 1, but in GCC the program prints 0 despite the fact that A class is incomplete in function print.
https://gcc.godbolt.org/z/qWW3hqbEv
Is GCC wrong here or there is a fault in my program?

Which compiler is correct is currently undecided. It's CWG Issue 1845.
The current wording of 13.8.4.1 [temp.point] does not define the point of instantiation of a variable template specialization. Presumably replacing the references to “static data member of a class template” with “variable template” in paragraphs 1 and 8 would be sufficient.
Given that issue is still in drafting stage (and no normative wording exists yet it the latest available draft), it remains unresolved. It isn't clear if a variable template can have multiple points of instantiation or not (though the direction the issue reporter suggests is clear).
For entities with multiple points of instantiation, the end of the translation unit is one as well. Also, if two points disagree on the definition of template, it's an ODR violation, plain and simple. GCC seems to provide two points, so you see the result of that. And I tend to agree with GCC here. A variable template is in many ways a shorthand for a static data member of a class template, and static data members do have multiple points of instantiation.
Either way, this is playing with the risk of nasal demons. Checking a type for complete-ness is not really possible reliably if that state can change in a TU (and possibly even if it can change in the entire program).

It looks like the finding whether a class is incomplete will be a violation of one definition rule (ODR) if at some point it becomes complete, so there should be no valid solution to the question.
Some more quotes from https://eel.is/c++draft/temp.point that I was suggested:
1 For a function template specialization ... the point of instantiation ... immediately follows the namespace scope declaration or definition that refers to the specialization.
7 A specialization for a function template ... may have multiple points of instantiations within a translation unit, and in addition to the points of instantiation described above,
for any such specialization that has a point of instantiation within the ... the translation-unit ..., the point after the ... [end of] the translation-unit is also considered a point of instantiation,
If two different points of instantiation give a template specialization different meanings according to the one-definition rule, the program is ill-formed, no diagnostic required.

Related

Point of template instantiation, dependent names and ADL

It's my understanding from post that argument dependent lookup is used when template dependent names are bound at the point of instantiation. I understand the first simple form below, however am unsure how exactly ADL and binding works on the second. I have a specific question regarding overriding sort().
Appreciate if anyone can guide me on how ADL and binding is occurring in the second code post. Also, if there is a way to redefine sort() to not bind to the std::sort and to bind to some other implementation.
template<typename T> T f(T a) {
// Can find ::ns::g(Q) only via ADL on T for
// an instantiation of f with T == ::ns::Q.
return g(a);
}
namespace ns {
class Q {};
Q g(Q e) { return e; }
} // namespace ns
int main() {
(void) f(::ns::Q{});
return 0;
}
Form that I need help with:
#include <iostream>
#include <vector>
template<typename T>
void print_sorted(std::vector<T>& v)
{
//typename std::vector<T>::iterator it;
sort(v.begin(),v.end()); // ADL looks at return type of begin()?
for (const auto& x : v)
std::cout << x << '\n';
}
int main(int argc, char *argv[])
{
std::vector<std::string> v = {"b", "a"};
print_sorted(v); // sort using std::sort, then print using std::cout
return 0;
}
#include <algorithm> // adl of sort() would allow std::sort to be defined here
Compilation:
clang++ -pedantic -Wall -std=c++11 test187.cc && ./a.out
a
b
I tried the OP's code on Godbolt. Just as OP claimed, it compiles under Clang (and, it seems, on GCC as well). If #include <algorithm> is commented out, both compilers claim that the name sort is unknown to them.
I believe that the reason for this behaviour is that these compilers defer the instantiation of the print_sorted function until the end of the translation unit. The license to do so is given by C++17 [temp.point]/8:
A specialization for a function template, a member function template, or of a member function or static
data member of a class template may have multiple points of instantiations within a translation unit, and
in addition to the points of instantiation described above, for any such specialization that has a point
of instantiation within the translation unit, the end of the translation unit is also considered a point of
instantiation. A specialization for a class template has at most one point of instantiation within a translation
unit. A specialization for any template may have points of instantiation in multiple translation units.
If two different points of instantiation give a template specialization different meanings according to the
one-definition rule (6.2), the program is ill-formed, no diagnostic required.
In other words, regardless of where the function template specialization is instantiated, it is also considered to be instantiated at the end of the translation unit, and all such instantiations must have the same meaning, otherwise the program is ill-formed NDR.
Thus, the compiler writers may defer function template instantiations, as much as possible, until the end of the translation unit, possibly for efficiency reasons, without being concerned that this would change the meaning of the program. If it does change the meaning of the program, it means that the programmer messed up and the compiler is not required to catch this mistake. (See also CWG 993)
The wording of [temp.point]/8 is unclear as to whether it applies to the OP's program (since here, at one point of instantiation, the specialization is actually ill-formed due to using an undeclared name, as opposed to simply having a different meaning than it does at another point of instantiation). I think this is just a defect in the wording, and GCC and Clang are following the "spirit" of the rule.

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 can a dependent name be considered as complete even if the actual type is not defined until the very end

Consider this example:
template <class T>
void Yeap(T);
int main() {
Yeap(0);
return 0;
}
template <class T>
void YeapImpl();
struct X;
template <class T>
void Yeap(T) {
YeapImpl<X>(); // pass X to another template
}
template <class T>
void YeapImpl() {
T().foo();
}
struct X {
void foo() {}
};
Note that struct X is not defined until the very end. I used to believe that all odr-used names must be complete at the point of the instantiation. But here, how can the compiler treat it as a complete type prior to its definition?
I have checked the binding rules and lookup rules of dependent name and function template instantiation in cppreference, but none of them can explain what is happening here.
I believe this program is ill-formed, no diagnostic required.
[temp.point]/8 reads, editing out the irrelevant parts:
A specialization for a function template [...] may have multiple points of instantiations within a translation unit, and in addition to the points of instantiation described above, for any such specialization that has a point of instantiation within the translation unit, the end of the translation unit is also considered a point of instantiation. [...] If two different points of instantiation give a template specialization different meanings according to the one-definition rule, the program is ill-formed, no diagnostic required.
YeapImpl<X> has two points of instantiation: where it is called on the commented line in the question and at the end of the translation unit. In the first point of instantiation, X is incomplete which would make the body of the function ill-formed. In the second point of instantiation, X is complete which makes the body well-formed.
Those two specializations have [very] different meanings.

When is a C++ template instantiation type checked?

When compiling C++, gcc and clang seems to postpone the type-checking of template instantiations until after all declarations of the program have been processed. Is this guaranteed in the language?
To elaborate, I can keep a type incomplete at the point where a template is defined or a template instantiation is needed, as long as I complete the type somewhere later in the program:
class A;
class B;
extern A* pa;
// 1. template definition
template<typename T>
T* f() { return static_cast<T*>(pa); }
// 2. template instantiation
B* test() { return f<B>(); }
// 3. completing types
class A { };
class B : public A { };
Note that the definitions of A and B are required to type check the template instantiation (to make the static_cast valid). If you leave out step 3, step 2 will no longer compile.
In the organisation of my headers, can I rely that this order will be accepted by any standard C++ compiler?
The rule is called "two-phase name lookup".
The names, which are not dependant on the template parameters, are looked up and checked at definition, and the dependent names are checked at the point of instantiation.
For your example, there is one important detail: the end of translation unit is also considered a point of instantiation for function templates:
C++14 N4140 14.6.4.1 [temp.point] P8:
A specialization for a function template, a member function template, or of a member function or static
data member of a class template may have multiple points of instantiations within a translation unit, and
in addition to the points of instantiation described above, for any such specialization that has a point
of instantiation within the translation unit, the end of the translation unit is also considered a point of
instantiation.
Thus, although the type is incomplete at point "2", where explicit instantiation happens, it is complete at the end of file, which makes the template instantiation legitimate.
Note: Microsoft compiler does not implement this rule in full, violating the standard. In Microsoft compiler, all the lookup happens at the point of instantiation (thus the example should also work, but I don't have access to MSVC to check). Other major compilers do implement this rule correctly.