Logo

Blog


CppCon B2B Talk C++ Templates - Questions and Answers

This post tries to answer some of the questions posted during my two talks at CppCon 2020:

Here are the videos for both parts:


Are there requirements that are placed on custom types (say a *.h)? Compared to an int or char or string, primitive types?
No. The only thing that comes with templates, in general, is that the compiler needs to see the template and its implementation. That means you cannot forward a function-template declaration or separate a class-template into a header and source file.

Are there circumstances where the compiler can do an implicit conversion for templates?

Well, no. The exception being, if you tell the compiler by explicitly stating the arguments of a function-template. Suppose we have a function template and the two variables x and y like this:

1
2
3
4
5
6
7
8
template<typename T>
bool equal(const T& a, const T& b)
{
   return a == b;
}

int x{3};
unsigned int y{4};

When we call equal with the two variables equal(x, y), the compiler refuses to compile it. It tells us that both parameters of equal must be of the same type. No implicit conversion happens. For a case like this, it could be desirable to make it work. You can do that by explicitly calling the template for a type equal<int>(x, y). This turns implicit conversions on, as we practically ask the compiler to make up an instantiation for int and then call that function. If there is no type deduction going on, which we disabled in this case, we have implicit conversions.


How can the Array class be initialized with the string initializer? There is no constructor here?

To set the context here, we are talking about this example form my talk (I modified it here to be shorter):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
template<typename T,   // #A A type parameter
         size_t SIZE>  // #B A NTTP
struct Array
{
  T*       data()
  {
    return std::addressof(mData[0]);
  }
  const T* data() const
  {
    return std::addressof(mData[0]);
  }
  constexpr size_t size() const { return SIZE; }
  T*               begin() { return data(); }
  T*               end() { return data() + size(); }
  T& operator[](size_t idx) { return mData[idx]; }

  T mData[SIZE];
};

Array<int, 2>    ai{3, 5};
Array<double, 2> ad{2.0};

As you can see, Array is a struct and mData is a public member. With that, Array works like an aggregate. This is the version libc++ uses as it creates no overhead. You can even create an uninitialized version.


Can you expand on why we can define classes multiple times? Why doesn't that violate the ODR?

We cannot define classes multiple times. However, each template parameter combination for a class creates a new type. For example:

1
2
3
4
5
template<typename T>
class A { };

A<int> ai;
A<double> ad;

In the code above A<int> is a type and A<double> is another type. They start with or use the same class as template, but the stamped out versions are different types. Think of it as filling out a registration form for a conference. We all fill in the blanks with different values. My name is probably different from yours. I hope that at least my bank account number differs. So the result is that the organizer gets a lot of different results (instantiations) for the same form (template). We are all attending the same event, yet we are all different people.


If you create arrays with the same type but different size (e.g. Array<int,2> and Array<int,3>), does it generate code for the class of each of those separably? Does this have implications on size/speed? _Yes, it does. See the question above, Array<int,2> and Array<int,3> are two different types. The first has an internal array of size 2 while the second has one of size 3. What the size method returns is also different. That means that you end up with code for both. However, remember that you explicitly requested them. Without templates, you would probably have created Array2 and Array3 by hand and via copy & paste.

You can use C++ Insights to get a better view of the insides. Here is an example cppinsights.io/s/bb1fbd72. In the transformation, you can see that you get practically two distinct types:_

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* First instantiated from: insights.cpp:10 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct Array<int, 3>
{
  inline auto size() const;

  int mData[3];
};
#endif


/* First instantiated from: insights.cpp:11 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct Array<int, 5>
{
  inline int size() const
  {
    return 5;
  }

  int mData[5];
};
#endif

Could you speak a bit on header-only libraries/programs? Are there any benefits/drawbacks to that approach? Is there a reason that's become a modern idiom?

Well, templates more or less require to be declared and implemented in a header file. The compiler needs to see and know the code we have written for a certain function template or class template method. Otherwise, it is not able to instantiate it. This makes header-only libraries, especially when it comes to templates, the defacto default. Since C++17, we can also have inline static member variables. They can be initialized inline. This drops another reason for having a source file along with the header. We no longer need that source file for the initialization code of our class template.


Must Bar also be a class template, or is the following also okay?

1
class Bar : public Foo<int> { };

Bar doesn't need to be a class template. In the original example (as shown below), I made Bar a class template as well. This was to show that to call a method from Foo in Bar comes with some exceptions if Bar is a class template as well.

1
2
template<typename T>
class Bar : public Foo<int> { };

If a non-template (or template) class inherits from a template class, can you use dynamic_cast to convert to/from the parent/child class?

Yes, you can, but you have to provide the full type. That means the class template with its template parameters. Suppose you have a class template A:

1
2
template<typename T>
class A { };

When you like to use dynamic_cast with A, then you have to say, for example: dynamic_cast< A<int> >(yourInstance).


Isn't template inheritance also call "The Curiously Recurring Template Pattern"?

No. CRTP refers to a special kind of inheritance where the base class template takes the derived class as template argument. You find a definition with an example at wikipedia.org


Can inheritance and templates be combined to call say member function foo from base to derived by explicitly calling derived Foo using class template type rather than dynamic pointer? Inheriting from a class template is no different than inheriting from a regular class. The compiler still needs to adjust the this-pointer when calling a method in the base class.


How is the interaction between the templates and the modules?

They work together. A module can export the definition of a template.


Could you please share some good textbooks/reference bibles related to TMP/MP?

I hope this answers your questions. Should I have misunderstood a question, or you need further clarification, feel free to reach out to me via email or Twitter.

Andreas