Storing C++ template function definitions in a .CPP file
Asked Answered
C

14

725

I have some template code that I would prefer to have stored in a CPP file instead of inline in the header. I know this can be done as long as you know which template types will be used. For example:

.h file

class foo
{
public:
    template <typename T>
    void do(const T& t);
};

.cpp file

template <typename T>
void foo::do(const T& t)
{
    // Do something with t
}

template void foo::do<int>(const int&);
template void foo::do<std::string>(const std::string&);

Note the last two lines - the foo::do template function is only used with ints and std::strings, so those definitions mean the app will link.

My question is - is this a nasty hack or will this work with other compilers/linkers? I am only using this code with VS2008 at the moment but will be wanting to port to other environments.

Cribbing answered 22/9, 2008 at 15:55 Comment(2)
The thing that stomps me is the usage of do as an identifier :pRowenarowland
related: #495521Reichenberg
K
319

The problem you describe can be solved by defining the template in the header, or via the approach you describe above.

I recommend reading the following points from the C++ FAQ Lite:

They go into a lot of detail about these (and other) template issues.

Kalie answered 22/9, 2008 at 16:0 Comment(2)
Just to complement the answer, the referenced link answers the question positively, i.e. it is possible to do what Rob suggested and have the code to be portable.Bristling
Can you just post the relevant parts in the answer itself? Why is such referencing even allowed on SO. I have no clue what to look for in this link as it has been heavily changed since.Poddy
M
177

For others on this page wondering what the correct syntax is (as did I) for explicit template instantiation (or at least in VS2008), its the following...

In your .h file...

template<typename T>
class foo
{
public:
    void bar(const T &t);
};

And in your .cpp file

template <class T>
void foo<T>::bar(const T &t)
{ }

// Explicit template instantiation
template class foo<int>;
Meares answered 19/12, 2012 at 12:21 Comment(6)
Do you mean "for explicit CLASS template specialiastion". In that case will that cover every function that the templated class has ?Idocrase
@Arthur seems not, I have some template methods stay in header and most of other methods in cpp, works fine. Very nice solution.Tourniquet
In the asker's case, they have a function template, not a class template.Musteline
So, you can put multiple template class foo<...> at the bottom of certain file, right? So, one file for definitions for int, for example, Other for float, if there are any differences, if there are not differences you can just pul template class foo<float> under int? Am I getting this right?Vazquez
I noticed my link time (clang) increased significantly taking this approach.Secretory
I'm completely completely confused about your use of typename AND class, here..Newbold
D
36

Your example is correct but not very portable. There is also a slightly cleaner syntax that can be used (as pointed out by @namespace-sid, among others).

However, suppose the templated class is part of some library that is to be shared...

Should other versions of the templated class be compiled?

Is the library maintainer supposed to anticipate all possible templated uses of the class?

An Alternate Approach

Add a third file that is the template implementation/instantiation file in your sources.

lib/foo.hpp - from library

#pragma once

template <typename T>
class foo {
public:
    void bar(const T&);
};

lib/foo.cpp - compiling this file directly just wastes compilation time

// Include guard here, just in case
#pragma once

#include "foo.hpp"

template <typename T>
void foo::bar(const T& arg) {
    // Do something with `arg`
}

foo.MyType.cpp - using the library, explicit template instantiation of foo<MyType>

// Consider adding "anti-guard" to make sure it's not included in other translation units
#if __INCLUDE_LEVEL__
  #error "Don't include this file"
#endif

// Yes, we include the .cpp file
#include <lib/foo.cpp>
#include "MyType.hpp"

template class foo<MyType>;

Organize your implementations as desired:

  • All implementations in one file
  • Multiple implementation files, one for each type
  • An implementation file for each set of types

Why??

This setup should reduce compile times, especially for heavily used complicated templated code, because you're not recompiling the same header file in each translation unit. It also enables better detection of which code needs to be recompiled, by compilers and build scripts, reducing incremental build burden.

Usage Examples

foo.MyType.hpp - needs to know about foo<MyType>'s public interface but not .cpp sources

#pragma once

#include <lib/foo.hpp>
#include "MyType.hpp"

// Declare `temp`. Doesn't need to include `foo.cpp`
extern foo<MyType> temp;

examples.cpp - can reference local declaration but also doesn't recompile foo<MyType>

#include "foo.MyType.hpp"

MyType instance;

// Define `temp`. Doesn't need to include `foo.cpp`
foo<MyType> temp;

void example_1() {
    // Use `temp`
    temp.bar(instance);
}

void example_2() {
    // Function local instance
    foo<MyType> temp2;

    // Use templated library function
    temp2.bar(instance);
}

error.cpp - example that would work with pure header templates but doesn't here

#include <lib/foo.hpp>

// Causes compilation errors at link time since we never had the explicit instantiation:
// template class foo<int>;
// GCC linker gives an error: "undefined reference to `foo<int>::bar()'"
foo<int> nonExplicitlyInstantiatedTemplate;
void linkerError() {
    nonExplicitlyInstantiatedTemplate.bar();
}

Note: Most compilers/linters/code helpers won't detect this as an error, since there is no error according to C++ standard. But when you go to link this translation unit into a complete executable, the linker won't find a defined version of foo<int>.


Alternate approach from: https://mcmap.net/q/23170/-why-can-templates-only-be-implemented-in-the-header-file

Desiredesirea answered 22/12, 2016 at 22:48 Comment(11)
what does this buy you? You still need to edit foo-impl.cpp in order to add a new specialization.Sturgeon
Separation of implementation details (aka definitions in foo.cpp) from which versions are actually compiled (in foo-impl.cpp) and declarations (in foo.h). I dislike that most C++ templates are defined entirely in header files. That is counter to the C/C++ standard of pairs of c[pp]/h for each class/namespace/whatever grouping you use. People seem to still use monolithic header files simply because this alternative is not widely used or known.Desiredesirea
@Sturgeon I was putting the explicit template instantiations at the end of the definition in the source file at first until I needed further instantiations elsewhere (e.g. unit tests using a mock as the templated type). This separation allows me to add more instantiations externally. Furthermore, it still works when I keep the original as a h/cpp pair although I had to surround the original list of instantiations in an include guard, but I could still compile the foo.cpp as normal. I am still quite new to C++ though and would be interested to know if this mixed usage has any additional caveat.Midinette
I think it is preferable to decouple foo.cpp and foo-impl.cpp. Do not #include "foo.cpp" in the foo-impl.cpp file; instead, add the declaration extern template class foo<int>; to foo.cpp to prevent the compiler from instantiating the template when compiling foo.cpp. Ensure that the build system builds both .cpp files and passes both of the object files to the linker. This has multiple benefits: a) it's clear in foo.cpp that there is no instantiation; b) changes to foo.cpp do not require a recompilation of foo-impl.cpp.Plenipotentiary
This is a very good approach to the problem of templates definitions that takes best of both worlds -- header implementation and instantiation for frequently used types. The only change I would make to this setup is to rename foo.cpp into foo_impl.h and foo-impl.cpp into just foo.cpp. I also would add typedefs for instantiations from foo.cpp to foo.h, likewise using foo_int = foo<int>;. The trick is to provide users two header interfaces for a choice. When user needs pre-defined instantiation he includes foo.h, when user needs something out of order he includes foo_impl.h.Quinby
Shouldn't lib/foo.cpp be lib/foo.inl so project-generating tools like cmake know it shouldn't be compiled directly?Knickknack
@Knickknack If that's the standard you want to follow, you're welcome to do that. I don't like doing so because I like my C++ source files to have a .cpp file extension.Desiredesirea
@Knickknack There is no reason for it to be .cpp file as it includes nothing that couldn't be in a header; I would rename it as .h file.Nurmi
@Nurmi Could could put all functions into a header. There are plenty of reasons to not.Desiredesirea
What is "not very portable" supposed to mean here? The question's syntax is that defined by the standard (modulo do).Merralee
@DavisHerring good question. "Portable" doesn't have a strong definition here. What I mean is that OP's example only works with the predeclared template variants - you can't (easily) declare that more should be built and therefore can't "move" to other codebases. Splitting the "implementation" file from the "definition" file is required to allow correct building of necessary template variants.Desiredesirea
U
27

This code is well-formed. You only have to pay attention that the definition of the template is visible at the point of instantiation. To quote the standard, § 14.7.2.4:

The definition of a non-exported function template, a non-exported member function template, or a non-exported member function or static data member of a class template shall be present in every translation unit in which it is explicitly instantiated.

Undone answered 22/9, 2008 at 16:15 Comment(9)
What does non-exported mean?Insight
@Dan Visible only inside its compilation unit, not outside it. If you link multiple compilation units together, exported symbols can be used across them (and must have a single, or at least, in the case of templates, consistent definitions, otherwise you run into UB).Undone
Thanks. I thought that all functions are (by default) visible outside the compilation unit. If I have two compilation units a.cpp (defining the function a() {}) and b.cpp (defining the function b() { a() }), then this will successfully link. If I'm right, then the above quote would seem not to apply for the typical case... am I going wrong somewhere?Insight
@Dan Trivial counterexample: inline functionsUndone
For exported functions (a typical case, if I understand correctly), though, from the quoted paragraph, would it not then be true that the definition of the template function does not need to be present in a translation unit in which the template function is explicitly instantiated? If so, this seems to violate the implication of the accepted answer, the answer being that it is a correct approach to explicitly instantiate the template function in the same source file (and hence the same translation unit) as where it is defined. If you have time, I'd love to have that clarified! Thanks.Insight
@Dan Function templates are implicitly inline. The reason being that without a standardised C++ ABI it’s hard/impossible to define the effect that this would otherwise have.Undone
watch out, recursive Q&A :)Dilworth
@DanNissenbaum I think the non-exported refers to the export keyword.Rhizotomy
@KonradRudolph: Function templates are not implicitly inline, although they do have similar behavior with regard to the ODR and the linker. (This distinction matters in other situations, including the explicit instantiations mentioned here.)Merralee
F
18

This should work fine everywhere templates are supported. Explicit template instantiation is part of the C++ standard.

Ferraro answered 22/9, 2008 at 16:1 Comment(0)
E
9

That is a standard way to define template functions. I think there are three methods I read for defining templates. Or probably 4. Each with pros and cons.

  1. Define in class definition. I don't like this at all because I think class definitions are strictly for reference and should be easy to read. However it is much less tricky to define templates in class than outside. And not all template declarations are on the same level of complexity. This method also makes the template a true template.

  2. Define the template in the same header, but outside of the class. This is my preferred way most of the times. It keeps your class definition tidy, the template remains a true template. It however requires full template naming which can be tricky. Also, your code is available to all. But if you need your code to be inline this is the only way. You can also accomplish this by creating a .INL file at the end of your class definitions.

  3. Include the header.h and implementation.CPP into your main.CPP. I think that's how its done. You won't have to prepare any pre instantiations, it will behave like a true template. The problem I have with it is that it is not natural. We don't normally include and expect to include source files. I guess since you included the source file, the template functions can be inlined.

  4. This last method, which was the posted way, is defining the templates in a source file, just like number 3; but instead of including the source file, we pre instantiate the templates to ones we will need. I have no problem with this method and it comes in handy sometimes. We have one big code, it cannot benefit from being inlined so just put it in a CPP file. And if we know common instantiations and we can predefine them. This saves us from writing basically the same thing 5, 10 times. This method has the benefit of keeping our code proprietary. But I don't recommend putting tiny, regularly used functions in CPP files. As this will reduce the performance of your library.

Note, I am not aware of the consequences of a bloated obj file.

Egotist answered 21/8, 2018 at 18:1 Comment(0)
E
6

This is definitely not a nasty hack, but be aware of the fact that you will have to do it (the explicit template specialization) for every class/type you want to use with the given template. In case of MANY types requesting template instantiation there can be A LOT of lines in your .cpp file. To remedy this problem you can have a TemplateClassInst.cpp in every project you use so that you have greater control what types will be instantiated. Obviously this solution will not be perfect (aka silver bullet) as you might end up breaking the ODR :).

Effeminize answered 6/11, 2011 at 21:44 Comment(3)
Are you certain it will break the ODR? If the instantiation lines in TemplateClassInst.cpp refer to the identical source file (containing the template function definitions), isn't that guaranteed not to violate the ODR since all definitions are identical (even if repeated)?Insight
Please, what is ODR?Aside
@nonremovable: ODR stands for "One Definition Rule". See here for an explanation.Sweetandsour
L
6

Let's take one example, let's say for some reason you want to have a template class:

//test_template.h:
#pragma once
#include <cstdio>

template <class T>
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

template <>
void DemoT<int>::test()
{
    printf("int test (int)\n");
}


template <>
void DemoT<bool>::test()
{
    printf("int test (bool)\n");
}

If you compile this code with Visual Studio - it works out of box. gcc will produce linker error (if same header file is used from multiple .cpp files):

error : multiple definition of `DemoT<int>::test()'; your.o: .../test_template.h:16: first defined here

It's possible to move implementation to .cpp file, but then you need to declare class like this -

//test_template.h:
#pragma once
#include <cstdio>

template <class T>
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

template <>
void DemoT<int>::test();

template <>
void DemoT<bool>::test();

// Instantiate parametrized template classes, implementation resides on .cpp side.
template class DemoT<bool>;
template class DemoT<int>;

And then .cpp will look like this:

//test_template.cpp:
#include "test_template.h"

template <>
void DemoT<int>::test()
{
    printf("int test (int)\n");
}


template <>
void DemoT<bool>::test()
{
    printf("int test (bool)\n");
}

Without two last lines in header file - gcc will work fine, but Visual studio will produce an error:

 error LNK2019: unresolved external symbol "public: void __cdecl DemoT<int>::test(void)" (?test@?$DemoT@H@@QEAAXXZ) referenced in function

template class syntax is optional in case if you want to expose function via .dll export, but this is applicable only for windows platform - so test_template.h could look like this:

//test_template.h:
#pragma once
#include <cstdio>

template <class T>
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

#ifdef _WIN32
    #define DLL_EXPORT __declspec(dllexport) 
#else
    #define DLL_EXPORT
#endif

template <>
void DLL_EXPORT DemoT<int>::test();

template <>
void DLL_EXPORT DemoT<bool>::test();

with .cpp file from previous example.

This however gives more headache to linker, so it's recommended to use previous example if you don't export .dll function.

Leukas answered 28/6, 2019 at 4:47 Comment(2)
Excellent answerSchramke
This answer concerns explicit specializations (which don't use the primary template at all), not explicit instantiations.Merralee
S
4

Yes, that's the standard way to do specializiation explicit instantiation. As you stated, you cannot instantiate this template with other types.

Edit: corrected based on comment.

Sheared answered 22/9, 2008 at 16:0 Comment(1)
Being picky about terminology it's an "explicit instantiation".Holder
A
4

There is, in the latest standard, a keyword (export) that would help alleviate this issue, but it isn't implemented in any compiler that I'm aware of, other than Comeau.

See the FAQ-lite about this.

Arouse answered 22/9, 2008 at 16:7 Comment(3)
AFAIK, export is dead because they are facing newer and newer issues, each time they resolve the last, making the overall solution more and more complicated. And the "export" keyword won't enable you to "export" from a CPP anyway (still from H. Sutter's anyway). So I say: Don't hold your breath...Teleplay
To implement export the compiler still requires the full template definition. All you gain is having it in a sort-of-compiled form. But really there's no point to it.Directional
...and it's gone from the standard, due to excessive complication for minimal gain.Frae
M
1

Time for an update! Create an inline (.inl, or probably any other) file and simply copy all your definitions in it. Be sure to add the template above each function (template <typename T, ...>). Now instead of including the header file in the inline file you do the opposite. Include the inline file after the declaration of your class (#include "file.inl").

I don't really know why no one has mentioned this. I see no immediate drawbacks.

Marker answered 22/3, 2013 at 1:48 Comment(4)
The immediate drawbacks is it is fundamentally the same as just defining the template functions directly in the header. Once you #include "file.inl", the preprocessor is going to paste the contents of file.inl directly into the header. Whatever reason you wanted to avoid the implementation going in the header, this solution doesn't solve that problem.Philippa
- and means you're, technically unnecessarily, burdening yourself w/ the task of writing all the verbose, mind-bending boilerplate needed by out-of-line template definitions. I get why people want to do it - to achieve the most parity with non-template declarations/definitions, to keep the interface declaration looking tidy, etc. - but it's not always worth the hassle. It's a case of evaluating the trade-offs on both sides and picking the least bad. ... until namespace class becomes a thing :O [please be a thing]Lachrymatory
@Andrew It seems to have gotten stuck in the Committee's pipes, although I think I saw someone saying that wasn't intentional. I wish it had made it into C++17. Maybe next decade.Lachrymatory
@CodyGray: Technically, this is indeed the same for the compiler and it is therefore not reducing the compile time. Still I think this is worth mentioning and practiced in a number of projects I have seen. Going down this road helps to separate Interface from the definition, which is a good practice. In this case it doesn't help with ABI compatibility or the like, but it eases reading and understanding the Interface.Oquendo
E
1

None of above worked for me, so here is how y solved it, my class have only 1 method templated..

.h

class Model
{
    template <class T>
    void build(T* b, uint32_t number);
};

.cpp

#include "Model.h"
template <class T>
void Model::build(T* b, uint32_t number)
{
    //implementation
}

void TemporaryFunction()
{
    Model m;
    m.build<B1>(new B1(),1);
    m.build<B2>(new B2(), 1);
    m.build<B3>(new B3(), 1);
}

this avoid linker errors, and no need to call TemporaryFunction at all

Eudoca answered 13/5, 2020 at 19:57 Comment(3)
Your answer same as the question and it does not work!Maris
Down voted, but this code doesn't compile and work when you actually try and include the header file in another source file to start using this templated class. You will get unresolved symbol linker errors.Posology
Implicit instantiations, like those in TemporaryFunction, do not allow another translation unit to rely on those instantiations to exist (typically because the implementation inlines the calls and does not emit symbols for the function template specializations).Merralee
T
0

Without using modules, I see people usually have two approaches:

  1. Have two "header" files: file.h and file_impl.h. You include file_impl.h at the end of file.h. This is basically the same as having all template definitions after the class declarations, at the end of the file. This is what I do, now.
  2. Have one "header" and one "cpp": file.h and file.cpp. Then, you do some explicit instantiation at the end of the .cpp file. The .cpp has to be compiled and linked. The draw back is that you have do define a priori what template instantiations are allowed, and cannot (can't you???) use other types.

I would like to suggest a third approach. I will try it on my next project, because I think meson with ninja does not work with c++ modules, yet.

Create a mylib.h with the declarations. Create a mylib_impl.h with the template definitions. Create a mylib.cpp... and make it to include mylib_impl.h and do explicit instantiations for the most used types.

Notice that mylib_impl.h may #include "mylib.h". But mylib.h will not include mylib_impl.h at the end.

Now, most of people will include mylib.h. If this user happens to need some "non standard" types, the user will have to include mylib_impl.h OR get this new instantiation to be added to mylib.cpp.

Probably the user will need to be educated to SELDOM use mylib_impl.h. This will be less tempting if mylib_impl.h does not include mylib.h. In this case, anyone that needs a new instantiation will have to include both.

Also, notice that mylib.cpp will have to be linked to.

// MyTemplate.h
template<class T>
class MyTemplate {
public:
    void Foo();
};

// MyTemplate_impl.h
template<class T>
void MyTemplate<T>::Foo() {
    // ...
}

// MyTemplate.cpp
#include "MyTemplate.h"
#include "MyTemplate_impl.h"
template class MyTemplate<int>;
template class MyTemplate<char>;

// code_1.cpp
#include "MyTemplate.h"
MyTemplate<int> instance;

// code_2.cpp
#include "MyTemplate.h"
#include "MyTemplate_impl.h"
MyTemplate<float> instance;

Finally, I am sorry if I am saying something silly. Please, be patient. :-)

Thorson answered 6/3 at 20:20 Comment(0)
L
-1

There is nothing wrong with the example you have given. But i must say i believe it's not efficient to store function definitions in a cpp file. I only understand the need to separate the function's declaration and definition.

When used together with explicit class instantiation, the Boost Concept Check Library (BCCL) can help you generate template function code in cpp files.

Labialize answered 27/10, 2008 at 15:13 Comment(1)
What is inefficient about it?Philippa

© 2022 - 2024 — McMap. All rights reserved.