What is a reliable way to specialize templates in C ++ for split header / source

In medium or even large complex projects, it is useful to separate declaration and template definition to reduce compilation time.

However, in complex code, small programmer errors can lead to an imperceptible change in behavior, for example, instead of specialization, the universal version is called.

Example: Template specialization has become invisible due to a missing declaration.

///////////////////// file A.hpp ///////////////////// #include <iostream> template <typename T> class A { public: void foo() { std::cerr << " calling generic foo " << std::endl ; } }; // forgetting following declaration leads to an unintended program behaviour template <> void A< int >::foo(); ///////////////////// file A-foo-int.cpp ///////////////////// #include "A.hpp" template <> void A< int >::foo() { std::cerr << "calling <int> version of foo" << std::endl; } ///////////////////// file main.cpp ///////////////////// #include "A.hpp" int main(int argc , char** argv) { A<int>* a = new A<int>(); a->foo(); return 0; } ///////////////////// Makefile ///////////////////// CC = g++ CPPFLAGS += -Wall -O3 CXXFLAGS += --std=gnu++0x all: nonrobust-template-setup nonrobust-template-setup: main.o A-foo-int.o $(CC) $(CPPFLAGS) main.o A-foo-int.o -o nonrobust-template-setup clean: rm -rf *.o nonrobust-template-setup ////////////////////////////////////////// 

Question: is it a more reliable setting (independent of the compiler and platform) and if, what would it look like?

If not, then what is the way to verify that the desired functional version is being called?

+6
source share
4 answers

You cannot separate declarations and definitions this way: if you cancel the definition of your specialized member functions in a separate .cpp file, regardless of whether you declare your specialization immediately after the main template, the compiler will not be able to instantiate, and the linker will complain unresolved links.

Typically, the definition of the member functions of a class template is contained in the header file, unless you specify the explicit creation of instances of the corresponding class templates:

 template class X<int>; // You should add this to make your program build, // or instantiate the corresponding class template // specialization and invoke the foo() method in the // same translation unit (A.cpp) 

In general, if you do not encounter really terrible compile-time problems, I suggest you follow the usual practice and put everything in the header file, which should be included with all translation units that should use the class template:

 ///////////////////// file A.hpp ///////////////////// #include <iostream> template <typename T> class A { public: void foo() { std::cerr << "error: called generic foo " << std::endl ; } }; template <> void A< int >::foo() { std::cerr << "calling <int> version of foo" << std::endl; } ///////////////////// file main.cpp ///////////////////// #include "A.hpp" int main(int argc , char** argv) { A<int>* a = new A<int>(); a->foo(); return 0; } 

If you run into really terrible compile-time problems, you can separate the definitions of a member function and put them in separate translation units with explicit instances, but in C ++ 11 there is no clean / easy way to make sure that all the specializations that you reject in separate .cpp files are declared immediately after the main template (a recommendation on good practice is recommended). If this were so, I think it would be so popular that you would not need to come here and ask about it, because everyone is faced with such a design problem.

In some cases, some trendy macros can help, but with doubt they will be more useful than pain in maintenance in really complex projects.

The solution to this problem was undertaken in the C ++ 03 standard by introducing the export keyword, but the implementation experience proved to be too difficult to support compiler providers, so export no longer part of C ++ Standard (starting with C ++ 11).

We hope that the best solution for modules will turn it into C ++ 14 and provide a solution for template design.

+2
source

I think the best you can do is static_assert so that the generic template is never created with types that need to be specialized.

The following code is for illustration only - I would probably use BOOST_STATIC_ASSERT (and std::is_same if I could use C ++ 11). The basic idea is to prevent implicit copying of a non-specialized template using a set of types that you forbid. Of course, if you forget to add a static statement AND a specialization with which you still fail.

 template<class T, class U> struct is_same { enum { value = false }; }; template<class T> struct is_same<T, T> { enum { value = true }; }; template <bool enable> struct StaticAsserter { char test[enable]; }; template <typename T> struct foo { // Make sure we can't implicit instantiate foo for int. StaticAsserter<!is_same<int, T>::value> DisallowInt; }; int main() { foo<unsigned> hi; foo<int> fail; return 0; } 
+1
source

To verify this, you do not need to specify a common foo() pattern. There is no need to declare specializations when you do this as follows:

 // Ah template <typename T> struct A { void foo(); }; 
 // main.cc #include "Ah" int main ( int c, char **v ) { A<int>().foo(); // A<long>().foo(); // this line will compile but not link } 
 // A.cc #include <cstdio> #include "Ah" template<> void A<int>::foo() { puts("foo!"); } 
0
source

Well, from the comments that instantiate the common implementation of A<T>::foo() , it is not necessarily an error only if you provided the specialization elsewhere.

So, you want to find universal templates whose names duplicate specializations that should have been created only in a specific list of compiler object files, which boils down to finding matching fields in two data sets. There is a join for this:

 # every object and where it defined nm -A *.o | c++filt | grep ' T ' \ | sort -k3 > @all.definitions # definitions that shouldn't be duplicated: nm -A A-foo-int.o | c++filt | grep ' T ' \ | sort -k3 > @my.definitions # everything that shows on both lists: join -j3 @my.definitions @all.definitions 

edit: sed syntax for grep templates doesn't work very well.

0
source

All Articles