Search code examples
c++arm

ARM gcc uint32_t != unsigned int


I'm not sure what is going on here, hoping someone can enlighten me.

The short version

I have a templated function, for which I declare a limited family of specializations, including for uin32_t arguments. When I try to call this function with an "unsigned int" argument, the gcc linker says it can not find such a function. I checked, uint32_t and "unsigned int" are both 4 bytes long on my ARM Cortex. Why are they not interchangeable?

The long version

util.h

#include <cstdint>

template<uint8_t base=10, class T>
void func(T value, char * p_rtn, uint8_t rtn_len);

util.cpp

#include "util.h"

uint32_t useless_global;

template<uint8_t base, class T>
void func(T value, char * const p_rtn, const uint8_t rtn_len) {
    useless_global += sizeof(value);
}

template void func< 2>(uint32_t value, char * p_rtn, uint8_t rtn_len);
template void func<10>(uint32_t value, char * p_rtn, uint8_t rtn_len);
template void func<16>(uint32_t value, char * p_rtn, uint8_t rtn_len);

template void func< 2>(uint64_t value, char * p_rtn, uint8_t rtn_len);
template void func<10>(uint64_t value, char * p_rtn, uint8_t rtn_len);
template void func<16>(uint64_t value, char * p_rtn, uint8_t rtn_len);

// uncommenting this resolves the compiler error.    
//template void func<10>(unsigned int value, char * p_rtn, uint8_t rtn_len); 

test.cpp

#include "util.h"

int main() {
    char buff[128];
    func<10>(sizeof(unsigned int), buff, 128);
}

I compiled it as:

arm-none-eabi-c++.exe -Wall -std=c++20 -o main .\test.cpp .\util.cpp 

The arm-none-eabi-c++ tells me

C:\SiliconLabs\SimplicityStudio\v5_2\developer\toolchains\gnu_arm\12.2.rel1_2023.7\bin\arm-none-eabi-c++.exe -Wall -std=c++20 -o main .\test.cpp .\util.cpp

    test.cpp:(.text+0x1c): undefined reference to `void func<(unsigned char)10, unsigned int>(unsigned int, char*, unsigned char)'

If I compile this with Windows g++ there is no error.

Some one marked this as a duplicate of "Why can templates only be implemented in the header file?", but this is different.

  1. GCC for Windows does not error out.
  2. As @user4581301 mentions "util.cpp defines explicit instantiations that should cover that problem."

Solution

  • Fixed width integer alias types have implementation definitions. The only guarantee privided by standard is strict order on their relative sizes. The order in ascending size order is as follows: signed char, short, int, long and long long. Same order applies to the unsigned counterparts. Any consecutive 2 among the above types may actually have the same size, and it's upto the implementation to choose the proper type for fixed width integers. The list of built-in integer types are distinct - although any number of them might have same sizes. OTOH some fixed width alises might map to the same built-in type. If you need to have function overloads for distinct integral types, you need to define them for built-in types. Otherwise, if you just need a specific bit depth, then you should always use the proper fixed width alias. You might already know, but fixed width types include fast and least counterparts(eg. uint_fast32_t, or int_least16_t) for speed vs size customization; the actual built-in types might be same or different - depending on platform.