0

I maintain an Arduino library which uses the following code (simplified) to print results received by infrared.

unsigned long long decodedData; // for 8 and 16 bit cores it is unsigned  long decodedData;
Print MySerial;
MySerial.print(decodedData, 16);

Most of the 32 bit arduino cores provide the function size_t Print::print(unsigned long long n, int base) and compile without errors.

But there are 32 bit cores, which do not provide size_t Print::print(unsigned long long n, int base), they only provide size_t Print::print(unsigned long n, int base) and there I get the expected compile time error call of overloaded 'print(decodedData, int)' is ambiguous.

I tried to understand Check if a class has a member function of a given signature but still have no clue.

I want to use

    MySerial.print((uint32_t)(decodedData >> 32), 16);
    MySerial.print((uint32_t)decodedData & 0xFFFFFFFF, 16);

in case the function size_t Print::print(unsigned long long n, int base) is not provided.

I tried

template<typename T>
struct has_uint64_print {

    template<typename U, size_t (U::*)(unsigned long long, int)> struct SFINAE {
    };
    template<typename U> static char test(SFINAE<U, &U::print>*);

    template<typename U>
    static int test(...);

    static const bool has64BitPrint = sizeof(test<T>(nullptr)) == sizeof(char);
};

and this works (Thanks to Remy Lebeau) :-).

But this check does not work, since it still references the long long print function (update: and using if constexpr () -which is not available for all cores- does not help).

                    if(has_uint64_print<Print>::has64BitPrint){
                        MySerial.print(decodedData, 16);
                    } else {
                        MySerial.print((uint32_t)(decodedData >> 32), 16);
                        MySerial.print((uint32_t)decodedData & 0xFFFFFFFF, 16);
                    }

Is there any chance to avoid this compile error?

BTW. I do not want to substitute all occurences of the 64 bit print with the 2 32 bit prints, only for one seldom used and lazy implemented 32 bit core, since all mainsteam cores work well with the 64 bit print.

Armin J.
  • 435
  • 1
  • 4
  • 10
  • 1
    `uint64_t` and `unsigned long long` are not generally the same thing. Which one do you really want to test for? – user17732522 Nov 30 '22 at 02:51
  • 1
    "*this [if expression] of course does not work, since it still references the long long print function*" - use [`if constexpr (...)`](https://en.cppreference.com/w/cpp/language/if#Constexpr_if) instead of a plain `if (...)`, then it should work. – Remy Lebeau Nov 30 '22 at 02:52
  • 1
    Also you are giving the test the wrong return type according to your description of `print` at the beginning. (`void` vs `size_t`) – user17732522 Nov 30 '22 at 02:53
  • When dealing with libraries for multiple different cores its best to make code for each general purpose situation. you can do this by defining if its AVR or not. You could come up with a define for the 8 16 and 32 bits and do the same. good example is this library: https://github.com/adafruit/Adafruit-Motor-Shield-library/blob/master/AFMotor.h – Monogeon Nov 30 '22 at 15:03
  • @user17732522 uint64_t and unsigned long long are the same for all Arduino 32 bit cores, and especially for the problematic one. Thanks for pointing out, that I used the wrong return type void instead of size_t! I corrected it in the question now. – Armin J. Dec 01 '22 at 09:41
  • @Monogeon Thanks, but I know. The library currently supports around 20 different boards and 16 cores. What define would you propose to exclude the problematic core from seeduino https://files.seeedstudio.com/arduino/package_seeeduino_boards_index.json with Board Seeduino XIAO and arm-none-eabi-gcc\7-2017q4/bin/arm-none-eabi-g++ ??? – Armin J. Dec 01 '22 at 09:49
  • @RemyLebeau if constexpr is not available for c++11 and does not help, even if enabled with `-std=c++1z` :-(. Thanks anyway. – Armin J. Dec 01 '22 at 10:09
  • Sorry, but to avoid any further confusion I changed the example to always use `unsigned long long` instead of `uint64_t`. – Armin J. Dec 01 '22 at 14:46

2 Answers2

1

With C++11 you can do something like this:

#include <iostream>
#include <iomanip>
#include <type_traits>

// First implementation of printer
class Impl1 {
public:
    static void print(uint64_t value, int base) {
        std::cout << "64-bit print: " << std::setbase(base) << value << "\n";
    }
};


// Second implementation of printer
class Impl2 {
public:
    static void print(uint32_t value, int base) {
        std::cout << "32-bit print: " << std::setbase(base) << value << "\n";
    }
};


// Template to automatically select proper version
template<typename Impl, typename = void>
class Print;

template<typename Impl>
class Print<Impl, typename std::enable_if<std::is_same<decltype(Impl::print), void(uint64_t, int)>::value>::type>
{
public:
    static void print(uint64_t value, int base)
    {
        Impl::print(value, base);
    }
};

template<typename Impl>
class Print<Impl, typename std::enable_if<std::is_same<decltype(Impl::print), void(uint32_t, int)>::value>::type>
{
public:
    static void print(uint64_t value, int base)
    {
        Impl::print(static_cast<uint32_t>(value >> 32), base);
        Impl::print(static_cast<uint32_t>(value), base);
    }
};

int main()
{
    Print<Impl1>::print(0x100000001, 16);
    Print<Impl2>::print(0x100000001, 16);
}

Second version, with function overloads and standard types:

#include <iomanip>
#include <iostream>
#include <type_traits>

// Set to 1 to see effect of using 64 bit version
#define HAS_64 0

class Print {
    public:
    size_t print(unsigned int value, int base) {
        return base + 1;  // dummy
    };
    size_t print(long value, int base) {
        return base + 2;  // dummy
    };
    size_t print(unsigned long value, int base) {
        return base + 3;  // dummy
    };
#if HAS_64    
    size_t print(unsigned long long value, int base) {
        return base + 4;  // dummy
    };
#endif
};
Print MySerial;

// If you have C++17 you can just use std::void_t, or use this for all versions
#if __cpp_lib_void_t >= 201411L
template<typename T>
using void_t = std::void_t<T>;
#else
template<typename... Ts> struct make_void { typedef void type; };
template<typename... Ts> using void_t = typename make_void<Ts...>::type;
#endif

// Detecting if we have 'print(unsigned long long value, int base)' overload
template<typename T, typename = void>
struct has_ull_print : std::false_type { };
template<typename T>
struct has_ull_print<T, void_t<decltype(std::declval<T>().print(0ull, 0))>> : std::true_type { };

// Can be either class or namesapce
namespace PrintXYZ {
    template <typename Impl, typename std::enable_if<!has_ull_print<Impl>::value, bool>::type = true>
    size_t print(Impl &p, unsigned long long value, int base) {
        p.print(static_cast<uint32_t>(value >> 32), base);
        p.print(static_cast<uint32_t>(value), base);
        return 0; // Not sure about return value here.
    }

    template <typename Impl, typename std::enable_if<has_ull_print<Impl>::value, bool>::type = true>
    size_t print(Impl &p, unsigned long long value, int base) {
        return p.print(value, base);
    }
};

int main() {
    PrintXYZ::print(MySerial, 0x100000001, 16);
}
sklott
  • 2,634
  • 6
  • 17
  • This results in lot of errors like: `E:\WORKSPACE_SLOEBER\libraries\IRremote\src/IRReceive.hpp:1049:7: error: 'Print' is not a template class Print;` and `E:\WORKSPACE_SLOEBER\libraries\IRremote\src/IRReceive.hpp:1052:7: error: 'Print' is not a class template class Print::value>::type> {` – Armin J. Dec 01 '22 at 14:35
  • Are you sure you try it with C++11 flag? May be some headers are missing too, but it work on godbolt: https://godbolt.org/z/Tnxbb9TY5 – sklott Dec 01 '22 at 14:47
  • The godbolt example for your proposal (still giving errors) is [here](https://godbolt.org/z/zr3fbTx9W) Keep in mind that I have to use the existent `Print` class and cannot substitute it by another construct. – Armin J. Dec 01 '22 at 20:42
  • Added version with plain types and overloaded function detection. – sklott Dec 01 '22 at 21:31
  • Thank you very much. [Here](https://github.com/Arduino-IRremote/Arduino-IRremote/blob/4d6301a007801248e530584cf17f67803db37b8e/src/IRProtocol.hpp#L77) is your code used. You are a genius! – Armin J. Dec 02 '22 at 00:34
0

Problem is poor documentation. I've found this, but it doesn't provide definition of Serial::print overloads.

I suspect it looks like this:

class Serial
{
public:
    ...
    void print(uint8_t x, int base);
    void print(uint16_t x, int base);
    void print(uint32_t x, int base);
    void print(uint64_t x, int base);
};

So when you use this with unsigned long long you are expecting it matches overload with uint64_t, but on some platforms uint64_t is not a unsigned long long, but unsigned long. This mismatch leads to situation that overload resolution can't decide which fallback use and reports error: call of overloaded 'print(decodedData, int)' is ambiguous.

So instead complicate your life with "Avoid calling of function", just fix your code by explicitly use type uint64_t. Note this type definition explains your intent, so you should use it definitely.

uint64_t decodedData;
Print MySerial;
MySerial.print(decodedData, 16);

If I'm wrong pleas provide better link to this API documentation. Also include full error log when build fails, so we can see what overloads are available.

Marek R
  • 32,568
  • 6
  • 55
  • 140
  • To avoid any further confusion I changed the example to always use `unsigned long long` instead of `uint64_t`. – Armin J. Dec 01 '22 at 14:48
  • A full error log can be found here https://godbolt.org/z/zr3fbTx9W – Armin J. Dec 01 '22 at 20:45
  • Thanks, will improve my question, when only have more time (it will be long). I will explain what is actual root cause and how I think it should be fixed. – Marek R Dec 02 '22 at 13:02