C++:使用可变数据结构的成员函数调用转发
C++: Member function call forwarding using variadic data structure
我正在尝试创建一个 class(类似于 std::tuple
),它可以保存对不同类型对象的可变数量的引用,并且可以将对特定成员函数的调用转发到它的所有成分。这是一个例子:
// g++ Test7.C -std=c++17 -o Test7
#include <iostream>
struct P1 { void operator()(void) const { std::cout<<"P1 "; } };
struct P2 { void operator()(void) const { std::cout<<"P2 "; } };
struct P3 { void operator()(void) const { std::cout<<"P3 "; } };
template <class... EmitterList> struct Emitters
{
inline void operator()(void) const { std::cout<<std::endl; }
};
template <class Emitter, class... EmitterList>
class Emitters<Emitter, EmitterList...> : public Emitters<EmitterList...>
{
public:
using Base = Emitters<EmitterList...>;
Emitters(const Emitter & e, const EmitterList & ... eList)
: emitter(e), Base(eList...) {}
inline void operator()(void) const { emitter(); this->Base::operator()(); }
private:
const Emitter & emitter;
};
int main(void)
{
P1 p1;
P2 p2;
P3 p3;
Emitters e0; e0(); // works
//Emitters e1{p1}; e1(); // does not work
//Emitters e2{p1,p2}; e2(); // does not work
//Emitters e3{p1,p2,p3}; e3(); // does not work
return 0;
}
期望e1()
会输出“P1\n”(调用p1()
),e2()
会输出“P1 P2\n”(调用p1(); p2()
),e3()
将输出“P1 P2 P3 \n”(调用 p1(); p2(); p3();
)。但是代码中有些地方不对:它为 e0
编译但不为其他情况编译。您能否帮助我了解我在这里做的事情以及如何解决它?
非常感谢您的帮助!
看起来您正在尝试 partial specialization,而且您已经接近了。而不是这个
template <class... EmitterList> struct Emitters
{
inline void operator()(void) const { std::cout<<std::endl; }
};
考虑
template <class... Ts>
struct Emitters;
template <>
struct Emitters<> {
void operator()(void) const { std::cout<<std::endl; }
};
template <class Emitter, class... EmitterList>
class Emitters<Emitter, EmitterList...> : public Emitters<EmitterList...>
{
// Unmodified from your example ...
}
这里的第一个声明说“有一个东西叫做 Emitters
,但我还没有告诉你它是什么”。这是一个不完整的类型定义。然后我们定义两种情况:一种带有Emitters<>
(空的基本情况),另一种(递归情况)用于我们至少有一个参数的情况。
这些class现在可以编译,但是使用起来不方便。由于语言的复杂性,构造函数调用不如常规函数那样推断模板参数。所以像这样的一行
Emitters e1{p1};
还不够好。我们可以明确
Emitters<P1> e1{p1};
但这只是毫无意义的冗长程度。相反,我们可以查看具有相同问题的 std::tuple
。标准库定义了一个函数 std::make_tuple
,它除了调用元组构造函数之外什么都不做。但由于它是一个普通函数,我们得到模板参数推导。
template <typename... Ts>
Emitters<Ts...> make_emitters(const Ts&... args) {
return Emitters<Ts...>(args...);
}
现在我们可以在不指定模板参数的情况下创建实例
Emitters e0 = make_emitters(); e0();
Emitters e1 = make_emitters(p1); e1();
Emitters e2 = make_emitters(p1,p2); e2();
Emitters e3 = make_emitters(p1,p2,p3); e3();
现在您的示例应该可以正常工作了。
关于构造函数的最后一点说明。您应该始终在任何字段之前首先列出基础 class 构造函数。使用 -Wall
编译会警告您这一点。编译器会在内部为您重新排序它们,因此最佳做法是将它们以正确的顺序开始。而不是
Emitters(const Emitter & e, const EmitterList & ... eList)
: emitter(e), Base(eList...) {}
考虑
Emitters(const Emitter & e, const EmitterList & ... eList)
: Base(eList...), emitter(e) {} // Note: Base and emitter are switched
完整示例:
#include <iostream>
struct P1 {
void operator()(void) const {
std::cout << "P1 ";
}
};
struct P2 {
void operator()(void) const {
std::cout << "P2 ";
}
};
struct P3 {
void operator()(void) const {
std::cout << "P3 ";
}
};
template <class... Ts>
struct Emitters;
template <>
struct Emitters<> {
void operator()(void) const {
std::cout << std::endl;
}
};
template <class T, class... Ts>
struct Emitters<T, Ts...> : public Emitters<Ts...> {
public:
using Base = Emitters<Ts...>;
Emitters(const T& e, const Ts&... eList)
: Base(eList...), emitter(e) {}
void operator()(void) const {
emitter();
this->Base::operator()();
}
private:
const T& emitter;
};
template <typename... Ts>
Emitters<Ts...> make_emitters(const Ts&... args) {
return Emitters<Ts...>(args...);
}
int main(void) {
P1 p1;
P2 p2;
P3 p3;
Emitters e0 = make_emitters(); e0();
Emitters e1 = make_emitters(p1); e1();
Emitters e2 = make_emitters(p1,p2); e2();
Emitters e3 = make_emitters(p1,p2,p3); e3();
return 0;
}
另一个答案很好,但您实际上不需要任何这些更改。部分专业化清理很好而且优雅,我建议使用它,但您的原始代码也可以。 make_...
函数很笨重,在现代 C++ 中几乎不需要。你需要的是一份推演指南:
template <typename ... P>
Emitters(const P& ... ) -> Emitters<P...>;
我正在尝试创建一个 class(类似于 std::tuple
),它可以保存对不同类型对象的可变数量的引用,并且可以将对特定成员函数的调用转发到它的所有成分。这是一个例子:
// g++ Test7.C -std=c++17 -o Test7
#include <iostream>
struct P1 { void operator()(void) const { std::cout<<"P1 "; } };
struct P2 { void operator()(void) const { std::cout<<"P2 "; } };
struct P3 { void operator()(void) const { std::cout<<"P3 "; } };
template <class... EmitterList> struct Emitters
{
inline void operator()(void) const { std::cout<<std::endl; }
};
template <class Emitter, class... EmitterList>
class Emitters<Emitter, EmitterList...> : public Emitters<EmitterList...>
{
public:
using Base = Emitters<EmitterList...>;
Emitters(const Emitter & e, const EmitterList & ... eList)
: emitter(e), Base(eList...) {}
inline void operator()(void) const { emitter(); this->Base::operator()(); }
private:
const Emitter & emitter;
};
int main(void)
{
P1 p1;
P2 p2;
P3 p3;
Emitters e0; e0(); // works
//Emitters e1{p1}; e1(); // does not work
//Emitters e2{p1,p2}; e2(); // does not work
//Emitters e3{p1,p2,p3}; e3(); // does not work
return 0;
}
期望e1()
会输出“P1\n”(调用p1()
),e2()
会输出“P1 P2\n”(调用p1(); p2()
),e3()
将输出“P1 P2 P3 \n”(调用 p1(); p2(); p3();
)。但是代码中有些地方不对:它为 e0
编译但不为其他情况编译。您能否帮助我了解我在这里做的事情以及如何解决它?
非常感谢您的帮助!
看起来您正在尝试 partial specialization,而且您已经接近了。而不是这个
template <class... EmitterList> struct Emitters
{
inline void operator()(void) const { std::cout<<std::endl; }
};
考虑
template <class... Ts>
struct Emitters;
template <>
struct Emitters<> {
void operator()(void) const { std::cout<<std::endl; }
};
template <class Emitter, class... EmitterList>
class Emitters<Emitter, EmitterList...> : public Emitters<EmitterList...>
{
// Unmodified from your example ...
}
这里的第一个声明说“有一个东西叫做 Emitters
,但我还没有告诉你它是什么”。这是一个不完整的类型定义。然后我们定义两种情况:一种带有Emitters<>
(空的基本情况),另一种(递归情况)用于我们至少有一个参数的情况。
这些class现在可以编译,但是使用起来不方便。由于语言的复杂性,构造函数调用不如常规函数那样推断模板参数。所以像这样的一行
Emitters e1{p1};
还不够好。我们可以明确
Emitters<P1> e1{p1};
但这只是毫无意义的冗长程度。相反,我们可以查看具有相同问题的 std::tuple
。标准库定义了一个函数 std::make_tuple
,它除了调用元组构造函数之外什么都不做。但由于它是一个普通函数,我们得到模板参数推导。
template <typename... Ts>
Emitters<Ts...> make_emitters(const Ts&... args) {
return Emitters<Ts...>(args...);
}
现在我们可以在不指定模板参数的情况下创建实例
Emitters e0 = make_emitters(); e0();
Emitters e1 = make_emitters(p1); e1();
Emitters e2 = make_emitters(p1,p2); e2();
Emitters e3 = make_emitters(p1,p2,p3); e3();
现在您的示例应该可以正常工作了。
关于构造函数的最后一点说明。您应该始终在任何字段之前首先列出基础 class 构造函数。使用 -Wall
编译会警告您这一点。编译器会在内部为您重新排序它们,因此最佳做法是将它们以正确的顺序开始。而不是
Emitters(const Emitter & e, const EmitterList & ... eList)
: emitter(e), Base(eList...) {}
考虑
Emitters(const Emitter & e, const EmitterList & ... eList)
: Base(eList...), emitter(e) {} // Note: Base and emitter are switched
完整示例:
#include <iostream>
struct P1 {
void operator()(void) const {
std::cout << "P1 ";
}
};
struct P2 {
void operator()(void) const {
std::cout << "P2 ";
}
};
struct P3 {
void operator()(void) const {
std::cout << "P3 ";
}
};
template <class... Ts>
struct Emitters;
template <>
struct Emitters<> {
void operator()(void) const {
std::cout << std::endl;
}
};
template <class T, class... Ts>
struct Emitters<T, Ts...> : public Emitters<Ts...> {
public:
using Base = Emitters<Ts...>;
Emitters(const T& e, const Ts&... eList)
: Base(eList...), emitter(e) {}
void operator()(void) const {
emitter();
this->Base::operator()();
}
private:
const T& emitter;
};
template <typename... Ts>
Emitters<Ts...> make_emitters(const Ts&... args) {
return Emitters<Ts...>(args...);
}
int main(void) {
P1 p1;
P2 p2;
P3 p3;
Emitters e0 = make_emitters(); e0();
Emitters e1 = make_emitters(p1); e1();
Emitters e2 = make_emitters(p1,p2); e2();
Emitters e3 = make_emitters(p1,p2,p3); e3();
return 0;
}
另一个答案很好,但您实际上不需要任何这些更改。部分专业化清理很好而且优雅,我建议使用它,但您的原始代码也可以。 make_...
函数很笨重,在现代 C++ 中几乎不需要。你需要的是一份推演指南:
template <typename ... P>
Emitters(const P& ... ) -> Emitters<P...>;