I'm not sure what is going on here, hoping someone can enlighten me.
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?
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.
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.