我有一个使用虚拟函数指针作为模板参数的问题。问题似乎在于,编译器不会在基类中查找所有函数,或者无法将它们视为派生类的函数。

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类,用于为给定实例执行一系列成员函数。
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);
    }
};

用于按给定顺序灵活执行功能的实际类
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 {}
};

不幸的是,在我的情况下,我无法更改类D12,还有另一种可能使其运行吗?
我认为编译器知道继承层次结构,因此他应该知道哪个函数在哪个继承级别是已知/可访问的。但是可能我遗漏了一些为什么它不起作用?

最佳答案

不要使用成员函数指针,也不要完全正确地获取类型(不进行转换)。

确实,与成员函数指针分离。在不使用T*的情况下,存储一个使用std::function的函数对象的元组(如果您关心一个字节或几个字节,则通过私有(private)继承(启用空基本优化))。

所以

template<class T, class...Fs>

我们创建std::tuple<Fs...>。我们通过遍历元组来执行(对此有很多堆栈溢出问题,谷歌可以找到它们)。

我们可以使用lambda来描述调用成员函数,或者如果您不喜欢实际传递无状态对象,则可以编写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)...};
  };
};

给定一个lambdas的元组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 #1Live example #2。我使用std::array,因为它很像元组(支持get和element count特质),但很少输入3个元素。

关于c++ - 如何使用来自不同继承层次结构级别的多个虚拟函数指针作为模板参数?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/40575060/

10-11 18:55
查看更多