This is a simplified version of a well-know std::getline function. This declaration contains the necessary information for the compiler. It needs to know the number of input parameters, their sizes and order to be able to generate the machine code for its calls. But it’s not just that, it also contains valuable information for humans, users of this function. Intuition suggests that str is a so-called out parameter, meaning we expect the result, the ‘line’ to be stored in that str . It would’ve been a different story if it was const std::string& str) . But that extra ‘const’ doesn’t make any difference in the machine code generated. In fact, your compiler doesn’t care about const-s. Well, it kind of does when doing a name lookup, but that’s just a check, it doesn’t affect the generated machine code anyway. Const correctness is a beautiful concept and quite unique to C++ (some other languages tried, but nothing). It serves as a contract between those who define the interface and those who use it.

To sum up, you define your interfaces such that the users will understand you, what your intentions are.

Back to our code

When one says, the code is wrong, it doesn’t necessarily mean there’s a bug. It might perform poorly or have a so-called code smell. This term is a vast generalization of constructs that are technically valid, execute the intention correctly (thus are not bugs), but still alarm about a deeper issue. And by the way, these smelly constructs vary from language to language, from paradigm to paradigm.

To understand what’s wrong in our code, let’s quickly recap what std::shared_ptr is. It’s a smart pointer that wraps the raw pointer along with a reference counter, providing a pointer-like interface. At any time the reference counter indicates how many shared_ptr objects are there co-owning this resource. It increments every time a co-owner is copied and decrements every time a co-owner is destroyed (out of scope, destructed). Eventually, when this counter comes to 0, the raw pointer is deleted, the resource is freed.

With this in mind, I’ll go ahead and write that function’s declaration again, thinking out loud:

void foo(... widget);

I know the type will have Widget somewhere somehow, but how exactly? Well, it depends. I ask myself the following questions:

Am I going to modify the original object? Is it expensive to copy a Widget ? Do I take responsibility for the lifetime of the object?

There are 8 (2³) possible answer combinations to these questions, but because they are codependent, some of those combinations are irrelevant.

Before moving on, I suggest an experiment. Go over this table from the other way around: look at the argument and try to answer the questions. Imagine, you're looking at a function with such an argument and try to figure out what was the intention behind.

So, not only the specific case dictates an interface, but also the latter explains a specific case. With this in mind, let’s finally get to our initial code.

Why don’t we have the case of the const std::shared_ptr<Widget>& in our table? Is it because we missed a scenario? Let’s find out by working our way form the other side, let’s try to understand what someone wants to tell us with such an interface.

I understand this so:

I want a pointer to a Widget, but not a regular one, I want a smart one, a shared one more specifically.

As it’s not a shared_ptr<const Widget> , I might modify the original object, so beware.

, I might modify the original object, so beware. Remember, when I said I want a shared pointer? Well, I’m not going to create a copy of it and become a co-owner. Basically, I’m not going to use the shared_ptr-ness of it, but if that object doesn't happen to be wrapped in a shared_ptr, you’re supposed to make it happen. If so, you’ll most probably cause a double deletion.

Not only this doesn’t make sense but it’s also a legit code smell. It entails uncertainty and the potential of new bugs.