这是为成员函数的 C++ 模板部分特化构造类的好方法吗?

Posted

技术标签:

【中文标题】这是为成员函数的 C++ 模板部分特化构造类的好方法吗?【英文标题】:Is this a good way to construct the class for a C++ template partial specialisation of a member function? 【发布时间】:2021-07-17 02:52:56 【问题描述】:

我正在通过一系列练习自学 C++。 我喜欢研究如何仅使用语言来完成哈希表的想法 并且没有标准调用。我发现你做不到 “类成员函数部分模板专业化”等致力于选项 这将如何实现。 下面的代码是哈希表有用部分的精简版。 只留下足够显示所需内容的结构。

有没有更好的方法来构建类 - IE 是解决部分专业化的更好习语?

我尝试过全类专业化和结构部分模板专业化 但这似乎是迄今为止最好的。

我会对 cme​​ts 感兴趣,了解调用成员和函数的其他方式。 我想到了:

    函数中所有成员/函数的“使用”行。 一个“使用”行来创建可用作前缀的 typedef。 每次使用时都使用完整的演员表。
#include "stdio.h"

template <typename K, typename H>
class hash_table_common 
public:
    H& hash_type()                          // Standard CRTP helper function
    
        return static_cast<H&>(*this);
    
    int& operator[](K key)
    
        return hash_type().get_value(key);
    
    size_t hash(int key)
    
        return key % 10;
    
    int m_storage[10] 0 ;
;

template <typename K>
class hash_table : public hash_table_common<K, hash_table<K>> 
public:
    class hash_table_common<K, hash_table<K>>& hash_type()
    
        return static_cast<hash_table_common<K, hash_table<K>>&>(*this);
    
    int& get_value(K key)
    
        int hashable = 3; // value_to_int(); Real code will go here, for this demo it works for one value!
        int index1 = hash_type().hash(hashable);
        return hash_type().m_storage[index1];
    
;

template <>
class hash_table<const char*> : public hash_table_common<const char*, hash_table<const char*>> 
public:
    class hash_table_common<const char*, hash_table<const char*>>& hash_type()
    
        return static_cast<hash_table_common<const char*, hash_table<const char*>>&>(*this);
    
    int& get_value(const char* key)
    
        int hash_as_int = (int)key[0];
        int index = hash_type().hash(hash_as_int);
        return hash_type().m_storage[index];
    
;
#endif

int main() 
    class hash_table<const char*> a;
    class hash_table<float> b;
    a["word"] = 3;
    b[4.5f] = 14;
    printf("%d %d", a["word"], b[4.5f]);
    return 0;


【问题讨论】:

不相关的技术说明:What are the rules about using an underscore in a C++ identifier? int&amp; operator=(int value) return value; ; - 你会在这里返回一个悬空引用(你不应该把 ; 放在函数定义之后) 恕我直言,代码太多了。工作代码部分可以对codereview.stackexchange.com 提出问题,而非工作部分隐藏在#ifdef 后面,错误消息应该在问题中。 要求“最可接受的 c++ 样式..”可能是基于意见的。 std::hash 被“接受”,虽然我不知道它是否被“最接受”;)。我建议关注标题中的一般问题、关于哈希的一般问题或代码中的特定错误,但不要同时关注所有问题。通常,对重点问题的回答有助于看到更大的图景(无论如何+1 :) int hash_as_int = *((int*)&amp;key); 这是超级未定义的行为。如果 Key 只有 1 个字节怎么办? 【参考方案1】:

首先,让我们考虑您实现哈希表的示例。我对您的主要内容做了一些小修改:

int main()     
    my_hash_table<const char*> c;
    my_hash_table<float> d;
    c["word"] = 3;
    d[4.5f] = 14;
    std::cout << c["word"] << " " << d[4.5f] << "\n";

让我们把它作为我们想要通过的测试用例。

忽略实现的缺陷,只谈论设计,这就是“通过测试”所需的,并且对const char*float有专业化:

#include <map>
#include <iostream>
#include <type_traits>

template <typename T>
struct my_hash 
    size_t operator()(const T& t) return t;
;
template <typename key_t,typename hash = my_hash<key_t>,typename value_t = int>
struct my_hash_table 
    std::map<size_t,value_t> table;
    value_t& operator[](key_t key) return table[hash(key)]; 
;
template <>
struct my_hash<const char*> 
    size_t operator()(const char* t) return t[0]; 
;
template <>
struct my_hash<float> 
    size_t operator()(const float& t)  return t*100; 
;

不要误会我的意思,但您的代码看起来像是试图将所有内容都压缩到一个类中。并非与类相关的所有内容都必须在类的定义中。相反,类的定义越少越好。函子通常是相当轻量级的,它们有一个operator()(即它们可以被调用),通常就是这样。您可以轻松地为 my_hash 添加专业化。

此外,继承也有它的位置,但老实说,我不明白它在您的代码中的用途。最后但并非最不重要的一点是,您将散列函数与容器混合,但这些是单独的问题。不同的容器可能使用相同的散列函数,而您可能希望使用具有不同散列的相同数据结构。

“类成员函数部分模板特化”不是一回事。不过,这本身并不构成问题。这只是我们盒子里没有的工具。当您希望类模板的成员函数仅根据其中一个参数执行某些特殊操作时,您可以这样做。要么如上,要么C++17引入if constexpr

template <typename A> struct foo  void operator()()  ;
template <> struct foo<int>  void operator()() std::cout << "A is int\n";;

template <typename A,typename B>
struct bar 
    void do_something() 
        
        foo<A>();

        if constexpr (std::is_same_v<A,int>)
            std::cout << "A is int\n";
        
    ;
;

Live Example

【讨论】:

哇,我能说什么。从中可以学到很多东西。我会把它拿走,看看我是否可以更改我的代码以匹配。我尝试了我喜欢的结构方法,但我从来没有弄错语法。我错过了typename hash = my_hash&lt;key_t&gt;hash。我认为我可以以相同的方式调用非运算符函数。让我试试看,然后回复你。 忽略实现的缺陷,只谈论设计感谢您理解我所要求的关键部分。 我现在看到不会将多个函数放在一个结构中。所以每次调用都会是一个仿函数。这将开始看起来很混乱,有 4 或 5 个功能,因为它们都属于一个专业。尤其是为了得到正确的声明。 好的,所以我更好地理解它,我认为这并不是对原始问题的真正答案。也许使用哈希图太分散注意力了。主要目标是获得部分专业化,其中两个部分都可以使用另一部分的功能和成员。当然,您已经解决了代码,但我仍然不确定我们是否正在解决 我们的盒子中没有的工具 的最佳替代品。对于这种情况,我可能能够继续使用它来获得完整的哈希映射(我正在使用我的方法这样做,我现在将尝试你的方法)但我被主要问题所关注。 @StoneMonkeyMark 问题是您错过的功能没有确切的替代品(如果有 exact 替代品,那将不是替代品,而只是功能)。您必须考虑手头的问题并选择不同的解决方案。我承认,在写作时我担心我没有完全理解您要解决的实际问题【参考方案2】:

使用来自@largest 的结构方法,这是此用例的最佳习语的另一个候选解决方案。我想我仍然喜欢 CRTP 选项,因为它以对称的方式调用其他部分函数和成员。 hash_type()

在下面的代码中,hash_table_functionstable 必须有更好的名称 因为这些应该是不可见的重定向,我想说__ 但下划线警察会追上我(哈哈)。

#include <stdio.h>
#include <stdint.h>

template <typename key_t>
struct hash_table_struct;

template <typename key_t, typename hash_table_functions = hash_table_struct<key_t>>
class hash_table 
public:
    int& operator[](key_t key)
    
        return hash_table_functions.get_value(*this, key);
    ;
    size_t hash(uint32_t key)
    
        return key % 10;
    
    int m_storage[10];
;

template <typename key_t>
struct hash_table_struct 
    int& get_value(hash_table<key_t>& table, key_t key)
    
        uint32_t hashable = (uint32_t)key;
        size_t index = table.hash(hashable);
        return table.m_storage[index];
    
;

template <>
struct hash_table_struct<const char*> 
    int& get_value(hash_table<const char*>& table, const char* key)
    
        uint32_t hashable = (uint32_t)key[0];
        size_t index = table.hash(hashable);
        return table.m_storage[index];
    
;

#endif

int main() 
    class hash_table<const char*> a;
    class hash_table<float> b;
    a["word"] = 3;
    b[4.5f] = 14;
    printf("%d %d", a["word"], b[4.5f]);
    return 0;


【讨论】:

当您正在寻找成语时,您可能对policy based design 感兴趣。 “现代”前缀在 C++ 环境中有点陈旧,但即使在 20 年后,这些书也值得一读 实际上看起来维基页面上的示例比我的答案更接近您正在寻找的内容,尽管我还需要阅读更多内容才能详细了解它。【参考方案3】:

仅适用于 C++17 的 constexpr 版本。模拟 is_same_v 以保持原始规则。这很有用,因为它教会了我这一切是如何运作的。 这样可以使代码更加简洁易读。

// C++ 17 required
#include <stdio.h>
#include <stdint.h>

template<class type1, class type2>
struct is_same

    static constexpr bool _value = false;
    constexpr operator bool() const noexcept  return _value; 
;

template<class type1>
struct is_same<type1, type1>

    static constexpr bool _value = true;
    constexpr operator bool() const noexcept  return _value; 
;

template<class type1, class type2>
inline constexpr bool is_same_v = is_same<type1, type2>::_value;

template <typename key_t>
class hash_table 
public:
    int& operator[](key_t key)
    
        return get_value(key);
    ;
    size_t hash(int key)
    
        return key % 10;
    
    int m_storage[10] 0 ;
    int& get_value(key_t key)
    
        uint32_t hashable;
        if constexpr ( is_same_v<key_t, const char*> )
        
            hashable = (uint32_t)key[0];
        
        else
        
            hashable = (uint32_t)key;
        
        size_t index = hash(hashable);
        return m_storage[index];
    
;

int main()

    class hash_table<const char*> a;
    class hash_table<float> b;
    a["word"] = 3;
    b[4.5f] = 14;
    printf("%d %d", a["word"], b[4.5f]);
    return 0;

【讨论】:

编译器应该能够将所有内容合并为一个 constexpr,因为该函数也是一个 constexpr。 输出看起来没问题godbolt.org/z/caYn9hbxb 这是如何工作的? template&lt;class T, class U&gt; struct is_same : std::false_type ; 这个结构如何计算为布尔值? 请考虑将问题作为问题发布。 std::false_type 有一个到 bool 的转换运算符,就像你的 is_same 有一个,见这里:en.cppreference.com/w/cpp/types/integral_constant

以上是关于这是为成员函数的 C++ 模板部分特化构造类的好方法吗?的主要内容,如果未能解决你的问题,请参考以下文章

C++ 函数模板部分特化?

C++模板,静态函数特化

C++中模板类成员函数的特化

使用 STL 容器的部分 C++ 模板特化

联合作为模板化基类的部分特化

C++,2参数类模板的部分特化:无法将函数定义与现有声明匹配