将非模板 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 specializationfunction 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=charT=const char* 混淆。另外,解决..

template<typename T>
void Print(T val);

T 在上述上下文中不能是 doublefloat... 见下文。

如果 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 = doubleT = 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() 函数正确处理每种类型?我不确定我在这里做错了什么...

-注-

您必须实施自己的 type_traits 模板。这并不难。
下面是一个使用标准 type_traits.

的例子
  • 编辑:添加自定义 type_traits 示例。您必须实施其他 is_floating_pointis_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);
};