如何使用来自不同继承层次级别的多个虚函数指针作为模板参数?

How to use multiple virtual function pointer from different inheritance-hierarchy levels as template argument?

我在使用虚函数指针作为模板参数时遇到问题。问题似乎是编译器不会在基 classes 中查找所有函数,或者无法将它们视为派生 class.

的函数
struct B1
{
    virtual void b1() = 0;
    virtual ~B1() = default;
};

struct B2
{
    virtual void b2() = 0;
    virtual ~B2() = default;
};

struct D1
    : virtual public B1
{
    void b1() override {}
};

struct D12
    : virtual public D1
    , virtual public B2
{
    void b2() override {}
};

Helper class 为给定实例执行一系列成员函数。

template<
    typename T,
    void(T::*...Fs)()>
struct Executor
{
    static
    void
        execute(
            T & t)
    {}
};

template<
    typename T,
    void(T::*F)(),
    void(T::*...Fs)()>
struct Executor<
    T, F, Fs...>
{
    static
    void
        execute(
            T & t)
    {
        (t.*F)();
        Executor<T, Fs...>::execute(t);
    }
};

实际class 用于按给定顺序灵活执行功能

template<
    typename T,
    void(T::*...Fs)()>
struct FlexBind
{
    std::unique_ptr<T> t;
    void b()
    {
        Executor<T, Fs...>::execute(*t);
    }
};

我的用例是我喜欢静态定义函数的调用顺序(编译时),但是调用这些函数的对象实例是动态定义的(运行时)。

int main()
{
    FlexBind<D12, D12::b1, D12::b2> FB1;//compile error
    FlexBind<D12, D12::b2, D12::b1> FB2;
    FB1.t.reset(new D12());
    FB1.b();
    FB2.t.reset(new D12());
    FB2.b();
    return 0;
}

我得到的错误是:

error: '&D1::b1' is not a valid template argument for type
    'void (D12::*)()' because it is of type 'void (D1::*)()'

编译器无法匹配void (D12::*)()void (D1::*)()
如果我添加一个函数 b1D12 调用 D1::b1 一切都会编译并运行。

struct D12
    : virtual public D1
    , virtual public B2
{
    void b1() override {D1::b1();}//would solve the problem, but is not feasible
    void b2() override {}
};

不幸的是,在我的情况下,我无法更改 class D12,是否有其他可能获得它 运行?
我认为编译器知道继承层次,所以他应该知道哪些函数在哪个继承级别 known/accessible 。但可能我遗漏了一些东西,为什么它不起作用?

不要使用成员函数指针,或者获取完全正确的类型(无转换)。

真的,与成员函数指针解耦。在不使用 std::function.

的情况下存储 T* 消费函数对象的元组(如果您关心一个或几个字节,则通过私有继承(启用空基优化))

所以

template<class T, class...Fs>

我们创造 std::tuple<Fs...>。我们通过遍历元组来执行(关于这个的stack overflow问题有很多,google可以找到)

我们可以使用 lambdas 来描述调用成员函数,或者写一个 template<class U, void(U::*mem)()> 无状态助手,如果你不喜欢必须实际传递无状态对象。

这里有一些 C++14 助手:

template<class=void,std::size_t...Is>
auto indexer( std::index_sequence<Is...> ) {
  return [](auto&&f)->decltype(auto) {
    return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
  };
}
// takes a number N
// returns a function object that, when passed a function object f
// passes it compile-time values from 0 to N-1 inclusive.
template<std::size_t N>
auto indexer() {
  return indexer( std::make_index_sequence<N>{} );
}
// takes a function object f
// returns a function object that takes any number of arguments
// and invokes `f` on each of them
template<class F>
auto for_each_arg(F&& f) {
  return [f = std::forward<F>(f)](auto&&...args)->void {
    // this is a bit insane.  We want to expand our parameter pack
    // args... in a way that we do it from left to right.  As it happens,
    // creating a C-style array is one of the legal ways to do this.
    // So we create an anonymous C-style array, and discard it immediately
    // The void stuff is a mixture of suppressing warnings and
    // ensuring that if someone has a hostile `operator,` it doesn't cause
    // any issues
    // the result of this expression is an array of `int` full of `0`,
    // plus the function `f` invokes on each of the `args...` in order:
    using discard=int[];
    (void)discard{0,(void(
      f( decltype(args)(args) )
    ),0)...};
  };
};

给定一个 lambda 元组 bob,我们可以像这样在某个指针 p 上调用它们:

// the pack 0 to N-1, where N is the size of bob:
auto index = indexer<std::tuple_size<decltype(bob)>{}>();

// From a compile time `i`, do what we want:
auto invoker = [&](auto i) {
  std::get<i>(bob)(p);
};

// For each compile time integer from 0 to N-1,
// call invoker:
index(for_each_arg(invoker));

所有这些在 C++17 中变得更加容易。

上面的代码充满了微优化,其中一些使其更难理解。如果你想了解更多,直接找一个关于这个主题的SO问题,或者如果找不到就问一个。


以上部分为C++14。在 C++11 中,我们必须手动扩展其中一些 lambda。

例如,indexer 变为:

template<std::size_t...Is>
struct indexer_t {
  template<class F>
  auto operator()( F&& f ) const
  -> decltype(std::forward<F>(f)( std::integral_constant<std::size_t, Is>{}... ))
  {
    return std::forward<F>(f)( std::integral_constant<std::size_t, Is>{}... );
  }
};

template<class=void,std::size_t...Is>
indexer_t<Is...> indexer( std::index_sequence<Is...> )
{ return {}; }
template<std::size_t N>
auto indexer()
-> decltype( indexer(std::make_index_sequence<N>{}) ) )
{ return {}; }

或类似的东西。

一些名义上的 C++14 编译器可能也需要上述帮助(如 MSVC2015),因为它们不允许您从 lambda 的封闭上下文中扩展参数包。

Live example #1 and Live example #2。我使用 std::array 是因为它类似于元组(支持 get 和元素计数特性),但更少输入 3 个元素。

[conv.mem]/2 A prvalue of type “pointer to member of B of type cv T”, where B is a class type, can be converted to a prvalue of type “pointer to member of D of type cv T”, where D is a derived class (Clause 10) of B. If B is an inaccessible (Clause 11), ambiguous (10.2), or virtual (10.1) base class of D, or a base class of a virtual base class of D, a program that necessitates this conversion is ill-formed.

强调我的。

[expr.unary.op]/3 The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue or a qualified-id. If the operand is a qualified-id naming a non-static member m of some class C with type T, the result has type “pointer to member of class C of type T” and is a prvalue designating C::m... [ Example:

struct A { int i; };
struct B : A { };
... &B::i ... // has type int A::*

—end example ]

第二段说 &D12::b1void (D1::*)() 类型,而不是 void (D12::*)(),因为 D12 本身没有名为 b1 的成员。第一段说 void (D1::*)() 类型的指针由于虚拟继承而不能转换为 void (D12::*)()