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

How and Why to use Namespaces

How and Why to use Namespaces

Namespaces are a feature of C++ which address the problem of name conflicts. There is a "global" namespace, where everything lives which was declared without namespace. Especially the Arduino environment declares a huge amount of ...
Read More
Units of Measurements for Safe C++ Variables

Units of Measurements for Safe C++ Variables

In your program code, you often deal with unitless values. You can add the units of measurements in the variable or function name, or by adding a comment, but there is still the risk you ...
Read More
Consistent Error Handling

Consistent Error Handling

Error handling in firmware is a difficult task. If you decide to ignore errors, the best you can expect is a deadlock, but you could also damage the hardware. When reviewing existing code, I often ...
Read 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
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
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

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 ReplyCancel reply

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

Stay Updated / Donate

Join me on Mastodon!

☕️ Support my Work

Top Posts & Pages

  • Simple Box with Lid for 3D-Print
  • Storage Boxes System for 3D Print
  • Real Time Counter and Integer Overflow
  • Logic Gates Puzzles
  • Circle Pattern Generator
  • Welcome to My Shop
  • Circle Pattern Generator
  • Units of Measurements for Safe C++ Variables
  • Use Enum with More Class!
  • Learn C++

Latest Posts

  • New Version 2 of the Pattern Generator2023-07-25
  • 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

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.