前提条件:
我使用编译器资源管理器https://godbolt.org/运行下面截取的代码。
问题描述:
我尝试在使用通用事件(“_”下划线)处理 on_entry/on_exit 定义时访问事件成员。
这仅适用于编译器资源管理器上提供的最新 gcc 编译器 x86-64 gcc 11.1。对于旧版本,它会抛出错误。
所以我尝试了不同的解决方案来解决这个问题。
在下面的代码片段中,您会发现 3 个不同的版本可用于测试此问题。
1. 和 2. 版本仅适用于最新的 gcc 编译器 x86-64 gcc 11.1
3.版本允许使用尴尬的reinterpret_cast来解决这个问题,我认为这不是一个很好的解决方案。
问题:
A) 我当前的reinterpret_cast 解决方案是安全的解决方案还是您看到一些陷阱?
如果您发现一些陷阱,请解释它们。谢谢!
B) 是否有任何其他解决方案可以在不使用reinterpret_cast 的情况下解决旧版gcc 编译器(如x86-64 gcc 11.1)的此问题? 谢谢!
代码片段:
#include <https://raw.githubusercontent.com/boost-ext/sml/master/include/boost/sml.hpp>
#include <iostream>
namespace sml = boost::sml;
namespace {
struct BaseEvent {
BaseEvent(int value = 200) : i(value) {}
int i;
};
struct e1 : BaseEvent {};
struct e2 {};
struct e3 : BaseEvent { e3() : BaseEvent(300){} };
struct OnEntry{
// Handle BaseEvent's and access members of that event
void operator()(const auto& event){
// 1. Version) Works only with compiler x86-64 gcc 11.1
const int i = event.i;
// 2. Version) Works only with compiler x86-64 gcc 11.1
//const int i = (static_cast<const BaseEvent&>(event)).i;
// 3. Version) Works with all compilers
//const int i = (reinterpret_cast<const BaseEvent&>(event)).i;
printf("[OnEntry] %s | BaseEvent related event with value=%d\n", sml::aux::get_type_name<decltype(event)>(), i);
}
// Used to handle non BaseEvent's
void operator()(const e2& event){
printf("[OnEntry] %s | other events\n", sml::aux::get_type_name<decltype(event)>());
}
void operator()(const sml::back::initial& event){}
};
struct TestSm {
auto operator()() const noexcept {
using namespace sml;
return make_transition_table(
*"idle"_s + event<e1> = "s1"_s,
"s1"_s + event<e2> = "s2"_s,
"s2"_s + event<e3> = "s3"_s,
// The on_entry is defined to handle all events,
// but OnEntry operator should handle the real event e1 provided from process_event.
// This only works for compiler x86-64 gcc 11.1 for older versions
// like x86-64 gcc 10.3 it won't work and requires an
// awkward reinterpret_cast (see definition of OnEntry above).
"s1"_s + on_entry<_> / OnEntry(),
"s2"_s + on_entry<_> / OnEntry(),
"s3"_s + on_entry<_> / OnEntry()
);
}
};
} // namespace
int main() {
sml::sm<TestSm> sm;
sm.process_event(e1{});
sm.process_event(e2{});
sm.process_event(e3{});
}
如果删除以下重载,则会发生无效的内存读取。 这是一个陷阱。
// Used to handle non BaseEvent's
void operator()(const e2& event){
printf("[OnEntry] %s | other events\n", sml::aux::get_type_name<decltype(event)>());
}
您可以使用
if constexpr
和 type_traits std::is_base_of_v
元函数来解决问题。
这是解决方案代码:
void operator()(const auto& event){
if constexpr (std::is_base_of_v<BaseEvent, std::remove_reference_t<decltype(event)>>) {
const int i = event.i;
printf("[OnEntry] %s | BaseEvent related event with value=%d\n", sml::aux::get_type_name<decltype(event)>(), i);
}
}
我认为使用模板而不是
auto
参数类型更简单。
请参阅以下代码:
template <typename T>
void operator()(const T& event){
if constexpr (std::is_base_of_v<BaseEvent, T>) {
const int i = event.i;
printf("[OnEntry] %s | BaseEvent related event with value=%d\n", sml::aux::get_type_name<decltype(event)>(), i);
}
}
我已经检查了我的解决方案是否适用于 gcc 7.3.0 及更高版本以及 clang 3.9.1 或更高版本。
在 C++14 中,您需要将
if constexpr
替换为 SFINAE。
所以代码如下:
// Handle BaseEvent's and access members of that event
template <typename T>
std::enable_if_t<std::is_base_of<BaseEvent, T>::value>
operator()(const T& event){
const int i = event.i;
printf("[OnEntry] %s | BaseEvent related event with value=%d\n", sml::aux::get_type_name<decltype(event)>(), i);
}
// Used to handle non BaseEvent's
template <typename T>
std::enable_if_t<!std::is_base_of<BaseEvent, T>::value>
operator()(const T& event){
printf("[OnEntry] %s | other events\n", sml::aux::get_type_name<decltype(event)>());
}
根据 Takatoshi Kondo 的回答以及 c++14 标准中缺乏使用 if-constexpr 的情况,我找到了另一种与 Takatoshi 非常相似的解决方案。
以下代码片段必须替换原始问题中的整个 OnEntry 结构:
template <bool B>
struct Handle{ };
// Handle's BaseEvent and access members of that event
template<>
struct Handle<true>
{
void operator()(const BaseEvent& event)
{
const int i = event.i;
printf("[OnEntry] %s | BaseEvent related event with value=%d\n", sml::aux::get_type_name<decltype(event)>(), i);
}
};
// Handles non-BaseEvent's
template <>
struct Handle<false>
{
template<class T>
void operator()(const T& event) {
printf("[OnEntry] %s | non-BaseEvent related event\n", sml::aux::get_type_name<decltype(event)>());
}
};
struct OnEntry{
template <class T>
void operator()(const T& event){
Handle<std::is_base_of<BaseEvent, T>::value>()( event );
}
};
我唯一不满意的是,由于编译器错误消息“错误:非命名空间范围内的显式专业化”,我无法将此显式模板专业化移动到 OnEntry 结构中。