near-empty sub-subclass rejects initializer values, while subclass works

I have a dhDevice class, with subclass dhDeviceOut.

The nearly empty sketch attached compiles when allocating an array of dhDeviceOut.

dhDeviceOut sets ON and OFF as LOW and HIGH respectively, while my relay board uses the opposite, so the sub-subclass is declared simply to override those values.

Switching the active declaration to the relay subclass causes error “relay:4:47: error: conversion from ‘int’ to non-scalar type ‘dhRelay’ requested”

My library files are header dhTest.h

#ifndef dhMegaLib
#define dhMegaLib

#include "Arduino.h"

#include "SoftwareSerial.h"
#include "AltSoftSerial.h"


// some types to use

enum srlTyps {
  NONE, HARDWARE, SOFTSERIAL, ALTSOFTSERIAL
};

//possible status for the bt device
enum btStatStates {
  BTOFF, BTON, BTPOWERED, BTPENDING, BTAT
};

//an empty class that will get optimized out if used.

//this lets us keep our debugging code in the sketch

class nullSerial {
    void begin(void);
    void println(void);
    void print(void);
    int available();
    void read(void);
    void write(void);
    void end(void);
};




class dhDevice
{
public:
  dhDevice();
  //~dhDevice();
  // should these be declared virtual or somesuch?

  //turn on device with optional turnoff
  void dhDevice::turnOn(long runTime=0);
  
  //turn off device
  void dhDevice::turnOff();
  
  //report status
  // (shouldn't the return type be variable?  void doesn't make sense!)
  void dhDevice::reportStatus();
  
  void dhDevice::update();
  
  
};

class dhDeviceOut : dhDevice{
  public:
    dhDeviceOut(byte thePin);
    bool status();
    void turnOn(unsigned long turnOffTime=-1);
    void turnOff();
    unsigned long offTime();
    void update();
    byte pinReport();
  private:
    bool myStatus=false;
    unsigned long myOffTime;
    byte myOutPin;

    //default is to turn on with high, and off with low
    const byte ON=HIGH;
    const byte OFF=LOW;


};


class dhLed: dhDeviceOut {
};


class dhRelay : dhDeviceOut {
  private:
    //these boards use low to turn on
    const byte ON=LOW;
    const byte OFF=HIGH;
};


// a nibble function

byte eeNibble(unsigned int nibblePos) ;


#endif

and the cpp file:

// dhMegaLib.  dochawk's library of useful things for atMega projects.
//
// Copyright MMXIX


// a root device class from which to derive others

#include "dhTest.h"
#include "EEPROM.h"

//the base device
dhDevice::dhDevice(){};

// should these be declared virtual or somesuch?

//turn on device with optional turnoff
void dhDevice::turnOn(long runTime=0);

//turn off device 
void dhDevice::turnOff();

//report status
// (shouldn't the return type be variable?  void doesn't make sense!)
void dhDevice::reportStatus();

void dhDevice::update();

//a subclass for output devices

dhDeviceOut::dhDeviceOut(byte thePin) : myOutPin(thePin){ } 

void dhDeviceOut::turnOn(unsigned long turnOffTime){
              digitalWrite(myOutPin,ON);
              if (turnOffTime!=0){
                      myOffTime=turnOffTime;
              } else {
                      //set to leave on indfinitely
                      myOffTime=0;
              }
      }

void dhDeviceOut::turnOff(){
              digitalWrite(myOutPin,OFF);
              myStatus=false;
	      myOffTime=0;
      }

unsigned long dhDeviceOut::offTime(){
              return myOffTime;
      }

void dhDeviceOut::update(){
              //update the current lights
              //for the moment, this assumes millis() will never overflow
              if( (myStatus==true) && (millis()>=myOffTime)) {
                      turnOff();
              }
        }

byte dhDeviceOut::pinReport() {return myOutPin;}


// a nibble function for the EEPROM

byte eeNibble(unsigned int nibblePos) {
  if (nibblePos % 2 == 0) {
    //even, high order nibble
    return EEPROM.read(nibblePos / 2) / 16;
  } else {
    //odd, low nibble
    return EEPROM.read(nibblePos / 2) % 16;

  }
}

I’m assuming that I"m missing something simple here.

relay.ino (279 Bytes)

Adding a constructor to pass the pin number to the parent constructor helps:

class dhRelay : dhDeviceOut
{
  public:
      dhRelay(int pin) : dhDeviceOut(pin) {};

Then constructing each before storing them in the array:

dhRelay sprinklers[] = {dhRelay(4), dhRelay(5), dhRelay(6), dhRelay(7), dhRelay(A2), dhRelay(A3), dhRelay(A4)};
//dhDeviceOut sprinklers[] = {4, 5, 6, 7, A2, A3, A4};

Maybe that's what I'm not understanding: I have (or at least think I do :) ) a constructor for the parent class:

dhDeviceOut::dhDeviceOut(byte thePin) : myOutPin(thePin){ }

Shouldn't this be inherited?

Or in the alternative, if that's not done correctly, shouldn't it fail to compile when switch which line is commented out?

Without the constructor for dhRelay I get this error:

ketch_feb21a:162:46: error: no matching function for call to 'dhRelay::dhRelay(int)'
 dhRelay sprinklers[] = {dhRelay(4), dhRelay(5), dhRelay(6), dhRelay(7), dhRelay(A2), dhRelay(A3), dhRelay(A4)};
                                              ^

With the constructor, the errors go away. I don't know why why the constructor doesn't seem to inherit.

This is what I got after merging the three files and working out the errors and warnings:

#include "SoftwareSerial.h"

// some types to use

enum srlTyps
{
  NONE, HARDWARE, SOFTSERIAL, ALTSOFTSERIAL
};

//possible status for the bt device
enum btStatStates
{
  BTOFF, BTON, BTPOWERED, BTPENDING, BTAT
};

//an empty class that will get optimized out if used.

//this lets us keep our debugging code in the sketch

class nullSerial
{
    void begin(void);
    void println(void);
    void print(void);
    int available();
    void read(void);
    void write(void);
    void end(void);
};

class dhDevice
{
  public:
    dhDevice();
    //~dhDevice();

    //turn on device with optional turnoff
    virtual void turnOn(unsigned long runTime) = 0;  // PURE VIRTUAL

    //turn off device
    virtual void turnOff() = 0;  // PURE VIRTUAL

    // 'update' device
    virtual void update() = 0;  // PURE VIRTUAL
};

class dhDeviceOut : dhDevice
{
  public:
    dhDeviceOut(byte thePin);
    bool status();
    void turnOn(unsigned long turnOffTime = 0);
    void turnOff();
    unsigned long offTime();
    void update();
    byte pinReport();
  private:
    bool myStatus = false;
    unsigned long myOffTime;
    const byte myOutPin;

    //default is to turn on with high, and off with low
    const byte ON = HIGH;
    const byte OFF = LOW;
};

class dhLed: dhDeviceOut
{
};


class dhRelay : dhDeviceOut
{
  public:
    dhRelay(byte pin) : dhDeviceOut(pin) {};

  private:
    //these boards use low to turn on
    const byte ON = LOW;
    const byte OFF = HIGH;
};

// a nibble function
byte eeNibble(unsigned int nibblePos) ;

// dhMegaLib.  dochawk's library of useful things for atMega projects.
//
// Copyright MMXIX


// a root device class from which to derive others

#include "EEPROM.h"

//the base device
dhDevice::dhDevice() {};

//a subclass for output devices

dhDeviceOut::dhDeviceOut(byte thePin) : myOutPin(thePin) { }

void dhDeviceOut::turnOn(unsigned long turnOffTime)
{
  digitalWrite(myOutPin, ON);
  if (turnOffTime != 0)
  {
    myOffTime = turnOffTime;
  }
  else
  {
    //set to leave on indfinitely
    myOffTime = 0;
  }
}

void dhDeviceOut::turnOff()
{
  digitalWrite(myOutPin, OFF);
  myStatus = false;
  myOffTime = 0;
}

unsigned long dhDeviceOut::offTime()
{
  return myOffTime;
}

void dhDeviceOut::update()
{
  //update the current lights
  //for the moment, this assumes millis() will never overflow
  if ( (myStatus == true) && (millis() >= myOffTime))
  {
    turnOff();
  }
}

byte dhDeviceOut::pinReport()
{
  return myOutPin;
}


// a nibble function for the EEPROM

byte eeNibble(unsigned int nibblePos)
{
  if (nibblePos % 2 == 0)
  {
    //even, high order nibble
    return EEPROM.read(nibblePos / 2) / 16;
  }
  else
  {
    //odd, low nibble
    return EEPROM.read(nibblePos / 2) % 16;

  }
}

dhRelay sprinklers[] = {dhRelay(4), dhRelay(5), dhRelay(6), dhRelay(7), dhRelay(A2), dhRelay(A3), dhRelay(A4)};
//dhDeviceOut sprinklers[] = {4, 5, 6, 7, A2, A3, A4};

void setup()
{
  // put your setup code here, to run once:
}

void loop()
{
  // put your main code here, to run repeatedly:
}

dochawk: Shouldn't this be inherited?

No, the constructors need to be explicitly defined for all classes, except for default and copy constructors.