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...>;

That's it.