【问题标题】:How to make SFINAE work to choose template method inherited through multiple inheritance如何使 SFINAE 工作选择通过多重继承继承的模板方法
【发布时间】:2023-03-07 22:57:01
【问题描述】:

尝试实现观察者模式,我希望主题和观察者代表所有可能的事件类型(避免重复代码)。 我还想允许类通过多重继承从主题中多次继承,以便被多个事件观察到。一种解决方案是制作 register/notify/etc... 方法模板,使其“喜欢”不同的名称但具有相同的代码,并消除调用的歧义,而无需制作包装函数。

我想要什么:

object obj;
obj.registerEvent<Event1>(observer1);
obj.registerEvent<Event2>(observer2);
obj.registerEvent<Event3>(observer3);
...

实际代码:

#include <algorithm>
#include <type_traits>
#include <set>
#include <gtest/gtest.h>

template<typename Event>
class Observer
{
public:

protected:
    Observer() = default;

public:
    virtual void update() = 0;
};

template<typename Event>
class Subject
{
private:
    std::set<Observer<Event>*> m_observers;

protected:
    Subject() = default;

public:

#define same_template \
    template<class T, class = typename std::enable_if<std::is_same<T, Event>::value>::type>

    same_template
    void registerObserver(Observer<Event>* observer, Event* = 0)
    {
        m_observers.insert(observer);
    }

    same_template
    void removeObserver(Observer<Event>* observer, Event* = 0)
    {
        m_observers.erase(observer);
    }

    same_template
    void notifyObservers()
    {
        std::for_each(m_observers.begin(), m_observers.end(), [] (Observer<Event> *observer) {
            observer->update();
        });
    }
};

TEST(PatternObserverTest, CompilationTest)
{

    struct Move {};
    struct Jump {};
    struct Player : Subject<Move>, Subject<Jump> {

        void move()
        {
            notifyObservers<Move>(); // ERROR: member found in multiple bases classes of different types
        }

        void jump()
        {
            notifyObservers<Jump>(); // ERROR: member found in multiple bases classes of different types
        }
    };

    struct Level : Observer<Move>, Observer<Jump> {

        void update()
        {

        }
    };

    Player player;
    Level level;

    player.template registerObserver<Move>(&level); // ERROR: member found in multiple bases classes of different types
}

我什至尝试添加一个 foo 参数 (Event*),如果它会破坏 ODR。

问题是,要选择好的方法覆盖。看起来 SFINAE 不适用于多重继承。我怎样才能让它发挥作用?

我的猜测: 我认为编译器会创建一组所有可能的方法,然后通过实例化消除错误的候选方法,然后如果只剩下 1 个方法,则调用它,或者抛出错误。 看起来编译器会这样做:它创建了一组可能的方法,并且因为有多个候选方法,所以通过仅查看函数名称而不是查看函数名称,直接推断出调用是模棱两可的,因为在实例化方法之前范围是不同的函数签名。

我知道可以使用通用超类和强制转换(方法 2)或为每种类型生成唯一 id(方法 3)来解决问题,但我想坚持使用由于语法而无法编译和崩溃的 C++错误。

【问题讨论】:

  • 请注意 registerObserverremoveObserver 不需要是模板(T 未使用,参数 Observer&lt;Event&gt;* 将允许区分重载)
  • 我将它作为模板,例如,Level 同时继承自 Observer 和 Observer(带有实际代码)
  • 确实如此,但是您可以使用语法player.Subject&lt;Move&gt;::registerObserver(&amp;level) 来消除歧义。

标签: c++ templates multiple-inheritance sfinae


【解决方案1】:

函数必须在同一范围内才能进行重载解析,并且可以通过 using 声明将其引入 Player 范围:

struct Player : Subject<Move>, Subject<Jump>
{
    using Subject<Move>::notifyObservers;
    using Subject<Jump>::notifyObservers;
    using Subject<Move>::registerObserver;
    using Subject<Jump>::registerObserver;

    void move()
    {
        notifyObservers<Move>();
    }

    void jump()
    {
        notifyObservers<Jump>();
    }
};

DEMO

【讨论】:

  • 是的。作为旁注,我认为您不需要 SINFAE,因为简单的重载解决方案应该可以解决问题 (demo 2)
  • 好的,谢谢。 SFINAE 是如果一个观察者从多个观察者继承而来。一世。 e. struct Level : Observer&lt;Move&gt;, Observer&lt;Jump&gt;;。最后还添加一个事件参数使其更简单(notifyObservers(Jump{}))并且可以发送更多信息。
  • 我删除了关于Subject&lt;Move&gt;::notifyObserver语法的评论,因为这不适用于registerObserver
  • (除非你打算写player.Subject&lt;Move&gt;::registerObserver(...)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-05
  • 1970-01-01
  • 2015-11-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-19
相关资源
最近更新 更多