Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is a robust way of template specialization in C++ for separated header/source

In moderate-sized or even big complex projects separating template declaration and definition is useful to reduce compilation time.

However, in a complex code small programmer mistakes may lead to unnoticed behaviour change, e.g. a generic version is called instead of a specialization.

Example: Template specialization became invisible due to a missed 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 a more robust setup possible (compiler- and platform-independent) and if, how would it look like?

If not, what is a good way to test that a desired function version is called?

like image 987
Jakob Kroeker Avatar asked Mar 12 '13 16:03

Jakob Kroeker


2 Answers

You cannot separate declarations and definitions that way: if you relegate the definition of your specialized member functions in a separate .cpp file, no matter if you declare your specialization immediately after the primary template, the compiler won't be able to instantiate it, and the linker will complain about unresolved references.

Normally, the definition of member functions of a class template goes in a header file, unless you provide an explicit instantiation for 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, unless you are facing really horrible compilation time issues, I would suggest you to follow the common practice and put everything in a header file to be included by all the translation units that need to 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 are facing really horrible compilation time issues, then you could separate the member function definitions and put them into separate translation units with explicit instantiations, but in C++11 there is no clean/easy way to make sure that all the specializations you relegate in separate .cpp files are declared immediately after the primary template (as good practice recommends). If there were, I guess it would be so popular that you wouldn't have needed to come here and ask about it, because everybody faces such a design issue.

In some cases some fancy macros could help, but doubtfully they would bring more benefit than maintenance pain in really complex projects.

A solution to this problem was attempted in the C++03 standard by introducing the export keyword, but implementation experience proved it too hard to support for compiler vendors, which is why export is no more part of the C++ Standard (since C++11).

Hopefully, a better solution for modules will make it into C++14 and provide a solution for template design.

like image 117
Andy Prowl Avatar answered Nov 08 '22 18:11

Andy Prowl


I think the best you can do is to static_assert that the generic template is never instantiated with the types that are supposed to be specialized.

The following code is to illustrate only - I'd probably use BOOST_STATIC_ASSERT (and std::is_same if I could use c++11). The basic idea is to prevent implicitly instantiating the non-specialized template with the set of types you forbid. Of course if you forget to add the static assert AND the specialization you're still going to 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;
}
like image 1
Mark B Avatar answered Nov 08 '22 19:11

Mark B