0

I made a class for an arduino program. Inside the class I would like to toss a compiler error if a wrong pin number is passed as an argument.

class AnalogOutput : public AnalogBlock
{
public:

    AnalogOutput( uint8_t _pin ) : pin( _pin )
    {        
        static_assert
        (       pin ==  3 
            ||  pin ==  5
            ||  pin ==  6
            ||  pin ==  9
            ||  pin == 10
            ||  pin == 11 , "INVALID PWM PIN USED" 
        ) ;
    }

    void run()
    {
        if( IN2 != prevIn )
        {   prevIn  = IN2 ;                // if incoming change, update PWM level

            analogWrite( pin, IN2) ;
        }
    }
  
private:
    const uint8_t pin ;
    uint8_t       prevIn ;
} ;

The constructor is only called with compile-time constants.

static ServoMotor M1 = ServoMotor( 3 ) ; // 3 is the pin number

Yet I get me this compiler error

error: non-constant condition for static assertion
         static_assert (
         ^~~~~~~~~~~~~
error: use of 'this' in a constant expression

I looked here but it did not make me wizer. It is the first time that I am trying to use static_assert().

First question: what I am trying to do, can that be done in the first place? Second question: providing that the previous answer is 'yes' how can it be done?

In responds to Erel's answer: I tried this:

template<uint8_t pin>
class AnalogOutput : public AnalogBlock
{
public:

    AnalogOutput( uint8_t _pin ) : pin( _pin )
    {        
        static_assert
        ( 
                pin ==  3 
            ||  pin ==  5
            ||  pin ==  6
            ||  pin ==  9
            ||  pin == 10
            ||  pin == 11 , "INVALID PWM PIN USED" 
        ) ;
    }

    void run()
    {
        if( IN2 != prevIn )
        {   prevIn  = IN2 ;                // if incoming change, update PWM level

            analogWrite( pin, IN2) ;
        }
    }
  
private:
    const uint8_t pin ;
    uint8_t       prevIn ;
} ;

I construct an object

static  AnalogInput a1 =  AnalogInput(0) ;

And this give me this error

error: invalid use of template-name 'AnalogOutput' without an argument list
 static AnalogOutput a1 = AnalogOutput(0) ;

I also get several notes:

note: class template argument deduction is only available with -std=c++1z or -std=gnu++1z

note: 'template<unsigned char pin> class AnalogOutput' declared here
 class AnalogOutput : public AnalogBlock
       ^~~~~~~~~~~~

I compile with avr-gcc

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
bask185
  • 377
  • 1
  • 3
  • 12
  • You could templatize only the constructor instead of the whole class. – Sebastian Sep 28 '22 at 07:58
  • You could use a class enum or another type as parameter instead, which ensures that the pin number is correct before calling the constructor. – Sebastian Sep 28 '22 at 08:02

3 Answers3

1

A way without using any templates, is to make a consteval function that only returns the constructed object if the condition is met. If the condition was false, it returns nothing and therefore the compiler will throw an error:

#include <cstdint>

struct ServoMotor {
    constexpr ServoMotor(uint8_t pin) {}
};

constexpr bool valid_pin(uint8_t pin) {
    return (pin == 3 || pin == 5 || pin == 6 ||
            pin == 9 || pin == 10 || pin == 11);
}
consteval auto make_motor(uint8_t pin) {
    if (valid_pin(pin)) {
        return ServoMotor(pin);
    }
}

int main() {
    auto m3 = make_motor(3);
    auto m4 = make_motor(4);
}

the line on m3 works as expected, but there is an error C7595 on the m4 line, since 4 is not a valid pin.


Try it out here.

Stack Danny
  • 7,754
  • 2
  • 26
  • 55
0

static_assert is for compile-time constants only, thus, because you try to run something in the context of object construction (hence, runtime), the compiler refuses to generate code.

An alternative solution would be to use a templated class like such:

template<uint8_t pin>
class AnalogOutput : public AnalogBlock
{
        AnalogOutput() : AnalogBlock(/*whatever goes in there*/){}

        static_assert
        (       pin ==  3 
            ||  pin ==  5
            ||  pin ==  6
            ||  pin ==  9
            ||  pin == 10
            ||  pin == 11 , "INVALID PWM PIN USED" 
        ) ;
    //blablabla
};

And instead of doing

AnalogOutput o = AnalogOutput(pin_number);

You do

AnalogOutput<pin_number> o {/*Arguments for constructor go here*/};

Though I remember having bad times with template in Arduino due to bugs in their compiler. It was a few years ago, but still, use at your own risks.

Erel
  • 512
  • 1
  • 5
  • 14
  • I cannot get this to compile. Does it matter that my class is a subclass? – bask185 Sep 27 '22 at 10:15
  • @bask185 I don't know, you did not post the error you got or a godbolt showing the code you tried. – Erel Sep 27 '22 at 15:39
  • Erel, I edited the question. – bask185 Sep 28 '22 at 06:24
  • @bask185 Okay, this is simple to fix. It's simply because you don't construct the object the same way as before. I edited my answer. I also advise you use [https://godbolt.org/](https://godbolt.org/) for such situations instead of editing the question. – Erel Sep 28 '22 at 08:39
0

Instead of taking an uint8_t, take an auto and pass an std::integral_constant<uint8_t, value>(), where value is the value you'd like to pass (e.g. 3). It can be checked compile-time and can be converted to the underlying integral type, i.e., uint8_t.

lorro
  • 10,687
  • 23
  • 36