The standard allows function templates to be instantiated after the enclosing namespace-scope declaration or at the end of the translation unit when they are referred to from a non-template context: [temp.point]/1
For a function template specialization, a member function template
specialization, or a specialization for a member function or static
data member of a class template, if the specialization is implicitly
instantiated because it is referenced from within another template
specialization and the context from which it is referenced depends on
a template parameter, the point of instantiation of the specialization
is the point of instantiation of the enclosing specialization.
Otherwise, the point of instantiation for such a specialization immediately follows the namespace scope declaration or definition that
refers to the specialization.
[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, the program is ill-formed, no diagnostic
required.
Now consider this minimal reproducible example:
#include <iostream>
#include <array>
struct A {};
std::array<char, 2> show(float, A)
{
std::cout << "2\n";
return {};
}
template<typename T>
struct Fun {
decltype(show(0, T{})) b;
};
template <typename T>
void func(T, int c = sizeof(Fun<T>{}.b))
{
show(0, T{});
std::cout << c << '\n';
}
int main()
{
func(A{});
}
char show(int, A)
{
std::cout << "1\n";
return {};
}
Both GCC and Clang output 1 2 (godbolt).
Here, the instantiation of func<A> (triggered in main) has two points of instantiation: one immediately after main (and thus before the second show) and another at the end of the translation unit. The first 1 indicates that the compilers to instantiate func<A> at the end of the translation unit. However, the default argument sizeof(Fun<T>{}.b) causes Fun<A> to be instantiated, and the second 2 suggests that Fun<A> is instantiated before the second show.
Now, the point of instantiation of default arguments is specified to be that of func<A>: [temp.point]/2
If a function template or member function of a class template is
called in a way which uses the definition of a default argument of
that function template or member function, the point of
instantiation of the default argument is the point of instantiation of
the function template or member function specialization.
Hmm ... This seems to suggest that the two numbers should be the same.
I feel I'm missing something here. Is there any detail that I happened to neglect? Or did I make mistakes?
As quoted in the question [temp.point]/8 says:
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.
According to the one-definition rule two definitions are not the same if function call overload resolution of a name used in the definition will yield different entities defined outside the definition. ([basic.def.odr]/6.2)
Overload resolution of the two calls to show in func<A> and in Fun<A> will choose different function overloads depending on whether func<A>'s point of instantiation is immediately after main or at the end of the translation unit, both of which are allowed points of instantiation.
Therefore the program is ill-formed, no diagnostic required.
Related
Consider the following small code fragment:
#include <iostream>
template<class T>
int test();
int main()
{
std::cout << test<int>() << "\n";
}
// POI for test<int>() should be right here
template<class T>
int test()
{
return 0;
}
Live Example that compiles and prints 0 for both Clang and g++.
Here's the draft Standard quote on the point of instantiation of function templates
14.6.4.1 Point of instantiation [temp.point]
1 For a function template specialization, a member function template specialization, or a specialization for a
member function or static data member of a class template, if the specialization is implicitly instantiated
because it is referenced from within another template specialization and the context from which it is referenced
depends on a template parameter, the point of instantiation of the specialization is the point of
instantiation of the enclosing specialization. Otherwise, the point of instantiation for such a specialization
immediately follows the namespace scope declaration or definition that refers to the specialization.
Vandevoorde and Josuttis have the following to say about this:
In practice, most compilers delay the actual instantiation of
noninline function templates to the end of the translation unit. This
effectively moves the POIs of the corresponding template
specializations to the end of the translation unit. The intention of
the C++ language designers was for this to be a valid implementation
technique, but the standard does not make this clear.
Question: are Clang/g++ non-conforming because they delay the POI to the end of the translation unit?
Core Working Group defect report 993 was created to address this issue:
993. Freedom to perform instantiation at the end of the translation unit
Section: 14.6.4.1 [temp.point] Status: C++11 Submitter: John Spicer Date: 6 March, 2009[Voted into the WP at the March, 2011 meeting.]
The intent is that it is a permissible implementation technique to do template instantiation at the end of a translation unit rather than at an actual point of instantiation. This idea is not reflected in the current rules, however.
Proposed resolution (January, 2011):
Change 14.6.4.1 [temp.point] paragraph 7 as follows:
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...
Paragraph 14.6.4.1/7 in C++11 is 14.6.4.1/8 in N3936:
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 (3.2), the program is ill-formed, no diagnostic required.
So yes, it is allowable for implementations to delay the point of instantiation of templates to the end of the translation unit.
Explicitly specializing after the explicit call to the template will fail at compilation.
#include <iostream>
template<class T>
int test(T y);
int main()
{
std::cout << test<int>(0) << "\n";
}
template<class T>
int test(T y)
{
return 0;
}
// POI for test<int>() should be right here
template<>
int test(int y)
{
return 2;
}
Check the compilation error here
Compilation error time: 0 memory: 0 signal:0
prog.cpp:21:15: error: specialization of ‘int test(T) [with T = int]’ after instantiation
int test(int y)
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.
Code:
#include <memory>
struct Data;
std::unique_ptr<Data> make_me();
int main()
{
std::unique_ptr<Data> m = make_me();
return 0;
}
Which of course fails:
In file included from <source>:1:
In file included from /opt/compiler-explorer/gcc-7.1.0/lib/gcc/x86_64-linux-gnu/7.1.0/../../../../include/c++/7.1.0/memory:80:
/opt/compiler-explorer/gcc-7.1.0/include/c++/7.1.0/bits/unique_ptr.h:76:16: error: invalid application of 'sizeof' to an incomplete type 'Data'
static_assert(sizeof(_Tp)>0,
^~~~~~~~~~~
/opt/compiler-explorer/gcc-7.1.0/include/c++/7.1.0/bits/unique_ptr.h:268:4: note: in instantiation of member function 'std::default_delete<Data>::operator()' requested here
get_deleter()(__ptr);
^
8 : <source>:8:31: note: in instantiation of member function 'std::unique_ptr<Data, std::default_delete<Data> >::~unique_ptr' requested here
std::unique_ptr<Data> m = make_me();
^
3 : <source>:3:8: note: forward declaration of 'Data'
struct Data;
^
1 error generated.
Compiler returned: 1
But adding below line at the end of above code compiles fine:
struct Data {};
My question is why this code compiles and works when Data is declared after point of instantiation of std::unique_ptr? Seemingly, both cases should fail with the same/similar error..
Whole example on godbolt: https://godbolt.org/g/FQqxwN
This works because the point of instantiation of a template is located after the definition of Data. From the Standard:
[temp.point]
For a class template specialization, a class member template specialization, or a specialization for a class member of a class template, 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.
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, the program is ill-formed, no diagnostic required.
Note that this is probably ill-formed (NDR) due to the last sentence in the quote. I am not confident enough to tell whether this is certainly ill-formed or not.
If you read a little closer, the problem is with the deletion of the contained Data object. The
get_deleter()(__ptr)
part is the big hint.
What happens here is that the unique pointer object m goes out of scope at the end of the main function, so the data pointed to needs to be deleted. However, since there is no destructor the default deleter can not handle it.
To solve it you can add a definition of the structure, which will make it defined and the default deleter will be able to know the type. Or you could add a new deleter for the pointer, one which (in this case) might do nothing:
auto null_deleter = [](Data*){ /* Do nothing */ };
...
std::unique_ptr<Data, decltype(null_deleter)> m = make_me();
Of course, if you want to actually delete the data, then either define the structure or modify the deleter so it delete the pointer (which makes it need the full structure definition anyway, but then the deleter could be defined in another tranaslation-unit, probably the same where make_me is defined).
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.
Consider the following small code fragment:
#include <iostream>
template<class T>
int test();
int main()
{
std::cout << test<int>() << "\n";
}
// POI for test<int>() should be right here
template<class T>
int test()
{
return 0;
}
Live Example that compiles and prints 0 for both Clang and g++.
Here's the draft Standard quote on the point of instantiation of function templates
14.6.4.1 Point of instantiation [temp.point]
1 For a function template specialization, a member function template specialization, or a specialization for a
member function or static data member of a class template, if the specialization is implicitly instantiated
because it is referenced from within another template specialization and the context from which it is referenced
depends on a template parameter, the point of instantiation of the specialization is the point of
instantiation of the enclosing specialization. Otherwise, the point of instantiation for such a specialization
immediately follows the namespace scope declaration or definition that refers to the specialization.
Vandevoorde and Josuttis have the following to say about this:
In practice, most compilers delay the actual instantiation of
noninline function templates to the end of the translation unit. This
effectively moves the POIs of the corresponding template
specializations to the end of the translation unit. The intention of
the C++ language designers was for this to be a valid implementation
technique, but the standard does not make this clear.
Question: are Clang/g++ non-conforming because they delay the POI to the end of the translation unit?
Core Working Group defect report 993 was created to address this issue:
993. Freedom to perform instantiation at the end of the translation unit
Section: 14.6.4.1 [temp.point] Status: C++11 Submitter: John Spicer Date: 6 March, 2009[Voted into the WP at the March, 2011 meeting.]
The intent is that it is a permissible implementation technique to do template instantiation at the end of a translation unit rather than at an actual point of instantiation. This idea is not reflected in the current rules, however.
Proposed resolution (January, 2011):
Change 14.6.4.1 [temp.point] paragraph 7 as follows:
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...
Paragraph 14.6.4.1/7 in C++11 is 14.6.4.1/8 in N3936:
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 (3.2), the program is ill-formed, no diagnostic required.
So yes, it is allowable for implementations to delay the point of instantiation of templates to the end of the translation unit.
Explicitly specializing after the explicit call to the template will fail at compilation.
#include <iostream>
template<class T>
int test(T y);
int main()
{
std::cout << test<int>(0) << "\n";
}
template<class T>
int test(T y)
{
return 0;
}
// POI for test<int>() should be right here
template<>
int test(int y)
{
return 2;
}
Check the compilation error here
Compilation error time: 0 memory: 0 signal:0
prog.cpp:21:15: error: specialization of ‘int test(T) [with T = int]’ after instantiation
int test(int y)