Generic Programming is just Programming

Document #: xxx
Date: 2022-08-21
Project: Programming Language C++
SG17
Reply-to: Mihail Naydenov
<>

1 Abstract

This paper argues, current generic programming strays too much from the promise of being like regular programming, to the point correct generic programming is an expert-only endeavor.

2 Motivation

“Generic Programming is just Programming”
B. Stroustrup

Good code is reusable code and reusable code is often parametrized. Currently however, if the programmer wants to make a piece of code reusable by abstracting away the types, he/she will run into trouble:

Original version


void silly(string& s)
{
  using char_type = string::char_type;
  // ...
  if(s.starts_with("hello"))
     s.clear();
  // ...
}

Quite Generic version

template<class S>
void silly(S& s)
{
  using char_type = S::char_type;
  // ...
  if(s.starts_with("hello"))
    s.clear();
  // ...
}

Let’s start with the good news: So far, we do keep the promise, “generic programming is just programming”! With very little effort (and syntax for that matter) we turned our silly function into a generic function, one that will work with “any string”. In fact, this is so good, that it is, arguably, the de facto standard for 99% of the C++ developers. Every non-expert, non-library-writer most probably will write generic code in that manner.

It is all bad news from here on. As we know, the above code is not generic enough, in more aspects then one. However, the moment we want to go “the extra mile”, make the code “right”, we:

  1. must considerably change how we write the function.
  2. fall into a pit of severe problems and heavy compromises (P2279)1.

While there are efforts to solve the pressing issues in B, it seems we have accepted the loss of A. We are living with the fact, we will have to write something in the lines of:
Super Generic version:

template<class S>
void silly(S& s)
{
  using char_type = strings::traits<S>::char_type;
  // ...
  if(strings::starts_with(s, "hello"))
    strings::clear(s);
  // ...
}

This proposal begs to strongly disagree with that sentiment.

3 Problem

There are few problems with the last piece of code, outside to the ones, already covered in P2279.
First and foremost, by forcing new programming style, it is less likely people actually doing it! After all, “turning on templates” requires little to no code change and is (arguably) good enough. In a way, to use the correct style, one must plan ahead and commit to it, simple migration is not practically possible for most programmers.
But there is a bigger issue - the style is objectively worse.
First there is the extra verbosity because we want (need) to be explicit. Then there is the often- awkward use of a free-function call. While these two might be considered “nitpicking”, they are just the visible side of the real issue: The type mapping is moved inside the function body!
Where in the original, non-generic function, no mapping is needed, and in the standard template any functionality mapping is a job for the types themselves, here we inject extra code, unrelated to the algorithm that is implemented, in order to create an extra layer of mapping b/w the types and the required entities:

template<class S>
void silly(S& s)
{
  using char_type = strings::traits<S>::char_type; //< verbose, extra, unrelated to the algorithm code
  // ...
  if(strings::starts_with(s, "hello")) //< verbose, extra, unrelated to the algorithm code
    strings::clear(s); //< verbose, extra, unrelated to the algorithm code
  // ...
}

One might argue, “it is not that bad”, but it is.
Instead of a trivial type query, we create full blown metafunction, instead of a trivial method call, we create customization object. In the place of fundamental language constructs we inject non-trivial machinery. All this inside the function, where our only care should be the algorithm itself!
Faced with all that, and remember, these 3 issues are in addition to all listed in P2279, a sane person will question “Why bother?”.

4 Proposal

If there is one thing, this proposal is aiming to achieve is to draw attention to the fact, we are sleepwalking into making generic code a separate language and a worse one at that. While focused on solving the real-world issues we currently have, we are shaping up something that is not exactly attractive in the first place.
This proposal argues, we should make an effort to solve the mapping issue (of entities to a type) in a broader way. A way that would give us an unified system, not one that does one thing for methods, another for types and constants, doing it for both far from great. Ideally, and highly, highly desirably, this system should be moved outside the function body:

template<some-true-magic S> //< all mapping should happen up to here
void silly(S& s)
{
  using char_type = S::char_type;
  // ...
  if(s.starts_with("hello")) 
    s.clear(); 
  // ...
}

This is not a proposal on how to do it, but that we should. We should strive on preserving the natural form and style of the code, we should strive to be non-obtrusive and non-verbose. We should avoid machinery replacing first-week-at-school concepts.

In other words, if we are to use the list from P2279, we should add.

  • non-obtrusive

Where behind “obtrusive” we mean both verbosity and code style change. Only then we can deliver on the promise, “Generic programming is just programming”, hopefully making it more broadly appealing, outside “library code”.

Failing to do that would mean, regular programmers will either continue using the notably inferior, but infinitely easier “just replace with tparams” method, or switch the language altogether, if possible.

5 Disclaimer

This proposal does not imply member access syntax is inherently better. Ideally, the author of the original algorithm should be able to choose their coding style and the transition to generic form should not force a change in that regard.



  1. We need a language mechanism for customization points: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2279r0.html↩︎