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

时间:2014-04-18 21:27:37

标签: c++ c++11 lambda overloading

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

#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;
}

无法编译:

$ 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)>)

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

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

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

4 个答案:

答案 0 :(得分:8)

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

如果你通过非const引用获取std::function,那么它将它作为候选者消除,因为将lambda转换为std::function需要临时,而临时不能绑定到非{ const参考。只留下f(bool)作为候选人,所以没有歧义。

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

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

或者你可以施放lambda:

f(std::function<double(int)>([](int _idx){return 7.9;}));

你可以有一个辅助功能:

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

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

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

答案 1 :(得分: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;
}

这为我们提供了一个伪概念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";
}

鲍勃是你的叔叔。

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

答案 2 :(得分:3)

您可以通过创建辅助类

来摆脱隐式转换
#include <functional>
#include <iostream>

struct Boolean { 
    bool state; 
    Boolean(bool b):state(b){} 
    operator bool(){ return state; } 
};
void f(Boolean _switch) {
    std::cout << "Nothing really " << _switch << 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;});
    f(true);
    return 0;
}

你是否想要用例如f来打电话。一个指针,并期望它调用第一个重载,你必须将其转换为bool或者将相应的构造函数/强制转换为辅助类。

答案 3 :(得分:2)

Vaughn Cato's answer的另一个选项:

template<typename F>
void f(F _f) {
    std::cout << "Nothing really, too: " << _f(3) << std::endl;
}

现在第二个重载是一个模板,因此它被选择用于lambda(或任何东西),第一个被选择用于bool。所以调用f并不比需要复杂。

但是,一个问题是如果你想添加更多的重载,另一个问题是只有与bool完全匹配时才会调用第一个重载。