template<typename T>
struct is_message : std::false_type {};
template<typename T>
class Publisher {
static_assert(is_message<T>::value, "This message type is not supported");
public:
void Publish(T& msg) {}
};
之前的代码是来自第三方库的简化草图(ROS2 很简陋,我们不应该更改此代码库), 它使用类型特征
is_message
将消息类型限制为其生成的 IDL C++ 消息,该消息生成代码来专门化先前的类型特征模板,如下所示:
class GeneratedIdlMessageX {
public:
};
template<> struct is_message<GeneratedIdlMessageX> : std::true_type {};
然后用户可以像这样删除正在工作的发布者:
#include <ros2_headers.h>
#include <ros2idl_generated_message.h>
Publisher<GeneratedIdlMessageX> publisher;
现在的问题是我们也想支持 protobuf 消息,但我们无法修改 protobuf IDL 代码生成来添加此专门化。 因此,我们必须在每个 protobuf 消息发布声明上手动添加此专业化,如下所示:
#include <ros2_headers.h>
#inlcude <protobuf_generated_message.h>
template<> struct is_message<ProtoBufMessageY> : std::true_type {};
Publisher<ProtoBufMessageY> publisher;
怎样才能省掉这个烦人的工作呢?
一个直接的想法是引入一个预定义的通用标头,它将所有 protobuf 生成的消息专门用于发布者用户。 考虑到所有protobuf生成的c++消息都是从
MessageLite
继承的,我尝试直接专门化基类,显然它不起作用:
// of course this won't work
template <> struct is_message<protobuf::MessageLite> : std::true_type{};
现在我因为无法修改第三方(ros2)和 protobuf IDL 生成代码而陷入困境,感谢任何帮助。
注意:编译和运行环境限制c++标准低于c++17(c++20概念不可用)。
C++20 概念的救援:
template <std::derived_from<protobuf::MessageLite> T>
struct is_message<T> : std::true_type {};
在 C++17 及更早版本中,这只能通过主模板的配合来实现,主模板必须包含虚拟模板参数:
template <typename T, typename = void>
struct is_message : std::false_type {};
template <typename T>
struct is_message<T, std::enable_if_t<std::is_base_of_v<protobuf::MessageLite, T>>>
: std::true_type {};
如果库缺少此模板参数,最好将其报告为错误。
在 C++20 中,您可以使用其他一些解决方案。不幸的是,你的双手被 C++17 束缚了。
一个非常肮脏的解决方案是这样的:
template <typename T>
struct Message;
template<typename T>
struct is_message<Message<T>> : std::true_type {};
template <typename T>
class Publisher<Message<T>> : public Publisher<T> {
public:
using Publisher<T>::Publisher; // inherit all constructors
};
您为
is_message
和 Publisher
定义部分特化,以便 is_message
检查成功。
然后可以像这样使用:
Publisher<Message<ProtoBufMessageX>> publisher;
此解决方案显然有一个缺点,即您需要对所有协议缓冲区消息使用
Message
包装器。
为了隐藏这个丑陋之处,您可以添加一个方便的别名:
// If upgraded to C++20, this could eventually be turned into just
// = Publisher<T>
template <typename T>
using PublisherType = Publisher<Message<ProtoBufMessageX>>;
PublisherType<ProtoBufMessageX> publisher;
另一个选项是分别为每条消息定义显式专业化,就像您建议的那样:
template<> struct is_message<ProtoBufMessageX> : std::true_type {};
template<> struct is_message<ProtoBufMessageY> : std::true_type {};
template<> struct is_message<ProtoBufMessageZ> : std::true_type {};
// ...