An insidious C++ error related to my use of auto ended up driving me nuts for hours this week.
I was revisiting a driver I wrote, and I noticed that my latest version was giving me consistent bogus readings. I checked an older version, which was providing correct values. I skimmed the code but couldn’t find any obvious functional differences, so I started looking at the I2C traffic. Even more maddening, the raw bus traffic was identical!
I won’t bury the lede too much: the root cause of my problem was that I missed an & on an auto variable declaration. This happened in a function that reads calibration data from the sensor and stores it in a struct in memory. The calibration data is then used when converting the raw sensor data. The problem is that auto cal = inst.calibration created a local copy of the struct. The register reads populated the local copy, which is destructed when exiting the function, leaving the actual calibration data empty.
static void readCompensationData(BME280& inst)
{
// Make a convenience alias to save some typing
auto cal = inst.calibration; // <--- BUG: this is a COPY!
/*
... reads all calibration values into the local copy
*/
// End of function, the local `cal` is destructed
}
The solution was to simply make it a reference by declaring as auto &.
static void readCompensationData(BME280& inst)
{
- auto cal = inst.calibration;
+ auto& cal = inst.calibration;
Definitely user error here rather than a language failure. Nonetheless, hours were spent finding that single missing character.
The frustrating part to me was that I know auto behaves this way: it’s simply mirroring the type definition, and declares another struct rather than a reference. I’m also used to looking out for this in other contexts, as it’s a common mistake you can make when working with range-based for loops in C++.
Yet, it ended up being such an insidious error:
autosilently made a copy, nothing warned me that my intent here was not being executed- The code reads perfectly fine when you’re looking over it
- The hardware interaction was suspect, but ended up being correct
I’m reflecting on how the whole reason I ended up in this situation was “convenience.” Had I specified the type name explicitly rather than using auto, I would have likely caught the copy right away rather than having my brain gloss over it. Ultimately I could have just lived with inst.calibration rather than seeking an easier way. Based on all the time I spent debugging this shortened alias, I could have handwritten inst.calibration on a whiteboard hundreds of times.
Now I’m wondering just how many hours attempts at convenience or expedience have cost me.

The auto keyword is only a type specifier. It does not automatically make something a reference or a pointer. If you want a reference or pointer, you must write it explicitly. In your case, the problem was fixed when you changed it to auto&.
I have faced this issue before. If you want to detect this kind of mistake at compile time, you can delete the copy and move constructors and the assignment operators for types that should not be copied like a sensor driver. This way, they can only be passed by reference or pointer, otherwise the compiler complains.
How does “auto” behave differently from the typename in this example?
To be clear: I’m aware that it doesn’t make a reference, the type declaration is just a struct, and this is totally just user error. Nonetheless I spent two hours on it 👨💻😭
Modern languages introduce a lot of nuances that take some getting used to. I wrote a tech debt Jira ticket on a very similar issue (still unaddressed), except in our case it is to save stack space. We have had some instances of excessive stack wastage and I traced at least one occurrence to use of “auto” instead of “auto &”, meaning whole structures were being copied onto the stack instead of just using references. I figured there are probably more, so I wrote the ticket about it, but we’ve had very little time to address things like this. Maybe this is something we can have Claude do.
I always thought that the use of ‘auto’ shall be avoided: it just serves to avoid typing entity types, because we are software/firmware developers, we are clever, we don’t want to write comments because “the code documents itself”.
We are just the laziest people in the world.
And yes, this post is auto-critical. Pun intended.
How fo you use “cal”? Why don’t you have an error from code such as “p->” or “*p” when it’s no longer a pointer ?
I stopped being lazy at typing a long time ago. It’s just another form of technical debt and you will pay for it later.
Use explicit types, variable, class and function names. It makes the code more readable (even for AI agents).