Lucky Resistor
Menu
  • Home
  • Learn
    • Learn C++
    • Product Photography for Electronics
      • Required Equipment and Software
    • Soldering for Show
  • Projects
  • Libraries
  • Applications
  • Shop
  • About
    • About Me
    • Contact
    • Stay Informed
  •  
Menu

Write Less Code using the “auto” Keyword

Posted on 2019-07-122022-09-04 by Lucky Resistor

The auto keyword was introduced with C++11. It reduces the amount of code you have to write, reduces repetitive code and the number of required changes. Sadly, many C++ developers are not aware of how useful this keyword is. Especially embedded code can gain a lot by its usage.

In this article, I try to explain a number of useful cases of the auto keyword, using Arduino example code. The same principles are valid for any embedded environment which is using a modern C++ compiler. The C++11 standard is eight years old, and meanwhile, every C++ compiler should support it.

The examples are written for Arduino or Adafruit SAMD boards using the Arduino IDE. The toolchain for these boards use a compiler which supports more features explained in this article. Using auto with AVR code is possible too, just give it a try.

The “auto” Keyword

The auto keyword was introduced to instruct the compiler to automatically deduce a type from the given context. In programming language terms, they call this a placeholder type specifier.

C++11 mainly introduced the auto keyword for variable declarations. These are the main focus of this article.

Simple Variable Declarations

At any point in your code, where you declare a variable which is initialized, you can use auto instead of a type.

auto variable = <some expression>

The following example uses auto in various places:

const auto cFrameSize = 100;
auto gMiddleValue = 0;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void waitUntil(uint32_t timePoint) {
  while (static_cast<int32_t>(timePoint - millis()) > 0) {}  
}

void loop() {
  const auto currentTime = millis();
  const auto middlePoint = currentTime + (cFrameSize - gMiddleValue);
  const auto framePoint = currentTime + cFrameSize;
  digitalWrite(LED_BUILTIN, HIGH);
  waitUntil(middlePoint);
  digitalWrite(LED_BUILTIN, LOW);
  waitUntil(framePoint);
  gMiddleValue += 1;
  if (gMiddleValue >= cFrameSize) {
    gMiddleValue = 0;
  }
}

Variables from Literal Values

The example starts with two variable declarations which are initialized from integer literals:

const auto cFrameSize = 100;
auto gMiddleValue = 0;

The first line is also adding const to create a constant while the second line declares a regular global variable.

Using auto with integer values as shown is not very useful and just used as a demonstration. Especially most people will now know the actual type generated (int). It starts to make sense if you are working with a class, a struct and especially template classes.

If you read my previous article, there I declare a new template class Duration and user-defined literals for time values. To use a millisecond constant, I would have to write:

#include "Duration.hpp"

const Millisecond cSerialTimeout = 100_ms;

If I change the value from milliseconds to microseconds, I also have to change the variable type:

#include "Duration.hpp"

const Microseconds cSerialTimeout = 100_us;

If I use auto as a variable type, no change is needed. I especially do not need to remember the correct type for _ms or _us literals:

#include "Duration.hpp"

const auto cSerialTimeout = 100_us;

Variables for Immediate Results

In the example, I also use auto to create variables for immediate calculation results:

const auto currentTime = millis();
const auto middlePoint = currentTime + (cFrameSize - gMiddleValue);
const auto framePoint = currentTime + cFrameSize;

The const keyword is added to create a constant variable instead of a regular one.

Using auto for immediate variables makes the code shorter and more flexible. If the real-time counter type would change from uint32_t to uint16_t, you do not have to change this code at all.

Simplify Range Based For Loops

The auto keyword is great in range based for loops:

const char* magicLines[] = {
  "Twinkle, twinkle, little star,",
  "How I wonder what you are!",
  "Up above the world so high,",
  "Like a diamond in the sky.",
  "When this blazing sun is gone,",
  "When he nothing shines upon,",
  "Then you show your little light,",
  "Twinkle, twinkle, through the night.",
};

void setup() {
  Serial.begin(115200);
  while (!Serial) {};
  for (auto line : magicLines) {
    Serial.println(line);
  }
}

void loop() {
}

If you are using complex types, it makes sense to use a const reference in the for loop as shown in the next example:

struct MagicLine {
  const char *text;
  uint32_t delay;
};

const MagicLine magicLines[] = {
  {"Twinkle, twinkle, little star,", 800},
  {"How I wonder what you are!", 1000},
  {"Up above the world so high,", 1200},
  {"Like a diamond in the sky.", 2000},
  {"When this blazing sun is gone,", 500},
  {"When he nothing shines upon,", 800},
  {"Then you show your little light,", 1000},
  {"Twinkle, twinkle, through the night.", 500},
};

void setup() {
  Serial.begin(115200);
  while (!Serial) {};
  for (const auto &line : magicLines) {
    Serial.println(line.text);
    delay(line.delay);
  }
}

void loop() {
}

Make Register Access Configurable

In embedded software, you often have to deal with the low-level hardware API for performance reasons. This usually includes direct register access.

The hardware API of the manufacturer of most microcontrollers is an unfathomable mess of macros and nested structures. It can take a very long time to find the correct type names in these files.

Instead of doing all this research, you can simply use an auto reference to the registers or structures. You can give this reference a short, easy to read name. As shown in the following example:

// For Adafruit Feather M0

constexpr auto &port = PORT->Group[0];
constexpr auto &portDir = port.DIR.reg;
constexpr auto &portOut = port.OUTTGL.reg;

const auto mask = 0b100000000000000000u;

void setup() {
  portDir |= mask;
}

void loop() {
  portOut = mask;
  delay(200);
}

The variables port, portDir and portOut create a simple reference to the registers and/or structures which can be easily used in the following code. Changing the port can be quickly done in the first line, without touching the rest of the code.

I use constexpr in front of these variables to make sure the compiler is using the original location of the reference and does not implement the variable as a pointer to these registers.

Inline Functions made Simple

Another interesting use of the auto keyword is to declare a variable for an labda function quickly. In some cases, you need a very small function which only makes sense in the context of another function. Many languages support inline functions for this situation.

In C++ you can use lambda functions in this situation, they are declared as shown in the following example:

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  auto flashLed = [](uint32_t duration) {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(duration);
    digitalWrite(LED_BUILTIN, LOW);
    delay(100);
  };
  flashLed(100);
  flashLed(200);
  flashLed(300);
}

The example above could be easily written as shown in the next example:

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void flashLed(uint32_t duration) {
  digitalWrite(LED_BUILTIN, HIGH);
  delay(duration);
  digitalWrite(LED_BUILTIN, LOW);
  delay(100);
}

void loop() {
  flashLed(100);
  flashLed(200);
  flashLed(300);
}

Inline functions get interesting if you access local function variables you otherwise had to pass as parameters. This is the case in the following abstract example:

void addStep(int step, int maximum, int &counterA, int &counterB, int &counterC) {
  counterA += step;
  counterB += step;
  counterC += step;
  if (counterA > maximum) {
    counterA = maximum;
  }
}

void increaser(int maximum) {
  int counterA = 0;
  int counterB = 2;
  int counterC = 10;
  addStep(6, maximum, counterA, counterB, counterC);
  addStep(20, maximum, counterA, counterB, counterC);
  addStep(12, maximum, counterA, counterB, counterC);
  addStep(20, maximum, counterA, counterB, counterC);
}

The example above can be written like this:

void increaser(int maximum) {
  int counterA = 0;
  int counterB = 2;
  int counterC = 10;
  auto addStep = [&](int step) {
    counterA += step;
    counterB += step;
    counterC += step;
    if (counterA > maximum) {
      counterA = maximum;
    }    
  };
  addStep(6);
  addStep(20);
  addStep(12);
  addStep(20);
}

The second implementation is shorter with less repetitition. It also removes the function addStep from the global namespace, which is only used by the increaser function.

Learn More

  • Real Time Counter and Integer Overflow
  • Guide to Modular Firmware
  • Class or Module for Singletons?
  • Event-based Firmware
  • It’s Time to Use #pragma once
  • How and Why to use Namespaces

Conclusion

The auto keyword can make your code shorter and reduces repetitive parts. If used with care, it improves the overall code quality and even simplifies refactoring. Give it a try!

If you have questions, miss some information or have any feedback, feel free to add a comment below.

 

 

 

 

Learn More

Real Time Counter and Integer Overflow

Real Time Counter and Integer Overflow

After writing the article about event-based firmware, I realised there are some misunderstandings about how real-time counters are working and should be used. Especially there is a misconception about an imagined problem if such a ...
Read More
C++ Templates for Embedded Code

C++ Templates for Embedded Code

Templates are a powerful feature of the C++ language but their syntax can be complex. This causes some developers to not use them, while others are concerned that templates might cause bloat to the compiled ...
Read More
Event-based Firmware (Part 1/2)

Event-based Firmware (Part 1/2)

You start with small, simple firmware. But with each added feature, the complexity grows and grows. Soon, you need a good design in order to maintain the firmware and ensure the code remains clean and ...
Read More
How to Deal with Badly Written Code

How to Deal with Badly Written Code

Sadly there is a ton of badly written code out in the wild. Hardware related code, seem to suffer more in this regards. I imagine, many developer in this segment are unwillingly to invest time ...
Read More
Event-Based Firmware (Part 2/2)

Event-Based Firmware (Part 2/2)

In the first part of this series, we explored the general concept of event-based firmware. To read that article, follow this link. The concepts we discussed were directly tailored to one specific firmware. Now, let’s ...
Read More
Bit Manipulation using Templates

Bit Manipulation using Templates

Did you read my article about C++ templates for embedded code? You learned how to use function templates. This post adds a practical example to this topic. Bit Manipulation You may be familiar with bit ...
Read More

2 thoughts on “Write Less Code using the “auto” Keyword”

  1. Zaar Hai says:
    2019-09-16 at 08:20

    Thanks for writing those articles! For someone who didn’t touch C++ for 10+ years and spent all that time with Python this info brings the sanity back!

    Reply
    1. Lucky Resistor says:
      2019-09-16 at 11:23

      Thank you! I am glad you liked the articles and have a look at all the nice new features in C++17 and C++20. 🙂

      Reply

Leave a Reply Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Stay Updated

Join me on Mastodon!

Top Posts & Pages

  • How and Why to use Namespaces
  • Storage Boxes System for 3D Print
  • Use Enum with More Class!
  • Circle Pattern Generator
  • Real Time Counter and Integer Overflow
  • Circle Pattern Generator
  • Logic Gates Puzzles
  • C++ Templates for Embedded Code
  • C++ Templates for Embedded Code (Part 2)
  • Logic Gates Puzzle 101

Latest Posts

  • The Importance of Wall Profiles in 3D Printing2023-02-12
  • The Hinges and its Secrets for Perfect PETG Print2023-02-07
  • Better Bridging with Slicer Guides2023-02-04
  • Stronger 3D Printed Parts with Vertical Perimeter Linking2023-02-02
  • Logic Gates Puzzle 1012023-02-02
  • Candlelight Emulation – Complexity with Layering2023-02-01
  • Three Ways to Integrate LED Light Into the Modular Lantern2023-01-29
  • The 3D Printed Modular Lantern2023-01-17

Categories

  • 3D Printing
  • Build
  • Common
  • Fail
  • Fun
  • Learn
  • Projects
  • Puzzle
  • Recommendations
  • Request for Comments
  • Review
  • Software
Copyright (c)2022 by Lucky Resistor. All rights reserved.