在模板参数列表中使用参数包

Use parameter pack in template parameters list

提问人:Amir Hossein Sarebani 提问时间:8/24/2023 最后编辑:Amir Hossein Sarebani 更新时间:8/24/2023 访问量:69

问:

我想编写一个抽象类,该类获取一些具有特定签名的函数作为模板参数,并根据输入数据的解析阶段运行它们。这是我写的代码:Pipeline

#include <array>

enum class ParseState
{
  EXTRACTED,   /**< EXTRACTED */
  IN_PROGRESS, /**< IN_PROGRESS */
  REJECTED,    /**< REJECTED */
};

struct ParseStage
{
  std::size_t stage;

public:
  ParseStage() : stage(0)
  {}

  std::size_t get_stage() const
  {
    return stage;
  }

  std::size_t next_stage()
  {
    return ++stage;
  }

  void reset()
  {
    stage = 0;
  }
};

template <typename InputDataType>
using ParseResult = std::pair<ParseState, InputDataType>;

template <typename InputDataType, typename ParserState, typename... Args>
using StageFunctionRefType = ParseResult<InputDataType> (&)(const InputDataType &, ParserState &, Args... args);

template <typename InputDataType, typename ParserState, typename... Args,
          StageFunctionRefType<InputDataType, ParserState, Args...>... StageFunctions>
class Pipeline
{
  static constexpr std::array Stages{ StageFunctions... };

public:
  static ParseResult<InputDataType> feed(const InputDataType &input_data, ParserState &parser_state,
                                         ParseStage &parse_stage, Args... args)
  {
    if (parser_state.parse_state == ParseState::REJECTED)
      return { ParseState::REJECTED, input_data };

    auto remaining_data = input_data;

    while (parse_stage.get_stage() < Stages.size() && remaining_data.size())
    {
      const auto parse_result = Stages[parse_stage.get_stage()](remaining_data, parser_state, args...);
      remaining_data = parse_result.second;
      if (parse_result.first == ParseState::EXTRACTED)
        parse_stage.next_stage();
      else if (parse_result.first == ParseState::REJECTED)
        return parse_result;
    }

    if (parse_stage.get_stage() == Stages.size())
      return { ParseState::EXTRACTED, remaining_data };

    return { ParseState::IN_PROGRESS, remaining_data };
  }
};

但我收到一条错误消息:parameter pack 'Args' must be at the end of the template parameter list

我该如何解决这个问题?或者您还有其他解决方案吗? 性能对我来说真的很重要,所以我想避免使用虚拟或。std::function

C++ 模板 variadic-templates 参数包

评论

0赞 yeputons 8/24/2023
template<typename StageFunction, StageFunction... StageFunctions>,(可选)添加一个概念/约束,以完全适用于某些参数。static_assertStageFunctionStageFunctionRefType
0赞 Amir Hossein Sarebani 8/24/2023
@yeputons 我不明白你的意思。你能为我写一下吗,或者请解释一下。

答:

1赞 Amir Kirsh 8/24/2023 #1

基于 如何在可变参数模板中拥有多个参数包? 实现目标的关键是使用模板专用化。

您正在寻找类似的东西

template <typename... Args>
using foo = void (*)(Args... args);

template <auto...>
class FoosHolder;

template <typename... Args,
          foo<Args...>... Foos>
class FoosHolder<Foos...>
{
public:
    static void call(Args... args) {
        return (..., Foos(args...));
    }
};

使用以下使用示例:

void foo1(int i) {
    std::cout << i << '\n';
}

void foo2(int i, double d){
    std::cout << "i = " << i << ", d = " << d << '\n';
}

void foo3(int i, double d){
    std::cout << "i * d = " << i * d << '\n';
}

int main() {
    FoosHolder<foo1> f1;
    FoosHolder<foo2, foo3> f2;
    f1.call(7);
    f2.call(2, 2.5);
}

预期输出为:

7
i = 2, d = 2.5
i * d = 5

但是,上面的代码有点局限性。因为它不允许 lambda 作为 FoosHolder 的函数。这就提出了一个问题,为什么我们需要首先声明。一个可能更好的方法是使用一个简单的模板,而不需要任何专业化,就像这样foo

template <auto... Foos>
class FoosHolder
{
public:
    template <typename... Args>
    static void call(Args... args) {
        return (..., Foos(args...));
    }
};

现在我们也可以做(使用 C++20):

FoosHolder<[](){std::cout << "lambda!\n";}> f3;
f3.call();

FoosHolder<[](char c){std::cout << "lambda with params!" << c;}> f4;
f4.call('\n');

这种方法有一个小缺点(恕我直言,这是一个合理的缺点),因为它允许创建一个 FoosHolder,其函数需要不同数量的参数,这不会在创建类型时被捕获,而只有在调用操作时才会被捕获:call

FoosHolder<foo1, foo3> f5; // passes compilation
f5.call(3, 1.5); // fails compilation only here

评论

0赞 Amir Hossein Sarebani 8/24/2023
谢谢你的回答。它真的帮助了我。当我更改为它无法编译时,我有一个问题。你能说为什么吗?template <auto... Foos> template<typename... Foos>
0赞 Amir Kirsh 8/24/2023
因为发送到 FoosHolder 的函数不是类型,所以它们是实际函数(即非类型模板参数)。所以不适合这里,这是救援的地方,因为它可以表示任何类型的非类型模板参数:-)typenameauto
0赞 Jarod42 8/24/2023
@AmirKirsh:“有点局限性。它不允许 lambda 作为 FoosHolder 的函数。将非泛型、非捕获 lambda 转换为函数指针只是一个额外的字符 (/)。“改进”代码不/无法处理捕获。所以唯一的一点是通用 lambda。+*