如何使用来自不同继承层次级别的多个虚函数指针作为模板参数?
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::*)()
如果我添加一个函数 b1
到 D12
调用 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::b1
是 void (D1::*)()
类型,而不是 void (D12::*)()
,因为 D12
本身没有名为 b1
的成员。第一段说 void (D1::*)()
类型的指针由于虚拟继承而不能转换为 void (D12::*)()
。
我在使用虚函数指针作为模板参数时遇到问题。问题似乎是编译器不会在基 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::*)()
如果我添加一个函数 b1
到 D12
调用 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 cvT
”, whereB
is a class type, can be converted to a prvalue of type “pointer to member ofD
of type cvT
”, whereD
is a derived class (Clause 10) ofB
. IfB
is an inaccessible (Clause 11), ambiguous (10.2), or virtual (10.1) base class ofD
, or a base class of a virtual base class ofD
, 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 memberm
of some classC
with typeT
, the result has type “pointer to member of classC
of typeT
” and is a prvalue designatingC::m
... [ Example:struct A { int i; }; struct B : A { }; ... &B::i ... // has type int A::*
—end example ]
第二段说 &D12::b1
是 void (D1::*)()
类型,而不是 void (D12::*)()
,因为 D12
本身没有名为 b1
的成员。第一段说 void (D1::*)()
类型的指针由于虚拟继承而不能转换为 void (D12::*)()
。