C++ 11 Lambda函数隐式转换为bool vs. std :: function

Haa*_*hii 8 c++ lambda overloading c++11

考虑这个简单的示例代码:

#include <functional>
#include <iostream>

void f(bool _switch) {
    std::cout << "Nothing really" << std::endl;
}

void f(std::function<double (int)> _f) {
    std::cout << "Nothing really, too" << std::endl;
}

int main ( int argc, char* argv[] ) {
    f([](int _idx){ return 7.9;});
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

它无法编译:

$ g++ --std=c++11 main.cpp
main.cpp: In function ‘int main(int, char**)’:
main.cpp:15:33: error: call of overloaded ‘f(main(int, char**)::<lambda(int)>)’ is ambiguous
main.cpp:15:33: note: candidates are:
main.cpp:6:6: note: void f(bool)
main.cpp:10:6: note: void f(std::function<double(int)>)
Run Code Online (Sandbox Code Playgroud)

但是,如果我用引用参数替换第二个函数,它编译得很好.如果它被const引用替换它又失败了.

所以我对这个例子有一些疑问:

  • 为什么lambda函数首先可以隐式转换为bool
  • 为什么采用std :: function引用可以解决歧义?
  • 对我来说最重要的是,我该如何避免这个问题呢?我需要第二个函数来获取一个(副本)std :: function或一个const引用.

Vau*_*ato 8

没有捕获的lambda函数可以转换为常规函数指针,然后标准转换为bool.

如果你采用std::function非const引用,那么它将它作为候选者,因为将lambda转换为a std::function需要临时,而临时不能绑定到非const引用.这只是f(bool)作为候选人留下的,所以没有歧义.

有很多方法可以避免歧义.例如,您可以先创建一个std::function变量:

std::function<double(int)> g = [](int _idx){ return 7.9;};
f(g);
Run Code Online (Sandbox Code Playgroud)

或者你可以施放lambda:

f(std::function<double(int)>([](int _idx){return 7.9;}));
Run Code Online (Sandbox Code Playgroud)

你可以有一个辅助函数:

template<typename T>
std::function<T> make_function(T *f) { return {f}; } 

int main ( int argc, char* argv[] ) {
    f(make_function([](int _idx){ return 7.9;}));
    return 0;
}  
Run Code Online (Sandbox Code Playgroud)

或者你可以抓住你感兴趣的特定功能:

int main ( int argc, char* argv[] ) {
    void (*f_func)(std::function<double(int)>) = f;
    f_func([](int _idx){ return 7.9;});
    return 0;
}
Run Code Online (Sandbox Code Playgroud)


Yak*_*ont 4

namespace details{
  template<class Sig,class=void>
  struct invoke {};
  template<class F, class...Args>
  struct invoke<F(Args...),decltype(void(
    std::declval<F>()(std::declval<Args>()...)
  ))>{
    using type=decltype(std::declval<F>()(std::declval<Args>()...));
  };
}
template<class Sig>struct invoke:details::invoke<Sig>{};

template<typename Sig, typename T, typename=void>
struct invoke_test:std::false_type {};
template<typename R, typename...Args, typename T>
struct invoke_test<R(Args...), T,
  typename std::enable_if<
    std::is_convertible<
      typename invoke<T(Args...)>::type,
      R
    >::value
  >::type
>:std::true_type {};
template<typename...Args,typename T>
struct invoke_test<void(Args...),T,
  decltype( void( typename invoke<T(Args...)>::type ) )
>:std::true_type{};
template<typename Sig, typename T>
constexpr bool invokable() {
  return invoke_test<Sig,T>::value;
}
Run Code Online (Sandbox Code Playgroud)

这给了我们一个伪概念invokable

然后我们可以像这样使用它:

template<typename F>
typename std::enable_if<invokable<double(int),F>()>::type
f(F&&){
  std::cout << "can be invoked\n";
}
void f(bool) {
  std::cout << "is bool\n";
}
Run Code Online (Sandbox Code Playgroud)

鲍勃是你叔叔。

真正的问题是 的std::function<double(int)>构造函数没有进行类似的测试,而是声称(错误地)它可以从任何东西构建。这是标准中的一个缺陷,我怀疑一旦概念标准化,这个缺陷就会得到修复。