Who owns the memory?

Author: ChloĆ© Lourseyre
Editor: Peter Fordham

Have you ever heard of “ownership” of memory, in C++, when speaking about pointers?

When you use raw pointers, you have to delete them (or else, you’ll leak memory). But if the said pointer is passed down functions and complex features, or returned by a factory, you have to know whose job it is to delete.

Ownership means “responsibility to cleanup”. The owner of the memory is the one who has to delete its pointer.

Deletion can either be explicit (through the keyword delete of the function free() regarding raw pointers) or bound to the lifetime of an object (through smart pointers and RAII1).

In this article, the term “pointer” will refer to both raw and smart pointers.

The problem of memory ownership

The problem is addressed by this post: You Can Stop Writing Comments About Pointer Ownership (gpfault.net).

TL;DR: smart pointer allows you to cover every case that could have used raw pointers, so don’t use raw pointers. Move semantics on smart pointers allow ownership rules to be checked at compile time.

The post is quite interesting, but misses a huge point: what do we do with existing raw pointers? What do we do when we are forced to use raw pointers2?

In the rest of the article, these are the questions I will try to answer.

First, when you have to use a feature that requires raw pointers, you have to ask yourself: how does the feature behave regarding the ownership of the resources?

Once that is done, we can enumerate four cases:

  • When you receive a pointer and the ownership
  • When you receive a pointer but not the ownership
  • When you transfer a pointer but not the ownership
  • When you transfer a pointer and the ownership

When you receive a pointer and the ownership

This one’s probably the easiest. Since we can construct a std::unique_ptr or std::shared_ptr using a raw pointer, all we have to do is put the received raw pointer into any smart pointer and it will be properly destroyed.

Example

#include <memory>
#include <iostream>

struct Foo
{
    Foo() { std::cout << "Leak?" << std::endl; }
    ~Foo() { std::cout << "No leak" << std::endl; }
};

// We don't own this function, so we can't change the return value
Foo * make_Foo()
{
    return new Foo();
}

int main()
{
    std::unique_ptr<Foo> foo_ptr(make_Foo());
    // The instance of Foo is properly destroyed when function ends
    return 0;
}

The resulting output will be:

Leak?
No leak

When you receive a pointer but not the ownership

This case is a bit trickier. It does not happen often, but sometimes, for internal reasons (or just because it is badly conceived), a library gives you a pointer you must not delete.

Here, we can not use a smart pointer (like in the previous case) because it will delete the pointer.

For instance, in the following example, the class IntContainer creates a pointer to an int and deletes it when it is destroyed:

// We don't own this container, we can't change its interface
struct IntContainer
{
    IntContainer(): int_ptr(new int(0)) {}
    ~IntContainer() { delete int_ptr; }

    int * get_ptr() { return int_ptr; }

    int * int_ptr;
};

If we try to use an unique_ptr, like so:

int main()
{
    IntContainer int_cont;
    std::unique_ptr<int>(int_cont.get_ptr());
    // Double delete
    return 0;
}

We get undefined behavior. With my compiler (GCC 11.2) I got a runtime exception: free(): double free detected in tcache 2

There is a simple solution to this problem. Instead of using a pointer, we can get a reference to the object pointed by the raw pointer returned by IntContainer. This way, we can access the pointed value just as we can with a pointer, without risking deleting it.

int main()
{
    IntContainer int_cont;
    int & int_ref = *int_cont.get_ptr();
    // We have access to the value of int_ptr via the reference
    return 0;
}

When you transfer a pointer but not the ownership

Some libraries require that you give them raw pointers. In most cases, you keep the ownership of that pointers, but the problem of having to pass a raw pointer exists.

There are 2 situations:

  • You have the object as a value or a reference.
  • You have a smart pointer to the object.

Situation 1: You have the object as a value or a reference

In that situation, all you have to do is use the & operator to pass the address of your object down to the feature that requires a raw pointer. Since it won’t try to delete it, nothing bad will happen.

#include <iostream>

struct Foo
{
    Foo() { std::cout << "Leak?" << std::endl; }
    ~Foo() { std::cout << "No leak" << std::endl; }
};

// Function that requires a raw pointer
void compute(Foo *)
{
    // ...
}

int main()
{
    Foo foo;
    // ...
    compute(&foo);
    return 0;
}

Situation 2: You have a smart pointer to the value

When all you have is a smart pointer to the object, you can use the member function get() to get the raw pointer associated with the smart pointer. Both std::unique_ptr and std::shared_ptr implement this function3.

#include <memory>
#include <iostream>

struct Foo
{
    Foo() { std::cout << "Leak?" << std::endl; }
    ~Foo() { std::cout << "No leak" << std::endl; }
};

// Function that requires a raw pointer
void compute(Foo *)
{
    // ...
}

int main()
{
    std::unique_ptr<Foo> foo_ptr = std::make_unique<Foo>();
    // ...
    compute(foo_ptr.get());
    return 0;
}

When you transfer a pointer and the ownership

Probably the rarest case of all4, but hypothetically there could be a feature that requires a raw pointer and take ownership.

Situation 1: You have the object as a value or a reference

If you have the plain object (or a reference), the only way to make it a raw pointer that the feature will be able to delete safely is by calling new.

However, just a new will copy the object and this is unwanted. Since the ownership is theoretically given to the feature, we can do a std::move on the object to call the move constructor (if existent) and avoid a perhaps heavy copy of the object

So we just need to call the feature that requires a raw pointer, with a new to create the desired pointer, in which we move the value.

#include <iostream>

struct Foo
{
    Foo() { std::cout << "Leak?" << std::endl; }
    Foo(const Foo &&) { std::cout << "Move constructor" << std::endl; }
    ~Foo() { std::cout << "No leak" << std::endl; }
};

void compute(Foo *foo)
{
    // ...
    delete foo;
}

int main()
{
    Foo foo;
    // ...
    compute(new Foo(std::move(foo)));
}

Situation 2: You have a smart pointer to the value

The get() member function does not give ownership, so if we use it to pass the raw pointer to the feature, we’ll have double deletion.

The member function release(), however, releases the ownership and returns the raw pointer. This is what we want to use here.

#include <iostream>
#include <memory>

struct Foo
{
    Foo() { std::cout << "Leak?" << std::endl; }
    ~Foo() { std::cout << "No leak" << std::endl; }
};

void compute(Foo *foo)
{
    // ...
    delete foo;
}

int main()
{
    std::unique_ptr<Foo> foo_ptr = std::make_unique<Foo>();
    // ...
    compute(foo_ptr.release());
    return 0;
}

The problem is that release() is only a member of unique_ptr. Shared pointers can have multiple instances pointing to the same value, therefore they don’t own the pointer in the first place.

How do I know the intended ownership?

This is key when you do this kind of refactoring, because misidentification of the intended ownership may lead to memory leaks or undefined behavior.

Usually, the feature documentation will point out whose job it is to cleanup memory.

How to deal with memory allocated with malloc?

The cases that are presented in this article only concern memory that is allocated with new and freed with delete.

But there will be rare cases where the feature you want to use will have recourse to malloc and free.

When a feature asks for a raw pointer and you have to delete it, this problem is non-existent (you have control of the allocation and the cleanup)

When a feature gives you a raw pointer (created using malloc) and you mustn’t delete it, you have nothing to do since it’s not your job to free the resource. You can work with a reference to the resource as is demonstrated earlier.

When a feature asks for a raw pointer and you mustn’t delete it (because it uses free on it), you will have to do the malloc yourself. If you have a smart pointer on the object, you will unfortunately still have to use malloc.

Last, when a feature gives you a raw pointer (created using malloc) and you have to delete it, this is the tricky part. The best way to do this is probably to use a unique_ptr, with a custom deleter as second template. The second template parameter of unique_ptr is a function object (i.e. a class that implements operator()) that will be called to free the memory. In our specific case, the deleter we will need to implement will simply call free. Here is an example:

#include <memory>
#include <iostream>

struct Foo {};

// We don't own this function, so we can't change the return value
Foo * make_Foo()
{
    return reinterpret_cast<Foo*>(malloc(sizeof(Foo)));
}

// This deleter is implemented fo Foo specifically, 
// but we could write a generic template deleter that calls free().
struct FooFreer
{
    void operator()(Foo* foo_ptr)
    {
        free(foo_ptr);
    }
};

int main()
{
    std::unique_ptr<Foo, FooFreer> foo_ptr(make_Foo());
    // The instance of Foo is properly destroyed when function ends
    return 0;
}

Wrapping up

Here is a table summarizing what has been demonstrated here:

I receive a raw pointerI transfer a raw pointer
I must delete itStore it in a unique_ptr or in a shared_ptrUse the & operator or the .get() member function
I mustn’t delete itGet a reference to the objectUse the new operator and move or the .release() member function

With these tools, you can safely remove raw pointers from your code, even when some libraries still use them

The solutions proposed are very simple, but it is critical to identify which one to use in each situation. The main problem with this method is that the refactorer has to correctly identify ownership (but that’s a problem that cannot be avoided).

Thanks for reading and see you next time!

Author: ChloĆ© Lourseyre
Editor: Peter Fordham

Addendum

Notes

  1. In case you didn’t know, RAII is an essential technique in modern C++, that is about resource acquisition and release. We often say that “something is RAII” when it cleans up perfectly and can not lead to memory leaks, as soon as it is released from the stack. For instance, raw pointers are not RAII because if you forget to delete them, you have a memory leak. On the contrary, std::string and std::vector are RAII because they release their internal allocations as soon as their destructor is called.
  2. It is sometimes hard for some developers to understand how can something can be “enforced” in your code. Here are a few situations when it occurs:
    – When you arrive on an existing project. You can’t refactor everything, on your own. You have to adapt and take you time to change things.
    – When parts of the code are not in your jurisdiction. On many projects, some essential code is developed by another team, in which you can’t meddle.
    – When you have to prioritize refactoring. You can’t always rewrite everything you’d like to at once. You have to choose your battles.
    – When management doesn’t give you approval or budget for the refactoring you are asking for. It happens, and there is nothing much you can do about it.
  3. No case presented in this article can work with a std::weak_ptr.
  4. While writing this article, I could not find, either on the Internet or in my memory, one single example of a feature that requires a pointer, then deletes it for you.

Constant references are not always your friends

Author: ChloĆ© Lourseyre
Editor: Peter Fordham

Early on, when we teach modern C++, we teach that every non-small1 data should be passed, by default, as constant reference:

void my_function(const MyType & arg);

This avoids the copy of these parameters in situations where they don’t need to be copied.

Other situations call for other needs, but today we will focus on constant references.

I found out that people tend to over-do const refs, thinking they are the best choice in every situation, and should be used everywhere they can be used.

But are they always better than the alternatives? What are the dangers and hidden traps within them?

NB: In the whole article, I use “constant reference” (or the shorter “const ref”) for what is, really, a reference to a constant. This is a convention that, though technically inaccurate, is way more practical.

First situation: const ref as a parameter

This is kind of a textbook case, involving a non-optimal usage of a const ref.

Take this class:

struct MyString
{
     // Empty constructor
    MyString()
    { std::cout << "Ctor called" << std::endl; }
    
    // Cast constructor
    MyString(const char * s): saved_string(s) 
    { std::cout << "Cast ctor called" << std::endl; }
    
    std::string saved_string;
};

This is basically a std::string wrapper, with outputs to see if and when the constructors are called. We will use it to see if there are unnecessary calls to constructors and if there are any implicit conversions3. From now on, we’ll consider that constructing a MyString is heavy and unwanted.

Using a constant reference

Let’s take a function that takes a constant reference to MyString as a parameter:

void foo(const MyString &)
{
    // ...
}

And now, let’s call it with, let’s say, a literal string:

int main()
{
    foo("toto");
}

It compiles, it works, and it prompts the following message on the standard output:

Cast ctor called

The cast constructor is called. How come?

The thing is, const MyString & can’t refer to the "toto" we pass down to foo(), because "toto" is a const char[]. So, naively, it shouldn’t compile. However, since the reference is constant, and so won’t modify the source object, the compiler reckons it can be copied, somewhere in memory, with the correct type. Thus, it performs an implicit conversion.

This is not neat, because this conversion is heavy for a lot of types, and in the collective unconscious passing down a const ref does not copy the object. It’s the fact that it is implicit (thus not clear) that is thus unwelcome.

Using the explicit keyword

In C++, we can use the explicit keyword to specify that a constructor or a conversion function cannot be used implicitly.

explicit MyString(const char * s): saved_string(s) 
{ std::cout << "Cast ctor called" << std::endl; }

With that keyword, you cannot use the foo() function with a literal string anymore:

foo("toto"); // Does not compile

You have to cast it:

foo(static_cast<MyString>("toto")); // Does compile

However, there is a major downside: you can’t use explicit on STD types (such as std::string) or types you import from external libraries. How can we work around that?

Using a plain reference

Let’s put aside the explicit keyword and consider that MyString in external and can not be edited.

We’ll tune the foo() function so that the reference it takes as a parameter is not constant anymore:

void foo(MyString &)
{
    // ...
}

So what happens now? If we try to call foo() with a literal string we got the following compilation error:

main.cpp: In function 'int main()':
main.cpp:24:9: error: cannot bind non-const lvalue reference of type 'MyString&' to an rvalue of type 'MyString'
   24 |     foo("toto");
      |         ^~~~~~
main.cpp:11:5: note:   after user-defined conversion: 'MyString::MyString(const char*)'
   11 |     MyString(const char * s): saved_string(s)
      |     ^~~~~~~~
main.cpp:17:10: note:   initializing argument 1 of 'void foo(MyString&)'
   17 | void foo(MyString &)
      |          ^~~~~~~~~~

Here, the compiler cannot perform an implicit conversion any more. Because the reference is not constant, and thus may be modified within the function, it cannot copy and convert the object.

This is actually a good thing, because it warns us that we are trying to perform a conversion and asks us to explicitly perform the conversion.

If we want this code to work, we do have to call the cast constructor4 explicitly:

int main()
{
    MyString my_string("toto");
    foo(my_string);
}

This compiles, and gives us the following message on the standard output:

Cast ctor called

But this is better than the first time, because here the cast constructor is called explicitly. Anyone who reads the code knows that the constructor is called.

However plain references have downsides. For one, it discards the const-qualifier.

Using template specialization

Finally, an other way to prevent implicit conversion is to use template specialization:

template<typename T>
void foo(T&) = delete;

template<>
void foo(const MyString& bar)
{
    // ā€¦
}

With this code, when you try to call foo() with anything that isn’t a MyString, you’ll try to call the generic templated overload of foo(). However, this function is deleted and will cause a compilation error.

If you call it with a MyString, though, it is the specialization that will be called. Thus, you’ll be sure that no implicit conversion can be done.

Conclusion of the first situation

Sometimes, constant references can perform implicit conversions. Depending on the type and the context, this may be undesirable.

To avoid that, you can use the explicit keyword. This forbids implicit conversion.

When you can’t use explicit (because you need it on an external type), you can use a plain reference instead or a template specialization as seen above, but both have implications.

Second situation: const ref as an attribute

Let’s take (again) a wrapper to a std::string, but this time, instead of storing the object, we’ll store a constant reference to the object:

struct MyString
{    
    // Cast constructor
    MyString(const std::string & s): saved_string(s) {}
    
    const std::string & saved_string;
};

Using a constant reference stored in an object

Let’s use it now, and see if it works:

int main()
{
    std::string s = "Toto";
    MyString my_string(s);

    std::cout << my_string.saved_string << std::endl;
    
    return 0;
}

With that code, we get the following standard output:

Toto

So this seems to work fine. However, if we try to edit the string from outside the function, like this:

int main()
{
    std::string s = "Toto";
    MyString my_string(s);

    s = "Tata";

    std::cout << my_string.saved_string << std::endl;
    
    return 0;
}

The output changes to that:

Tata

It seems that the fact that we stored a constant reference does not mean the value cannot be modified. In fact, it means that it can not be modified by the class. This is a huge difference that can be misleading.

Trying to reassign a constant reference

With that in mind, you might want to try and reassign the reference stored in the object rather than modifying its value.

But in C++, you can’t reseat a reference. As it is said in the IsoCpp wiki: Can you reseat a reference? No way. (Source: References, C++ FAQ (isocpp.org)).

So beware, because if you write something like this:

int main()
{
    std::string s = "Toto";
    MyString my_string(s);

    std::string s_2 = "Tata";
    my_string.saved_string = s_2;

    std::cout << my_string.saved_string << std::endl;
    
    return 0;
}

This won’t compile, because you are not trying to reseat my_string.saved_string to the reference of s_2, you are actually trying to assign the value of s_2 to the object my_string.saved_string refers to, which is constant from MyString‘s point of view (and thus can’t be assigned).

If you try to work around that and de-constify the reference stored inside MyString, you may end up with this code:

struct MyString
{    
    // Cast constructor
    MyString(std::string & s): saved_string(s) {}
    
    std::string & saved_string;
};

int main()
{
    std::string s = "Toto";
    MyString my_string(s);

    std::string s_2 = "Tata";
    my_string.saved_string = s_2;

    std::cout << my_string.saved_string << std::endl;
    
    return 0;
}

The output is, as expected, Tata. However, try and print the value of s and you’ll have a little surprise:

std::cout << s << std::endl;

And you’ll see that it prints Tata again!

Indeed, as I said, by doing that you do try to reassign the value referred by my_string.saved_string, which is a reference to s. So by reassigning my_string.saved_string you reassign s.

Conclusion of the second situation

In the end, the keyword const for the member variable const std::string & saved_string; does not mean “saved_string won’t be modified”, it actually means that “a MyString can’t modify the value of its saved_string“. Beware, because const does not always mean what you think it means.

Types that should be passed by value and not by reference

Using constant references is also a bad practice for some types.

Indeed, some types are small enough that passing by const ref instead of passing by value is actually not an optimization.

Here are examples of types that should not be passed by const ref:

  • int (and short, long, float etc.)
  • pointers
  • std::pair<int,int> (any pair of smal types)
  • std::span
  • std::string_view
  • … and any type that is cheap to copy

The fact that these types are cheap to copy tells us that we can pass-by-copy, but it does doesn’t tell us why we should pass them by copy.

There are three reasons why. These three reasons are detailed by Arthur O’Dwyer in the following post: Three reasons to pass `std::string_view` by value ā€“ Arthur O’Dwyer ā€“ Stuff mostly about C++ (quuxplusone.github.io)

Short version:

  1. Eliminate a pointer indirection in the callee. Passing by reference forces the object to have an address. Passing by value enables the possibility to pass using only registries.
  2. Eliminate a spill in the caller. Passing by value and using registries sometimes eliminates the need for a stack frame in the caller.
  3. Eliminate aliasing. Giving a value (i.e. a brand new object) to the callee give it greater opportunities for optimization.

Wrapping up

Here are two dangers of constant references:

  • They can provoke implicit conversions.
  • When stored in a class, they can still be modified from the outside.

Nothing is inherently good or bad — thus nothing is inherently better or worse.

Most of the time, using constant references to pass down non-small parameters is best. But keep in mind that it has its own specificities and limits. That way, you’ll avoid the 1% situation where const refs are actually counter-productive.

They are several semantic meanings to the keyword const. Sometimes, you think it means something while in fact it means another thing. But I keep that for another article.

Thanks for reading and see you next time5!

Author: ChloĆ© Lourseyre
Editor: Peter Fordham

Addenda

Examples in Godbolt

First situation: const ref as a parameter: Compiler Explorer (godbolt.org) and Compiler Explorer (godbolt.org)

Second situation: const ref as an attribute: Compiler Explorer (godbolt.org)

Notes

  1. “Small data” refers, in that context, to PODs2 that are small enough to be passed down without losing efficiency — such as simple integers and floating values.
  2. POD means “Plain Old Data” and refers to data structures that are represented only as passive collections of field values without using object-oriented features.
  3. MyString is just a placeholder for heavier classes. There are classes -such as std::string that are costly to construct or copy.
  4. What I call “cast constructor” is the one with one parameter. These kinds of constructors are often called that way because it’s the ones that the static_cast use.
  5. Scientific accuracy has always been one of my goals. I don’t always reach it (I don’t often reach it) but I try to as much as I can. That’s why, from now on, I won’t say “See you next week” since according to the stats, I publish two-point-eight articles per month on average.

The three types of development

Author: ChloĆ© Lourseyre
Editor: Peter Fordham

This week we’ll discuss a serious topic affecting the developer community. This touches several languages, but the C++ community is one of the most affected by it1.

There are several “ways” to write C++. I mean “way” as a collection of constraints and circumstances that will affect what you can do, what you should do, and how you can and should do it.

This may seem vague, but think of it as types of environments that can drastically change your approach to the code you reading, editing, and writing.

Based on my experience, I can distinguish three types of development2.

The three categories

The (almost) solo development

This is the type of development that has the fewest constraints (if not none at all). When you are developing alone or with very few collaborators, you can freely choose what to do and how you want to do it.

The collaborative licensed development

When you are on a bigger project, you will see constraints arise. Most of the time, these constraints will consist in which library you can or cannot use.

For instance, if you want to sell your software, you can’t use a JRL licensed software, because it prohibits commercial uses.

This is generally a type of development that concerns small companies or freelance developers.

The industrial development

Some projects are launched by big companies or company groups. They can be developed over numerous years (even decades if you include the maintenance phase of these projects), but more importantly they have heavy constraints overs which libraries you can use and in what environment the development takes place.

This is typically the type of development where the C++ version is the oldest (often prior to C++17, sometimes even in C++03). This is because the management (not to say the salesmen) that pilot the budget of this kind of project and decide whether the environment can be migrated or not.

A lot of developers that work on this kind of project arrive in the middle of it and face heavy resistance when they try to improve the environment3.

In this kind of project, you often have to deal with legacy code or with a part of the codebase that you can’t edit4.

What is specific to C++?

C++ is a complicated language, not only because of its syntax and language specification, but also because there are hundreds (if not thousands) of different possible environments.

There are dozens of C++ compilers, ported on numerous operating systems. As of today, there are 5 different versions of the standard5 that are present in professional projects.

It is thus essential for each C++ developer to adapt their advice to the person they are talking to. Because depending on the situation, you may say the complete opposite of what you would have said otherwise.

Clashing grounds

There is one place where the three types of development can be represented at the same time: on the internet. When you lurk on dedicated forums, you’ll eventually find people that are currently working on the different types of projects.

Overall this is a good thing, that all kinds of developers can meet over the internet, but it can lead to communication issues.

Indeed, if one developer who has only performed one type of development ever tries to give advice or feedback to a developer from another type of environment, a lot of this advice and feedback will not take the developerā€™s constraints and circumstances into account and will not be useful.

Let’s take a few examples to illustrate that.

Example from r/cpp

Here is an example that comes for Reddit, specifically from the subreddit r/cpp:

This example is typical: while courteous, it misses the point and is based on two sophisms:

  • “Every modern C++ compiler produces warnings if […]”. It greatly depends on what “modern” means, but there is a lot of compilers that do not work like your standard compiler. I’m thinking about compiling for embedded systems, experimental compilers, and home-made compilers that you can sometimes encounter on very specific projects, or even older compilers that did not implement the said warning at that time. Trying to generalize in this context is somewhat of a fallacy, especially since “printf API […] doesn’t enforce it”.
  • “[…] honestly you should have compiler warnings enabled anyway.”. I hear that a lot, and I think most of those who say it never have worked under project and environment constraints. When you arrive on a project, you don’t always have a say on how the environment works, especially if the project was ongoing for several years when you arrived. Our work (as C++ experts and such) is to try and change mentalities, but sometimes it doesn’t work, unfortunately. There are also situations where when you arrive on the projects, there are hundreds and hundreds of warnings, and the management won’t give you the time to correct them all. In this context warnings-hunting is a lost cause.

Of course, we should always try to change the world for the better and try to destroy improper environments, but denying the existence of these contexts is denying the reality, how the world of development sometimes really works.

When that occurs, try to add nuance to your statements, leave it open for people to explain what are their constraints.

Instead of

“Yes you have to enable -Wall but honestly you should have compiling warnings enabled anyway.”

say something like:

“If you can you should enable -Wall because it will help you prevent the issue and others as well.”

Example from SO

Here is another example, taken from Stack Overflow:

Pretty short, but a lot to say nonetheless.

“Best advice is not to write macros like that.” Okay, no problem, but why? Because of how macros work? Because you can’t do whatever you want to do with it? Because macros are bad design and there is a working alternative?

The question states the following constraint:

Is the question “Why do you need to use __LINE__?” really relevant? Since the question is based on the statement just above, whether you know why does the user need __LINE__ or not won’t help the original poster6.

Writing relevant advice is really easy when you put your thought into it. For instance:

This comment simply states that pointers are usually bad, while admitting that depending on the case they may be needed. It has been written to hint at the original poster about the dangers of pointers while remaining relevant.

Wrapping up

When you want to be helpful to other developers, you have to pay attention to their circumstances. Your answer won’t reach its target if it is irrelevant.

Plus you have to ask yourself: are you really helping anyone if your advice can be summarized by “You have to change your environment” to someone who can’t or won’t? You have to adapt to these situations, put your words into perspective, so the person you are talking to will acknowledge your advice, even if they can’t apply it directly.

It’s easy to fall into sophism and authoritative arguments. Always try to explain your arguments, even if they seem trivial to you. This will give weight to them. Moreover, maybe it’s trivial to you but it may not be so for others. And if you don’t manage to simply explain your argument, there is a really good chance that it’s a fallacy.

Thanks for reading and see you next week!

Author: ChloĆ© Lourseyre
Editor: Peter Fordham

Addendum

Notes

  1. In this article, I’ll use C++ to illustrate, but everything that is said can be applied to any programming language. I explain why C++ is specifically affected by this later in the article.
  2. Depending on your own experience, you may discover other types of development. They supplement the existing ones.
  3. The definition of improve here is the key. What a new developer on a project might consider an improvement isn’t the same as what a senior developer, project manager, accountant, salesman or customer would consider an improvement. “It’s great that you’ve spent a year bringing the codebase up to C++20 with new GCC and clang , but you haven’t fixed any of the reported bugs, implemented the new features we promised to the customer and now we don’t support our legacy platform anymore…”
  4. For instance: because it is owned by another team or company, because it has already been sold to the client, or because it has already been QA’d and it’d take weeks to be QA’d again.
  5. I’m only counting from C++03 (so C++03, 11, 14, 17 and 20) since C++98 is very similar to C++03.
  6. It may sometimes occur that the original poster states a constraint that they could avoid. But it is unconstructive to “babysit” the OP in that case, it would be better to propose alternative with examples.

Is my cat Turing-complete?

Author: ChloĆ© Lourseyre
Editor: Peter Fordham

This article is an adaptation of a Lightning Talk I gave at CppCon2021. Link here : https://www.youtube.com/watch?v=RtqTGSOdmBo

I’ll touch on a lighter subject this week, nonetheless quite important: is my cat Turing-complete?

Meet Peluche

Peluche (meaning “plush” in French) is a smooth cat that somehow lives in my house.

She will be our test subject today.

Is Peluche Turing-complete?

What is Turing-completeness

Turing-completeness is the notion that if a device can emulate a Turing machine, then it can perform any kind of computation1.

It means that any machine that implements the eight following instructions is a computer (and can thus execute any kind of computation):

  • . and ,: Inputting and outputting a value
  • + and -: Increase and decrease the value contained in a memory cell2.
  • > and <: Shift the current memory tape left or right.
  • [ and ]: Performing loops.

So, if Peluche can perform these eight instructions, we can consider her Turing-complete.

Proof of the Turing-completeness

Input and output

First, I tried to poke Peluche if I could get a reaction.

She looked at me, then just turned around.

So here it is: I poked her, and I got a reaction. So she can process inputs and give outputs.

Input/output confirmed!

Increase and decrease memory value

The other day, I came back from work to this:

Kibbles everywhere…

But then I took a closer look and realized that the slabs could be numbered, like this:

This looks pretty much like a memory tape to me! Since she can spill kibbles on the tiles and then eat them directly from the floor, she can increase and decrease the values contained in a given memory cell.

Increase/decrease confirmed!

Shift the current memory cell left or right

Another time, I was doing the dishes and inadvertently spilled some water on Peluche. She began to run everywhere around the kitchen, making a huge mess.

If you look close (at the tip of the red arrow), you may notice that while making this mess, she displaced her bowl.

Displacing her bowl means she will spill her kibbles in another tile. This counts as shifting the memory head to edit another memory cell.

Shift of the memory tape confirmed!

Perform loops

So, after this mess, I (obviously) had to clean up.

No more than five minutes later, I went back to the kitchen to this:

Yeah… she can DEFINITELY perform loops…

Loops confirmed!

We have just proven that Peluche is, indeed, Turing-complete. So now, how can we use her to perform high-performance computations?

What to do with her?

Now that I’ve proven that Peluche is Turing-complete, I can literally do anything with her!

Thus, I tried to give her simple code to execute3:

šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾
šŸ˜æ
šŸˆšŸ˜¾
šŸˆšŸ˜¾šŸ˜¾
šŸˆšŸ˜¾šŸ˜¾šŸ˜¾
šŸˆšŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾
šŸˆšŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾
šŸˆšŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾
šŸˆšŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾
šŸˆšŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾
šŸˆšŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾
šŸˆšŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾
šŸˆšŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾
šŸˆšŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾
šŸˆšŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾
šŸˆšŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾
šŸˆšŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾
šŸˆšŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾
šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ¾
šŸ˜ø
šŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸ™€šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»
šŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸ¾šŸ¾šŸ¾šŸ™€šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»
šŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸ¾šŸ¾šŸ¾šŸ¾šŸ™€šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»
šŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸ¾šŸ¾šŸ¾šŸ¾šŸ™€šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»
šŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸ¾šŸ™€šŸ˜¾šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»
šŸˆšŸˆšŸˆšŸˆšŸ™€šŸ˜»šŸ˜»šŸ˜»šŸ˜»
šŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸ¾šŸ™€šŸ˜¾šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»
šŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸ¾šŸ™€šŸ˜¾šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»
šŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸ˜¾šŸ˜¾šŸ™€šŸ¾šŸ¾šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»
šŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸ¾šŸ¾šŸ¾šŸ¾šŸ™€šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»
šŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸ¾šŸ¾šŸ¾šŸ¾šŸ™€šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜¾šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»
šŸˆšŸˆšŸˆšŸˆšŸˆšŸˆšŸ¾šŸ¾šŸ™€šŸ˜¾šŸ˜¾
šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ˜»šŸ™€

The result was final: she wouldn’t do a thing.

Though they can, maybe cats are not designed to execute code after all?

About “cat-computing”

Jokes aside, cat-computing is the name I give to this generalized practice. In my experience, it happens quite often that when someone discovers a new feature of a language, they begin to use it everywhere, just because they can and they want to.

However, just like you can execute code using a cat4 but shouldn’t, it’s not because you can use a feature that you should.

They were too busy wondering if they could to think about whether they should.

Dr Ian Malcolm, Jurassic Park

Wrapping up

Cat-computing seems to be a rookie mistake (and it is), but even the most experienced developers sometimes make rookie mistakes (and there’s no shame in that).

Every three years, a new version of C++ is published. Every time, it makes me want to use the new features in every possible situation. Though this is a good opportunity to build some experience around that (one of the best ways to avoid misuses of a feature is to perform these misuses once, in my opinion), this is also favorable ground for acquiring bad practices.

Always ask yourself if a feature is necessary5 before using it, or else you may do cat-computing.

Also, cat-computing is animal abuse, so don’t do it šŸ˜ .

Thanks for reading and see you next week!

(No cats were harmed during the writing of this article, but one was gently poked.)

Author: ChloĆ© Lourseyre
Editor: Peter Fordham

Addendum

Notes

  1. This is a simplified definition, very inaccurate but accurate enough for this example. If you want the real definition, go there: Turing completeness – Wikipedia
  2. I did not state it explicitly, but a Turing machine has a “memory tape” with “memory cells” on it. The machine is always pointing to a memory cell, which is the mentioned “current” memory cell.
  3. You may not be able to read this sample of code — this is a fancy new language I designed called “braincat”.
  4. Actually, you can’t execute code using a cat, I know, but it’s for the sake of the metaphor that I assume you can.
  5. Of course, necessity occurs when there is a known benefit to the feature. I’m not talking about “absolute necessity” but about “practical necessity”.

Yet another reason to not use printf (or write C code in general)

Author: ChloƩ Lourseyre

Recently, Joe Groff @jckarter tweeted a very interesting behavior inherited from C:

Obviously, it’s a joke, but we’re gonna talk more about what’s happening in the code itself.

So, what’s happening?

Just to be 100% clear, double(2101253) does not actually double the value of 2101253. It’s a cast from int to double.

If we write this differently, we can obtain this:

#include <cstdio>

int main() {
    printf("%d\n", 666);
    printf("%d\n", double(42));
}

On the x86_64 gcc 11.2 compiler, the prompt is as follows:

666
4202506

So we can see that the value 4202506 has nothing to do with the 666 nor the 42 values.

In fact, if we launch the same code in the x86_64 clang 12.0.1 compiler, things are a little bit different:

666
4202514

You can see the live results here: https://godbolt.org/z/c6Me7a5ee

You may have guessed it already, but this comes from line 5, where we print a double as an int. But this is not some kind of conversion error (of course that your computer knows how to convert from double to int, it will do it fine if this was what was happening), the issue comes from somewhere else.

The truth

If we want to understand how it works that way, we’ll have to take a look at the assembly code (https://godbolt.org/z/5YKEdj73r):

.LC0:
        .string "%d\n"
main:
        push    rbp
        mov     rbp, rsp
        mov     esi, 666
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        mov     rax, QWORD PTR .LC1[rip]
        movq    xmm0, rax
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 1
        call    printf
        mov     eax, 0
        pop     rbp
        ret
.LC1:
        .long   0
        .long   1078263808

(use this Godbolt link to have a clearer matching between the C++ code and the assembly instructions: https://godbolt.org/z/5YKEdj73r)

In the yellow zone of the assembly code (lines 6-to 9, the equivalent to printf("%d\n", 666);) we can see that everything’s fine, the 666 value is put in the esi register and then the function printf is called. So it’s an educated guess to say that when the printf function reads a %d in the string it is given, it’ll look in the esi register for what to print.

However, we can see in the blue part of the code (lines 10 to 14, the equivalent to printf("%d\n", double(42));) the value is put in another register: the xmm0 register. Since it is given the same string as before, it’s pretty guessable that the printf function will look into the esi register again, whatever there is in there.

We can prove that statement pretty easily. Take the following code:

#include <cstdio>

int main() {
    printf("%d\n", 666);
    printf("%d %d\n", double(42), 24);
}

It’s the same code, with an additional integer that is print in the second printf instruction.

If we look at the assembly (https://godbolt.org/z/jjeca8qd7):

.LC0:
        .string "%d %d\n"
main:
        push    rbp
        mov     rbp, rsp
        mov     esi, 666
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        mov     rax, QWORD PTR .LC1[rip]
        mov     esi, 24
        movq    xmm0, rax
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 1
        call    printf
        mov     eax, 0
        pop     rbp
        ret
.LC1:
        .long   0
        .long   1078263808

The double(42) value still goes into the xmm0 register, and the 24 integer, logically, ends up in the esi register. Thus, this happens in the output:

666
24 0

Why? Well, since we asked for two integers, the printf call will look into the first integer register (esi) and print its content (24, as we stated above), then look in the following integer register (edx) and print whatever is in it (incidentally 0).

In the end, the behavior we see occurs because of how the x86_64 architecture is made. If you want to learn more about that, follow these links:

What does the doc say?

The truth is that according to the reference (printf, fprintf, sprintf, snprintf, printf_s, fprintf_s, sprintf_s, snprintf_s – cppreference.com):

If a conversion specification is invalid, the behavior is undefined.

And this same reference is unambiguous about the %d conversion specifier:

converts a signed integer into decimal representation [-]dddd.
Precision specifies the minimum number of digits to appear. The default precision is 1.
If both the converted value and the precision are ā€‹0ā€‹ the conversion results in no characters.

So, giving a double to a printf argument where you are supposed to give a signed integer is UB. So it was our mistake to write this in the first place.

This actually generates a warning with clang. But with gcc, you’ll have to activate -Wall to see any warning about that.

Wrapping up

The C language is a very, very old language. It’s older than the C++ (obviously) that is itself very old. As a reminder, the first edition of the K&R has been printed in 1978. This was thirteen years before my own birth. And unlike us humans, programming languages don’t age well.

I could have summarized this article with a classic “don’t perform UB”, but I think it’s a bit off-purpose this time. So I’ll go and say it: don’t use printf at all.

The problem is not with printf itself, it’s with using a feature from another language1 that was originally published forty-three years ago. In short: don’t write C code.

Thanks for reading and see you next week!

1. Yeah, like it or not, but C and C++ and different languages. Different purpose, different intentions, different meta. That is exactly why I always deny job offers that have the tag “C/C++” because they obviously can’t pick a side.

Author: ChloƩ Lourseyre

You shouldn’t assume accessors are fast

Author: ChloƩ Lourseyre

“Accessors are fast.” I heard this leitmotiv so many times over the years that I couldn’t not write about it.

Explanation

What’s the use for accessors?

During the researches I made for this article, I was surprised that there actually is a formal definition of accessors1.

You can find this definition in section Ā§ 18.3.5 Accessor functions of the fourth edition of The C++ Programming Language (Bjarne Stroustrup).

Long story short, the definition says that, though is unadvised to abuse that feature, you can design a method to read and edit private attributes of a class to write a clean algorithm (I’m paraphrasing).

So, in short, accessors are used as an interface between the private members of a class and the user of this class.

Like any kind of interface, accessors can thus obfuscate how the actual access of the private members is done, to encapsulate more complex code and ease how the class is used.

1. I’m not discussing the fact that there is a pseudo-consensual definition of accessors. However, since this article aims to deconstruct that definition, I won’t rely on it to build my arguments. You can find this pseudo-consensual definition in the following paragraph, where I describe why it is a bad one.

People don’t use accessors properly…

Every day I see people using accessors improperly. Here are a few examples:

  • Writing the simplest get/set for every member of a class without thinking. If your class makes every attributes available with no subtlety and nothing else is private, then it’s not a class, it’s a struct2.
  • Writing accessors that are never used. Don’t write something you don’t need, it’s just polluting the codebase.
  • Realizing that there are not accessors for a given attribute and implementing them without questioning. Sometimes (often), there is a good reason that an attribute doesn’t have accessors.
  • Declaring a getter that is not const (even though it should be). By default, make everything const unless you need it non-const.
  • Declaring a getter that is const even though it shouldn’t be. This one is very rare, but I did see several times writing a dangerous const-cast just to make a getter const. You should avoid writing const-casts at all costs (and the examples I recall there wasn’t a good reason for it).

There are a lot of other bad practices that are too verbose to describe in this article (which purpose is not to teach you to use accessors properly).

2. Here, I abusively use the word class to name a data structure with private members and the struct to name a data structure that is nothing more than a data bucket. This is done on purpose to smooth the read of the article.

…leading to false assumptions

Bad practices lead to a bad way of thinking. And a bad way of thinking leads to false assumptions.

The most widespread false assumption over accessors is the following:

All accessors should be fast to execute.

Assuming accessors are fast is a useless limitation

Without talking about the fact that it can be harmful (if you use a function thinking it is fast, the day it is not you may have a bad surprise), by making this assumption you put a limitation on yourself.

This limitation is not effectively useful (at best, it saves you the time to read the documentation of the accessors — which should not be overlooked anyway), it restrains your ability to innovate and do something useful with your accessor.

Demonstration with a useful pattern

I’m going to show you a pattern that I love to use because it is really useful and saves a lot of lines of code.

Consider this: you have to implement a class that, given several input parameters, provides several outputs (two in our example). You need to use this class to fill two arrays with the outputs, but the parameters don’t always change (so you don’t always have to perform the computation). Since the computation is time-consuming, you don’t want to re-compute when you don’t have to3.

One (naĆÆve) way to do this would be that (full code here https://godbolt.org/z/a4x769jra) :

class FooBarComputer
{
public:
    FooBarComputer();

    // These update the m_has_changed attribute if relevant
    void set_alpha(int alpha);
    void set_beta(int beta);
    void set_gamma(int gamma);

    int get_foo() const;
    int get_bar() const;

    bool has_changed() const;
    void reset_changed();

    void compute();

private:
    int  m_alpha, m_beta, m_gamma;
    int m_foo, m_bar;
    bool m_has_changed;
};


//  ...


//==============================
bool FooBarComputer::has_changed() const
{
    return m_has_changed;
}

void FooBarComputer::reset_changed()
{
    m_has_changed = false;
}


//==============================
// Output getters
int FooBarComputer::get_foo() const
{
    return m_foo;
}

int FooBarComputer::get_bar() const
{
    return m_bar;
}


//  ...


//==============================
// main loop
int main()
{
    std::vector<int> foo_vect, bar_vect;
    FooBarComputer fbc;

    for (int i = 0 ; i < LOOP_SIZE ; ++i)
    {
        fbc.set_alpha( generate_alpha() );
        fbc.set_beta( generate_beta() );
        fbc.set_gamma( generate_gamma() );

        if ( fbc.has_changed() )
        {
            fbc.compute();
            fbc.reset_changed();
        }

        foo_vect.push_back( fbc.get_foo() );
        bar_vect.push_back( fbc.get_bar() );
    }
}

However, it is possible to write a better version of this class by tweaking the getter, like so (full code here https://godbolt.org/z/aqznsr6KP):

class FooBarComputer
{
public:
    FooBarComputer();

    // These update the m_has_changed attribute if relevant
    void set_alpha(int alpha);
    void set_beta(int beta);
    void set_gamma(int gamma);

    int get_foo();
    int get_bar();

private:
    void check_change();
    void compute();

    int  m_alpha, m_beta, m_gamma;
    int m_foo, m_bar;
    bool m_has_changed;
};


//  ...


//==============================
void FooBarComputer::check_change()
{
    if (m_has_changed)
    {
        compute();
        m_has_changed = false;
    }
}


//==============================
// Output getters
int FooBarComputer::get_foo()
{
    check_change();
    return m_foo;
}

int FooBarComputer::get_bar()
{
    check_change();
    return m_bar;
}


//  ...


//==============================
// main loop
int main()
{
    std::vector<int> foo_vect, bar_vect;
    FooBarComputer fbc;

    for (int i = 0 ; i < LOOP_SIZE ; ++i)
    {
        fbc.set_alpha( generate_alpha() );
        fbc.set_beta( generate_beta() );
        fbc.set_gamma( generate_gamma() );

        foo_vect.push_back( fbc.get_foo() );
        bar_vect.push_back( fbc.get_bar() );
    }
}

Here are the benefits:

  • The main code is shorter and clearer.
  • You don’t need to make the compute() public anymore.
  • You don’t need has_changed(), instead, you have a check_change() that is private.
  • Users of your class will be less likely to misuse it. Since the compute() is now lazy, you are sure that no one will call the compute() recklessly.

This is that last point that is the most important. Any user could have made the mistake to omit the if conditional in the first version of the loop, rendering it inefficient.

3. If you want, you can imagine that the parameters change only once a hundred times. Thus, this is important to not call the compute() every time.

Isn’t it possible to do otherwise?

The most recurring argument I hear about this practice is the following: “Well, why don’t you just name your method otherwise? Like computeFoo()?”.

Well, the method does not always perform the computation so it’s wrong to call it that way. Plus, semantically, compute refers to a computation and does not imply that the value is returned. Even if some people do it, I don’t like to use the word compute that way.

“Then call it computeAndGetFoo() and be done with it!”

Except, again, it’s not what it does. It does not always compute, so a more appropriate name would be sometimesComputeAndAlwaysGet(), which is plain ridiculous for such a simple method.

“Then find a proper name!”

I did. It’s getFoo(). It’s what it does, it gets the foo. The fact that the compute() is lazy does not change the fact that it’s a getter. Plus, this is mentioned in the comments that the get may be costly, so read the documentation and everything will be fine.

“Couldn’t you put the check into the compute() instead of the getter?”

I could have, but to what end? Making the compute() public is useless since you need the getters to access the data.

There is a way to do otherwise…

There actually is a way to do otherwise, which is especially useful when you also need a getter than you are sure will never call the compute (this is useful if you need a const getter for instance).

In that case, I advise that you don’t use the same name for both getters, because they don’t do the same thing. Using the same name (with the only difference being the constness) will be confusing.

I personally use the following names:

  • getActiveFoo()
  • getPassiveFoo()

I like this way of writing it because you explicitly specify if the getter is potentially costly or not. Moreover, it also implies that the passive getter may give you an outdated value.

Plus, anyone who tries to call getFoo() will not succeed and have to choose between both versions. Forcing the user to think about what they write is always a good thing.

Most importantly: spread the word

Since it is useful to put performance-sensitive code in getters, you will find developers who do it.

The most dangerous behavior is ignoring that fact and letting other people assume that accessors are fast to execute.

You should spread the word around you that accessors are, at the end of the day, like any other method: they can be long to execute, and everyone must read the associated documentation before using them.

In this article, I talked about execution time performance, but this also applies to any other form of performance: memory consumption, disk access, network access, etc.

Thank you for reading and see you next week!

Author: ChloƩ Lourseyre

Don’t use raw loops

Author: ChloƩ Lourseyre

Intentions

Sean Parent once said “No raw loops“. This was eight years ago.

Today, among the people who don’t have a strong C++ background, almost nobody knows this statement (let alone to know who Sean Parent is). As a result, in 2021, many C++ projects use tons of raw loops and almost no algorithms.

This article intends to teach you, the people who still use raw loops in 2021, why and how to use algorithms instead.

Resources

This subject has been covered by many C++ experts already, so if you feel technical and have some free time, check the following resources:

Sean Parent’s talk that contains the no raw loops statement:
GoingNative 2013 C++ Seasoning – YouTube

A Jason Turner’s talk about code smells (including the use of raw loops):
C++ Code Smells – Jason Turner – CppCon 2019 – YouTube

Jonathan Boccara’s talk about STL algorithms:
CppCon 2018: Jonathan Boccara ā€œ105 STL Algorithms in Less Than an Hourā€ – YouTube.

As a bonus, here is a map representation of the STL algorithms:
The World Map of C++ STL Algorithms – Fluent C++ (fluentcpp.com).

What are raw loops?

What we call raw loops are you most basic design of loops. It is usually the use of the keywords for, while, or do while accompanied by a block of code.

Raw loops are opposed to algorithms, that are encapsulated loops (or non-loops, how could you know since it’s encapsulated?) serving a specific or generic purpose and that can be called using dedicated functions.

Why you should not use raw loops

It’s a matter of semantics. A raw loop can only express the technical fact that it’s a loop, you write how your algorithm proceeds.

When you call an algorithm, you write what you intend, you write what you want to obtain.

For example, let’s look at this sample of code:

//...

for (size_t i = 0; i < my_vect.size() && predicate(my_vect, i) ; ++i)
{
    //...
}

//...

This tells you that you perform a loop, indexed over a vector and controlled by a custom predicate, but no more. It doesn’t tell you what the loop does, and what result is expected from it. You’ll have to study the body of the loop to know that.

Now let’s look at this algorithm call:

//...

auto it_found = std::find_if(cbegin(my_vect), cend(my_vect), predicate);

//...

Even if you don’t know how find_if() proceeds, you understand it will return an element of my_vect that matches the condition predicate(). You understand what it does, not how it is done.

From there, there are several points to consider:

  • Algorithms raise the level of abstraction and help you understand the intention behind the code.
  • Good semantics leads to good readability, good readability leads to better maintainability, better maintainability leads to fewer regressions.
  • Calling an algorithm is less verbose than re-writing it.
  • Raw loops are prone to several common mistakes, like off-by-one, empty loops, naive complexity, etc.

What to do when there is no existing algorithm?

There are times when you need an algorithm to perform a specific task, but this is so specific that none of the existing algorithms is suitable. What to do in this case?

Combine algorithms into a new one

Often, your specific algorithm can be resolved by doing a combination of existing algorithms, or by implementing a specific version of an existing one. To do so, just implement a function that does the combination for you and give it an explicit name. Then anyone can call it like any algorithm.

For example, you need to verify if, in a vector, all elements that match condition A also match condition B. To do this, you can use the algorithm std::all_of with a custom predicate:

template< typename Iterator, typename PredA, typename PredB >
bool both_or_none( Iterator first, Iterator last, PredA & predA, PredB & predB )
{
    auto pred = [&predA,&predB](const auto& elt)
    {
        return predA(elt) == predB(elt); 
    };
    return all_of(first, last, pred);
}

The body of the algorithm is pretty short: it creates a function that combines both predicates to implement our specific condition, then applies std::all_of(), the algorithm that verifies that the condition is true on every element of the collection.

Write a raw loop inside a function

There are some times when trying to combine existing algorithms is futile and may feel forced and artificial.

What you need to do when this occurs is to write your own raw loop in a dedicated function, that will act as your algorithm. Be sure to give it an explicit name.

For example, you have a collection and you need to get the maximum element that matches a condition. This is what the algorithm max_if() would be if it existed.

However, you can’t pull it out easily by combining existing algorithms. You would first need to get the subset of your collection that matches the condition, then calling std::max() on it. But the only way1 to get that subset is to use a std::copy_if, which copies elements. Copies may be expensive so you don’t want that.

What to do then? Write the raw loop that implements the max_if() yourself, and encapsulate it in a function:

template< typename Iterator, typename Pred >
constexpr Iterator max_if( Iterator first, Iterator last, Pred & pred )
{
    Iterator max_element = last;
    for (auto it = first ; it != last ; ++it)
    {
        if (pred(*it) && (max_element == last || *it > *max_element))
            max_element = it;
    }
    return max_element;
}

Then, the use of max_if will be semantically explicit, matching all the upsides of a true algorithm.

1There actually are other ways, like using find_if then remove in successions, but they are pretty much worse.

STL algorithms examples

There are plenty of algorithms in the STL. I suggest you be curious and explore by yourself: Algorithms library – cppreference.com.

As an appetizer, here are a few very basic and common algorithms that, if you don’t already know them, should learn about.

  • std::find(): Searches for an element equal to a given value.
  • std::find_if(): Searches for an element for which a given predicate returns true.
  • std::for_each(): Applies the given function object to the result of dereferencing every iterator in the range, order.
  • std::transform(): Applies the given function to a range and stores the result in another range, keeping the original elements order.
  • std::all_of(): Checks if the given unary predicate returns true for all elements in the range.
  • std::any_of(): Checks if the given unary predicate returns true for at least one element in the range.
  • std::copy_if(): Copies the elements for which the given predicate returns true.
  • std::remove_if(): Removes the elements for which the given predicate returns true.
  • std::reverse(): Reverses the order of the elements in the range.
  • And many more…

If you want to go further, here is a one-hour talk presenting more than a hundred algorithms: CppCon 2018: Jonathan Boccara ā€œ105 STL Algorithms in Less Than an Hourā€ – YouTube

Wrapping up

Many C++ experts agree that loops are to disappear in the higher abstraction levels, only used to write lower-level algorithms. This statement is not an absolute, but an ideal to keep in mind when you code.

If like many C++ developers, you tend to use raw loops instead of algorithms, you certainly should check the resources provided in this article. As you get familiar with the most basic algorithm and begin to use them in practice, you’ll find them more and more convenient.

Thanks for reading and see you next week!

Author: ChloƩ Lourseyre

A list of bad practices commonly seen in industrial projects

Author: ChloƩ Lourseyre

If you ever worked in a company-size software project (with numerous developers), there is a good chance that the codebase was, at least, pretty messy.

In these days, most industrial C++ developers are not experts. You will often work with developers with a Java or Python background, people who just learnt C++ at school and don’t really care that much about the language and old developers who code in “C with classes” instead of C++.

Having worked on a few industrial projects myself, I realized there are some recurring patterns and bad practices. If you happen to teach your coworkers to avoid these bad practices, you will all take a huge step toward a beautiful codebase and it will be beneficial to you, your coworkers and your project.

Here is a non-exhaustive list of these common bad practices.

Bad practice : Overly long functions

I am not one to set hard restrictions over the number of lines in a function. However, when a function reach more that a thousand lines (or even tens of thousands of lines), it is time to put a stop at it.

A function is a architectural block. If it is too big, it will be harder to understand. If is it split into different blocks, with explicit names and comprehensible comments, your mind will be able to turn its attention to each blocks in turns, which are individually easier to understand, and will put them back together to understand the globality of the function.

Sometimes, your function is just calling auxiliary functions in succession, and that’s ok. It’s short, easy to understand, and each auxiliary function, which are small, are also easy to understand.

To solve a big problem, split it in several smaller problems.

The limit I generally use is 200 lines per functions. Sometimes more, sometimes less.

Bad practice : Create classes when you don’t need to

This is something that is surprisingly fairly common, and probably due to other object-oriented languages that force you to use classes for everything.

There are two ways this bad practice can occur :

Full-static classes (sometimes with constructors)

It is easier to illustrate with an example, so here we go :

class MyMath
{
public:
    MyMath();
    ~MyMath();
    static int square(int i);
};

MyMath::MyMath()
{
}

MyMath::~MyMath()
{
}

int MyMath::square(int i)
{
    return i*i;
}

int main()
{
    MyMath mm;
    int j = mm.square(3);
    return j;
}

Here are the problematic points :

  • Why would you implement useless constructor and destructor, where you just could have used the default ones ?
  • Why would you implement a constructor and a destructor for a full-static class ?
  • Why would you instantiate an object just to call the static method of the class ?
  • Why would you use class at all, where a namespace would suffice ?

Here is what should have been written :

namespace MyMath
{
    int square(int i);
};

int MyMath::square(int i)
{
    return i*i;
}

int main()
{
    int j = MyMath::square(3);
    return j;
}

Shorter, better, smarter.

True, sometimes a full-static class can be useful, but in situations like that example, they are not.

There is no benefit in using a class where you could not. If you are worried that the namespace could be used as a class in the future (with attributes and methods), just remember this little rule that every one should know :

Do not code thinking of an hypothetical future that may or may not occur. The time you spend coding in anticipation is most certainly wasted, as you can always refactor later.

Fully transparent classes

I put this one in second because it is the most controversial.

Just to clear : the only difference between class and struct is that, by default, the members of a class are private and the members of a struct are public. This is truly the only difference.

So, if your class :

  • … only has public methods
  • … has both accessors (getter and setter) to all its attributes.
  • … has only very simple accessors.

… then it’s not a class, it’s a struct.

Here, to illustrate :

class MyClass
{
    int m_foo;
    int m_bar;

public:
    int addAll();
    int getFoo() const;
    void setFoo(int foo);
    int getBar() const;
    void setBar(int bar);
};

int MyClass::addAll()
{ 
    return m_foo + m_bar;
}
int MyClass::getFoo() const
{
    return m_foo;
}
void MyClass::setFoo(int foo)
{
    m_foo = foo;
}
int MyClass::getBar() const
{
    return m_bar;
}
void MyClass::setBar(int bar)
{
    m_bar = bar;
}

Is better written that way :

struct MyClass
{
    int foo;
    int bar;

    int addAll();
};

int MyClass::addAll()
{ 
    return foo + bar;
}

This is pretty much the same. You just withdraw a (useless) level of encapsulation for a more concise and more readable code.

The controversial part occur one the “useless” statement just above, because in a full-object mindset, no encapsulation is useless. In my opinion, this kind of structure don’t need encapsulation because it is just a data structure, and I don’t like when people overdo the concept of encapsulation.

Watch out, though, because this practice is only valid if all your attribute are in direct-access in both writing and reading. If one of our attribute need a specific accessor or you have read-only or write-only attributes, don’t use a struct (well, you can, but you need to seriously think about it).

Bad practice : Implementing undefined behavior

To say it short, an undefined behavior is a promise you make to the compiler that some behavior will never be implemented by your hand. Using that, the compiler will be able to make assumptions and optimize your code with those assumptions.

Go watch the talk of Piotr Padlewski : CppCon 2017: Piotr Padlewski ā€œUndefined Behaviour is awesome!ā€ – YouTube. It will teach you everything you need to know about UB.

Here is a non-exhaustive list of undefined behaviors. You need to know that list by heart in order to avoid unexpected undefined behavior in you codebase :

  • Calling main
  • Integer overflow
  • Buffer overflow
  • Using uninitialized values
  • Dereferencing nullptr
  • Forgetting the return statement
  • Naming variable starting with double underscore
  • Defining function in namespace std
  • Specializing non-user defined type in namespace std
  • Taking the address of a std function

So, it has to be said once and for all : do not ever rely on integer overflow to end a loop or for anything else. Because it does not mean what you think it means and one fateful day it will backfire hard.

Bad practice : Comparing signed and unsigned integer

When you compare signed and unsigned, an arithmetical conversion will occur that has a very good chance to distort the values, thus nullifying your comparison.

Use size_t when it’s relevant, and static_cast your variable if needed.

Bad practice : Trying to optimize the code as you write it

Yeah, that pill may be pretty hard to swallow. But here are two facts :

  • 80% of the time, the code you write doesn’t need to be optimized. Since most of your execution only occurs in 20% of your program (Pareto principle at work), the remaining 80% does not need to be optimized.
  • Optimization should not be a prior concern. You are to write your code, see the big picture, and optimize in consequence.

What is the most important is not how optimized is your program. The thing you should concern yourself about is whether your code is tidy, concise and maintainable. If it is, you can only come back later to optimize it.

Bad practice : Being too dumb

On the opposite, you shouldn’t under-optimize either.

You must know the specificities of the algorithms and the data structures in order to use the correct ones in your code. You need to understand some design patterns and be able to implement them so you don’t reinvent the wheel each time, you mustn’t be afraid to check the documentation before using a feature.

There is a good balance between coding without thinking and over-optimizing the code while you are writing it.

Bad practice : “If we need to do that later…”

I said it a few paragraphs above, but don’t plan on a hypothetical future. The only future you can be sure of is your most basic design. Your needs may change, your specs may change, what the client wants may change, anything beside the core design of your project may change. Sometimes it won’t, but often it will. Be sure to remember that.

When in doubt, be sure to ask yourself :

If I implement that later, will it cost more ?

Often, the answer is “no” or “not that much“. When so, leave the future to the future.

In the end…

Here you have a good start to write beautiful and maintainable code. If every dev on your project apply them, you will all benefit from it.

However, there are many many other basic bad practices I didn’t cover in this article, so the subject is not closed. Maybe I will publish a part 2 someday, maybe not.

Thanks for reading and see you next week!

Author: ChloƩ Lourseyre

Yet another pamphlet about inlining

Author: ChloƩ Lourseyre

Introduction

This week’s topic will be about the inline keyword.

This is a very old subject that, for me, has been settled for years. And everytime I re-enter the subject and document myself about, I end up with the same conclusions.

However, I very often read code that misuse this keyword, ending up in counterproductive and risky situations.

I will try here to summarize all you need to remember about inlining, giving pros, cons and situations to use it and to not use it.

Definition

I’ll start with the academical definition of inline :

The original intent of the inline keyword was to serve as an indicator to the optimizer that inline substitution of a function is preferred over function call, that is, instead of executing the function call CPU instruction to transfer control to the function body, a copy of the function body is executed without generating the call. This avoids overhead created by the function call (passing the arguments and retrieving the result) but it may result in a larger executable as the code for the function has to be repeated multiple times.
Since this meaning of the keyword inline is non-binding, compilers are free to use inline substitution for any function that's not marked inline, and are free to generate function calls to any function marked inline. Those optimization choices do not change the rules regarding multiple definitions and shared statics listed above.

Source : inline specifier – cppreference.com

What you need to retain from that are two things :

  • inline is used to avoid function overhead, replacing the call by the implementation at compile time.
  • It is only a hint given to the compiler. Some function declared inline may not really be inlined, and vice-versa.

With that in mind, let’s take a tour of the pros and the cons of this feature.

Pros

First of all, even if it’s a hint, the keyword may influence the compiler. It may not, but it also may. We can for instance think of the stupidest compiler that will just strictly respect the inline keyword, without trying to optimize himself. This is not against the standard (according to the C++11 standard Ā§12.1.5) and the keyword is useful in this case.

According to What is C++ inline functions – C++ Articles (cplusplus.com), the pros are :

  1. It speeds up your program by avoiding function calling overhead.
  2. It save overhead of variables push/pop on the stack, when function calling happens.
  3. It save overhead of return call from a function.
  4. It increases locality of reference by utilizing instruction cache.
  5. By marking it as inline, you can put a function definition in a header file (i.e. it can be included in multiple compilation unit, without the linker complaining).

Point number 1, 2 and 3 are overally the main benefits of this feature, and the original goal of the introduction of this keyword. Those who know a bit of assembly know that pushing a lot of parameters on the stack to call function can cost more instructions that the function holds.

Point number 4 seems to be a non-negligible side-benefit of this method, but since instruction cache is far from being my specialty, I will nt develop on this.

Point number 5 is pro only in certain specific cases, but a pro nonetheless.

Cons

According to What is C++ inline functions – C++ Articles (cplusplus.com), the cons are :

  1. It increases the executable size due to code expansion.
  2. C++ inlining is resolved at compile time. Which means if you change the code of the inlined function, you would need to recompile all the code using it to make sure it will be updated
  3. When used in a header, it makes your header file larger with information which users donā€™t care.
  4. As mentioned above it increases the executable size, which may cause thrashing in memory. More number of page fault bringing down your program performance.
  5. Sometimes not useful for example in embedded system where large executable size is not preferred at all due to memory constraints.

Points 1 and 4, which are unknown to a lot of developpers, are the main reason that function inlining can decrease performance. It is important to remember that when using this feature.

Point 2 can be a major inconvenient, depending on your project, but don’t occur so often in my experience.

Point 3 is, in my opinion, the main con of this feature. In order to have maintainable code you need to have to be clear and organized. Inlining is a huge code smells in that regard.

Point 5 is about specific projects, so I will not develop further. But keep in mind that if you have memory constraints, inlining may have consequences.

Conlusion : when to use the inline keyword ?

Avoiding function overhead is only usefull if you are in a performance critical part of your code.

You probably already know the Pareto’s law : “80% of the execution happens in 20% of the code”. This means that the program spends most of it’s time in bottlenecks. Thus, if you inline code that is not within a bottleneck, this will have little to no effect on your program performance, while increasing its size.

What urged me to write this article is that during my coder’s life, I saw many many codebase polluted by useless inlines. I can safely say that more than 95% of the inlining I saw in industrial projects was used in non-critical code.

There is absolutly no need in decreasing the readability of the code and increasing the executable size for that end.

Here is my advice : don’t use inline unless you are 100% sure it is called inside a bottleneck.

This is the only way to be both efficient and clean.

Another type of inlining

I will end this article by adding a word about a special case of “inlining”.

If you are someone who likes to write things like this :

inline void setValue(int i) { m_value = i; }

and by that I mean, the prototype and the implementation all on one line, please understand that this prevent many debuggers from doing their jobs properly.

For instance, in VisualStudio, if you put a breakpoint inside this setValue method, when it hits the debugger won’t be able to give you the value of m_value.

So please, I’m begging you, stop doing that. It won’t cost you much to add a newline.

Thank you, and see you next week!

Author: ChloƩ Lourseyre

Passing an enum by parameter

Author: ChloƩ Lourseyre

Let’s say you have a function you have to call using two parameters :

  • The first parameter is a configuration option symbolized by an enum. Like the “mode of use” of your function.
  • The second parameter is a true numeral parameter.

Let’s use the following example to represent that :

#include <iostream>

// Enum describing the mode used in the following function
enum class ChangeMode
{
    Before,
    After
};

// Function that squares a value and increases it (wether before or after), then prints the result
void increase_and_square(const ChangeMode& m, int v)
{
    if (m == ChangeMode::Before)
        ++v;
    
    v = v*v;
    
    if (m == ChangeMode::After)
        ++v;

    std::cout << v << std::endl; 
}

// main function
int main()
{
    const int a = 42;
    increase_and_square(ChangeMode::Before, a);
    increase_and_square(ChangeMode::After, a);
}

In this example, we perform slightly different operation depending on the “mode” used. This is a fairly common behavior that can occur in may different forms.

However, implemented as it is there is not the most optimized way to do this. A best way to obtain the same result is to use the ChangeMode as a template instead of a plain parameter.

Like this :

#include <iostream>

// Enum describing the mode used in the following function
enum class ChangeMode
{
    Before,
    After
};

// Function that squares a value and increases it (wether before or after), then prints the result
template<ChangeMode m>
void increase_and_square(int v)
{
    if (m == ChangeMode::Before)
        ++v;
    
    v = v*v;
    
    if (m == ChangeMode::After)
        ++v;

    std::cout << v << std::endl; 
}

// main function
int main()
{
    const int a = 42;
    increase_and_square<ChangeMode::Before>(a);
    increase_and_square<ChangeMode::After>(a);
}

Impementing the function like is actually better in every way :

  • Operations done at compile time : of course, and as the main advantage of using templates, we evaluate more expressions a compile time here, saving run time.
  • Compiler optimization : since the compiler will be able to evaluate more things at compile time, it will be able to optimize the code, effectively discarding the if statements.
  • Executable size : more impressive, the executable will be smaller with the templated version. This is so because the fact that we discard the if statement actually greatly reduce the size of the functions.

To illustrate this, here is the template instanciation the compiler generates :

void increase_and_square<ChangeMode::Before>(int v)
{
    ++v;
    v = v*v;
    std::cout << v << std::endl; 
}

void increase_and_square<ChangeMode::After>(int v)
{
    v = v*v;
    ++v;
    std::cout << v << std::endl; 
}

These are way simpler functions that the big bulky one above.

If you are not convinced, here is the number of assembly instructions the compiler generates (I used Compiler Explorer (godbolt.org) with clang as compiler and --std=c++20 -O3 as compiler options).

Parameter

123

Template

76

The template version is clearly better, faster, prettier.

Perk : double default values

Using the template version of this code also provides another perk : the ability to give a default value to your mode and to your parameter.

Since default values are decorrelated for templates and parameters, you can write this :

#include <iostream>

// Enum describing the mode used in the following function
enum class ChangeMode
{
    Before,
    After
};

// Function that squares a value and increases it (wether before or after), then prints the result
template<ChangeMode m=ChangeMode::Before>
void increase_and_square(int v = 2)
{
    if (m == ChangeMode::Before)
        ++v;
    
    v = v*v;
    
    if (m == ChangeMode::After)
        ++v;

    std::cout << v << std::endl; 
}

int main()
{
    const int a = 42;
    increase_and_square(a);
    increase_and_square<ChangeMode::After>();
    increase_and_square();
}

This is interesting because the mode and the parameter actually have different semantic meanings, thus it is only logical that they have separate behavior.

Limitations

Why does this work ? Because the “mode” we template is actually an enum with very few values.

If you happen to use an bigger enum (with 5+ different values) or other types, the template version will be way worse than the parameter version.

In general, you will have no use doing what we saw in this article, but in the very specific case you have an enum that describes a “mode of use” of your function and has 2 or 3 possible values, take time to consider using it as template.

Keep in mind that this model, if seducing, is very specific and will not apply in your day-to-day coding.

Semantic

I will conclude this article by talking a bit about semantics.

What we achieved here could be seen as an overload of the increase_and_square() function. Indeed, we did several implementations of the same function (well, the template did it for us) that has a similar-yet-different behavior. This is exactly what overloading is about.

This is also why I described the enum as a “mode” : it describes how the function must work, and is more that than a real parameter.

Thanks for your attention and see you next week!

Author: ChloƩ Lourseyre