【问题标题】:C++ distinguish Lambdas from Function pointers inside a vectorC++ 将 Lambda 与向量内的函数指针区分开来
【发布时间】:2015-10-01 16:55:08
【问题描述】:

我正在编写一个小的事件管理器类,其中我将一些函数指针存储在一个向量中。我使用std::function<void(int)> 作为向量类型,我测试了在其中插入 lambdas 和普通函数,它可以工作:

void t(int p){
  /*things*/
}
[...]
event.bind([](int p){/*things*/});
event.bind(t);

现在,(在某些时候我需要删除 lambdas 而不是函数,)我的问题是:

是否可以区分 lambda 和函数?如果是,如何?

编辑:
既然我澄清了我的疑问,这个问题就变成了标题所说的

【问题讨论】:

  • 什么意思,删除 lambdas?为什么 lambda 的处理方式与函数指针不同?你的设计看起来……错了。
  • 您可以改为将std::pairbool 作为第二个参数,bool 将表明它是函数指针还是 lambda。
  • 如果你想区分 lambda 和函数指针,不要把它们放在一个向量中以同样的方式对待它们......(PS:+1 对 Yakk 的评论,如何:删除lambda 的?)
  • 我认为 OP 意味着他们想从向量中“删除”它们...
  • @RomanHudylko ...这个指针在哪里?我没有看到指针。如果您以某种方式创建了一个指向 lambda 的指针(无论出于何种原因),如果您删除了指向自动存储中某些内容的指针(未使用 new 分配),则您的程序格式错误。 Lambda 不是指针,Lambda 是带有operator() 的匿名类型。闭包就是这种类型的实例。您可以使用类型擦除的奇迹将闭包存储在 std::function 按值中。

标签: c++ function c++11 lambda


【解决方案1】:

真正的答案是:你不想这样做。如果你真的想知道原始类型,它就破坏了类型擦除函子的意义,以防万一。这只是闻起来像糟糕的设计。


您可能正在寻找的是std::function::target_type。这是一种提取function 对象正在存储的目标函数的底层type_info 的方法。每个type_info 都有一个name(),它可以被分解。请注意,这是一个非常深的兔子洞,您基本上必须对各种奇怪的边缘情况进行硬编码。多亏了 Yakk 的爱心帮助,我一直在做。

不同的编译器会以不同的方式处理它们的 lambda 名称,因此这种方法甚至不像可移植性。快速检查显示clang 抛出$gcc 抛出{lambda...#d},因此我们可以尝试通过编写如下代码来利用它:

bool is_identifier(std::string const& id) {
    return id == "(anonymous namespace)" ||
        (std::all_of(id.begin(), id.end(),
        [](char c){
            return isdigit(c) || isalpha(c) || c == '_';
        }) && !isdigit(id[0]));
}

bool is_lambda(const std::type_info& info)
{
    std::unique_ptr<char, decltype(&std::free)> own {
        abi::__cxa_demangle(info.name(), nullptr, nullptr, nullptr),
        std::free
    };

    std::string name = own ? own.get() : info.name();

    // drop leading namespaces... if they are valid namespace names
    std::size_t idx;
    while ((idx = name.find("::")) != std::string::npos) {
        if (!is_identifier(name.substr(0, idx))) {
            return false;
        }
        else {
            name = name.substr(idx+2);
        }
    }

#if defined(__clang__)
    return name[0] == '$';
#elif defined(__GNUC__)
    return name.find("{lambda") == 0;
#else
    // I dunno?
    return false;
#endif
}

然后将其放入您的标准擦除删除习语中:

void foo(int ) { }
void bar(int ) { }
long quux(long x) { return x; }

int main()
{
    std::vector<std::function<void(int)>> v;

    v.push_back(foo);
    v.push_back(bar);
    v.push_back(quux);
    v.push_back([](int i) { std::cout << i << '\n';});

    std::cout << v.size() << std::endl; // prints 4

    v.erase(
        std::remove_if(
            v.begin(),
            v.end(),
            [](std::function<void(int)> const& f){
                return is_lambda(f.target_type());
            }),
        v.end()
        );

    std::cout << v.size() << std::endl; // prints 3
}

【讨论】:

  • template&lt;class F&gt; struct evil { F f; template&lt;class...Ts&gt;auto operator()(Ts&amp;&amp;...ts)const{return f(std::forward&lt;Ts&gt;(ts)...);} }; template&lt;class F&gt; evil&lt;std::decay_t&lt;F&gt;&gt; make_evil( F&amp;&amp; f ) { return {std::forward&lt;F&gt;(f)}; } v.push_back( make_evil( [](int x){ std::cout &lt;&lt; "evil\n"; } ) ); 将被您的代码删除,尽管它不是 lambda。 ;)
  • @Yakk 做得很好:) 是的,我想is_lambda 检查必须更加精致。像 clang 一样,它必须是 some::namespaces::then::$_\d+
  • 你也可以用函数指针来做,但有点乱:template&lt;class F&gt; void foo(int); auto blah=[]{}; v.push_back( foo&lt;decltype(blah)&gt; ); 做到了(我认为第一次尝试实际上没有用)
  • @Yakk 好了,evil 已处理 :)
  • namespace { auto blah = [](int){}; }?我想我的意思是,一旦你开始解析类型 id,你最好实际解析类型 id。哦,它适用于英特尔编译器吗?微软? ;)
【解决方案2】:

不,不是一般的。

std::function&lt;void(int)&gt; 可以存储一个函数指针,指向可以通过传递单个右值 int 来调用的任何函数。这样的签名数不胜数。

lambda 的类型是每个声明的唯一匿名类。两个不同的 lambda 不共享任何类型关系。

您可以确定 std::function&lt;void(int)&gt; 存储特定类型的变量,但在函数指针和 lambda 情况下,可以考虑在 std::function 中存储的不同类型的数量不限。而且你只能测试“完全等于一个类型”。

您可以访问类型 ID 信息,但那里没有可移植的表示,并且通常将该信息用于身份匹配(和相关)或调试以外的任何事情是一个坏主意。

现在,这个问题的受限版本(你能知道std::function&lt;void(int)&gt; 是否包含void(*)(int) 类型的函数指针)很容易解决。但总的来说,这样做仍然是一个坏主意:首先,因为它很微妙(代码远离你使用它的点,比如对函数签名的细微更改,可能会破坏事情),其次,检查和改变你的行为基于存储在std::function 中的类型仅应在极端情况下完成(通常涉及将代码从使用void* 样式回调更新为std::function 样式回调)。

【讨论】:

    【解决方案3】:

    无论是函数指针还是 lambda,它最终都会在 vector 中以 std::function&lt;void(int)&gt; 结尾。然后,std::function&lt;void(int)&gt; 负责管理函数指针或 lambda,而不是您的。这意味着,您只需从vector 中删除您想要的std::function&lt;void(int)&gt;s。 std::function&lt;void(int)&gt; 的析构函数知道如何正确地做事。在您的情况下,这将与函数指针无关并调用 lambdas 的析构函数。 std::function&lt;void(int)&gt; 使您能够以一种良好而统一的方式对待不同的事物。不要滥用它。

    【讨论】:

      【解决方案4】:

      注意:这个答案的前提是有一个有限的、不同数量的函数签名可以分配为事件处理程序。它假定为任何旧函数分配错误的签名是错误的。

      您可以使用std::function::target 来确定哪些是函数指针,并通过消除过程找出哪些必须是lambdas

      void func1(int) {}
      void func2(double) {}
      
      int main()
      {
      
          std::vector<std::function<void(int)>> events;
      
          events.push_back(func1);
          events.push_back([](int){});
          events.push_back(func2);
      
          for(auto& e: events)
          {
              if(e.target<void(*)(int)>())
                  std::cout << "funcion int" << '\n';
              else if(e.target<void(*)(double)>())
                  std::cout << "funcion double" << '\n';
              else
                  std::cout << "must be lambda" << '\n';
          }
      }
      

      这是因为std::function::target 在参数类型不匹配时返回一个空指针

      单变量示例:

      void func(int) {}
      
      int main()
      {
      
          std::function<void(int)> f = func;
      
          if(f.target<void(*)(int)>())
              std::cout << "not a lambda" << '\n';
      }
      

      【讨论】:

      • 你能给我举个例子来说明这个应用于单个变量std::function&lt;void(int)&gt;吗?因为我试图实现你的代码,但我没有成功......
      • void problem(double){}; events.push_back(problem); 不会被您的上述代码检测为函数指针,但会编译。
      • @Yakk 这就是我说“消除过程”的原因,但不是很清楚。我更新了代码以使其更清晰。
      • @Galik 什么?如果将double 分配给int array[10],则它变为int。如果您将double(*)(int) 分配给std::function&lt;void(int)&gt;,如果您查看target(),它仍然是 double(*)(int)...
      • @Galik std::function&lt;void(int)&gt; 可以存储指向与传递单个 int 调用兼容的任何函数的函数指针。问题是“你能区分一个存储函数指针和一个存储 lambda”。你回答的不是“你能区分一个存储函数指针的函数指针,该函数指针正好是 void(int) 类型的函数和其他任何东西”。
      猜你喜欢
      • 2015-11-25
      • 1970-01-01
      • 2011-08-04
      • 2012-02-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-05
      相关资源
      最近更新 更多