代码之家  ›  专栏  ›  技术社区  ›  Ghita

重载解析不明确[重复]

c++
  •  1
  • Ghita  · 技术社区  · 6 年前

    考虑以下简单的示例代码:

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

    但是,如果我用一个引用参数替换第二个函数,它就可以编译了。同样,如果它被常量引用替换,它将失败。

    关于这个例子,我有一些问题:

    • 为什么lambda函数可以隐式转换为 bool 首先?
    • 为什么使用std::函数引用可以解决歧义?
    • 对我来说最重要的是,我怎样才能避免这个问题?我需要第二个函数来获取(std::function的副本)或它的常量引用。
    0 回复  |  直到 11 年前
        1
  •  9
  •   Vaughn Cato    11 年前

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

    如果你接受 std::function 由于将lambda转换为 std::函数 需要临时引用,临时引用不能绑定到非常量引用。就这样离开了 f(bool) 作为一名候选人,所以没有含糊不清的地方。

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

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

    或者你可以投下兰姆达:

    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;
    }
    
        2
  •  4
  •   Yakk - Adam Nevraumont    10 年前
    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)> 的构造函数没有进行类似的测试,而是声称(错误地)它可以从任何东西构造出来。这是标准中的一个缺陷,我怀疑一旦概念标准化,这个缺陷就会被修复。

        3
  •  3
  •   example    11 年前

    您可以通过创建一个helper类来摆脱隐式转换

    #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 或者在helper类中添加相应的构造函数/强制转换。

        4
  •  2
  •   Community CDub    7 年前

    还有一个选择 Vaughn Cato's answer :

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

    现在,第二个重载是一个模板,所以它被选为lambda(或任何东西),第一个重载被选为 bool .这么说 f 并不比需要的复杂。

    但是,这样做的一个问题是,如果您想添加更多重载,另一个问题是,只有在与 布尔 .