I'm working on a class's member function. The class from its previous version looked like this:
#include "cstr.h" // contains all of the needed function templates, and these work appropriately
// to_string<T>(T val) - signed and unsigned types non hex mode
// to_hstring<T>(T val, char hex_mode) - unsigned types hex mode
// to_string<T>(T val, uint8_t decimal_places) - floating point types
class Foo {
public:
Print(const char* str) {
// implementation here
}
Print(char c) { PutChar(c); }
// Unsigned Integer Types
Print(uint64_t val) { Print(to_string<uint64_t>(val); }
Print(uint32_t val) { Print(to_string<uint32_t>(val); }
Print(uint16_t val) { Print(to_string<uint16_t>(val); }
Print(uint8_t val) { Print(to_string<uint8_t>(val); }
// Signed Integer Types
Print(int64_t val) { Print(to_string<int64_t>(val); }
Print(int32_t val) { Print(to_string<int32_t>(val); }
Print(int16_t val) { Print(to_string<int16_t>(val); }
Print(int8_t val) { Print(to_string<int8_t>(val); }
// Unsigned Integer Types - HexFormat
Print(uint64_t val, char hex_mode) { Print(to_hstring<uint64_t>(val); }
Print(uint32_t val, char hex_mode) { Print(to_hstring<uint32_t>(val); }
Print(uint16_t val, char hex_mode) { Print(to_hstring<uint16_t>(val); }
Print(uint8_t val, char hex_mode) { Print(to_hstring<uint8_t>(val); }
// Floating Point Types
Print(double val, uint8_t decimal_places) { Print(to_string<double>(val, decimal_places); }
Print(float val, uint8_t decimal_places) { Print(to_string<float>(val, decimal_places); }
};
I currently was using them like this:
void Bar {
Foo foo(/*needed params for construction */);
foo.Print("This is a test string\n"); // Prints: This is a test string, and moves cursor down to a newline
foo.Print((uint64_t)12345);
foo.Print( '\n' ); // moves cursor down to a newline
foo.Print((double)1234.56789, 3); // Prints 1234.567
foo.Print( '\n' );
foo.Print((uint32_t)598732, 'h' ); // Converts 598732 to hex and Prints its value
foo.Print( '\n' );
foo.Print((float)23.4587f, 2); // Prints 23.45f
};
As you can see from the above... this would be a good time to refactor this code using function templates...
I know that you can not use partial template specialization
with function templates
.
I would like to be able to turn these functions into templates... I'm assuming the needed declarations would be:
template<typename T>
void Print(T val );
template<typename T>
void Print(T val, char hex_mode);
template<typename T>
void Print(T val, uint8_t decimal_places);
However, when I try to convert these functions into the templates, I'm having no luck in getting them to either compile or link properly... I've tried to many various things to list all of them here... I've tried defining them outside of the class body, inlining them, I've tried defining them in it's Foo.cpp
file, etc...
I would like for these to resolve through auto type deduction...
There are special cases for different types...
If T = const char*
then this is the actually implementation that prints the string to the screen. If T = char
it calls PutChar
to print a single char or a newline to the screen. These I would assume resolve to using...
template<typename T>
void Print(T val);
If T = Signed or Unsigned Integer Type
. They should invoke Print(to_string<T>(val));
and can not become ambiguous with T=char
or T=const char*
. Also, resolving to..
template<typename T>
void Print(T val);
T
can not be double
or float
in the above context... see below.
If T = unsigned
AND the overload version with char hex_mode
is present, then it should resolve to...
template<typename T>
void Print(T val, char hex_mode) {
if (hex_mode == 'h')
Print(to_hstring<T>(val);
else
Print(to_string<T>(val);
}
If T = double
or T = float
it needs to resolve to this overloaded version:
template<typename T>
void Print(T val, unint8_t decimal_places) { Print(to_string<T>(val, decimal_places); }
I have gotten as far as being able to compile it, but now I'm getting linker issues with undefined references... Here is my class in it's current state.
#pragma once
#include "cstr.h"
class Foo {
public:
Foo(/*params*/) :
/*default initialize internal members*/
{}
template<typename T>
void Print(const char* str);
template<typename T>
void Print(char chr);
template<typename T>
void Print(T val);
template<typename T>
void Print(T val, const char hex_mode);
template<typename T>
void Print(T val, uint8_t decimal_places);
//void Print(uint64_t val, char hex_mode = '0');
//void Print(uint32_t val, char hex_mode = '0');
//void Print(uint16_t val, char hex_mode = '0');
//void Print(uint8_t val, char hex_mode = '0');
//void Print(int64_t val);
//void Print(int32_t val);
//void Print(int16_t val);
//void Print(int8_t val);
//void Print(double val, uint8_t decimal_places = default_decimal_places);
//void Print(float val, uint8_t decimal_places = default_decimal_places);
private:
void PutChar(char c);
// Private Members
};
template<typename T>
inline void BasicRenderer::Print(const char* str) {
char* chr = (char*)str;
while(*chr != 0) {
if (*chr == '\n') {
PutChar('\n');
chr++;
} else {
PutChar(*chr);
cursor_position_.x += 8;
if (cursor_position_.x + 8 > framebuffer_->Width) {
cursor_position_.x = 0;
cursor_position_.y += 16;
}
chr++;
}
}
}
template<typename T>
inline void BasicRenderer::Print(char c) { PutChar(c); }
template<typename T>
inline void BasicRenderer::Print(T val, const char hex_mode) {
if (hex_mode == 'h')
Print(to_hstring<T>(val));
else
Print(to_string<T>(val));
}
template<typename T>
inline void BasicRenderer::Print(T val, const uint8_t decimal_places) {
Print(to_string<T>(val, decimal_places));
}
Here is GCC's linker errors... Foo
above is just a Pseudo name for the actual class name which can be seen blow in the linker error output.
skilz420@skilz-PC:~/skilzOS/kernel$ make kernel
!==== LINKING
ld -T kernel.ld -static -Bsymbolic -nostdlib -o bin/kernel.elf lib/kernel.o lib/cstr.o lib/BasicRenderer.o
ld: lib/kernel.o: in function `_start':
kernel.cpp:(.text+0x3a): undefined reference to `void BasicRenderer::Print<char const*>(char const*)'
ld: kernel.cpp:(.text+0x4b): undefined reference to `void BasicRenderer::Print<unsigned long>(unsigned long)'
ld: kernel.cpp:(.text+0x5c): undefined reference to `void BasicRenderer::Print<char>(char)'
ld: kernel.cpp:(.text+0x6f): undefined reference to `void BasicRenderer::Print<long>(long)'
ld: kernel.cpp:(.text+0x80): undefined reference to `void BasicRenderer::Print<char>(char)'
ld: kernel.cpp:(.text+0x98): undefined reference to `void BasicRenderer::Print<double>(double)'
ld: kernel.cpp:(.text+0xa9): undefined reference to `void BasicRenderer::Print<char>(char)'
ld: kernel.cpp:(.text+0xd0): undefined reference to `void BasicRenderer::Print<char>(char)'
ld: kernel.cpp:(.text+0xe4): undefined reference to `void BasicRenderer::Print<float>(float)'
ld: lib/kernel.o: in function `void BasicRenderer::Print<unsigned int>(unsigned int, char)':
kernel.cpp:(.text._ZN13BasicRenderer5PrintIjEEvT_c[_ZN13BasicRenderer5PrintIjEEvT_c]+0x36): undefined reference to `void BasicRenderer::Print<char const*>(char const*)'
ld: kernel.cpp:(.text._ZN13BasicRenderer5PrintIjEEvT_c[_ZN13BasicRenderer5PrintIjEEvT_c]+0x54): undefined reference to `void BasicRenderer::Print<char const*>(char const*)'
make: *** [Makefile:33: link] Error 1
skilz420@skilz-PC:~/skilzOS/kernel$
What do I need to do to fix this to resolve these linker errors, and for the class's Print()
function to handle each type appropriately? I'm not sure of what I'm doing wrong here...
-Note-
STL
other than stdint.h
for the defined integer types. Please don't
suggest using STL
since this is part of a Kernel
Project!
std::cin
, std::cout
, nor any of the C
printf()
functions or any of its variants...obj.Print<type>(...);
I'd prefer to have them automatically resolve by calling them as such:obj.Print(...);
cstr.h
implementation, and or other functions not shown, don't hesitate to ask...kernel
project here are some resources:
You have to implement your own type_traits
templates. That's not very hard.
Below is an example using standard type_traits.
is_floating_point
, is_unsigned
, and other is_integral
specializations.namespace custom {
template<typename T, T v>
struct integral_constant {
using type = T;
static constexpr T value = v;
};
using false_type = integral_constant<bool, false>;
using true_type = integral_constant<bool, true>;
template <bool, typename T = void> struct enable_if {};
template <typename T> struct enable_if<true, T> { using type = T;};
template<bool v, typename T> using enable_if_t = typename enable_if<v, T>::type;
template<typename T> struct remove_const { using type = T;};
template<typename T> struct remove_const<const T> { using type = T; };
template<typename T> using remove_const_t = typename remove_const<T>::type;
template<typename T> struct remove_volatile { using type = T; };
template<typename T> struct remove_volatile<volatile T> { using type = T; };
template<typename T> using remove_volatile_t = typename remove_volatile<T>::type;
template<typename T> struct remove_cv { using type = remove_volatile_t<remove_const_t<T>>; };
template<typename T> using remove_cv_t = typename remove_cv<T>::type;
template<typename T> struct is_integral_impl : public false_type {};
template<> struct is_integral_impl<bool> : public true_type {};
template<> struct is_integral_impl<char> : public true_type {};
template<> struct is_integral_impl<int> : public true_type {};
// others...
template<typename T> struct is_integral : public is_integral_impl<remove_cv_t<T>> {};
}
class Foo {
public:
Foo(/*params*/) :
/*default initialize internal members*/
{}
// No templates. Just overload
void Print(const char* str);
// floating-point blocker
template<typename T, custom::enable_if_t<custom::is_integral<T>::value, int> = 0>
void Print(T val);
// specialization
template<>
void Print<char>(char chr);
// SFINAE because char and uint8_t is can be same
template<typename T, custom::enable_if_t<custom::is_unsigned<T>::value, int> = 0>
void Print(T val, const char hex_mode);
template<typename T, custom::enable_if_t<custom::is_floating_point<T>::value, int> = 0>
void Print(T val, uint8_t decimal_places);
};