实施 class 模板专业化以提供一种或多种方法

Enforce class template specializations to provide one or more methods

我正在使用“特征”模式,其中我有一个基本案例,表示为 class 模板

template <class>
struct DoCache {
  constexpr static bool value = false;
};

并且我希望用户专门针对他们的类型:

template <>
struct DoCache<MyType> {
  constexpr static bool value = true;

  static void write2Cache(MyType const&) { /* implementation */ }
  static optional<MyType> readFromCache(string name) { /* implementation */ }
};

典型的用途是检索并将其用作:

// Define a variable template
template <class T>
constexpr bool do_cache_v = DoCache<T>::value;

// Use the above trait in compile time branching:
if constexpr (do_cache_v<T>)
{
  write2Cache(arg);
}

这段代码有两个问题:

  1. 用户在专门化时只是间接强制提供一个“值”成员,更不用说使其成为正确的值(即true)。间接地,我的意思是他们会得到一堆编译错误,只有他们事先知道答案才能解决这些错误。
  2. 没有办法“要求”他们创建两个需要的方法,即 write2CachereadFromCache,更不用说拥有 (const) 正确的类型了。

在一些代码库中,我已经看到通过定义生成器宏来解决上述注意事项,例如:

#define CACHABLE(Type, Writer, Reader) ...

如能回答以上任何问题,我们将不胜感激

C++17:奇怪的重复模板模式

这似乎是 CRTP 的合适用例:

template<typename T> 
struct DoCache {
    void write2Cache() {
        static_cast<T*>(this)->write2Cache();
    }
    // ...
};

template<typename T>
void write2Cache(DoCache<T>& t) {
    t.write2Cache();
}

struct MyType : DoCache<MyType>
{
    void write2Cache() { /* ... */ }
};

int main() {
    MyType mt{};
    write2Cache(mt);
}

您要求客户实现自己的类型 in-terms-of(静态多态性)contract/facade 库类型,而不是要求客户专门化他们自己的类型。


C++20:概念

有了概念,您可以完全跳过多态性:

template<typename T>
concept DoCachable = requires(T t) {
    t.write2Cache();
};

template<DoCachable T>
void write2Cache(T& t) {
    t.write2Cache();
}

struct MyType {
    void write2Cache() { /* ... */ }
};

struct MyBadType {};

int main() {
    MyType mt{};
    write2Cache(mt);

    MyBadType mbt{};
    write2Cache(mbt);  // error: ...
      // because 'MyBadType' does not satisfy 'DoCachable'
      // because 't.write2Cache()' would be invalid: no member named 'write2Cache' in 'MyBadType'
}

然而,再次将要求放在客户端类型的定义站点上(与事后可以完成的专业化相反)。


Trait-based 有条件分派到 write2Cache()?

But how is the trait do_cache_v exposed this way?

C++17 方法

  • 由于 CRTP-based 方法通过继承提供了“is-a”关系,您可以简单地为“is-a DoCache<T> 实现一个特征":

    #include <type_traits>
    
    template<typename>
    struct is_do_cacheable             : std::false_type {};
    
    template<typename T>
    struct is_do_cacheable<DoCache<T>> : std::true_type {};
    
    template<typename T>
    constexpr bool is_do_cacheable_v{is_do_cacheable<T>::value};
    
    // ... elsewhere
    if constexpr(is_do_cacheable_v<T>) {
        write2Cache(t);        
    }
    

C++20 方法

  • 有了概念,概念本身就可以作为trait:

    if constexpr(DoCachable<T>) {
        write2Cache(t);        
    }
    

您可以使用一个概念来完整性检查专业化。在这里你只需要提供正确的,通过 name & type,方法因此 ::value 成员在 DoCache 可以被弃用:

template <class T>
concept Cacheable = requires (T const& obj) { 
    { DoCache<T>::write2Cache(obj) } 
        -> std::same_as<void>;
    { DoCache<T>::readFromCache(std::string{}) } 
        -> std::same_as<std::optional<T>>; 
};

用法类似于特征:

if constexpr (Cacheable<MyStruct>)

并强制执行 DoCache.

的适当专业化

Demo

上面显示的方法意味着允许用户以不正确的方式专门化 DoCache,从而导致“不可缓存”类型。为防止这种情况发生,您可以:

  1. 通过在专业化之后放置 static_assert(Cacheable<MyStruct>) 来使用防御性编程。
  2. 再次利用 constexpr static value 成员并在特化中实施 all or nothing policy,即没有为该类型提供特化,或者提供的特化包含概念中指定的所有成员。这意味着您将使用其模板参数为概念的特征。