将非模板 class 的成员函数转换为函数模板
Converting a non template class's member functions into function templates
我正在研究 class 的成员函数。之前版本的 class 看起来像这样:
#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); }
};
我目前是这样使用它们的:
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
};
正如您从上面看到的...现在是使用函数模板重构此代码的好时机...
我知道您不能将 partial template specialization
与 function templates
一起使用。
我希望能够将这些函数转换为模板...我假设所需的声明是:
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);
但是,当我尝试将这些函数转换为模板时,我没有运气让它们编译或 link 正确...我已经尝试了很多不同的东西来列出所有这些都在这里...我尝试在 class 主体之外定义它们,内联它们,我尝试在它的 Foo.cpp
文件中定义它们,等等...
我希望通过自动类型推导来解决这些问题...
不同类型有特殊情况...
If T = const char*
那么这是将字符串打印到屏幕的实际实现。如果 T = char
它调用 PutChar
将单个字符或换行符打印到屏幕上。这些我假设决心使用...
template<typename T>
void Print(T val);
如果T = Signed or Unsigned Integer Type
。他们应该调用 Print(to_string<T>(val));
并且不能与 T=char
或 T=const char*
混淆。另外,解决..
template<typename T>
void Print(T val);
T
在上述上下文中不能是 double
或 float
... 见下文。
如果 T = unsigned
并且存在带有 char hex_mode
的重载版本,那么它应该解析为...
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);
}
如果 T = double
或 T = float
它需要解析到这个重载版本:
template<typename T>
void Print(T val, unint8_t decimal_places) { Print(to_string<T>(val, decimal_places); }
我已经能够编译它了,但是现在我遇到了 linker 未定义引用的问题...这是我的 class 当前状态。
#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));
}
这是 GCC 的 linker 错误...上面的 Foo
只是实际 class 名称的伪名称,可以在 link 中看到错误输出。
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$
我需要做什么来解决这个问题以解决这些 linker 错误,并使 class 的 Print()
函数正确处理每种类型?我不确定我在这里做错了什么...
-注-
- 除了
stdint.h
之外,我没有对定义的整数类型使用任何 STL
。请不要
建议使用 STL
,因为这是 Kernel
项目的一部分!
- 我没有使用
std::cin
、std::cout
,也没有使用任何 C
printf()
函数或其任何变体...
- 我也不想在其他来源调用这些函数时必须这样做:
obj.Print<type>(...);
我希望通过这样调用它们来自动解析它们:
obj.Print(...);
- 如果您出于某种原因需要查看
cstr.h
实现和/或其他未显示的功能,请随时询问...
- 如果您需要有关此
kernel
项目的更多信息,这里有一些资源:
您必须实施自己的 type_traits
模板。这并不难。
下面是一个使用标准 type_traits.
的例子
- 编辑:添加自定义 type_traits 示例。您必须实施其他
is_floating_point
、is_unsigned
和其他 is_integral
专业化。
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);
};
我正在研究 class 的成员函数。之前版本的 class 看起来像这样:
#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); }
};
我目前是这样使用它们的:
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
};
正如您从上面看到的...现在是使用函数模板重构此代码的好时机...
我知道您不能将 partial template specialization
与 function templates
一起使用。
我希望能够将这些函数转换为模板...我假设所需的声明是:
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);
但是,当我尝试将这些函数转换为模板时,我没有运气让它们编译或 link 正确...我已经尝试了很多不同的东西来列出所有这些都在这里...我尝试在 class 主体之外定义它们,内联它们,我尝试在它的 Foo.cpp
文件中定义它们,等等...
我希望通过自动类型推导来解决这些问题...
不同类型有特殊情况...
If T = const char*
那么这是将字符串打印到屏幕的实际实现。如果 T = char
它调用 PutChar
将单个字符或换行符打印到屏幕上。这些我假设决心使用...
template<typename T>
void Print(T val);
如果T = Signed or Unsigned Integer Type
。他们应该调用 Print(to_string<T>(val));
并且不能与 T=char
或 T=const char*
混淆。另外,解决..
template<typename T>
void Print(T val);
T
在上述上下文中不能是 double
或 float
... 见下文。
如果 T = unsigned
并且存在带有 char hex_mode
的重载版本,那么它应该解析为...
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);
}
如果 T = double
或 T = float
它需要解析到这个重载版本:
template<typename T>
void Print(T val, unint8_t decimal_places) { Print(to_string<T>(val, decimal_places); }
我已经能够编译它了,但是现在我遇到了 linker 未定义引用的问题...这是我的 class 当前状态。
#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));
}
这是 GCC 的 linker 错误...上面的 Foo
只是实际 class 名称的伪名称,可以在 link 中看到错误输出。
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$
我需要做什么来解决这个问题以解决这些 linker 错误,并使 class 的 Print()
函数正确处理每种类型?我不确定我在这里做错了什么...
-注-
- 除了
stdint.h
之外,我没有对定义的整数类型使用任何STL
。请不要 建议使用STL
,因为这是Kernel
项目的一部分!- 我没有使用
std::cin
、std::cout
,也没有使用任何C
printf()
函数或其任何变体...
- 我没有使用
- 我也不想在其他来源调用这些函数时必须这样做:
obj.Print<type>(...);
我希望通过这样调用它们来自动解析它们:obj.Print(...);
- 如果您出于某种原因需要查看
cstr.h
实现和/或其他未显示的功能,请随时询问... - 如果您需要有关此
kernel
项目的更多信息,这里有一些资源:
您必须实施自己的 type_traits
模板。这并不难。
下面是一个使用标准 type_traits.
- 编辑:添加自定义 type_traits 示例。您必须实施其他
is_floating_point
、is_unsigned
和其他is_integral
专业化。
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);
};