-
Notifications
You must be signed in to change notification settings - Fork 128
Template Implementation Separation
ADIOS2 classes separate declarations from their implementation. Typically this is done by having a separate .h and .cpp file, however it get's more complicated when templates are involved. Template separation is beneficial for mainly 3 reasons:
- Cleaner code interface: separate declaration from implementation (definition).
- Reduce compile times: same effects as non-template functions when inlining doesn't produce any benefits (bottleneck is not in calling the template function).
- Limit types scope: via explicit template implementation and specialization in compiled object (*.o) files.
To maintain the distinct separation between template definition and implementation in ADIOS2, we use explicit instantiation with 4 different source file types:
-
ClassName.h
- The main header file containing only the class and member declarations with no implementation. This also contains the declarations for explicitly instantiated members.
-
ClassName.inl
- A file containing inline function implementations that need to be made public. This is to be included at the bottom of ClassName.h and should only contain implementations that need to be made public.
-
ClassName.tcc
- A file containing most of the template implementations that can be hidden through explicit instantiation.
-
ClassName.cpp
- A file containing the non-template implementations and the explicit instation of any template members.
Here is an example of a simple class Foo
with template member functions Bar1
and Bar2
#ifndef FOO_H_
#define FOO_H_
namespace adios
{
class Foo
{
public:
Foo()
: m_Bar1Calls(0), m_Bar2Calls(0), m_Bar3Calls(0);
{
}
virtual ~Foo() = default;
template<typename T>
void Bar1()
{
Bar1Helper<T>();
}
template<typename T>
void Bar2()
{
Bar2Helper<T>();
}
void Bar3()
{
Bar3Helper();
}
private:
template<typename T>
void Bar1Helper()
{
++m_Bar1Calls;
}
template<typename T>
void Bar2Helper()
{
++m_Bar2Calls;
}
void Bar3Helper()
{
++m_Bar3Calls;
}
size_t m_Bar1Calls;
size_t m_Bar2Calls;
size_t m_Bar3Calls;
};
} // end namespace adios
#endif // FOO_H_
In this example, we want to hide the template implementation from the header. We will implement this such that Bar1
is only callable from the core numeric types, i.e. ints, floats, and complex, while Bar2
is callable from all types. This will necessitate that Bar1
and it's helper function is implemented in a .tcc file with explicit instantiation for the allowed types while Bar2
and it's helper function will need to be inlined in the .inl file to be accessible for all types. We will also use a helper macro ADIOS provides to iterate over the core numeric types for the explicit instantiation of Bar1
.
#ifndef FOO_H_
#define FOO_H_
#include "ADIOSMacros.h"
namespace adios
{
class Foo
{
public:
Foo();
virtual ~Foo() = default;
template<typename T>
void Bar1();
template<typename T>
void Bar2();
void Bar3();
private:
template<typename T>
void Bar1Helper();
template<typename T>
void Bar2Helper();
void Bar3Helper;
size_t m_Bar1Calls;
size_t m_Bar2Calls;
size_t m_Bar3Calls;
};
// Create declarations for explicit instantiations
#define declare_explicit_instantiation(T) \
extern template void Foo::Bar1<T>();
ADIOS_FOREACH_TYPE_1ARG(declare_explicit_instantiation)
#undef(declare_explicit_instantiation)
} // end namespace adios
#include "Foo.inl"
#endif // FOO_H_
Note here that Bar1Helper does not need an explicit instantiation because it's not a visible funtion in the callable interface. It's implementaion will be available to Bar1 inside the tcc file where it's called from.
#ifndef FOO_INL_
#define FOO_INL_
#ifndef FOO_H_
#error "Inline file should only be included from it's header, never on it's own"
#endif
// No need to include Foo.h since it's where this is include from
namespace adios
{
template<typename T>
void Foo::Bar2()
{
Bar2Helper<T>();
}
template<typename T>
void Foo::Bar2Helper()
{
++m_Bar2Calls;
}
} // end namespace adios
#endif // FOO_INL_
#ifndef FOO_TCC_
#define FOO_TCC_
#include "Foo.h"
namespace adios
{
template<typename T>
void Foo::Bar1()
{
Bar1Helper<T>();
}
template<typename T>
void Foo::Bar1Helper()
{
++m_Bar1Calls;
}
} // end namespace adios
#endif // FOO_TCC_
Foo.cpp containing non-template implementations and explicit instantiations definitions for known types.
#include "Foo.h"
#include "Foo.tcc"
namespace adios
{
Foo::Foo()
: m_Bar1Calls(0), m_Bar2Calls(0), m_Bar3Calls(0)
{
}
void Foo::Bar3()
{
Bar3Helper();
}
void Foo::Bar3Helper()
{
++m_Bar3Calls;
}
// Create explicit instantiations of existing definitions
#define define_explicit_instantiation(T) \
template void Foo::Bar1<T>();
ADIOS_FOREACH_TYPE_1ARG(define_explicit_instantiation)
#undef(define_explicit_instantiation)
} // end namespace adios