Question about Templates and overloading

Hi all,

I've run into a strange problem - don't know if this is "how it is" or if I goofed somewhere.

Let's say I have a little library that accepts different data types AND ALSO a different number of parameters:

size_t Library::test (const char *str)
{
    // prints the string "str" until null encountered
}

template <class T> size_t Library::test (T value, uint8_t digits)
{
    // prints the numeric value of "value" with "digits" number of digits
}

Notice that the first piece of code accepts ONE parameter while the second one accepts TWO.

My problem is that if I try to print a numeric value (maybe a uint16_t), the code tries to use the first piece of code and since any number does not match a "const char *", the compile fails with "cannot convert (whatever int) to const char *".

Anyone know why this is?

Thanks!

Anyone know why this is?

Must be something with the code you didn't post.

Keep in mind that the compiler must be able to find the template implementations in the same translation unit as the code using it. In other words: it must be in the header file.

PieterP:
Keep in mind that the compiler must be able to find the template implementations in the same translation unit as the code using it. In other words: it must be in the header file.

Right, either in the same file or everything above the dashed line must go in a .h file and then #include(d).

class Library {
  public:
    static size_t test (const char *str)
    {
      Serial.println(str);
      return strlen(str);
    }

    template <class T> static size_t test (T value, int16_t digits)
    {
      Serial.println(value, digits);
      return digits;
    }
};

// --------------------------------------------------------------------------

void setup() {
  Serial.begin(115200);
  delay(2000);

  Library::test("Hello World");
  Library::test(3.14159, 3);
  Library::test(100, HEX);

}

void loop() {

}

PaulS:
Must be something with the code you didn't post.

Quite helpful... as usual.

gfvalvo:
Right, either in the same file or everything above the dashed line must go in a .h file and then #include(d).

class Library {

public:
   static size_t test (const char *str)
   {
     Serial.println(str);
     return strlen(str);
   }

template static size_t test (T value, int16_t digits)
   {
     Serial.println(value, digits);
     return digits;
   }
};

// --------------------------------------------------------------------------

void setup() {
 Serial.begin(115200);
 delay(2000);

Library::test("Hello World");
 Library::test(3.14159, 3);
 Library::test(100, HEX);

}

void loop() {

}

OK, let me see if I understand you. Here's CURRENTLY what I have (a part of it... but enough to know what I am doing):

/////////////////////// unsigned, print() ///////////////////////
size_t Print::print (uint8_t value, uint8_t base, uint8_t digits)
{
    return printInteger (value, base, digits);
}

size_t Print::print (uint16_t value, uint8_t base, uint8_t digits)
{
    return printInteger (value, base, digits);
}

size_t Print::print (uint32_t value, uint8_t base, uint8_t digits)
{
    return printInteger (value, base, digits);
}

size_t Print::print (uint64_t value, uint8_t base, uint8_t digits)
{
    return printInteger (value, base, digits);
}
//...........................
template <class T> size_t Print::printInteger (T value, uint8_t base, uint8_t digits)
{
    // code snipped out
}

...and the H file:

class Print {
    public:
        Print():write_error (0) {}
        int getWriteError (void) { return write_error; }
        void clearWriteError (void) { setWriteError(0); }
        size_t write (const char *str) {
            if (str == NULL) { return 0; }
            return write ((const uint8_t *) str, strlen (str));
        }
        size_t write (const char *buf, size_t siz) {
            return write((const uint8_t *) buf, siz);
        }
        virtual size_t write (uint8_t) = 0;
        virtual size_t write (const uint8_t *, size_t);
        size_t print (uint8_t, uint8_t=DEC, uint8_t=0);
        size_t print (uint16_t, uint8_t=DEC, uint8_t=0);
        size_t print (uint32_t, uint8_t=DEC, uint8_t=0);
        size_t print (uint64_t, uint8_t=DEC, uint8_t=0);
//......... snip ...........

You see? I have a generic function to print integers, but I need 4 separate functions to handle uint8_t, 16_t, 32_t and 64_t (actually there are more, but this is enough to see what I'm doing).

What I want to do is replace the 4 separate functions with one generic function... something like this:

// this would be in the .CPP file
template <class T> size_t print (T value, uint8_t base, uint8_t digits)
{
    return printInteger (value, base, digits);
}

My problem is that any calls to this function (which has 3 parameters: value, base and digits) ALSO tries to call a different function that only accepts ONE parameter, such as this:

size_t Print::print_P (const void *str)
{
    size_t n = 0;
    char c;
#if defined (pgm_read_byte_far)
    while ((c = pgm_read_byte_far (str + n))) {
#else
    while ((c = pgm_read_byte_near (str + n))) {
#endif
        print ((char) c);
        n++;
    }
    return n;
}

Even though I may try to print a uint16_t value, it also tries to call every other function (even those that take more or less than 3 parameters) which spews out a bunch of compile errors stating that "uint16_t cannot be converted to void *".

Since (as far as I understand it), overloading requires a function's parameter list types to match... and obviously "uint16_t, uint8_t, uint8_t" does not match "const void *" so why does it try to do so anyway??

Thanks!

Because the compiler only knows about the function with a single parameter. The others are template functions, and they are defined in a different translation unit, so the compiler can't possibly know whether they're applicable or not. Declaring the template functions in the header file is not enough, you have to define them as well.

A template function is not a "real" function, because the types are not defined. The compiler has to instantiate a version of the template function with concrete types being used in the actual code using it. It must have access to the implementation to instantiate it.

For instance:

template <class T>
T add(T a, T b) {
  return a + b;
}

If you were to instantiate this function with T = Print, for example, the compiler has to check the types, notice that there's no operator+ defined for Prints, and throw an error.
It cannot do that if the implementation is in a different translation unit.

Or look at it from a different perspective: Imagine a CPP (implementation) file containing only the template function above. There's nothing to compile, it doesn't make sense to compile anything, because there's an enormous amount of different types T that could be used. But the compiler doesn't know beforehand which types the template functions are going to be used with, because the user code is in a different translation unit.

Now, I would actually expect to see a linker error in that case, but I'm not sure why that's not what's happening.

PieterP:
Or look at it from a different perspective: Imagine a CPP (implementation) file containing only the template function above. There's nothing to compile, it doesn't make sense to compile anything, because there's an enormous amount of different types T that could be used. But the compiler doesn't know beforehand which types the template functions are going to be used with, because the user code is in a different translation unit.

Now, I would actually expect to see a linker error in that case, but I'm not sure why that's not what's happening.

OK... I'm starting to feel like an idiot... I'm not "getting it". Let me show you what I tried and maybe it will clear up what my problem is.

In the CPP file, I commented out all of the separate print functions and made one generic function:

template <class T> size_t Print::print (T value, uint8_t base, uint8_t digits)
{
    return printInteger (value, base, digits);
}

/////////////////////// unsigned, print() ///////////////////////
//size_t Print::print (uint8_t value, uint8_t base, uint8_t digits)
//{
//  return printInteger (value, base, digits);
//}
//
//size_t Print::print (uint16_t value, uint8_t base, uint8_t digits)
//{
//  return printInteger (value, base, digits);
//}
//------------ SNIP ------------------

...and in the H file, I commented out the declarations for the separate functions and added one for the new function:

        template <class T> size_t print (T, uint8_t=DEC, uint8_t=0); // NEW

//      size_t print (uint8_t, uint8_t = DEC, uint8_t = 0);
//      size_t print (uint16_t, uint8_t = DEC, uint8_t = 0);

Now, here's the compiler error I get:

```
*/usr/share/arduino-1.0.6/hardware/arduino/cores/arduino/Print.cpp: In instantiation of 'size_t Print::printInteger(T, uint8_t, uint8_t) [with T = const char; size_t = unsigned int; uint8_t = unsigned char]':

/usr/share/arduino-1.0.6/hardware/arduino/cores/arduino/Print.cpp:54:42:   required from 'size_t Print::print(T, uint8_t, uint8_t) [with T = const char*; size_t = unsigned int; uint8_t = unsigned char]'

/usr/share/arduino-1.0.6/hardware/arduino/cores/arduino/Print.cpp:309:29:   required from here

/usr/share/arduino-1.0.6/hardware/arduino/cores/arduino/Print.cpp:339:6: error: invalid conversion from 'int' to 'const char*' [-fpermissive]
 val = -1; // prepare for signed/unsigned test
     ^
/usr/share/arduino-1.0.6/hardware/arduino/cores/arduino/Print.cpp:343:12: error: wrong type argument to unary minus
   value = -value; // ... if actual value is negative invert it (decimal only)
           ^
/usr/share/arduino-1.0.6/hardware/arduino/cores/arduino/Print.cpp:361:13: error: invalid operands of types 'const char*' and 'uint8_t {aka unsigned char}' to binary 'operator/'
 while (val /= base) {
            ^
/usr/share/arduino-1.0.6/hardware/arduino/cores/arduino/Print.cpp:361:13: error:   in evaluation of 'operator/=(const char*, uint8_t {aka unsigned char})'

/usr/share/arduino-1.0.6/hardware/arduino/cores/arduino/Print.cpp:373:17: error: invalid operands of types 'const char*' and 'uint64_t {aka long long unsigned int}' to binary 'operator/'
  idx = ((value / intPower (base, digits)) % base); // get a digit**
```
** ^**

The function that the errors are complaining about is the "universal" printInteger(), but as you can see the complaint comes from the compiler trying to send char* values to printInteger... something that I am not doing! My test program, so far, ONLY tries to print a uint16_t value and an int16_t value... no "char *" stuff.

Lastly, here's "printInteger" in case it helps:

template <class T> size_t Print::printInteger (T value, uint8_t base, uint8_t digits)
{
    size_t n = 0;
    int8_t idx;
    int8_t pow;
    uint8_t pad;
    T val;
    val = -1; // prepare for signed/unsigned test

    if (val < 0) { // if unsigned it's never less than 0
        if ((value < 0) && (base == DEC)) { // less than 0 means it's signed so...
            value = -value; // ... if actual value is negative invert it (decimal only)
            n += print ((char) '-'); // minus sign
        }
    }

    // if base < 2 or base > 16 then default to decimal (prevent crash if base == 0)
    base = base < BIN ? DEC : base > HEX ? DEC : base;

    if (base == DEC) { // pad decimal with spaces...
        pad = ' '; // ascii space

    } else { // ...pad all else with zero
        pad = '0'; // ascii zero
    }

    pow = 1; // initialize power value
    val = value; // make a scrap copy of "value"

    while (val /= base) {
        pow++; // how many digits in "value" with a base of "base"
    }

    if (digits > 16) {
        digits = 16; // no more than 16 character places
    }

    // print at least req'd number of chars, or more.
    digits = pow < digits ? digits : pow;

    while (digits--) { // print each character
        idx = ((value / intPower (base, digits)) % base); // get a digit
        value -= (idx * intPower (base, digits)); // subtract from what's left

        if (idx > 9) {
            idx += 7; // print hex A-F
        }

        if (digits < pow) { // if it's part of the actual number
            n += print ((char) (idx + '0'));

        } else { // else it's a padding character
            n += print ((char) pad);
        }
    }

    return n;
}

...and the integer power function that printInteger uses:

uint64_t Print::intPower (uint8_t base, uint8_t exp)
{
    uint64_t result = 1;

    while (exp--) {
        result *= base;
    }

    return result;
}

I know I SHOULD be able to make this work, I just don't seem to get HOW to do it. :frowning:

Please include the code where you try to invoke the template (ie your .ino file). Having an MCVE makes it so much easier to reproduce your problem. That means COMPLETE files (I don't see any include guards in your .h file).

The point is that you cannot have a template function definition in a CPP file. Define it in your header file (or in a separate file that you include from within your header file).

Each CPP file is compiled separately. As explained above, you cannot compile template functions without knowing what versions will be used.