Hypothetical C++: easy type creation

In this essay, we will explore an imaginary extension of the C++ language that could simplify programming, help detect more errors automatically and make programs clearer.
Dev's Corner
Spiria's Dev Team
2019-01-28 11:27
5 minute read
<div><p>When I look at typical C++ code, I see a lot of uses of simple built-in types: <code>int</code>, <code>float</code>, <code>double</code>, <code>std::wstring</code>, <code>const char *--</code> even though in each instance they may be used for wildly different purposes. One may be a width or a height, another a temperature, a third, a range limit.</p><p>The reason programmers go for these simple types is easy to explain: they’re built-in, they have a lot of support functions, and their behavior is well known. But there is another reason behind their ubiquity: programming languages make it a chore to create a new type with enough functionality.</p><p>What if this weren’t the case? What I’d like to see in C++ is an easy way to create a new type from an existing one.</p><h2>The current state of affairs</h2><p>C++ has the <code>typedef</code> keyword, but that merely creates a synonym for an existing type, and the two are interchangeable. You cannot create <code>Celsius</code> and <code>Fahrenheit</code> types from <code>double</code> safely with a <code>typedef</code> because nothing will prevent adding <code>Celsius</code> to <code>Fahrenheit</code>.</p><p>On the surface, this property of <code>typedef</code> seems useful. If all we need is a short-hand version for a long type declaration or a semantic name for a type, <code>typedef</code> fits the bill. Or does it? Using a pure short-hand version saves some typing but does not introduce meaning. On the other hand, giving the <code>typedef</code> a semantic name, for example <code>ListOfPeople</code>, helps understand the code, but does not prevent assigning a list of file-names to it by mistake.</p><p>The current way to avoid such typing errors is to wrap the type in a class. The downside is that you have to painstakingly forward every member function, or create a lot of operators. The fact that this solution exists and yet is rarely used should tell us something. Code is still littered with <code>ints</code> and <code>doubles</code> and <code>typedef</code>, because they’re easy to create.</p><p style="display: block;padding: 0.5em 2em 0.7em 2em;margin: 1em 0 2em 0;background-color: #fff;border-radius:4px;box-shadow: 1px 7px 15px 0 rgba(47,57,64,0.2);text-align: center;"><b><i>Did you know that?</i></b><br>Spiria’s teams have a long experience in the development of complex <a href="https://www.spiria.com/en/services/purpose-built-development/desktop-application-development/">desktop applications</a> and can help you on any large-scale project.</p><h2>Easy Type Creation</h2><p>We need something as simple as a <code>typedef</code> which will actually create a new type. Let’s call it <code>typedecl</code>. Ideally, it would be so simple to use that programmers will reach for it by default. The barrier to entry should be as low as possible. Here’s what we need <code>typedecl</code> to do:</p><ol> <li>Create a new type.</li> <li>Allow easy-declaration of literals.</li> <li>Allow basic internal functionality automatically.</li> <li>Allow easy additional external functionality.</li></ol><h3>1. Create a new type</h3><p>Creating a new type is easy. Just make it the definition of <code>typedecl</code> in the language. With a new type, we avoid accidental assignments and allow function overloading. Taking our <code>Celsius</code> and <code>Fahrenheit</code> example, here are two function declarations that could not be written side-by-side if they were a <code>typedef</code>:</p><pre><code>Celsius convert(Fahrenheit);Fahrenheit convert(Celsius);</code></pre><p>While anyone could come up with a naming scheme to allow this to work with <code>typedef</code>, the fact that you need to come up with such a scheme and, more importantly, that you need to worry about such an issue in the first place, points to the problem of not having a unique type for each.</p><h3>2. Declaration of literals</h3><p>Easy declaration of literals is important for usability. Without usability, the feature would not be used. Somewhat like how a numeric literal will automatically be silently typed as a <code>int</code>, <code>long</code> or <code>double</code> if it fits the limits of the type, the same behaviour should be supported by <code>typedecl</code>.</p><h3>3. Allow internal functionality</h3><p>The need for internal functionality is again to fulfill our need for usability. For example, with numerical types (<code>int</code>, <code>double</code>, ...) we don’t want to have to declare all possible operations between two variables. If it’s tedious, the <code>typedecl</code> won’t be used, just like wrapping an integer in a class is not often used. The same should be true for more complex types that are used as the basis for a <code>typdecl</code>. A <code>typedecl</code> based on a <code>std::string</code> should bring its member functions, with all instances of <code>std::string</code> parameters replaced with the new type.</p><h3>4. Allow external functionality</h3><p>The hardest part is the last: allowing useful external functionality. Once again, how easy it is for the programmer will have a direct influence on how often it is used. It should be easy to clone an existing function for the new type. Ideally, it should be easy to clone a whole group of functions. The syntax I suggest is to reuse the same keyword, <code>typedecl</code>, with a clone modifier. This would allow the cloning of one or more functions. For example:</p><pre><code>typedecl Celsius clone std::abs;typedecl Celsius clone { std::abs; std::pow, ... }</code></pre><p>Ideally, it should be easy to clone an entire namespace, too:</p><pre><code>typedecl Celsius clone namespace std;</code></pre><p>Unfortunately, this is too broad and far-reaching in many cases. Ideally, we would need to add the equivalent of a <code>namespace</code> to C++, without creating an additional identifier while programming, but merely creating a semantic grouping that could be accessed. For example, all trigonometric functions could be grouped under one semantic, all I/O functions under another. Here’s what this hypothetical language feature could look like:</p><pre><code>namespace std{ namespace semantic trig { double cos(double); double sin(double); } namespace semantic io { ostream&amp; operator &lt;&lt; (ostream&amp;, double); // ... }}</code></pre><p>With this feature, the <code>cos()</code> function could still be accessed directly within the <code>std namespace</code>. The <code>trig</code> semantic <code>namespace</code> would be allowed, but optional. Cloning all trigonometric functions would then merely be:</p><pre><code>typedecl Celsius clone namespace std::trig;</code></pre><p>In some cases, it may be useful to change only some parameters of a function. In that case we could borrow the syntax of a deduction guide to give the compiler a map of how the automatic conversion should be done. For example:</p><pre><code>typedecl Celsius clone double std::pow(double, double) -&gt; Celsius std::pow(Celsius, double);</code></pre><h2>The payoff</h2><p>Now, I will show a few examples of code improvements that can be achieved with <code>typedecl</code>. First, it can lead to fewer coding errors where arguments to a function are misplaced:</p><pre><code>// Our world...void foo(){ int width = get_width(); int height = get_height(); bool stroked = should_stroke(); bool filled = should_fill(); // Is this call correct? draw_rect(width, height, stroked, filled);}// Hypothetical C++ world...void foo(){ Width width = get_width(); Height height = get_height(); Stroked stroked = should_stroke(); Filled filled = should_fill(); // The order of the arguments is necessarily correct. draw_rect(width, height, stroked, filled);}</code></pre><p>Second, it allows overload or templates to be specialized based on the semantic of a type instead of purely on its mechanical type. This is much better compared to <code>typedef</code>. With <code>typedef</code>, you need to know what the underlying type is to know if an overload or template instantiation really is different. If you used <code>typedef</code> from a third party, you would have to wrap it in a class, with all the interfacing annoyance. As an example, take the <code>std::variant</code> type. It allows you to access its elements by its type, but if two elements have the same type, then there is ambiguity. With <code>typedecl</code>, having different types makes this problem disappear.</p><h2>Conclusion</h2><p>With these changes to C++, we could finally get rid of a lot of incidental usage of purely mechanical types. There would no longer be any reason to use bare <code>int</code>, <code>double</code>, <code>std::string</code>, <code>std::map</code>, etc. in code. We could program with meaningful types that would provide even more type safety than we currently achieve because creating them would be simple.</p></div>

Want to Work Together?

Every great project starts with a conversation.