如何对来自抽象基础 class 的子 class 模板的成员执行操作?
How to do actions on members of child class templates from an abstact base class?
我知道这个问题被问了很多,但我有一个特定的用例,所以我不认为它是重复的!
我有一个抽象基础class:
template<int N>
class Child;
class Base
{
public:
// Factory-like generation of children as Base
static Ptr<Base> New(int baseN)
{
if (baseN == 2) return new Child<2>();
else if (baseN == 3) return new Child<3>()
}
// Update
virtual void update() = 0;
};
我正在写 Base
的一些子代作为 class 模板(在 int 上):
template<int N>
class Child
:
public Base
{
// Member, N is not the size of matrix, more like the size of a component in matrix
Matrix<N> m_member;
public:
// Implement update
virtual void update();
// Should call the passed callable on m_member
virtual void execute(std::function<void(Matrix<N>&)>&);
};
// Force compilation of Child<N> for some values of N (of interest, including 3) here
// Then,
int baseN = 3;
Ptr<Base> obj = Base::New(baseN); // will get me a Child<3> as a Base object
auto callable = [](Matrix<3>) ->void {};
// Can I access Child<3>::m_member ??
// Can't cast to Child<baseN> (baseN is not constexpr) and don't want to
// But want to do something like:
obj->execute(callable);
// Which forwards 'callable' to the method from concrete type, probably using a cast?
简而言之,我需要从声明的 Base
对象对 m_member
进行某种访问。
最好是一种从 Base
调用 Child<N>::execute
的方法,而不会将 Base 也作为 N 上的模板。
我 tried/thought-of 的东西包括:
Matrix<N>
的 - 'Type erasure' 通过将它们隐藏在接口后面,但是由于
Matrix<N>
的接口强烈依赖于 N,这样做会使 classes 变得无用(想想:例如 Vector<N>& Matrix<N>::diag()
)
Base::New
可以做些什么来记录它创建的具体类型吗?我对此表示怀疑,因为类型不是对象。
编辑:(顺便说一句,这是 C++11)
所以,我无意中想出了一个办法;但我不太明白为什么下面的作品(还不精通汇编):
- 我正在为对象使用数据库(
unordered_map<string, object*>
,其中 object
是每个注册对象都必须继承的 class。
- 创建 Child 后,我们将其注册到名称为
Child<N>
的数据库中。
- 然后,在应用程序级代码中,有一个
findChild<int N>
模板,它使用编译时递归来查找具体 class 是从(在运行时,通过 dynamicCasting 和测试)。当它找到它时,它可以通过静态方法 (findChild<N>::castToConcrete
) 将其转换为 void*
- 有趣的是,如果
Child<N>
是多态的,我们可以以某种方式使用 findChild<0>
来访问有问题的 findChild<N>
。这迫使我们最多只能有一个 Child 对象(对于所有可能的 N),我当然可以接受。
您可以在此处查看和检查最小代码示例:https://onlinegdb.com/CiGR1Fq5z
我很困惑的是 Child<0>
和其他 Child<N>
是完全不同的类型;那么我们怎样才能从一个指向另一个类型的指针访问一个人的成员呢?我很可能依赖 UB,甚至担心会有某种堆栈问题!
作为参考,我在此处包含代码以防 link 死亡。
#include <unordered_map>
#include <vector>
#include <functional>
#include <iostream>
using namespace std;
#ifndef MAX_N_VALUE
#define MAX_N_VALUE 10
#endif // !MAX_N_VALUE
// ------------------ Lib code
// A dummy number class for testing only
template <int N> struct Number { constexpr static int value = N; };
// Objects to register to the database
struct object
{
// Members
string name;
// construction/Destruction
object(const string& name) : name(name) {}
virtual ~object(){};
};
// Database of objects
struct DB
: public unordered_map<string, object*>
{
// See if we can the object of name "name" and type "T" in the DB
template <class T>
bool found(const string& name) const
{
unordered_map<string,object*>::const_iterator iter = find(name);
if (iter != end())
{
const T* ptr = dynamic_cast<const T*>(iter->second);
if (ptr) return true;
cout << name << " found but it's of another type." << endl;
return false;
}
cout << name << " not found." << endl;
return false;
}
// Return a const ref to the object of name "name" and type "T" in the DB
// if found. Else, fails
template <class T>
const T& getObjectRef(const string& name) const
{
unordered_map<string,object*>::const_iterator iter = find(name);
if (iter != end())
{
const T* ptr = dynamic_cast<const T*>(iter->second);
if (ptr) return *ptr;
cout << name << " found but it's of another type." << endl;
abort();
}
cout << name << " not found." << endl;
abort();
}
};
// Forward declare children templates
template<int N>
class Child;
// The interface class
struct Base
{
// Construction/Destruction
protected:
static unsigned counter;
Base(){}
public:
virtual ~Base() {}
// Factory-like generation of children as Base
// THIS New method needs to know how to construct Child<N>
// so defining it after Child<N>
static Base* New(int baseN, DB& db);
// Update
virtual void update() = 0;
// Call a callable on a child, the callable interface
// however is independent on N
virtual void execute(std::function<void(Base&)>& callable)
{
callable(*this);
}
};
unsigned Base::counter = 0;
// The concrete types, which we register to the DB
template<int N>
struct Child
:
public Base, public object
{
// members
vector<Number<N>> member;
// Construction/Destruction
Child() : Base(), object(string("Child") + to_string(N) + ">"), member(N, Number<N>()) {}
virtual ~Child() {}
// Test member method (Has to be virtual)
virtual vector<Number<N>> test() const
{
cout << "Calling Child<" << N << ">::test()" << endl;
return vector<Number<N>>(N, Number<N>());
}
// Implement update
virtual void update()
{
cout << "Calling Child<" << N << ">::update()" << endl;
};
};
// New Base, This can be much more sophisticated
// if static members are leveraged to register constructors
// and invoke them on demand.
Base* Base::New(int baseN, DB& db)
{
if (baseN == 2)
{
Child<2>* c = new Child<2>();
db.insert({string("Child<")+std::to_string(2)+">", c});
return c;
}
if (baseN == 3)
{
Child<3>* c = new Child<3>();
db.insert({string("Child<")+std::to_string(3)+">", c});
return c;
}
return nullptr;
}
// Finder template for registered children
template<int N>
struct findChild
{
// Concrete Type we're matching against
using type = Child<N>;
// Stop the recursion?
static bool stop;
// Compile-time recursion until the correct Child is caught
// Recursion goes UP in N values
static void* castToConcrete(const DB& db, Base* system)
{
if (N > MAX_N_VALUE) stop = true;
if (stop) return nullptr;
if (db.found<type>(string("Child<")+to_string(N)+">"))
{
type* ptr = dynamic_cast<type*>(system);
return static_cast<void*>(ptr);
}
// NOTE: This should jump to the next "compiled" child, not just N+1, but meh;
return findChild<N+1>::castToConcrete(db, system);
}
};
// Activate recursive behaviour for arbitraty N
template<int N>
bool findChild<N>::stop = false;
// Explicit specialization to stop the Compile-time recursion at a decent child
template<>
struct findChild<MAX_N_VALUE+1>
{
using type = Child<MAX_N_VALUE+1>;
static bool stop;
static void* castToConcrete(const DB& t, const Base* system)
{
return nullptr;
}
};
// Disactivate recursive behaviour for N = 11
bool findChild<MAX_N_VALUE+1>::stop = true;
// ------------------ App code
int main()
{
// Create objects database
DB db;
// --- Part 1: Application writers can't write generic-enough code
// Select (from compiled children) a new Base object with N = 2
// and register it to the DB
Base* b = Base::New(2, db);
b->update();
cout << "Access children by explicit dynamic_cast to Child<N>:" << endl;
// Get to the object through the objects DB.
// Child destructor should remove the object from DB too, nut meh again
const auto& oo = db.getObjectRef<Child<2>>("Child<2>");
cout << oo.test().size() << endl;
// --- Part 2: Application writers can write generic code if the compile
// Child<N> for their N
cout << "If Child<N> is polymorphic, we can access the correct child from findChild<0>:" << endl;
// Create a lambda that knows about db, which Base applies on itself
function<void(Base&)> lambda = [&db](Base& base) -> void {
// Cast and ignore the result
void* ptr = findChild<0>::castToConcrete(db, &base);
// Cast back to Child<0>
findChild<0>::type* c = static_cast<findChild<0>::type*>(ptr);
// Now access original Child<N> methods and members from Child<0>
cout << "Method:\n" << c->test().size() << endl;
cout << "Member:\n" << c->member.size() << endl;
};
b->execute(lambda);
return 0;
}
我使用 GCC 9 编译,选项如下:
-m64 -Wall -Wextra -Wno-unused-parameter -Wold-style-cast -Wnon-virtual-dtor -O0 -fdefault-inline -ftemplate-depth-200
您似乎希望继承不那么相关的组 类...
std::variant
(C++17) 可能更合适:
template<int N>
class Child
{
// Member, N is not the size of matrix, more like the size of a component in matrix
Matrix<N> m_member;
public:
void update();
void execute(std::function<void(Matrix<N>&)> f) { f(m_member); }
};
using Base = std::variant<Child<2>, Child<3>>;
然后:
void foo(Base& obj)
{
struct Visitor {
template <std::size_t N>
void operator()(Child<N>& c) const
{
auto callable = [](Matrix<N>) -> void {/*..*/};
c.execute(callable);
}
} visitor;
std::visit(visitor, obj);
}
要回答您的编辑,而您的可调用对象采用 Base
,您可以按如下方式链接 dynamic_cast
:
template <int N>
void foo_base(Base& b)
{
if (auto* child = dynamic_cast<Child<N>*>(&b)) {
// Job with Child<N>
std::cout << "Method:" << child->test().size() << std::endl;
std::cout << "Member:" << child->member.size() << std::endl;
}
}
template <int... Ns>
void foo_dispatch(std::integer_sequence<int, Ns...>, Base& base)
{
//(foo_base<Ns>(base), ...); // C++17
const int dummy[] = {0, (foo_base<Ns>(base), 0)...};
static_cast<void>(dummy); // Avoid warning about unused variable
}
调用类似于:
function<void(Base&)> lambda = [](Base& base) {
//foo_dispatch(std::integer_sequence<int, 2, 3>(), base);
foo_dispatch(std::make_integer_sequence<int, MAX_N_VALUE>(), base);
};
(std::integer_sequence
是C++14,但可以在C++11中实现)
Note: Jarod's answer is still a little bit better if you know possible
values of N in Child<N>
at compile-time and don't want to provide a way to extend them. Plus, of course, if you can use C++17.
这里我依赖于标准定义的“相似类型”:
4.4 Qualification conversions [conv.equal]
... trimmed ...
Two pointer types T1 and T2 are similar if there exists a type T and integer n > 0 such that:
T1 is cv(1,0) pointer to cv(1,1) pointer to ··· cv(1,n−1) pointer to cv(1,n) T
and
T2 is cv(2,0) pointer to cv(2,1) pointer to ··· cv(2,n−1) pointer to (cv2,n) T
where each cv(i,j) is const
, volatile
, const volatile
, or nothing
同段还给出了转换表达式的条件。
简而言之,通过从 Base
继承,所有 Child<N>*
指针类型都与 Base*
相似,因此彼此相似。
现在,我们知道我们可以 static_cast
Child<N>
到 Child<0>
没有问题。
但是从 Child<0>*
访问 Child<3>
成员安全吗?
3.10 Lvalues and rvalues [basic.lval]
- If a program attempts to access the stored value of an object through a glvalue of other than one of the
following types the behavior is undefined:
the dynamic type of the object,
... trimmed ...
a type similar (as defined in 4.4) to the dynamic type of the object
你知道了,通过 Child<0>*
访问 Child<3>
的值实际上是定义的行为。
这段代码:
Base* b = Base::New(2);
b->update();
Child<2>* c1 = static_cast<Child<2>*>(b);
c1->update();
cout << c1->t.sValue << " " << c1->t.rValue << endl;
Child<0>* c2 = static_cast<Child<0>*>(b);
c2->update();
cout << c2->t.sValue << " " << c2->t.rValue << endl;
实际会输出(注意静态变量Test<N>::sValue
的值):
Calling Child<2>::update()
Calling Child<2>::update()
2 2
Calling Child<2>::update()
0 2
静态成员将始终指向 Child<0>
,正因为如此,
Jarod 的回答是对这个问题更好的解决方案。
但是如果想允许扩展可能的N个值,这个方案是可以的;您只需要记住将静态变量放在 Base
而不是 Child<N>
.
中
这是一个最小的例子,展示了如何将 lambda 传递给 Base*
而实际上 lambda 将指针转换为 Child<0>
并对其进行操作:
我知道这个问题被问了很多,但我有一个特定的用例,所以我不认为它是重复的!
我有一个抽象基础class:
template<int N>
class Child;
class Base
{
public:
// Factory-like generation of children as Base
static Ptr<Base> New(int baseN)
{
if (baseN == 2) return new Child<2>();
else if (baseN == 3) return new Child<3>()
}
// Update
virtual void update() = 0;
};
我正在写 Base
的一些子代作为 class 模板(在 int 上):
template<int N>
class Child
:
public Base
{
// Member, N is not the size of matrix, more like the size of a component in matrix
Matrix<N> m_member;
public:
// Implement update
virtual void update();
// Should call the passed callable on m_member
virtual void execute(std::function<void(Matrix<N>&)>&);
};
// Force compilation of Child<N> for some values of N (of interest, including 3) here
// Then,
int baseN = 3;
Ptr<Base> obj = Base::New(baseN); // will get me a Child<3> as a Base object
auto callable = [](Matrix<3>) ->void {};
// Can I access Child<3>::m_member ??
// Can't cast to Child<baseN> (baseN is not constexpr) and don't want to
// But want to do something like:
obj->execute(callable);
// Which forwards 'callable' to the method from concrete type, probably using a cast?
简而言之,我需要从声明的 Base
对象对 m_member
进行某种访问。
最好是一种从 Base
调用 Child<N>::execute
的方法,而不会将 Base 也作为 N 上的模板。
我 tried/thought-of 的东西包括:
- 'Type erasure' 通过将它们隐藏在接口后面,但是由于
Matrix<N>
的接口强烈依赖于 N,这样做会使 classes 变得无用(想想:例如Vector<N>& Matrix<N>::diag()
) Base::New
可以做些什么来记录它创建的具体类型吗?我对此表示怀疑,因为类型不是对象。
Matrix<N>
的 编辑:(顺便说一句,这是 C++11)
所以,我无意中想出了一个办法;但我不太明白为什么下面的作品(还不精通汇编):
- 我正在为对象使用数据库(
unordered_map<string, object*>
,其中object
是每个注册对象都必须继承的 class。 - 创建 Child 后,我们将其注册到名称为
Child<N>
的数据库中。 - 然后,在应用程序级代码中,有一个
findChild<int N>
模板,它使用编译时递归来查找具体 class 是从(在运行时,通过 dynamicCasting 和测试)。当它找到它时,它可以通过静态方法 (findChild<N>::castToConcrete
) 将其转换为 - 有趣的是,如果
Child<N>
是多态的,我们可以以某种方式使用findChild<0>
来访问有问题的findChild<N>
。这迫使我们最多只能有一个 Child 对象(对于所有可能的 N),我当然可以接受。
void*
您可以在此处查看和检查最小代码示例:https://onlinegdb.com/CiGR1Fq5z
我很困惑的是 Child<0>
和其他 Child<N>
是完全不同的类型;那么我们怎样才能从一个指向另一个类型的指针访问一个人的成员呢?我很可能依赖 UB,甚至担心会有某种堆栈问题!
作为参考,我在此处包含代码以防 link 死亡。
#include <unordered_map>
#include <vector>
#include <functional>
#include <iostream>
using namespace std;
#ifndef MAX_N_VALUE
#define MAX_N_VALUE 10
#endif // !MAX_N_VALUE
// ------------------ Lib code
// A dummy number class for testing only
template <int N> struct Number { constexpr static int value = N; };
// Objects to register to the database
struct object
{
// Members
string name;
// construction/Destruction
object(const string& name) : name(name) {}
virtual ~object(){};
};
// Database of objects
struct DB
: public unordered_map<string, object*>
{
// See if we can the object of name "name" and type "T" in the DB
template <class T>
bool found(const string& name) const
{
unordered_map<string,object*>::const_iterator iter = find(name);
if (iter != end())
{
const T* ptr = dynamic_cast<const T*>(iter->second);
if (ptr) return true;
cout << name << " found but it's of another type." << endl;
return false;
}
cout << name << " not found." << endl;
return false;
}
// Return a const ref to the object of name "name" and type "T" in the DB
// if found. Else, fails
template <class T>
const T& getObjectRef(const string& name) const
{
unordered_map<string,object*>::const_iterator iter = find(name);
if (iter != end())
{
const T* ptr = dynamic_cast<const T*>(iter->second);
if (ptr) return *ptr;
cout << name << " found but it's of another type." << endl;
abort();
}
cout << name << " not found." << endl;
abort();
}
};
// Forward declare children templates
template<int N>
class Child;
// The interface class
struct Base
{
// Construction/Destruction
protected:
static unsigned counter;
Base(){}
public:
virtual ~Base() {}
// Factory-like generation of children as Base
// THIS New method needs to know how to construct Child<N>
// so defining it after Child<N>
static Base* New(int baseN, DB& db);
// Update
virtual void update() = 0;
// Call a callable on a child, the callable interface
// however is independent on N
virtual void execute(std::function<void(Base&)>& callable)
{
callable(*this);
}
};
unsigned Base::counter = 0;
// The concrete types, which we register to the DB
template<int N>
struct Child
:
public Base, public object
{
// members
vector<Number<N>> member;
// Construction/Destruction
Child() : Base(), object(string("Child") + to_string(N) + ">"), member(N, Number<N>()) {}
virtual ~Child() {}
// Test member method (Has to be virtual)
virtual vector<Number<N>> test() const
{
cout << "Calling Child<" << N << ">::test()" << endl;
return vector<Number<N>>(N, Number<N>());
}
// Implement update
virtual void update()
{
cout << "Calling Child<" << N << ">::update()" << endl;
};
};
// New Base, This can be much more sophisticated
// if static members are leveraged to register constructors
// and invoke them on demand.
Base* Base::New(int baseN, DB& db)
{
if (baseN == 2)
{
Child<2>* c = new Child<2>();
db.insert({string("Child<")+std::to_string(2)+">", c});
return c;
}
if (baseN == 3)
{
Child<3>* c = new Child<3>();
db.insert({string("Child<")+std::to_string(3)+">", c});
return c;
}
return nullptr;
}
// Finder template for registered children
template<int N>
struct findChild
{
// Concrete Type we're matching against
using type = Child<N>;
// Stop the recursion?
static bool stop;
// Compile-time recursion until the correct Child is caught
// Recursion goes UP in N values
static void* castToConcrete(const DB& db, Base* system)
{
if (N > MAX_N_VALUE) stop = true;
if (stop) return nullptr;
if (db.found<type>(string("Child<")+to_string(N)+">"))
{
type* ptr = dynamic_cast<type*>(system);
return static_cast<void*>(ptr);
}
// NOTE: This should jump to the next "compiled" child, not just N+1, but meh;
return findChild<N+1>::castToConcrete(db, system);
}
};
// Activate recursive behaviour for arbitraty N
template<int N>
bool findChild<N>::stop = false;
// Explicit specialization to stop the Compile-time recursion at a decent child
template<>
struct findChild<MAX_N_VALUE+1>
{
using type = Child<MAX_N_VALUE+1>;
static bool stop;
static void* castToConcrete(const DB& t, const Base* system)
{
return nullptr;
}
};
// Disactivate recursive behaviour for N = 11
bool findChild<MAX_N_VALUE+1>::stop = true;
// ------------------ App code
int main()
{
// Create objects database
DB db;
// --- Part 1: Application writers can't write generic-enough code
// Select (from compiled children) a new Base object with N = 2
// and register it to the DB
Base* b = Base::New(2, db);
b->update();
cout << "Access children by explicit dynamic_cast to Child<N>:" << endl;
// Get to the object through the objects DB.
// Child destructor should remove the object from DB too, nut meh again
const auto& oo = db.getObjectRef<Child<2>>("Child<2>");
cout << oo.test().size() << endl;
// --- Part 2: Application writers can write generic code if the compile
// Child<N> for their N
cout << "If Child<N> is polymorphic, we can access the correct child from findChild<0>:" << endl;
// Create a lambda that knows about db, which Base applies on itself
function<void(Base&)> lambda = [&db](Base& base) -> void {
// Cast and ignore the result
void* ptr = findChild<0>::castToConcrete(db, &base);
// Cast back to Child<0>
findChild<0>::type* c = static_cast<findChild<0>::type*>(ptr);
// Now access original Child<N> methods and members from Child<0>
cout << "Method:\n" << c->test().size() << endl;
cout << "Member:\n" << c->member.size() << endl;
};
b->execute(lambda);
return 0;
}
我使用 GCC 9 编译,选项如下:
-m64 -Wall -Wextra -Wno-unused-parameter -Wold-style-cast -Wnon-virtual-dtor -O0 -fdefault-inline -ftemplate-depth-200
您似乎希望继承不那么相关的组 类...
std::variant
(C++17) 可能更合适:
template<int N>
class Child
{
// Member, N is not the size of matrix, more like the size of a component in matrix
Matrix<N> m_member;
public:
void update();
void execute(std::function<void(Matrix<N>&)> f) { f(m_member); }
};
using Base = std::variant<Child<2>, Child<3>>;
然后:
void foo(Base& obj)
{
struct Visitor {
template <std::size_t N>
void operator()(Child<N>& c) const
{
auto callable = [](Matrix<N>) -> void {/*..*/};
c.execute(callable);
}
} visitor;
std::visit(visitor, obj);
}
要回答您的编辑,而您的可调用对象采用 Base
,您可以按如下方式链接 dynamic_cast
:
template <int N>
void foo_base(Base& b)
{
if (auto* child = dynamic_cast<Child<N>*>(&b)) {
// Job with Child<N>
std::cout << "Method:" << child->test().size() << std::endl;
std::cout << "Member:" << child->member.size() << std::endl;
}
}
template <int... Ns>
void foo_dispatch(std::integer_sequence<int, Ns...>, Base& base)
{
//(foo_base<Ns>(base), ...); // C++17
const int dummy[] = {0, (foo_base<Ns>(base), 0)...};
static_cast<void>(dummy); // Avoid warning about unused variable
}
调用类似于:
function<void(Base&)> lambda = [](Base& base) {
//foo_dispatch(std::integer_sequence<int, 2, 3>(), base);
foo_dispatch(std::make_integer_sequence<int, MAX_N_VALUE>(), base);
};
(std::integer_sequence
是C++14,但可以在C++11中实现)
Note: Jarod's answer is still a little bit better if you know possible values of N in
Child<N>
at compile-time and don't want to provide a way to extend them. Plus, of course, if you can use C++17.
这里我依赖于标准定义的“相似类型”:
4.4 Qualification conversions [conv.equal]
... trimmed ...
Two pointer types T1 and T2 are similar if there exists a type T and integer n > 0 such that:
T1 is cv(1,0) pointer to cv(1,1) pointer to ··· cv(1,n−1) pointer to cv(1,n) T
and
T2 is cv(2,0) pointer to cv(2,1) pointer to ··· cv(2,n−1) pointer to (cv2,n) T
where each cv(i,j) is
const
,volatile
,const volatile
, or nothing
同段还给出了转换表达式的条件。
简而言之,通过从 Base
继承,所有 Child<N>*
指针类型都与 Base*
相似,因此彼此相似。
现在,我们知道我们可以 static_cast
Child<N>
到 Child<0>
没有问题。
但是从 Child<0>*
访问 Child<3>
成员安全吗?
3.10 Lvalues and rvalues [basic.lval]
- If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:
the dynamic type of the object,
... trimmed ...
a type similar (as defined in 4.4) to the dynamic type of the object
你知道了,通过 Child<0>*
访问 Child<3>
的值实际上是定义的行为。
这段代码:
Base* b = Base::New(2);
b->update();
Child<2>* c1 = static_cast<Child<2>*>(b);
c1->update();
cout << c1->t.sValue << " " << c1->t.rValue << endl;
Child<0>* c2 = static_cast<Child<0>*>(b);
c2->update();
cout << c2->t.sValue << " " << c2->t.rValue << endl;
实际会输出(注意静态变量Test<N>::sValue
的值):
Calling Child<2>::update()
Calling Child<2>::update()
2 2
Calling Child<2>::update()
0 2
静态成员将始终指向 Child<0>
,正因为如此,
Jarod 的回答是对这个问题更好的解决方案。
但是如果想允许扩展可能的N个值,这个方案是可以的;您只需要记住将静态变量放在 Base
而不是 Child<N>
.
这是一个最小的例子,展示了如何将 lambda 传递给 Base*
而实际上 lambda 将指针转换为 Child<0>
并对其进行操作: