Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How does this default template struct assignment work?

I have been digging into some embedded C++ firmware used by DaveJone’s (eevblog) uSupply project

https://gitlab.com/eevblog/usupply-firmware.

There is common pattern of code that I just can’t quite wrap my head around what is happening.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

For example:
In the file "RegistersRCC.hpp" there is a template struct:

template <std::size_t A>
struct CR : public General::u32_reg<A>
{
    using base_t = General::u32_reg<A>;
    using base_t::base_t;

    //PLL register bits
    auto PLLRDY () { return base_t::template Actual<RCC_CR_PLLRDY>(); }
    auto PLLON  () { return base_t::template Actual<RCC_CR_PLLON>(); }

    //PLL Management functions
    void EnablePLL() noexcept
    {
        if ( not PLLON().Get() )
        {
            PLLON() = true;
            while ( not PLLRDY().Get() );
        }
    }
    void DisablePLL() noexcept
    {
        if ( PLLON().Get() )
        {
            PLLON() = false;
            while ( PLLRDY().Get() );
        }
    }

    //Enable clock security
    auto CSSON  () { return base_t::template Actual<RCC_CR_CSSON>(); }

    //High speed external oscillator bits
    auto HSEBYP () { return base_t::template Actual<RCC_CR_HSEBYP>(); }
    auto HSERDY () { return base_t::template Actual<RCC_CR_HSERDY>(); }
    auto HSEON  () { return base_t::template Actual<RCC_CR_HSEON>(); }

    //HSE Management functions
    void EnableHSE()
    {
        if ( not HSEON().Get() )
        {
            HSEON() = true;                 //Enable the clock
            while( not HSERDY().Get() );    //Wait for it to stable
        }
    }
    void DisableHSE()
    {
        if ( HSEON().Get() )
        {
            HSEON() = false;            //Disable the clock
            while( HSERDY().Get() );    //Wait for it to disable
        }
    }
    void ConnectHSE()
    {
        HSEBYP() = false;   //Connect it to system
    }
    void BypassHSE()
    {
        HSEBYP() = true;    //Disconnect it to system
    }

    //High speed internal oscillator bits
    auto HSICAL () { return base_t::template Actual<RCC_CR_HSICAL>(); }
    auto HSITRIM() { return base_t::template Actual<RCC_CR_HSITRIM>(); }
    auto HSIRDY () { return base_t::template Actual<RCC_CR_HSIRDY>(); }
    auto HSION  () { return base_t::template Actual<RCC_CR_HSION>(); }

    //HSI Management functions, No calibration provided
    // these chips are factory calibrated
    void EnableHSI()
    {
        if (not HSION().Get())
        {
            HSION() = true;
            while (!HSIRDY());
        }
    }
    void DisableHSI()
    {
        if ( HSION().Get() )
        {
            HSION() = false;
            while (HSIRDY());
        }
    }
};

This struct exists in the namespace:

namespace Peripherals::RCCGeneral
{

}

Within the same namespace/header file there is this "Default"

CR() -> CR<RCC_BASE + offsetof(RCC_TypeDef, CR)>;

I think this is where my gap in understanding lies. What is happening here? Specifically with the lvalue and arrow operator, and why this is located within the header.

Within the files that utilize the RCCRegisters you see usages like:

CR{}.DisablePLL();

>Solution :

This is called class template argument deduction(CTAD) which allows writing deduction guides to the compiler about how to deduce the template arguments from constructor calls.

It is a handy C++17 addition that saves on typing:

std::vector x{1.,2.,3.} //deduces std::vector<double>

C++14 and older requires to explicitly write std::vector<double> which gets tedious and too verbose for some more complex examples.

In this case, the guide

CR() -> CR<RCC_BASE + offsetof(RCC_TypeDef, CR)>;

specifies that the default constructor should deduce A template parameter to RCC_BASE + offsetof(RCC_TypeDef, CR).

The same could have been achieved by simply using a default template argument:

template <std::size_t A = default_value>
struct CR : public General::u32_reg<A>{ ... };

But here comes the catch, offsetof(RCC_TypeDef, CR) is not valid here because at this line, CR doesn’t exist yet.

So my assumption is this a fix around this limitation to allow making the default value depend on the class definition itself, quite clever I think.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading