【问题标题】:how to expand a statement multiple times based on template variable arguments如何根据模板变量参数多次扩展语句
【发布时间】:2017-11-26 18:12:06
【问题描述】:

我有以下伪代码:

template <typename... Ts>
void f(int index) {
    std::vector<std::function<void(void)>> funcs;

    funcs.push_back([](){ std::cout << typeid(type_1).name() << std::endl; });
    funcs.push_back([](){ std::cout << typeid(type_2).name() << std::endl; });
    funcs.push_back([](){ std::cout << typeid(type_3).name() << std::endl; });
    funcs.push_back([](){ std::cout << typeid(type_4).name() << std::endl; });

    funcs[index]();
}

假设Ts... 参数包包含type_1type_2type_3type_4

如何扩展参数包以实现这样的效果?我的意思是 - 如果模板包中有 4 个参数,并且在不同的 lambda 中也有不同的类型,我怎样才能获得 4 个 push_back() 调用?我不知道语法..

我真的可以在编译时得到某种这样的函数数组,所以在运行时没有 push_backs 吗?

C++17 解决方案还可以,但 C++14 最好。

【问题讨论】:

  • 请澄清一下?
  • 请说明您希望如何使用此功能。你会叫它几次吗?就一次?仅仅为了选择一个特定的索引而创建和填充一个向量似乎非常浪费。
  • 是的,我必须同意我上面的人。这似乎是对 XY 问题的过度简化。
  • std::function&lt;void&gt; 不是类型。
  • @Yakk 谢谢 - 修复它

标签: c++ c++11 c++14 variadic-templates c++17


【解决方案1】:

对于 C++17,我想是这样的

(funcs.push_back([](){ std::cout << typeid(Ts).name() << std::endl; }), ...);

或者,更好(恕我直言),使用emplace_back()

(funcs.emplace_back([](){ std::cout << typeid(Ts).name() << std::endl; }), ...);

但请记住

std::vector<std::function<void(void)>>

不是

std::vector<std::function<void>>

在 C++14(和 C++11)中,您可以通过初始化未使用数组的技巧获得类似的东西;函数可以写成

template <typename ... Ts>
void f (int index)
 {
   using unused = int[];

   std::vector<std::function<void(void)>> funcs;

   (void)unused { 0, (funcs.emplace_back([]()
      { std::cout << typeid(Ts).name() << std::endl; }), 0)... };

   funcs[index]();
 }

【讨论】:

    【解决方案2】:

    更新。

    通过重新阅读问题,我认为您只想为第 i 个类型调用一次函数。

    在这种情况下它在编译时是微不足道的:

    #include <array>
    #include <type_traits>
    #include <iostream>
    #include <string>
    
    template <class T>
    void show_type()
    {
        std::cout << typeid(T).name() << std::endl;
    }
    
    template <typename... Ts>
    void f(int index) {
    
        using function_type = void(*)();
        constexpr auto size = sizeof...(Ts);
        constexpr std::array<function_type, size> funcs = 
        {
            &show_type<Ts>...
        };
        funcs[index]();
    }
    
    int main()
    {
        for(int i = 0 ; i < 3 ; ++i)
            f<int, double, std::string>(i);
    }
    

    示例输出:

    i
    d
    NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
    

    【讨论】:

    • 如果您将它连接到迭代类型而不是值,这可能会更有帮助。您可以使用标签来携带类型并将它们提取到里面吗?或者我猜通过typeids?
    • funcs数组是否保证在编译时构建?
    • @onqtam 是的。它是 constexpr。
    • 如果你设法让this 使用 gcc 进行编译,你就是黄金(目前使用 MSVC 编译)
    • @onqtam constexpr 确实保证了变量的编译时初始化。不一定起作用。编译器确实“知道”变量的值是什么,因为它本身可以在 constexpr 常量中使用(例如作为模板参数)。编译器是否会选择复制 constexpr 对象以构建跳转表是 QoI 问题。在这方面,clang 比 gcc 好。如果变量是静态常量,gcc 实际上会生成您可能期望的代码。
    【解决方案3】:

    大概是这样的:

    template <typename... Ts>
    void f(int index) {
      int i = 0;
      auto _ = {
        (index == i++ ? ((std::cout << typeid(Ts).name() << std::endl) , 0) : 0) ...
      };
    }
    

    Demo

    【讨论】:

      【解决方案4】:

      如果您只想为模板参数包中的nth 类型做一些事情,其中​​n 是一个运行时变量,那么vector + function 方法并不是很好。最好在其中添加一个索引序列并折叠:

      template <typename T> struct tag_t { using type = T; };
      template <typename T> constexpr inline tag_t<T> tag{};
      
      template <class F, size_t... Is, typename... Tags>
      void match(F f, size_t i, std::index_sequence<Is...>, Tags... tags) {
          auto inner = [&](auto tag) { f(tag); return true; };
          bool matched = ((i == Is && inner(tags)) || ...);
          if (!matched) {
              // failure case?
          }
      }
      
      template <typename... Ts, class F>
      void match(F f, size_t i) {
          return match(f, i, std::index_sequence_for<Ts...>(), tag<Ts>... );
      }
      
      template <typename... Ts>
      void foo(int index) {
          match<Ts...>([](auto tag){
              std::cout << typeid(typename decltype(tag)::type).name() << std::endl;
          }, index);
      }
      

      这个结构允许你添加一个失败的情况,你可以用一些特殊的类型调用传入的函数:

      struct failure { };
      
      template <class F, size_t... Is, typename... Tags>
      void match(F f, size_t i, std::index_sequence<Is...>, Tags... tags) {
          auto inner = [&](auto tag) { f(tag); return true; };
          bool matched = ((i == Is && inner(tags)) || ...);
          if (!matched) {
              f(failure{});
          }
      }
      
      template <typename... Ts>
      void foo(int index) {
          match<Ts...>(overload(
              [](auto tag){
                  std::cout << typeid(typename decltype(tag)::type).name() << std::endl;
              },
              [](failure ) { /* ... */ }
          ), index);
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-05-28
        • 2013-04-23
        • 2014-04-12
        • 1970-01-01
        • 1970-01-01
        • 2014-10-30
        • 1970-01-01
        相关资源
        最近更新 更多