0

Many nowadays speeches on C++ are about templates and theirs usage for compile-time polymorphism implementation; virtual functions and run-time polymorphism are almost not discussed.

We can use compile-time polymorphism in many situations. And because it gives us compile-time checks instead of possible run-time errors related to runtime polymorphism, and well as some (usually insignificant, however) performance benefit, it looks nowadays most widely use libraries prefer compile-time polymorhism over run-time one.

However, for me it looks like compile-time polymorphism implemented with C++ templates result in much less self-documented and readable code than virtual types hierarchy.

As real life example we can review boost::iostreams. It implements stream as template that accepts device class as an argument. It results a situation when implementation of specific functionality is divided in many classes and files in different folders, so investigation in such code is much more complex than if streams will form classes hierarchy with virtual functions like we have in Java and .NET Framework? What is benefit of compile-time polymorphism here? File stream is something that reads and writes file, stream is something that reads and writes (anything), it is classical example of types hierarchy, so why not to use single FileStream class that overloads some protected functions instead of dividing semantically united functionality into different files and classes?

Another example is boost::process::child class. It uses templated constructor to setup standard i/o and other process parameters. It is not well-documented, and it is not obvious from this function prototype what arguments in what format will this template accept; implementation of member functions similar to SetStandardOutput will be much better self-documented and result in faster compile time, so what is benefit of template usage here? Again, I compare this implementation to .NET Framework here. For member functions similar to SetStandardOutput it is enough to read single header file to understand how to use the class. For templated constructor of boost::process::child we have to read many small files instead.

There are a lot of examples similar to this one. For any reason, well known open source libraries almost never use virtual classes hierarchy and prefer to use compile-time polymorhism (primarily templates-based) like boost does.

The question: are there any clear guidelines what we have to prefer (compile-time or run-time polymorphism) in situations where we can use both ones?

Vitalii
  • 4,434
  • 4
  • 35
  • 77
  • 1
    Your example is moot, because the actual compile-time solution is not template machinery but a plain set of overloads. That's even shorter than the ladder of casts. – Quentin Sep 26 '19 at 09:11
  • 6
    _"Generally speaking, in 90% of situation templates and virtual functions are interchangeable"_ [ [citation needed](https://xkcd.com/285/) ] – Max Langhof Sep 26 '19 at 09:14
  • Well, I think that placing here example from real life that contains 1000 lines of code will not be good idea. My objective was to show what I mean. – Vitalii Sep 26 '19 at 09:14
  • 1
    Comparing templates vs virtual functions on a case where you already have a polymorphic type hierarchy is not exactly meaningful. Both templates and polymorphy are tools that C++ offers, and they have significant differences that you are brushing over here (but that are too broad to cover in an answer). I suggest you become familiar with both to understand and recognize when one is the better tool for a given task. It's definitely not only a readability thing. – Max Langhof Sep 26 '19 at 09:16
  • 1
    @Vitalii maybe, but then you failed to make your point since this is actually a gross misuse of templates. One could equally craft an example where using virtual functions requires a huge amount of boilerplate and is just the wrong solution. – Quentin Sep 26 '19 at 09:19
  • 1
    As an example, please implement `std::list` or `std::vector` or any other generic container without templates. Give it a try, you will learn something about the differences between polymorphy and templates. – Max Langhof Sep 26 '19 at 09:20
  • Of course, for containers we better use templates. But what about more complex situations? For example, we have `Boost.Process` with templated constructor for `child` class, and its template parameters are not documented in any single place; from its prototype, we have no idea what arguments `child` expects; usage of member functions similar to `SetStandardOut` will be much better self-documented; so what reasons are behind templated constructor for this specific case? It is just one real life sample. – Vitalii Sep 26 '19 at 09:27
  • In my opinion, important fact is, many people often miss it, you can combine templates with virtual functions, Yes, template class can has virtual function and that is very useful, specially if you have old compiler for some embedded systems, and you want for example to implement callbacks, so that way, you can have best from both worlds, compile time and run time polymorphism. As an example you can parameterize function operator by return value and make it virtual, and there are many more use cases. – Boki Sep 26 '19 at 09:32
  • Another example from `boost`: we have `iostreams` where each stream is template that accepts device type as its argument. The question: why not to implement virtual methods that will perform actual read, write, etc operation? What is reason/benefit of templates here? – Vitalii Sep 26 '19 at 09:32
  • 2
    I'm afraid this question is devolving into "when to use templates, when to use virtual functions, and where to stop", which is both extremely broad due to the scope and versatility of both of these tools, and in part opinion-based. You'd have to ask the developer behind Boost.Process to know why they went the way of a policy-based constructor template instead of a set of mutating functions, for example. – Quentin Sep 26 '19 at 09:37
  • @Vitalii This is not a discussion forum. Please ask one concrete answerable question that is is on topic ([help/on-topic]). – Max Langhof Sep 26 '19 at 09:38
  • My question is in my question text: are there any clear guidelines what we have to prefer (templates or virtual types hierarchy) in situations where we can use both ways? So, situations similar to 2 samples from `boost`; there are tons of such samples, BTW. – Vitalii Sep 26 '19 at 09:41
  • "for me it looks like templates syntax is ugly often" Well, templates are easier to use than virtual functions in many cases. – L. F. Sep 26 '19 at 09:45
  • 3
    @Vitalii not that I know of. It's just about whether they fulfill your requirements, whether their respective drawbacks are problematic to your case, whether you manage to make maintenable code from them and whether your colleagues agree with the previous point. In general preserving information at compile time is always good, and C++ is currently developing more tools to help with that, which is why you're seeing a lot of templates. – Quentin Sep 26 '19 at 09:46
  • 1
    People on S.O are so much faster to close questions than to actually answer it. I'm certain it's easier/feels better and superior to close a question you think it's not fit than to actually be humble and write an answer. I was looking for more answers to this one. – Vinícius Sep 26 '19 at 10:56
  • Sure. Personally me very rarely down-vote questions or vote for closing; if question is not good, don't answer it and don't prevent other people to answer. This question is not attempt to sell something or political propaganda, I don't understand why it is bad to discuss it. – Vitalii Sep 26 '19 at 11:56

1 Answers1

3

Generally speaking, in 90% of situation templates and virtual functions are interchangeable.

First of all, we need a clarification what we are talking about. If you "compare" something, it must be in some criteria equivalent. My understand of your statement is not comparing virtual functions with templates but within the context of polymorphism!

Your examples are not well selected in that case and also dynamic cast is more a "big hammer" out of the toolbox as if we talk about polymorphism.

Your "template example" did not need to use templates as you have simple overloads which can be used without any templated code at all!

If we are talking of polymorphism and c++ we have at a first selection runtime-polymorphism and compile-time polymorphism. And for both we have standard solutions in c++. For runtime we go with virtual functions, for compile-time polymorphism we have CRTP as a typical implementation and not templates as a general term!

are there any comments or recommendations from C++ committee or any other authoritative source, when we have to prefer ugly syntax of templates over much more understandable and compact virtual functions and inheritance syntax?

The syntax isn't ugly if you are used to use it! If we are talking about implementing things with SFINAE, we have some hard to understand rules with template instantiation, especially often misunderstood deduced context.

But in C++20 we will have concepts, which can replace SFINAE in most contexts which is a great thing I believe. Writing code with concepts instead of SFINAE makes it more readable, easier to maintain and a lot easier to extend for new types and "rules".

Standard library if full of templates and has a very limited amount of virtual functions. Does it mean that we have to avoid virtual functions as much as possible and prefer templates always even if theirs syntax for some specific task is much less compact and much less understandable?

The question feels you misunderstood the C++ features. Templates allows use to write generic code while virtual functions are the C++ tool to implement runtime polymorphism. There is nothing which is 1:1 comparable.

While reading your example code, I would advice you to think again about your coding style!

  1. If you want to write functions specific for different data types, simply use overloads as you did in your "template" example, but without the unneeded templates!

  2. if you want to implement generic functions which works for different data types in the same code, use templates and if some exceptional code is needed for specific data types use template specialization for the selected specific code parts.

  3. If you need more selective template code which needs SFINAE you should start implementing with c++20 concepts.

  4. If you want to implement polymorphism decide to use run time or compile time polymorphism. As already said, for the first one virtual functions are the standard C++ tool to implement that and CRTP is one of standard solutions for the second one.

And my personal experience with "dynamic cast" is: Avoid it! Often it is a first hint that something is broken with your design. That is not a general rule but a check point to think again about the design. In rare cases it is the tool which fits. And in RTTI is not available for all targets and it has some overhead. On bare metal devices/embedded systems you sometimes can't use RTTI and also exceptions. If your code is intended to be used as a "platform" in your domain, and you have the mentioned restrictions, don't use RTTI!

EDIT: Answers from the comments

So, for now, with C++ we can make classes hierarchy with run-time polymorphism only.

No! CRTP builds also class hierarchies but for compile time polymorphism. But the solutions is quite different as you don't have a "common" base class. But as all is resolved in compile time, there is no technical need for the common base class. You simply should start reading about Mixins, maybe here: What are Mixins (as a concept) and CRTP as one of the implementation methods: CRTP article wikipedia.

don't know how to implemented something similar to virtual functions without run-time overhead.

See above CRTP and Mixin exactly implementing polymorphism without runtime overhead!

Templates give some possibility to do that.

Templates are only the base C++ tool. Templates are the same level as loops in C++. It is much to broad to say "templates" in this context.

So, if we need class hierarchy, does it mean that we have to use it even it will force us to use less compile time checks?

As said, a class hierarchy is only a part of the solution for the task to implement polymorphism. Think more in logical things to implement like polymorphism, serializer, database or whatever and the implementation solutions like virtual functions, loops, stacks, classes etc. "Compile time checks"? In most cases you don't have to write the "checks" your self. A simple overload is something like an if/else in compile time which "checks" for the data type. So simply use it out of the box, no template nor SFINAE is needed.

Or we have to use templates to implement some sort of compile-time classes hierarchy even it will make our syntax much less compact and understandable

Already mentioned: Template code can be readable! std::enable_if is much easier to read as some hand crafted SFINAE stuff, even both uses the same C++ template mechanics. And if you get familiar with c++ 20 concepts, you will see that there is a good chance to write more readable template code in the upcoming c++ version.

Klaus
  • 24,205
  • 7
  • 58
  • 113
  • 1
    @Vitalii: Added answers to your comments. But we should not start a discussion here. As you see, it is already a broad answer and you should start reading about mixin, sfinae, crtp and c++20 concepts. You should try to think in "things to solve and implement" and "technics to solve in given language". Read more about patterns and idioms in general. Your software design should not be implementation centric but more based on general concepts. – Klaus Sep 26 '19 at 10:40
  • Well, c++20 concepts is not something that is actual for today; what version of `gcc` are supplied by default with common Linux distributions? What C++ standard does Android NDK support? Even Microsoft Visual C++ doesn't support C++ 20 for now. Even C++ 17 is very young today, and for example Google coding style still discourages its usage. So, we'll get concepts available natively on most systems in at least 3-5 years I think. – Vitalii Sep 26 '19 at 12:03
  • @Vitalii: Most parts of concepts is already implemented in gcc since 2010! The deltas between C++20 and the early implementations are not such big as I know. It might be possible that you have to change some details later after gcc updates to c++20 standard. But you can start to read about and also implement with this feature as it available in a long list of older gcc versions. – Klaus Sep 26 '19 at 13:29