【问题标题】:std::function fails to distinguish overloaded functionsstd::function 无法区分重载函数
【发布时间】:2015-08-04 06:36:01
【问题描述】:

我试图理解为什么std::function 无法区分重载函数。

#include <functional>

void add(int,int){}

class A {};

void add (A, A){}

int main(){
        std::function <void(int, int)> func = add;
}

在上面显示的代码中,function&lt;void(int, int)&gt; 只能匹配其中一个函数,但它失败了。为什么会这样?我知道我可以通过使用 lambda 或指向实际函数的函数指针然后将函数指针存储在函数中来解决这个问题。但是为什么会失败呢?我想选择哪个功能的上下文不是很清楚吗?请帮助我理解为什么会失败,因为我无法理解为什么在这种情况下模板匹配会失败。

我在 clang 上遇到的编译器错误如下:

test.cpp:10:33: error: no viable conversion from '<overloaded function type>' to
      'std::function<void (int, int)>'
        std::function <void(int, int)> func = add;
                                       ^      ~~~
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1266:31: note: 
      candidate constructor not viable: no overload of 'add' matching
      'std::__1::nullptr_t' for 1st argument
    _LIBCPP_INLINE_VISIBILITY function(nullptr_t) : __f_(0) {}
                              ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1267:5: note: 
      candidate constructor not viable: no overload of 'add' matching 'const
      std::__1::function<void (int, int)> &' for 1st argument
    function(const function&);
    ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1269:7: note: 
      candidate template ignored: couldn't infer template argument '_Fp'
      function(_Fp,
      ^
1 error generated.

编辑 - 除了 MSalters 的回答,我在这个论坛上做了一些搜索,找到了失败的确切原因。我从 Nawaz 在这个post 的回复中得到了答案。

我在这里复制了他的回答:

    int test(const std::string&) {
        return 0;
    }

    int test(const std::string*) {
        return 0;
    }

    typedef int (*funtype)(const std::string&);

    funtype fun = test; //no cast required now!
    std::function<int(const std::string&)> func = fun; //no cast!

那么为什么std::function&lt;int(const std::string&amp;)&gt; 不能像上面的funtype fun = test 那样工作?

答案是,因为std::function 可以用任何对象初始化,因为它的构造函数是模板化的,这与您传递给std::function 的模板参数无关。

【问题讨论】:

  • 哪个编译器失败了?
  • gcc 4.8、clang 和 Visual Studio 2013。如果需要,我可以发布编译器错误,尽管它们不是很友好。
  • 编译器错误通常不友好但值得发布,奇怪的是,这在这里变得模棱两可,几乎就像 A 类的类型在某种程度上被视为与 int 相同,确实如果您将add 声明为采用doubles,错误仍然会发生?
  • 是的。当我在第二个函数中将参数类型更改为 double 时,它​​失败了。我已经编辑了问题以包含我遇到的错误。
  • 类似问题及其答案:stackoverflow.com/a/12500492/678093

标签: c++ c++11 overload-resolution std-function


【解决方案1】:

我们很清楚你打算选择哪个函数,但是编译器必须遵循 C++ 的规则,不要使用巧妙的逻辑跳跃(甚至不那么聪明的跳跃,像这样的简单情况!)

std::function的相关构造函数是:

template<class F> function(F f);

这是一个接受任何类型的模板。

C++14 标准确实限制了模板(自 LWG DR 2132 起),因此它:

除非f 对于参数类型ArgTypes... 和返回类型R 是可调用的(20.9.12.2),否则不应参与重载决议。

这意味着编译器只允许在Functorstd::function 的调用签名兼容时调用构造函数(在您的示例中为void(int, int))。从理论上讲,这应该意味着 void add(A, A) 不是一个可行的论点,因此“显然”您打算使用 void add(int, int)

然而,编译器无法测试“f is Callable for argument types ...”约束,直到它知道f的类型,这意味着它需要已经在void add(int, int)void add(A, A) 之间消除歧义之前它可以应用允许它拒绝其中一个功能的约束!

因此存在先有鸡还是先有蛋的情况,不幸的是,这意味着您需要通过准确指定要使用的 add 的哪个重载来帮助编译器,然后然后编译器可以应用约束并(相当多余地)确定它是构造函数的可接受参数。

可以想象,我们可以更改 C++,以便在这种情况下,所有重载函数都针对约束进行测试(因此我们不需要在测试之前知道要测试哪个函数)如果只有一个可行,那么就使用那个,但这不是 C++ 的工作方式。

【讨论】:

  • 非常感谢乔纳森·韦克利。在您发布此答案之前,我已经编辑了该问题,以表明我从另一个相关问题中发现了模板化构造函数。但你的答案是最接近我想要的。非常感谢您的澄清。
  • 当参数是重载集时,标准确实要求进行试推,但前提是参数是指向函数的指针、指向成员函数的指针或函数类型(参见 [temp.deduct.call] /p6) 在去除顶级 cv 限定符和引用之后。
  • @T.C.这是一个非常有趣的想法,但是在选择重载时只允许扣除失败,而不是替换失败。所以没有办法在std::function这样的通用上下文中应用该机制。
  • IMO 很糟糕,C++ 没有检查所有可能的重载。
【解决方案2】:

虽然你想要什么很明显,但问题是std::function 不能影响&amp;add 的重载分辨率。如果您要初始化一个原始函数指针 (void (*func)(int,int) = &amp;add),它确实有效。这是因为函数指针初始化是完成重载解析的上下文。目标类型是完全已知的。但是std::function 几乎可以接受任何可调用的参数。接受参数的灵活性确实意味着您不能对&amp;add 进行重载解析。 add 的多个重载可能是合适的。

显式转换将起作用,即static_cast&lt;void(*)(int, int)&gt; (&amp;add)

这可以用template&lt;typename F&gt; std::function&lt;F&gt; make_function(F*) 包裹起来,这样你就可以写auto func = make_function&lt;int(int,int)&gt; (&amp;add)

【讨论】:

  • @user3493289:我不希望它在未来发生变化。请注意,如果int 可以隐式转换为A 并返回,那么std::function&lt;int(int,int)&gt; 也可以保持A add(A,A)。正是这种灵活性使得重载不仅在技术上,而且在逻辑上也是不可能的。
  • 嗯。我写了这么多函数类,我显然忘记了std::function&lt;void(int,int)&gt; 缺少void(*)(int,int) 重载。这样的过载不会是无害的,并导致上述工作吗?
  • @Yakk:你仍然有两个 `std::function. How would you do overload resolution between the two, given &add` 的 ctor(此时它仍在尝试获取重载集的地址)?所以,不,ctor 重载决议必须在 &add 的重载决议之后发生,反之亦然,这在逻辑上是不可能的。
  • @msalt 除了它有效。我不是在问它是否有效:我知道它在实践中有效。我在问是否有缺点。
  • @user3493289:现在的 C++ 风格建议添加 std::function&lt;F&gt; function_from_ptr&lt;F&gt;(F* funptr),因为你可以写 auto f = function_from_ptr&lt;void()(int,int)&gt;(&amp;add)make_sharedmake_unique.suggests make_function 的模式,但请参阅 stackoverflow.com/questions/27825559/…
【解决方案3】:

试试:

std::function <void(int, int)> func = static_cast<void(*)(int, int)> (add);

void add(A, A)void add(int, int) 的地址显然不同。当您按名称指向函数时,编译器几乎不可能知道您需要哪个函数地址。 void(int, int) 这里不是提示。

【讨论】:

  • 嗨,我知道如何解决这个问题。但我的问题是“为什么首先有歧义”?函数参数本质上不是应该有助于选择正确函数的签名的一部分吗?
【解决方案4】:

解决这个问题的另一种方法是使用 C++14 中的通用 lambda:

int main() {
    std::function <void(int, int)> func = [](auto &&... args) { add(std::forward<decltype(args)>(args)...);
}

这将创建一个 lambda 函数,该函数将毫无歧义地解决问题。 我没有转发参数,

【讨论】:

    【解决方案5】:

    据我所知,这是 Visual Studio 的问题。

    c++11 标准(20.8.11)

    std::function synopsis
    template<class R, class... ArgTypes> class function<R(ArgTypes...)>;
    

    但 VisualStudio 没有这种专业化

    clang++ 和 g++ 完全可以重载 std::functions

    先前的答案解释了为什么 VS 不起作用,但他们没有提到这是 VS 的错误

    【讨论】:

    • 之前的答案给出的解释表明您的答案不正确。您是否有示例说明您的回复如何为已发布的答案添加内容?
    • 先前的答案解释了不良的 VS 行为,仅此而已。他们误导。就这么简单,请阅读标准
    • "clang++ 和 g++ 完全可以重载 std::functions" 不,它们不是。它不适用于任何编译器。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多