I am working on a source-to-source conversion tool, using Clang's AST via libTooling.
There is one problem which I can't seem to workaround. Consider the following C++ code:
template<typename T>
struct s {
void lazy1() {}
void lazy2() {} // unused
};
int main() {
s<int> inst;
inst.lazy1();
return 0;
}
In Clang's AST, definition of lazy1() is fully available (i.e., a CompoundStmt exists as child), while lazy2() has no children:
[...]
`-ClassTemplateSpecializationDecl 0x7de4a8 <line:1:1, line:5:1> line:2:8 struct s definition
[...]
|-CXXMethodDecl 0x7de790 <line:3:2, col:16> col:7 used lazy1 'void ()'
| `-CompoundStmt 0x7de310 <col:15, col:16>
|-CXXMethodDecl 0x7de840 <line:4:2, col:16> col:7 lazy2 'void ()'
[...]
As I understand, Clang only emits method definitions if they are actually used. In this example, lazy2() is unused and I can't access any definition data in the AST.
I need to access all method definitions of all template specializations.
I am interested in a non-hacky, reliable solution. I am ready to patch Clang, if necessary. I've already tried this answer on Stackoverflow, however, in my case it doesn't work.
Related
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
When compiling the following code with the compilation options "-std=c++17 -pedantic-errors" the compilation gives an error with gcc but no errors with clang (see compiler explorer links below). How is this possible? Is this undefined behaviour or does one of the compilers have a bug? Note that I am compiling with "-pedantic-errors" so the difference should not be because of a compiler extension.
template<typename T>
void f()
{
}
class C
{
friend void f<int>()
{
}
};
int main()
{
}
Run on https://godbolt.org
This is the compilation that gcc generates:
<source>:8:17: error: defining explicit specialization 'f<int>' in friend declaration
8 | friend void f<int>()
| ^~~~~~
<source>:8:17: error: ambiguating new declaration of 'void f()'
<source>:2:6: note: old declaration 'void f() [with T = int]'
2 | void f()
| ^
[dcl.meaning]/1 is supposed to forbid this:
An unqualified-id occurring in a declarator-id shall be a simple identifier except for […] and for the declaration of template specializations or partial specializations ([temp.spec]).
One could argue that this is in fact the declaration of a “template specialization”, and that the cross reference is not sufficient to restrict the form of the declaration to one of those described in that subclause. However, given that no such declaration is permitted outside a class (even by Clang), it’s much more reasonable to just call it a bug.
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…
Another question of type "who's right between g++ and clang++?" for C++ standard gurus.
The following code
template <int>
struct foo
{
template <typename>
friend void bar ()
{ }
};
int main ()
{
foo<0> f0;
foo<1> f1;
}
compile without problem with clang++ (only two "unused variable" warnings) but give a the following error
tmp_002-11,14,gcc,clang.cpp: In instantiation of ‘struct foo<1>’:
tmp_002-11,14,gcc,clang.cpp:27:12: required from here
tmp_002-11,14,gcc,clang.cpp:20:16: error: redefinition of ‘template<class> void bar()’
friend void bar ()
^~~
tmp_002-11,14,gcc,clang.cpp:20:16: note: ‘template<class> void bar()’ previously defined here
compiling with g++.
The question, as usual, is: who's right ? g++ or clang++ ?
Checked with clang++ 3.9.1 and g++ 6.3.0 in my Debian platform. But, trying in Wandbox, seems equals with more recent versions.
GCC is right in this case.
The relevant standard wording is in [temp.inst]/2:
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
[...]
However, for the purpose of
determining whether an instantiated redeclaration is valid according
to 6.2 and 12.2, a declaration that corresponds to a definition in the
template is considered to be a definition. [ Example: [...]
template<typename T> struct Friendly {
template<typename U> friend int f(U) { return sizeof(T); }
};
Friendly<char> fc;
Friendly<float> ff; // ill-formed: produces second definition of f(U)
— end example ]
The parts related to friends were added to this paragraph by DR2174 and published in C++17 (it's a defect report, so compilers should apply it to previous standard versions as well).
Recent versions of MSVC and EDG in strict mode also reject the code, complaining about a redefinition.
[temp.inject]/1 is somewhat related, but it only talks about friend functions, not friend function templates:
Friend classes or functions can be declared within a class template.
When a template is instantiated, the names of its friends are treated
as if the specialization had been explicitly declared at its point of
instantiation.
If a C++ template contains a blatant type error, such as a reference to a class member that does not exist, does the language standard guarantee to detect the error at the time the template is defined?
Or is the error guaranteed to be detected only when the template is instantiated?
Suppose the error does not involve any of the template parameters?
Suppose it does involve one of the template parameters?
Checking is allowed, but not required. [temp.res]/8:
Knowing which names are type names allows the syntax of every template
to be checked. The program is ill-formed, no diagnostic required, if:
no valid specialization can be generated for a template and that template is not instantiated, or
every valid specialization of a variadic template requires an empty template parameter pack, or
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
the interpretation of such a construct in the hypothetical instantiation is different from the interpretation of the
corresponding construct in any actual instantiation of the template.
Note that two-phase lookup doesn't actually require early checking: various name lookups are required to be done in the template definition context, but they are not actually required to be done at the time of definition.
As for your (original) title
Can a C++ template be typechecked without instantiating it?
Depends on what exactly is meant with typecheck.
does the language standard guarantee to detect the error at the time the template is defined?
Regarding the template declaration (!) and definition itself, it will be checked for syntactical correctness before instantiation, as you mentioned in your question.
There is some type checking done ...
template<typename T>
class Foo {
Foo() : x(y) {}
private:
int &x;
T z;
};
int main() {
}
clang =============
main.cpp:4:20: error: use of undeclared identifier 'y'
Foo() : x(y) {}
^
1 error generated.
gcc =============
main.cpp: In constructor 'Foo<T>::Foo()':
main.cpp:4:20: error: 'y' was not declared in this scope
Foo() : x(y) {}
^
See the Demo
... but not consistently with
template<typename T>
class Foo {
Foo() : x(Foo::y) {}
private:
int &x;
T z;
};
int main() {
}
clang =============
main.cpp:4:25: error: no member named 'y' in 'Foo<T>'
Foo() : x(Foo::y) {}
~~~~~^
1 error generated.
gcc =============
Demo
Where GCC also throws an error, when Foo is actually instantiated:
template<typename T>
class Foo {
public:
Foo() : x(Foo::y) {}
private:
int &x;
T z;
};
int main() {
Foo<int> foo; // <<<<<<<<<<<<<<<<<<<<<
}
clang =============
main.cpp:5:25: error: no member named 'y' in 'Foo<T>'
Foo() : x(Foo::y) {}
~~~~~^
1 error generated.
gcc =============
main.cpp: In instantiation of 'Foo<T>::Foo() [with T = int]':
main.cpp:12:18: required from here
main.cpp:5:26: error: 'y' is not a member of 'Foo<int>'
Foo() : x(Foo::y) {}
^
Demo
Or is the error guaranteed to be detected only when the template is instantiated?
So this seems to be true.
How far that goes seems to be a compiler implementation specific detail.
So no, obviously there are no standard guarantees regarding that.
Also as #Jarod42 has shown in their Clang/GCC sample
template <typename T>
void foo()
{
int a = "hello world";
const char* hello = 42;
}
int main()
{
}
clang =============
main.cpp:6:9: error: cannot initialize a variable of type 'int' with an lvalue of type 'const char [12]'
int a = "hello world";
^ ~~~~~~~~~~~~~
main.cpp:7:17: error: cannot initialize a variable of type 'const char *' with an rvalue of type 'int'
const char* hello = 42;
^ ~~
2 errors generated.
gcc =============
So I'm afraid there's nothing more available than can be found in section §14.5 of the c++ standard specification what is considered valid template declaration/definition syntax.
Regarding former versions of your question:
I'd like to know how much type checking can be done with a template definition, before the template is instantiated.
The compiler needs to see the concrete parameter types (and non type parameter values) to apply (and in turn instantiate) the constraint checking templates for these types.
So the template must be instatiated to do this.
Perhaps a template is a lot like a macro in Lisp: the compiler checks the syntax, but no typechecking is done until the template is instantiated. And each time the template is instantiated, the compiler runs the typechecker again.
That seems to come closest, though there's nothing like a typechecker that runs during compilation, but mostly instantiating other template classes and let a std::static_assert decide in the end, if the constraints for the type (or non type) parameter are met or not.
To see how the c++ standard library deals with that, see Library Concepts please.