【问题标题】:How comes std::invoke does not handle function overloadsstd::invoke 怎么不处理函数重载
【发布时间】:2017-08-04 19:59:48
【问题描述】:

我正在尝试一些 C++17 功能,并遇到了 std::invoke。然后我尝试了这段代码:

#include <iostream>
#include <functional>

void f(int i) { std::cout << "fa "; }
void f(float f) { std::cout << "fb "; }

int main() {
    f(0.f);
    std::invoke(f, 0.f);
}

我收到以下错误:

main.cpp: In function 'int main()':
main.cpp:9:23: error: no matching function for call to 'invoke(<unresolved overloaded function type>, float)'
 std::invoke(f, 0.f);

In file included from main.cpp:2:0:
c:/compilers/mingw/lib/gcc/x86_64-w64-mingw32/7.1.0/include/c++/functional:77:5: note: candidate: template<class _Callable, class ... _Args> std::invoke_result_t<_Callable, _Args ...> std::invoke(_Callable&&, _Args&& ...)
 invoke(_Callable&& __fn, _Args&&... __args)
 ^~~~~~

c:/compilers/mingw/lib/gcc/x86_64-w64-mingw32/7.1.0/include/c++/functional:77:5: note:   template argument deduction/substitution failed:
main.cpp:9:23: note:   couldn't deduce template parameter '_Callable'
 std::invoke(f, 0.f);
                   ^

我不是 cpp 专业人士,这只是为了实验目的。我可能永远不会在这种情况下使用它,但为什么它无法解决函数重载?

【问题讨论】:

  • 这与invoke无关。您使用的名称 f 没有指定您的意思是哪个 f。绕过它的方式有点难看:static_cast&lt;void(*)(int)&gt;(f).
  • 谢谢,很奇怪,就像我在答案中回复的那样,我认为它会更灵活。这有点不方便。

标签: c++


【解决方案1】:

Edgar 的回答解释了为什么这会给您一个错误,但有一种比使用强制转换更好的解决方法,这需要您提供确切的函数签名(可能不知道)。

它涉及将重载决议“提升”到模板化的operator() 函数中:

std::invoke([](auto&&... xs) -> decltype(auto) { return f(std::forward<decltype(xs)>(xs)...); }, 0.f);

现在这看起来很罗嗦,确实如此,但您可以使用宏使其更可口:

#define LIFT(F) \
    ([](auto&&... xs) -> decltype(auto) { \
        return F(::std::forward<decltype(xs)>(xs)...); \
    })

std::invoke(LIFT(f), 0.f);

【讨论】:

  • 也可以使用宏来进行该演员阵容:)
  • 这太棒了,感谢您的意见。我只是写了一个长长的评论,里面有一些问题,我想我在写的时候明白了为什么。我的假设:这种提升将给定的函数指针包装在函子周围。它以这种方式工作,因为它是一个类型实例(匿名结构)而不是原始(函数)指针。两个f 函数在编译时都有不同的符号,例如f_int f_float。现在当我尝试invoke(f, ...) 时,它不知道我指的是f_int 还是f_float,即使在编辑器中符号是相同的(f)。这样对吗 ?抱歉,顺便说一句
  • @Riptide 当你编写 std::invoke(f, 0.f) 时,编译器必须尝试找出你想要函数指针指向的 f 重载,但是因为 invoke 的这个参数是一个推导出的模板参数无法决定,您会模棱两可;它不会在invoke 内部查看它是如何使用的。现在使用LIFT 宏,我们正在传递一个实际对象。 “神奇”在于该对象可以用任何东西调用,因为它有一个模板化的operator()。因为重载决议现在发生在 operator() 内部,而不是在模板参数推导期间,所以没有歧义。
  • 哦,这很有道理,最后一句话让我意识到了这一点(“在模板参数推导期间”),我很高兴也很感激能从这里的每个人身上学到很多东西。
【解决方案2】:

您甚至可以将示例简化为:

void f(int i) {}
void f(float f) {}

template<typename F>
void foo(F) {}

foo(f); // compilation error here

编译错误的原因很简单:编译器不知道你要使用哪个f重载。

为了避免这样的错误,你应该帮助编译器推断出正确的类型,使用static_cast进行显式转换:

foo(static_cast<void(*)(float)>(f));

std::invoke 也是如此:

std::invoke(static_cast<void(*)(float)>(f), 0.f);

【讨论】:

  • 乍一看以为invoke使用模板推导函数类型,它会根据给定的函数推导出签名。
猜你喜欢
  • 1970-01-01
  • 2020-09-22
  • 1970-01-01
  • 2011-01-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-19
相关资源
最近更新 更多