I am experimenting and trying to make a template policy-based meta library. Example case is aggregating 2 classes for a device driver.
The classes implement device_logic
and connection_logic
, they don't need to depend on each other's type:
- device logic depends only on a communication protocol (messages).
- connection_logic is just a source of byte arrays and may use different kinds of connections: SerialPort, tcp, udp, custom PCI express device, etc.
The goal is not to force any interfaces or types on them. They must depend purely on the API specification and only provide necessary traits.
The STL approach is to define traits in a header and then use them inside a class. So the traits tags must be defined in a header of a template library.
// 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");
};
In this case aggregate_device
aggregates connection and device logic. If device logic is readable, the connection logic must provide input. If device logic is writeable, the connection must provide output.
// device_logic.h
#include <device_traits>
class device_logic
{
public:
using io_type = traits::readableWriteable;
// ... methdos, etc
};
This version works but introduces a dependency on the template library. Introducing dependency (even a header-only library) is not convenient for a developer and generally not good for a library. Someone might want to use device_logic
class in another module or project, but not want to pull a template library it depends on.
Another solution which removes the dependency is not to force a class provider to inject io_type
tags to his class but to define them on his own.
// 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>(), "");
Now I use the second approach and it works. The questions are:
- Is it a legit approach?
- Wouldn't it be confusing for a class provider?
- Will it be (at what extent) harder to maintain?
- What may be the differences in performance for compiling?