4

Note: Several related questions (e.g., this one) ended up being marked as duplicates of this question. I am aware of this particular question and follow the solution in the corresponding answers. However, different compilers yield different behavior and I don't know why.

My library features a class template and I would like to offer instances for certain template arguments in a library, as the template requires some significant compilation time. The class template could look like this (stack.hpp)

#ifndef MY_STACK
#define MY_STACK

template<class T>
class stack
{
public:
    stack();
};

#endif

and its implementation resides in a corresponding stack.tpp file

#ifndef MY_STACK_TPP
#define MY_STACK_TPP

#include <iostream>

template<class T>
stack<T>::stack()
{
    std::cout << "My stack constructor!" << std::endl;
}

#endif

As I would like to offer support for only certain template arguments, my stack.cpp creates the following explicit template instances:

#include "stack.hpp"

template class stack<double>;
template class stack<char>;

#include "stack.tpp"

This compiles with g++ and clang++ but there are differences in the symbols of resulting shared library:

g++ -std=c++11 -c stack.cpp -o stack.so
nm -C stack.so | grep stack
0000000000000049 t _GLOBAL__sub_I_stack.cpp
0000000000000000 W stack<char>::stack()
0000000000000000 W stack<char>::stack()
0000000000000000 n stack<char>::stack()
0000000000000000 W stack<double>::stack()
0000000000000000 W stack<double>::stack()
0000000000000000 n stack<double>::stack()

vs.

clang++-7 -std=c++11 -c stack.cpp -o stack.so
nm -C stack.so | grep stack
0000000000000050 t _GLOBAL__sub_I_stack.cpp

In my application, the constructor of such an explicitly instantiated class is not found using clang++, but it works fine with g++. I figure this basic MWE gives the reason. Can anyone tell me how I can get the constructor symbols for my class template using clang++?

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
carlosvalderrama
  • 465
  • 1
  • 6
  • 22
  • 3
    Perhaps you should try instantiating the template ***after*** its constructor is defined, and not before. Look carefully. When your compiler sees "template class stack;" how the heck would it know what the constructor consists off? It hasn't been defined yet. g++ seems to be pretty smart, and is probably defering instantiation until the entire translation unit is parsed. This is probably g++'s compiler-specific extension. I don't use clang and can't verify this, but I'm pretty sure this is the issue. – Sam Varshavchik Feb 14 '20 at 12:11
  • A confusion that could be (somewhat) avoided by *not using the additional indirection* of a `.tpp` file. That `.tpp` is (part of) an *implementation* file, but it looks like a header... – DevSolar Feb 14 '20 at 12:16
  • And the title "Hero Of The Day" goes to... @SamVarshavchik ! This actually was the issue. Thanks a lot -- if you would like to write an answer for future reference, I'll accept it. – carlosvalderrama Feb 14 '20 at 12:19
  • 2
    The provided answer is far more in depth than I would've written, and basically says the same thing. – Sam Varshavchik Feb 14 '20 at 12:58
  • @DevSolar I've always wondered (without finding a definite answer) what is the best pattern for separating e.g. the definition of a member function of a class template (say, with primary class template defined in `foo.h`) into a separate file (commonly `foo.tpp`, or `foo-impl.h`, or `foo-impl.cpp`), which in turn can be included by the related `foo.cpp` file and explicitly instantiated there for the particular entities known (for this kind of use case) to be used in production. I usually resort to `foo.tpp` and then import `foo.tpp` instead of `foo.h` in test code when I instantiate the ... – dfrib Feb 14 '20 at 14:11
  • ... class template with mocks (e.g. commonly in my use case a single type parameter used to statically inject outgoing calls where a mock class can be passed as type template argument in tests). What would be your view on this pattern, which `foo.tpp`/`foo-impl.h`/`foo-impl.cpp` approach would you prefer, and why? In this particular use case I need to import the impl file not only in `foo.cpp` (for production purposes) but also in test code, thus I've usually leaned for the "looks like a header" `.tpp` approach). – dfrib Feb 14 '20 at 14:12
  • @dfri: I am sure there could be a rich philosophical discussion be had on the pro's and con's, but please don't think me simple for boiling it down to one principle: If it's included, it's `*.hpp`; if it's compiled, it's `*.cpp`. – DevSolar Feb 14 '20 at 14:15
  • @DevSolar ok, thanks for the answer! I will probably lean towards `foo-impl.h` for those particular scenarios (where I actually include it) from now on. – dfrib Feb 14 '20 at 14:19

1 Answers1

6

This program is well-formed

Quoting [temp.explicit]/1 [emphasis mine]:

A class, function, variable, or member template specialization can be explicitly instantiated from its template. A member function, member class or static data member of a class template can be explicitly instantiated from the member definition associated with its class template. [..]

And, quoting [temp.explicit]/9 [emphasis mine]:

An explicit instantiation definition that names a class template specialization explicitly instantiates the class template specialization and is an explicit instantiation definition of only those members that have been defined at the point of instantiation.

Thus, in the OPs example, the explicit instantiation definition of stack<T> will not include an explicit instantiation definition of the constructor, as the explicit instantiation definition of stack<T> is placed before providing the definition of it constructor via the .tpp include.

Quoting [temp.point]/8 [emphasis mine]:

A specialization for a function template, a member function template, or of a member function or static data member of a class template may have multiple points of instantiations within a translation unit, and in addition to the points of instantiation described above, for any such specialization that has a point of instantiation within the translation unit, the end of the translation unit is also considered a point of instantiation. A specialization for a class template has at most one point of instantiation within a translation unit. A specialization for any template may have points of instantiation in multiple translation units. If two different points of instantiation give a template specialization different meanings according to the one-definition rule, the program is ill-formed, no diagnostic required.

Thus, for the cases wheres stack.cpp includes two different points of instantiations, and where one is before and one is after the inclusion of stack.tpp, then the program is ill-formed.

However, here it becomes a bit tricky, as the points of instantiations depends on how the class template and its member function (/constructor) is used. As covered by [temp.explicit]/9 quoted above, the explicit instantiation of stack<T> will not result in an explicit instantiation definition of its constructor, and we will instead need to fall back on [temp.point] for details, particularly clauses 1, 2 and 4, on when its use context will lead to an instantiation point before the inclusion of stack.tpp.

The stand-alone example in the question is not covered by any of these cases, and thus the program us not ill-formed.

GCC vs clang: seemingly different instantiation behaviour?

Can anyone tell me how I can get the constructor symbols for my class template using clang++?

As the constructor is never used, it should never (need to) be instantiated at all, but it seems as if (from the OPs symbol dump) GCC does so anyway (which is not illegal) whereas clang does not. If one where to use/refer to the constructor after the inclusion of track.tpp, then both GCC and clang naturally instantiates it (for the particular specialization that is used), as they are then required to do so.

dfrib
  • 70,367
  • 12
  • 127
  • 192
  • @walnut I agree that the above is a but ambiguous, and I've been trying to find, without success, a more spot-on reference. I'm unsure if templ.explicit/9 is more relevant as it explicitly mentions class template _specializations_. Instead I would like to find more details regarding what the standard considers a "class template definition" in the "complete" sense. Cppreference (not a normative reference) mentions "_complete_ definition"; _"The **complete definition** must appear before the explicit instantiation of a class template"_, but I cannot find this term in the standard. – dfrib Feb 14 '20 at 14:24
  • @walnut Ah I see, I thought term solely referred to explicit or partial specializations. I will read up a bit on this and update this answer accordingly. Thanks. – dfrib Feb 14 '20 at 14:30
  • @walnut I fully agree that templ.explicit/9 (templ.explicit/12 in the draft HEAD, maybe referring to N4659 would be better...) is the entry point for this topic. However, what about going forward from there with [temp.explicit/7](https://eel.is/c++draft/temp.explicit#7) instead of temp.point? See my updated answer. And thanks for the feedback! – dfrib Feb 14 '20 at 15:14
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/207849/discussion-between-dfri-and-walnut). – dfrib Feb 14 '20 at 15:33
  • I suggest you remove the section about [temp.point]/8, since it isn't really correct. Again I am sorry for causing the confusion, but I made a few mistakes in my previous comments. The instantiation points are not relevant to OP's problem. – walnut Feb 14 '20 at 17:52
  • @walnut (Was away from computer during weekend, will update shortly). If [temp.point]/8 is not relevant here, does this mean that the program well-formed no matter if the constructor is referred to _before_ or _after_ the inclusion of `track.tpp`? – dfrib Feb 17 '20 at 10:09