Zero-dependency 特征定义
Zero-dependency traits definition
我正在试验并试图制作一个模板 policy-based 元库。示例案例是为设备 driver 聚合 2 classes。
classes 实现了 device_logic
和 connection_logic
,它们不需要依赖彼此的类型:
- 设备逻辑仅取决于通信协议(消息)。
- connection_logic 只是字节数组的来源,可以使用不同类型的连接:SerialPort、tcp、udp、自定义 PCI express 设备等。
目标不是强加给它们任何接口或类型。它们必须完全依赖于 API 规范并且只提供必要的特征。
STL 方法是在 header 中定义特征,然后在 class 中使用它们。
所以traits标签必须在模板库的header中定义。
// device_traits.h
namespace traits
{
// tags to be defined as io_type
struct writeable;
struct readable;
struct wretableReadable;
template <typename T>
constexpr bool is_writeable()
{
return std::is_same_v<writeable, typename T::io_type>() ||
std::is_same_v<wretableReadable, typename T::io_type>();
}
// functions for readable and readableWriteable
}
template <typename ConnectionLogic,
typename DeviceLogic>
class aggregate_device
{
static_assert(!traits::readable<DeviceLogic>() ||
(traits::readable<DeviceLogic>() &&
traits::readable<ConnectionLogic>()),
"Device logic is readable so must be ConnectionLogic");
static_assert(!traits::writeable<DeviceLogic>() ||
(traits::writeable<DeviceLogic>() &&
traits::writeable<ConnectionLogic>()),
"Device logic is writeable so must be ConnectionLogic");
};
在这种情况下 aggregate_device
聚合了连接和设备逻辑。如果设备逻辑是可读的,连接逻辑必须提供输入。如果设备逻辑是可写的,连接必须提供输出。
// device_logic.h
#include <device_traits>
class device_logic
{
public:
using io_type = traits::readableWriteable;
// ... methdos, etc
};
此版本有效,但引入了对模板库的依赖。引入依赖项(即使是 header-only 库) 对开发人员来说并不方便,而且通常对库也不利。有人可能想在另一个模块或项目中使用 device_logic
class,但不想拉取它依赖的模板库。
另一种消除依赖性的解决方案是不强制 class 提供者将 io_type
标签注入他的 class,而是自己定义它们。
// device_traits.h
namespace traits
{
template<typename, typename = void>
struct is_writeable : std::false_type{};
// here we just check if a typename has a type writeable
template<typename T>
struct is_writeable<T, std::void_t<typename T::writeable>> : std::true_type{};
// functions for readable and readableWriteable
// aggregator class
}
// device_logic.h
// don't include nothing
class device_logic
{
public:
// define a type
struct writeable;
};
/////
#include <device_traits>
static_assert(traits::is_writeable<device_logic>(), "");
现在我使用第二种方法并且有效。
问题是:
- 这是一种合法的方法吗?
- class 提供商不会感到困惑吗?
- 是否(在何种程度上)更难维护?
- 编译性能可能有哪些差异?
Is it a legit approach?
Wouldn't it be confusing for a class provider?
标准使用不同的方法:
存在类型,例如应该具有“类型”的透明比较器is_transparent
(如using is_transparent = void;
)
特定标签为 iterator_tags。
甚至只是鸭子打字(不检查模板)
或 SFINAE 存在 method/properties。
这些类型可能是:
- 里面class(至于
is_transparent
)
- 或作为外部特征提供,例如
std::iterator_traits
(甚至允许在可能的情况下从 class 中提取内部 typedef)。
请注意,只有外部特征可能以非侵入式方式支持内置类型(指针、int
、...)或外部类型(第 3 个库或特征的标准库)。
Will it be (at what extent) harder to maintain?
之间存在取舍
“物理”依赖性,东西之间的联系更多,保持同步可能更简单,但会产生依赖性。
没有“物理”依赖性,因此可能更难保持同步。
What may be the differences in performance for compiling?
一如既往,你必须测量。
例如 build-bench.com.
要一起使用,似乎您必须包含相似的代码,但不一定要按相同的顺序,所以我敢打赌性能相似。
单独使用时,应避免多出一个#include
(所以要看#include
的size/number,如果使用pch,...)...
我正在试验并试图制作一个模板 policy-based 元库。示例案例是为设备 driver 聚合 2 classes。
classes 实现了 device_logic
和 connection_logic
,它们不需要依赖彼此的类型:
- 设备逻辑仅取决于通信协议(消息)。
- connection_logic 只是字节数组的来源,可以使用不同类型的连接:SerialPort、tcp、udp、自定义 PCI express 设备等。
目标不是强加给它们任何接口或类型。它们必须完全依赖于 API 规范并且只提供必要的特征。
STL 方法是在 header 中定义特征,然后在 class 中使用它们。 所以traits标签必须在模板库的header中定义。
// device_traits.h
namespace traits
{
// tags to be defined as io_type
struct writeable;
struct readable;
struct wretableReadable;
template <typename T>
constexpr bool is_writeable()
{
return std::is_same_v<writeable, typename T::io_type>() ||
std::is_same_v<wretableReadable, typename T::io_type>();
}
// functions for readable and readableWriteable
}
template <typename ConnectionLogic,
typename DeviceLogic>
class aggregate_device
{
static_assert(!traits::readable<DeviceLogic>() ||
(traits::readable<DeviceLogic>() &&
traits::readable<ConnectionLogic>()),
"Device logic is readable so must be ConnectionLogic");
static_assert(!traits::writeable<DeviceLogic>() ||
(traits::writeable<DeviceLogic>() &&
traits::writeable<ConnectionLogic>()),
"Device logic is writeable so must be ConnectionLogic");
};
在这种情况下 aggregate_device
聚合了连接和设备逻辑。如果设备逻辑是可读的,连接逻辑必须提供输入。如果设备逻辑是可写的,连接必须提供输出。
// device_logic.h
#include <device_traits>
class device_logic
{
public:
using io_type = traits::readableWriteable;
// ... methdos, etc
};
此版本有效,但引入了对模板库的依赖。引入依赖项(即使是 header-only 库) 对开发人员来说并不方便,而且通常对库也不利。有人可能想在另一个模块或项目中使用 device_logic
class,但不想拉取它依赖的模板库。
另一种消除依赖性的解决方案是不强制 class 提供者将 io_type
标签注入他的 class,而是自己定义它们。
// device_traits.h
namespace traits
{
template<typename, typename = void>
struct is_writeable : std::false_type{};
// here we just check if a typename has a type writeable
template<typename T>
struct is_writeable<T, std::void_t<typename T::writeable>> : std::true_type{};
// functions for readable and readableWriteable
// aggregator class
}
// device_logic.h
// don't include nothing
class device_logic
{
public:
// define a type
struct writeable;
};
/////
#include <device_traits>
static_assert(traits::is_writeable<device_logic>(), "");
现在我使用第二种方法并且有效。 问题是:
- 这是一种合法的方法吗?
- class 提供商不会感到困惑吗?
- 是否(在何种程度上)更难维护?
- 编译性能可能有哪些差异?
Is it a legit approach?
Wouldn't it be confusing for a class provider?
标准使用不同的方法:
存在类型,例如应该具有“类型”的透明比较器
is_transparent
(如using is_transparent = void;
)特定标签为 iterator_tags。
甚至只是鸭子打字(不检查模板)
或 SFINAE 存在 method/properties。
这些类型可能是:
- 里面class(至于
is_transparent
) - 或作为外部特征提供,例如
std::iterator_traits
(甚至允许在可能的情况下从 class 中提取内部 typedef)。
请注意,只有外部特征可能以非侵入式方式支持内置类型(指针、int
、...)或外部类型(第 3 个库或特征的标准库)。
之间存在取舍Will it be (at what extent) harder to maintain?
“物理”依赖性,东西之间的联系更多,保持同步可能更简单,但会产生依赖性。
没有“物理”依赖性,因此可能更难保持同步。
What may be the differences in performance for compiling?
一如既往,你必须测量。
例如 build-bench.com.
要一起使用,似乎您必须包含相似的代码,但不一定要按相同的顺序,所以我敢打赌性能相似。
单独使用时,应避免多出一个#include
(所以要看#include
的size/number,如果使用pch,...)...