实施 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);
}
这段代码有两个问题:
- 用户在专门化时只是间接强制提供一个“值”成员,更不用说使其成为正确的值(即
true
)。间接地,我的意思是他们会得到一堆编译错误,只有他们事先知道答案才能解决这些错误。
- 没有办法“要求”他们创建两个需要的方法,即
write2Cache
和 readFromCache
,更不用说拥有 (const) 正确的类型了。
在一些代码库中,我已经看到通过定义生成器宏来解决上述注意事项,例如:
#define CACHABLE(Type, Writer, Reader) ...
- 有没有更好的方法呢?
- 可以使用概念来限制专业化的外观吗?
- 是否有兼容C++17的方式?
如能回答以上任何问题,我们将不胜感激
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
.
的适当专业化
上面显示的方法意味着允许用户以不正确的方式专门化 DoCache
,从而导致“不可缓存”类型。为防止这种情况发生,您可以:
- 通过在专业化之后放置
static_assert(Cacheable<MyStruct>)
来使用防御性编程。
- 再次利用
constexpr static value
成员并在特化中实施 all or nothing policy,即没有为该类型提供特化,或者提供的特化包含概念中指定的所有成员。这意味着您将使用其模板参数为概念的特征。
我正在使用“特征”模式,其中我有一个基本案例,表示为 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);
}
这段代码有两个问题:
- 用户在专门化时只是间接强制提供一个“值”成员,更不用说使其成为正确的值(即
true
)。间接地,我的意思是他们会得到一堆编译错误,只有他们事先知道答案才能解决这些错误。 - 没有办法“要求”他们创建两个需要的方法,即
write2Cache
和readFromCache
,更不用说拥有 (const) 正确的类型了。
在一些代码库中,我已经看到通过定义生成器宏来解决上述注意事项,例如:
#define CACHABLE(Type, Writer, Reader) ...
- 有没有更好的方法呢?
- 可以使用概念来限制专业化的外观吗?
- 是否有兼容C++17的方式?
如能回答以上任何问题,我们将不胜感激
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
.
上面显示的方法意味着允许用户以不正确的方式专门化 DoCache
,从而导致“不可缓存”类型。为防止这种情况发生,您可以:
- 通过在专业化之后放置
static_assert(Cacheable<MyStruct>)
来使用防御性编程。 - 再次利用
constexpr static value
成员并在特化中实施 all or nothing policy,即没有为该类型提供特化,或者提供的特化包含概念中指定的所有成员。这意味着您将使用其模板参数为概念的特征。