C++11 Lambda functions implicit conversion to bool vs. std::function

前端 未结 4 733
时光取名叫无心
时光取名叫无心 2021-01-12 05:36

Consider this simple example code:

#include 
#include 

void f(bool _switch) {
    std::cout << \"Nothing really\" &l         


        
相关标签:
4条回答
  • 2021-01-12 06:10

    You can get rid of the implicit conversion by creating a helper class

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

    Should you ever want to call f with eg. a pointer and expect it to call the first overload you will have to cast it to either bool or add a corresponding constructor / cast to the helper class though.

    0 讨论(0)
  • 2021-01-12 06:12

    A lambda function with no capture can be converted to a regular function pointer, which then has a standard conversion to a bool.

    If you take the std::function by non-const reference, then that eliminates it as a candidate, since converting the lambda to a std::function requires a temporary, and a temporary cannot bind to a non-const reference. That just leaves f(bool) as a candidate, so there is no ambiguity.

    There are many ways you could avoid the ambiguity. For example, you could create a std::function variable first:

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

    or you could cast the lambda:

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

    You could have a helper function:

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

    or you could grab the particular function you are interested in:

    int main ( int argc, char* argv[] ) {
        void (*f_func)(std::function<double(int)>) = f;
        f_func([](int _idx){ return 7.9;});
        return 0;
    }
    
    0 讨论(0)
  • 2021-01-12 06:20

    One more option to Vaughn Cato's answer:

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

    Now the second overload is a template, so it is chosen for a lambda (or anything), and the first is chosen for bool. So calling f is no more complex than needed.

    But, one problem with that is if you want to add more overloads, and another is that the first overload will only be called if there is an exact match to bool.

    0 讨论(0)
  • 2021-01-12 06:30
    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;
    }
    

    this gives us a pseudo-concept invokable.

    We can then use it like:

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

    and bob is your uncle.

    The real problem is that std::function<double(int)> 's constructor does not do a similar test, and instead claims (falsely) that it can be constructed from anything at all. This is a flaw in the standard, one I suspect will be fixed once concepts are standardized.

    0 讨论(0)
提交回复
热议问题