Making things do stuff – Part 9

As a final instalment in this series on hardware manipulation I thought I’d revisit read-only and write-only register types.

Using tag dispatch is not the only way to solve the read- or write-only Register problem.  For completeness let’s explore two other alternatives – SFINAE and constexpr if.

For these examples I’m going to use a simplified version of our Register class.  I’m ignoring the bit proxy class and using a reduced API.  Once understood, the techniques below can be applied to these elements in the same way we applied tag dispatch.

As before, if you’ve jumped into this article I’d highly recommend going back and reading the previous articles to get an idea of the context of the problem.

SFINAE

Substitution Failure Is Not An Error (SFINAE) is a characteristic of template compilation.  When performing template deduction on a template function, if the substitution creates an invalid function declaration the compiler will remove that declaration from the set of viable candidates.  It does so silently, hence the acronym.  Programmers can exploit this mechanism to deliberately generate invalid function signatures for functions they don’t want used.

There are three common ways of generating an invalid function declaration:

  • An invalid template parameter (the one we’ll use this time)
  • An invalid function parameter
  • An invalid return type

In the case of our Register class we want to disable write functions for read-only types, read functions for write-only types; and only enable read-write functions for read-write types.

The standard idiom for doing this is to use the std::enable_if trait class.

We’ll keep the characteristic classes from the tag dispatch example and make use of the type trait std::is_base_of<>.  std::is_base_of<> will have a value of true if the first type is a base class of the second type.  For example (credit to cppreference.com for this example):

#include <iostream>
#include <type_traits>

using std::cout;
using std::endl;
using std::boolalpha;
using std::is_base_of;

class A { };

class B : public A { };

class C { };

int main()
{
  cout << boolalpha;
  cout << "A is base of B: " << is_base_of<A, B>::value << endl;
  cout << "B is base of A: " << is_base_of<B, A>::value << endl;
  cout << "C is base of B: " << is_base_of<C, B>::value << endl;
  cout << "C is same as C: " << is_base_of<C, C>::value << endl;
}

 

Note the last example (std::is_base_of<C, C>) will return true, even though a class can never be its own base.

Here’s our modified Register class.  (Note – I’ve omitted the Register_traits classes for brevity.  Have a look here to see what we’re trying to achieve with this code)

template <std::size_t sz, typename Register_Ty = read_write>
class Register
{
public:
  // Register_traits class omitted for brevity
  //
  using reg_type = typename Register_traits<sz>::internal_type;

  explicit Register(std::uint32_t address) :
    raw_ptr { reinterpret_cast<reg_type*>(address) }
  {
  }

  // Enable for objects that support write-only
  //
  template 
  <typename T = Register_Ty,
   typename 
   std::enable_if<is_base_of<write_only, T>::value, int>::type = 0>
  void operator=(reg_type bit_mask)
  {
    *raw_ptr = bit_mask;
  }

  // Enable for objects that support read-only
  //
  template 
  <typename T = Register_Ty,
   typename 
   std::enable_if<is_base_of<read_only, T>::value, int>::type = 0>
  operator reg_type() const
  {
    return *raw_ptr;
  }

  // Enable for objects that support read-write
  //
  template 
  <typename T = Register_Ty,
   typename 
   std::enable_if<is_base_of<read_write, T>::value, int>::type = 0>
  void operator|=( reg_type bit_mask)
  {
    *raw_ptr |= bit_mask;
  }

  template
  <typename T = Register_Ty,
   typename 
   std::enable_if<is_base_of<read_write, T>::value, int>::type = 0>
  void operator&=(reg_type bit_mask)
  {
    *raw_ptr &= bit_mask;
  }

private:
  volatile reg_type* raw_ptr;
};

 

If you’re not used to the SFINAE idiom this is pretty obnoxious code.  Let’s do a quick decomposition of our template functions to see what’s going on.

SFINAE only works on template deduction so our member functions must be template functions.  The first template parameter (T) is the function’s template parameter.  Normally, this would be deduced from the function call.  However, in this case we aren’t actually using T as a function parameter, so its type can’t be deduced.  Therefore, we are defaulting T to the Register_Ty type of the Register class.

std::enable_if requires two template parameters: A Boolean value and a type.

  • If the Boolean template parameter is true std::enable_if defines an alias, type, that is set to the second template parameter.
  • If the Boolean value is false, no alias is defined.

Thus, the following (pseudo) code

typename std::enable_if<true, int>::type = 0

 

Would collapse to

typename int = 0

 

This declares an – unnamed – template parameter (which is fine, we never refer to it again) of type int, with the default value of zero.  This therefore forms a valid function declaration; in other words, the function would be enabled.

The code

typename std::enable_if<false, int>::type = 0

 

Would collapse to

typename = 0

since the alias, type, is never declared.  Thus it gives an invalid function declaration; and the function is disabled.

As we saw earlier, std::is_base_of<read_only, T> returns a Boolean value, depending on whether T (which, remember, is always the same as Register_Ty) is a derived class of (or actually is) read_only.

We could make this code a little more palatable by noticing that the std::enable_if construct yields a type.  We can simplify type declarations with a using alias.

template <typename T>
using is_read_only = 
typename std::enable_if<is_base_of<read_only, T>::value, bool>::type;


template <typename T>
using is_write_only = 
typename std::enable_if<is_base_of<write_only, T>::value, bool>::type;

template <typename T>
using is_read_write =     
typename std::enable_if<is_base_of<read_write, T>::value, bool>::type;

 

Notice, we need to make our aliases templates so we can use them with template code.  Making this alias substitution our code becomes

template <std::size_t sz, typename Register_Ty = read_write>
class Register {
public:
  using reg_type = typename Register_traits<sz>::internal_type;

  explicit Register(std::uint32_t address) :
    raw_ptr { reinterpret_cast<reg_type*>(address) }
  {
  }

  template <typename T = Register_Ty, typename = is_write_only<T>>
  void operator=(reg_type bit_mask)
  {
    *raw_ptr = bit_mask;
  }

  template <typename T = Register_Ty, typename = is_read_only<T>>
  operator reg_type() const
  {
    return *raw_ptr;
  }

  template <typename T = Register_Ty, typename = is_read_write<T>>
  void operator|=(reg_type bit_mask)
  {
    *raw_ptr |= bit_mask;
  }

  template <typename T = Register_Ty, typename = is_read_write<T>>
  void operator&=(reg_type bit_mask)
  {
    *raw_ptr &= bit_mask;
  }

private:
  volatile reg_type* raw_ptr;
};

 

Our client code works as expected.

int main()
{
  Register<32, read_only>  ro_reg { 0x40020100 };
  Register<32, write_only> wo_reg { 0x40020104 };
  Register<32, read_write> rw_reg { 0x40020108 };


  std::uint32_t val  { };
  std::uint32_t data { 0x01 };

  val = ro_reg;       // OK
  ro_reg = data;      // FAIL
  ro_reg |= data;     // FAIL


  val = wo_reg;       // FAIL
  wo_reg = data;      // OK
  wo_reg |= data;     // FAIL


  val = rw_reg;       // OK
  rw_reg = data;      // OK
  rw_reg |= data;     // OK
}

 

Error messages are, by template standards, not too cryptic.  For example:

error: no viable overloaded '='

  ro_reg = data;   // FAIL
  ~~~~~~ ^ ~~~~

note: candidate function (the implicit copy assignment operator)
not viable: no known conversion from 'std::uint32_t'
(aka 'unsigned int') to 'const Register<32, read_only>'
for 1st argument

class Register
      ^
note: candidate function (the implicit move assignment operator)
not viable: no known conversion from 'std::uint32_t'
(aka 'unsigned int') to 'Register<32, read_only>'
for 1st argument

class Register
      ^
note: candidate template ignored: disabled by 'enable_if'
[with T = read_only]

  ENABLE_FOR(write_only)
  ^
note: expanded from macro 'ENABLE_FOR'
  typename std::enable_if<is_base_of<trait, T>::value,
  bool>::type = true>

 

Notice that, because the operator= overload has been disabled (by SFINAE) the compiler is attempting to use the built-in assignment operator (which fails because there is no way to convert a Register<>::reg_type into a Register<> – the constructor is marked explicit)

Constexpr if

Constexpr if is a new feature in C++17.  It provides compile-time conditionals.  Given a compile-time Boolean expression a constexpr if statement can be used to enable or disable blocks of code.

As an example, constexpr if can be used as a pre-processor replacement for conditional compilation – #if/#elif/#else/#endif.

Below, we have a function UART_enable() that may have different implementations (or even partially-different implementations) for different target systems.  We use an enum class to define the set of systems, and a global constexpr object that defines the current target.

If the constexpr if statement is true the code block is enabled; if false, the code block is discarded (that is, not compiled)

enum class uController { STM32F103, STM32F407 };

constexpr uController target { uController::STM32F407 };


void UART_enable()
{
  if constexpr(target == uController::STM32F103)
  {
    // STM32F103 implementation...
  }

  if constexpr(target == uController::STM32F407)
  {
    // STM32F407 implementation...
  }

  // Common code...
}


int main()
{
  UART_enable();  // Implementation for STM32F407
 ...
}

 

For our Register class we don’t want to enable code but disable it, based on the type of register we have.  Once again, we can exploit the std::is_base_of<> trait.

Here’s a first-pass at our class.  I’m only showing one method for clarity; the others all follow the same pattern.

template <std::size_t sz, typename Register_Ty = read_write>
class Register
{
public:
  using reg_type = typename Register_traits<sz>::internal_type;

  explicit Register(std::uint32_t address);


  void operator=(reg_type bit_mask)
  {
    if constexpr(!is_base_of<read_only, Register_Ty>())
        {
          // Compile-time error for read-only objects...
        }

    *raw_ptr = bit_mask;
  }


  operator reg_type() const;
  void operator|=(reg_type bit_mask);
  void operator&=(reg_type bit_mask);

private:
 volatile reg_type* raw_ptr;
};

 

We want to stop compilation if operator= is called on a read-only object.  The obvious way to do that is with static_assert

void operator=(reg_type bit_mask)
{
  if constexpr(!std::is_base_of<read_only, Register_Ty>())
  {
    static_assert(false, ”Writing to read-only object”);
  }

  *raw_ptr = bit_mask;
}

 

This looks good – it will give us a nice, human-understandable error message at compile-time if we attempt to write to a read-only Register instance.

Sadly, it won’t compile.

A static_assert() expression can be placed anywhere in code (not just inside a block).  Our operator= is a member function of a template class.  The static_assert(), however, is completely independent of the template parameter, therefore the compiler is free to evaluate the static_assert before even considering the template code.

We could try (in desperation) the following

void operator=(reg_type bit_mask)
{
  if constexpr(!std::is_base_of<read_only, Register_Ty>())
  {
    static_assert(std::false_type(), ”Writing to read-only object”);
  }

  *raw_ptr = bit_mask;
}

 

std::false_type is, again, an invariant not based on our template parameter; so it fails.

The solution is to ‘trick’ the compiler into instantiating the template before it evaluates the constexpr if statement.  To do this we build a type-dependent variant of std::false_type:

template<typename T> struct dependent_false : std::false_type { };

 

It doesn’t matter what the template parameter is, we’re never using it.

This new ‘false type’ can be put into our static_assert():

void operator=(reg_type bit_mask)
{
  if constexpr(!std::is_base_of<read_only, Register_Ty>())
  {
    static_assert(dependent_false<Register_Ty>(),
    ”Writing to read-only object”);
  }

  *raw_ptr = bit_mask;
}

 

And now our code compiles.

We could, if we desire, sprinkle some ‘macro sugar’ onto our code; for example

#define ASSERT_IF_NOT(trait, error_string)                     \
if constexpr(!is_base_of<trait, Register_Ty>())                \
{                                                              \
  static_assert(dependent_false<Register_Ty>(), error_string); \
}

 

Our (simplified) Register class becomes

template <std::size_t sz, typename Register_Ty = read_write>
class Register
{
public:
  using reg_type = typename Register_traits<sz>::internal_type;

  explicit Register(std::uint32_t address) :
    raw_ptr { reinterpret_cast<reg_type*>(address) }
  {
  }


  void operator=(reg_type bit_mask)
  {
    ASSERT_IF_NOT(write_only, "Writing to read-only object");

    *raw_ptr = bit_mask;
  }


  operator reg_type() const
  {
    ASSERT_IF_NOT(read_only, "Reading from write-only object");

    return *raw_ptr;
  }


  void operator|=(reg_type bit_mask)
  {
    ASSERT_IF_NOT(read_write, "Object must be read-write");

    *raw_ptr |= bit_mask;
  }


  void operator&=(reg_type bit_mask)
  {
    ASSERT_IF_NOT(read_write, "Object must be read-write");

    *raw_ptr &= bit_mask;
  }

private:
  volatile reg_type* raw_ptr;
};

 

Error messages in client code are now explicit

int main()
{
  Register<32, read_only>  ro_reg { 0x40020100 };
  Register<32, write_only> wo_reg { 0x40020104 };
  Register<32, read_write> rw_reg { 0x40020108 };

  std::uint32_t val  { };
  std::uint32_t data { 0x01 };

  val = ro_reg;       // OK
  ro_reg = data;      // FAIL
}

 

error: static_assert failed "Writing to read-only object"

  ASSERT_IF_NOT(write_only, "Writing to read-only object");
  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

note: expanded from macro 'ASSERT_IF_NOT'
  static_assert(dependent_false<Register_Ty>(), error_string); \
  ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

note: in instantiation of member function
'Register<32, read_only>::operator=' requested here

ro_reg = data;   // FAIL

 

Performance considerations

In this chapter we’ve focussed on code structure and error output, rather than run-time performance.

Both the SFINAE and constexpr if variants shown are compile-time checks, therefore they have no impact on run-time performance.  In fact, their performance will be identical to the tag dispatch variant shown earlier – which is (more or less) identical to ‘raw’ pointer manipulation.

 

Summary

There is a fear that more complex C++ constructs – like templates – should be avoided in embedded code for fear of significant memory and performance issues.

Tag dispatch and SFINAE are tried-and-tested techniques for enabling/disabling specific functions according to their particular characteristics.  Although not necessarily the most intuitive mechanisms they are worth incorporating into your programmer’s ‘toolbox’.  And now, with C++17, we have another weapon to add to our arsenal – constexpr-if.

Used carefully and judiciously, though, these constructs can produce very flexible abstractions at very low (almost zero) additional cost.

 

If you’d like to build up, or expand your C++ or design skills have a look at the following training courses.

C++11-501 – Modern C++ for Embedded Developers

C++11-502 – Real-Time Modern C++

AC++11-401 – Transitioning to C++11/C++14

C++-501 – C++ for Embedded Developers (C++03)

C++-502 – Real-Time C++ (C++03)

AC++-501 – Advanced C++ for Embedded Systems(C++-03)

DP-403 – Design patterns in C++

OO-504 – Real-Time Software Design with UML

Glennan Carnie
Dislike (0)
Website | + posts

Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.

He specialises in C++, UML, software modelling, Systems Engineering and process development.

About Glennan Carnie

Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry. He specialises in C++, UML, software modelling, Systems Engineering and process development.
This entry was posted in C/C++ Programming, Design Issues and tagged , , , , , , , , , , . Bookmark the permalink.

1 Response to Making things do stuff – Part 9

  1. I'd be interested to see how it would work out to use template helper classes instead of helper macros here, along the lines of https://www.fluentcpp.com/2017/06/02/write-template-metaprogramming-expressively/

    Like (0)
    Dislike (0)

Leave a Reply