Is it possible to set the definition of specialization of the function of the template member (withOUT default body) in the source file?

Here is what I mean:

// test.h class cls { public: template< typename T > void f( T t ); }; 

-

 // test.cpp template<> void cls::f( const char* ) { } 

-

 // main.cpp int main() { cls c; double x = .0; cf( x ); // gives EXPECTED undefined reference (linker error) const char* asd = "ads"; cf( asd ); // works as expected, NO errors return 0; } 

This is completely normal, right?

I began to doubt it because I just ran through the specialization of '...' after instantiation error specialization of '...' after instantiation , which was new to me. So, I "circumvented" this error, and everything seems to be working fine now, but still ...

Is this the correct behavior?


edit: And the same goes for functions that are not members of the template (functions declared without the participation of the template).

+8
c ++ templates member-functions template-specialization
source share
3 answers

The ease of racing in orbit led to why this does not fit the details of the Standard. There may be others in the vicinity.

I will try to explain more simply what the standard wording means, and I hope I get it right and finally explain the linker errors (or the lack of error):

  • What is the point of instantiating?
  • How does the compiler choose specialization?
  • What is needed at the time of instantiation?
  • Why is the linker error?

1 / What is the instantiation point?

An instance point of a template function is the point at which it is called or called ( &std::sort<Iterator> ) with all template parameters specified (*).

 template <typename T> void foo(T) { std::cout << typeid(T).name() << "\n"; } int main() { foo(1); } // point of instantiation of "foo<int>(int)" 

It may be delayed, although it does not match the exact site of the call, for templates called from other templates:

 template <typename T> void foo(T) { std::cout << typeid(T).name() << "\n"; } template <typename T> void bar(T t) { foo(t); } // not a point of instantiation, T is still "abstract" int main() { foo(1); } // point of instantiation of "bar<int>(int)" // and ALSO of "foo<int>(int)" 

This delay is very important because it allows you to record:

  • core recursive patterns (i.e. patterns that apply to each other)
  • custom majors

(*) Roughly speaking, there are exceptions, such as non-template methods of the template class ...


2 / How does the compiler choose specialization?

At the time of instantiation, the compiler should be able to:

  • decide which basic template function to call
  • and perhaps which of his specializations is causing

This old GotW shows specialization issues ... but in short:

 template <typename T> void foo(T); // 1 template <typename T> void foo(T*); // 2 

are overloads , and each generates a different family of possible specializations, the basis of which they are.

 template <> void foo<int>(int); 

is a specialization 1 and

 template <> void foo<int*>(int*); 

- specialization 2.

To allow a function call, the compiler first selects the best overload, ignoring the specialized templates, and then, if she selects a template function, check if she has any specialization that might be better applied.


3 / What is needed at the time of instantiation?

So, from the way the compiler resolves the call, we understand why the standard indicates that any specialization should be declared before its first instantiation point. Otherwise, it is simply not considered.

Thus, at the time of instantiation it was necessary to see:

  • declaration of the underlying template function to be used
  • announcement of the chosen specialization, if any

But what of the definition?

It is not necessary. The compiler assumes that it will either be provided later in the TU, or by the other TU as a whole.

Note: this loads the compiler, because it means that it must remember all the implicit instances that it encountered, and for which it could not emit the body of the function, so that when it finally encounters the definition, it can (finally ) emit all the necessary code for all the specializations that he encountered. It is interesting why this particular approach was chosen, as well as wondering why, even in the absence of an declaration, extern TU can end with undefined function bodies.


4 / Why is the linker error?

Since no definition is provided, gcc trusts you later, and simply issues a call to the unresolved character. If you manage to associate with another TU that provides this symbol, then everything will be fine, otherwise you will get a linker error.

Since gcc follows Itanium ABI , we can just see how it manages characters. It turns out that ABI does not make any difference in specialized specializations and implicit instances, therefore

 cls.f( asd ); 

calls _ZN3cls1fIPKcEEvT_ (which expands as void cls::f<char const*>(char const*) ) and specialization:

 template<> void cls::f( const char* ) { } 

also creates _ZN3cls1fIPKcEEvT_ .

Note: it is not clear to me whether explicit specialization can get another distortion.

+4
source share

No, I do not think that everything is in order:

[C++11: 14/6]: A function template, a member function of a class template, or a static data element of a class template must be defined in each translation unit in which it is implicitly instantiated (14.7.1), unless the relevant specialization is explicit not created (14.7.2) in some translation unit; no diagnostics required.

[C++11: 14.7.3/6]: If a template, member template or member of a class template is explicitly specialized, then this specialization must be declared before the first use of this specialization, which will cause implicit instantiation, in each translation unit in which such use occurs; no diagnostics required. [..]

Honestly, I can’t explain why it works for you.

+3
source share

I think your source code was wrong, and your “workaround” is also not up to standard (even though your compiler and linker handle it). Good quotes from the standard were given in the answer of @Lightness in-orbit races . See also the following example from the standard ([temp.expl.spec] 14.7.3 / 6):

 class String { }; template<class T> class Array { /* ... */ }; template<class T> void sort(Array<T>& v) { /* ... */ } void f(Array<String>& v) { sort(v); // use primary template // sort(Array<T>&), T is String } template<> void sort<String>(Array<String>& v); // error: specialization // after use of primary template template<> void sort<>(Array<char*>& v); // OK: sort<char*> not yet used 

I tagged my answer as a community wiki, because this is actually just a big comment.

+2
source share

All Articles