Type deduction results in ambiguous call of overloaded function.

huangapple go评论81阅读模式
英文:

Type deduction resullts in ambiguous call of overloaded function

问题

在混合类型推断和重载的情况下,您遇到了对于 Lambda 函数类型推断的一种行为,这一行为可能难以理解。

当编译这个程序时:

#include <functional>
#include <cstdlib>

int case_0(int const& x) {
    return 2*x;
}

int case_1(int& x) {
    x += 2;
    return 2*x;
}

class Test {
    public:
        Test(int const n) : n(n) {}
        int apply_and_increment(std::function<int(int const&)> f) {
            n++;
            return f(n);
        }   
        int apply_and_increment(std::function<int(int&)> f) {
            return f(n);
        }   
    private:
        int n;
};

int main() {
    Test t(1);

    auto f = [](int const& x) -> int {
        return 3*x;
    };  
    
    t.apply_and_increment(case_0);                                 // 编译失败
    t.apply_and_increment(std::function<int(int const&)>(case_0)); // 编译成功
    t.apply_and_increment(case_1);                                 // 编译成功
    t.apply_and_increment(f);                                      // 编译失败

    return EXIT_SUCCESS;
}

编译的输出是:

$ g++ -std=c++20 different_coonstness.cpp -o test 
different_coonstness.cpp: In function ‘int main()’:
different_coonstness.cpp:34:30: error: call of overloaded ‘apply_and_increment(int (&)(const int&))’ is ambiguous
   34 |  t.apply_and_increment(case_0);
      |                              ^
different_coonstness.cpp:16:7: note: candidate: ‘int Test::apply_and_increment(std::function<int(const int&)>)’
   16 |   int apply_and_increment(std::function<int(int const&)> f) {
      |       ^~~~~~~~~~~~~~~~~~~
different_coonstness.cpp:20:7: note: candidate: ‘int Test::apply_and_increment(std::function<int(int&)>)’
   20 |   int apply_and_increment(std::function<int(int&)> f) {
      |       ^~~~~~~~~~~~~~~~~~~
different_coonstness.cpp:37:25: error: call of overloaded ‘apply_and_increment(main()::<lambda(const int&)>&)’ is ambiguous
   37 |  t.apply_and_increment(f);
      |                         ^
different_coonstness.cpp:16:7: note: candidate: ‘int Test::apply_and_increment(std::function<int(const int&)>)’
   16 |   int apply_and_increment(std::function<int(int const&)> f) {
      |       ^~~~~~~~~~~~~~~~~~~
different_coonstness.cpp:20:7: note: candidate: ‘int Test::apply_and_increment(std::function<int(int&)>)’
   20 |   int apply_and_increment(std::function<int(int&)> f) {
      |       ^~~~~~~~~~~~~~~~~~~

就我所了解:

  • case_0 是模棱两可的,因为存在两个有效的类型转换,std::function<int(const int&)>std::function<int(int&)>,并且都可以应用于两个重载的函数 apply_and_increment()。这就是为什么需要显式类型转换 std::function<int(int const&)>(case_0)

  • case_1 中,唯一有效的转换是 std::function<int(int&)>,因此没有歧义。

我不太熟悉类型推断和 Lambda,所以我对 t.apply_and_increment(f) 无法通过编译感到有些惊讶。我预期函数的类型会通过 Lambda 函数的类型签名 [](int const& x) -> int 来推断,为什么 f 的类型不是 std::function<int(int const&)> 呢?

(Note: The original code provided has some HTML character escapes in angle brackets ("<" and ">") that may not be valid in C++. I've kept the escapes in the translation for consistency, but you may need to adjust them in the actual code.)

英文:

While mixing type deduction with overloading, I stumbled upon a behavior of type deduction for lambda functions that I find difficult to understand.

When compiling this program:

#include &lt;functional&gt;
#include &lt;cstdlib&gt;

int case_0(int const&amp; x) {
    return 2*x;
}

int case_1(int&amp; x) {
    x += 2;
    return 2*x;
}

class Test {
    public:
        Test(int const n) : n(n) {}
        int apply_and_increment(std::function&lt;int(int const&amp;)&gt; f) {
            n++;
            return f(n);
        }   
        int apply_and_increment(std::function&lt;int(int&amp;)&gt; f) {
            return f(n);
        }   
    private:
        int n;
};

int main() {
    Test t(1);

    auto f = [](int const&amp; x) -&gt; int {
        return 3*x;
    };  
    
    t.apply_and_increment(case_0);                                 // Fails compilation
    t.apply_and_increment(std::function&lt;int(int const&amp;)&gt;(case_0)); // Succeeds
    t.apply_and_increment(case_1);                                 // Succeeds
    t.apply_and_increment(f);                                      // Fails compilation

    return EXIT_SUCCESS;
}

The output of the compilation is:

$ g++ -std=c++20 different_coonstness.cpp -o test 
different_coonstness.cpp: In function ‘int main()’:
different_coonstness.cpp:34:30: error: call of overloaded ‘apply_and_increment(int (&amp;)(const int&amp;))’ is ambiguous
   34 |  t.apply_and_increment(case_0);
      |                              ^
different_coonstness.cpp:16:7: note: candidate: ‘int Test::apply_and_increment(std::function&lt;int(const int&amp;)&gt;)’
   16 |   int apply_and_increment(std::function&lt;int(int const&amp;)&gt; f) {
      |       ^~~~~~~~~~~~~~~~~~~
different_coonstness.cpp:20:7: note: candidate: ‘int Test::apply_and_increment(std::function&lt;int(int&amp;)&gt;)’
   20 |   int apply_and_increment(std::function&lt;int(int&amp;)&gt; f) {
      |       ^~~~~~~~~~~~~~~~~~~
different_coonstness.cpp:37:25: error: call of overloaded ‘apply_and_increment(main()::&lt;lambda(const int&amp;)&gt;&amp;)’ is ambiguous
   37 |  t.apply_and_increment(f);
      |                         ^
different_coonstness.cpp:16:7: note: candidate: ‘int Test::apply_and_increment(std::function&lt;int(const int&amp;)&gt;)’
   16 |   int apply_and_increment(std::function&lt;int(int const&amp;)&gt; f) {
      |       ^~~~~~~~~~~~~~~~~~~
different_coonstness.cpp:20:7: note: candidate: ‘int Test::apply_and_increment(std::function&lt;int(int&amp;)&gt;)’
   20 |   int apply_and_increment(std::function&lt;int(int&amp;)&gt; f) {
      |       ^~~~~~~~~~~~~~~~~~~

As far as I understand:

  • case_0 is ambiguous because there are 2 valid type conversions, std::function&lt;int(const int&amp;)&gt; and std::function&lt;int(int&amp;)&gt;, and both overloaded functions apply_and_increment() can be applied. This is why the explicit type conversion std::function&lt;int(int const&amp;)&gt;(case_0) is required.

  • in case_1, the only valid conversion is std::function&lt;int(int&amp;)&gt;, so there is no ambiguity.

I am not very familiar with type deduction and lambdas, so I am a bit surprised that t.apply_and_increment(f) fails to compile. I would expect that the type of the function would be deduced by the type signature, [](int const&amp; x) -&gt; int, in the lambda function.

Why is not f of type std::function&lt;int(int const&amp;)&gt;?

答案1

得分: 1

你对case_0case_1的重载解析理解是正确的:

  • 可以将一个非const引用分配给一个const引用,因此case_0()可以从两种使用的std::function类型中调用,因此在使用隐式转换时,需要明确指定所需的std::function类型,因此重载解析是模糊的。

  • 不能将const引用分配给非const引用,因此case_1()无法从std::function&lt;int(int const&amp;)&gt;调用,只能从std::function&lt;int(int&amp;)&gt;调用,因此在使用隐式转换时,重载解析不是模糊的。

一个独立的函数本身不是std::function对象,但可以被分配给兼容的std::function对象。

同样,lambda本身不是std::function对象,它是编译器定义的仿函数类型的实例,可以分配给兼容的std::function对象。

在这两种情况下,std::function充当代理,将其自己的参数传递给函数/lambda的参数,然后返回函数/lambda返回的任何内容。

因此,case_0f的重载解析之所以失败,原因完全相同。当编译器必须隐式case_0/f转换为std::function对象时,转换是模糊的,因为case_0/f可以从使用的两种std::function类型中调用。

英文:

Your understanding of overload resolution for case_0 and case_1 is correct:

  • A reference-to-non-const can be assigned to a reference-to-const, hence case_0() is callable from both of the std::function types being used, thus overload resolution is ambiguous when an implicit conversion is used, requiring you to specify the desired std::function type explicitly.

  • A reference-to-const cannot be assigned to a reference-to-non-const, hence case_1() is not callable from std::function&lt;int(int const&amp;)&gt;, only from std::function&lt;int(int&amp;)&gt;, thus overload resolution is not ambiguous when an implicit conversion is used.

A standalone function is not itself a std::function object, but can be assigned to a compatible std::function object.

Likewise, a lambda is not itself a std::function object, it is an instance of a compiler-defined functor type, which can be assigned to a compatible std::function object.

In both cases, std::function acts as a proxy, passing its own parameters to the function/lambda's parameters, and then returning whatever the function/lambda returns.

So, overload resolution fails for both case_0 and f for the exact same reason. When the compiler has to implicitly convert case_0/f into a std::function object, the conversion is ambiguous because case_0/f is callable from both of the std::function types being used.

huangapple
  • 本文由 发表于 2023年2月18日 09:09:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/75490545.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定