【问题标题】:Cannot deduce function return type无法推断函数返回类型
【发布时间】:2020-08-10 12:10:03
【问题描述】:

这不适用于 gcc-10 或 clang-10。

template <typename R, typename T>
auto invoke_function(R (&f)(T), T t) { return std::invoke(f, t); }

invoke_function(std::to_string, 42);

这适用于 gcc-10,但不适用于 clang-10。

template <typename R, typename T>
auto invoke_function(T t, R (&f)(T)) { return std::invoke(f, t); }

invoke_function(42, std::to_string);

错误消息在所有情况下都非常相似:“无法推断模板参数'R'”或“无法推断模板参数'R'”(gcc)。

目前尚不清楚此代码被拒绝的原因。由于推导出T,可以确定std::to_string的过载。对参数顺序的依赖特别烦人。它不应该工作吗?

我知道这个问题可以通过引入一个函数对象来回避:

struct to_string
{
    template<typename T> std::string operator()(T t) { return std::to_string(t); }
};

然后只使用std::invoke 就可以了。然而,这需要为每个重载集创建一个单独的函数对象。

有没有更好的办法?

【问题讨论】:

  • 不,你不能:Can I take the address of a function defined in standard library?。可能是骗子!顺便说一句,我们不能有一个通用的 lambda 而不是你提供的仿函数!不会少打字吗?
  • @JeJo 这似乎不太相关。 if we use our own overload set 存在同样的推导问题,而不能形成对标准函数的引用这一事实只是额外的破坏层。
  • IIRC,编译器应该尝试将模板参数与每个函数参数分开推导,并且只有当多个参数共享一个类型时它们匹配时才能推导成功。我很惊讶 GCC 从第二个版本开始工作。你真的需要RT 吗?有多种方法可以获取函数的返回类型,例如 using return_type = decltype(f(t));,您可以使用 SFINAE 检查函数是否可以使用 t 调用,或者使用静态资源来获取很好的错误消息。
  • @NathanOliver "有多种方法可以获取函数的返回类型" 确实,但是在获取单个函数之前,我们需要解决一个重载集。你是怎样做的?请注意,每个名为 std::to_string 的函数 都可以使用 int 参数调用。
  • @JeJo 好点。一个通用的 lambda 确实是一种改进。

标签: c++ c++17 language-lawyer template-argument-deduction


【解决方案1】:

目前尚不清楚此代码被拒绝的原因。由于推导出T,可以确定std::to_string的过载。

这不是它的工作原理。模板推导首先独立地推导每个参数/参数对 - 然后我们将所有推导组合在一起并确保它们是一致的。所以我们从42 推导出T,然后分别从std::to_string 推导出R(&amp;)(T)。但是std::to_string 的每个重载都与该模式匹配,所以我们不知道该选择哪一个。

但只有当我们可以独立地推断出每一对时,以上才是正确的。如果一个参数是不可推导的,我们跳过它,然后尝试返回并稍后填写。这就是这里的关键 - 我们重新构造推导,以便我们42 推导出 T

template <typename T>
auto invoke_function(std::string (&f)(std::type_identity_t<T>), T t) { return std::invoke(f, t); }

在这里,我们推导出Tint,现在我们从std::to_string 推导出std::string(&amp;)(int)。现在可以了,因为只有一个重载与该模式匹配。


除了现在这是未定义的行为,根据[namespace.std]/6

F 表示标准库函数([global.functions])、标准库静态成员函数或标准库函数模板的实例化。除非 F 被指定为可寻址函数,否则如果 C++ 程序显式或隐式尝试形成指向 F 的指针,则其行为未指定(可能格式错误)。

std::to_string 不是可寻址函数。

所以 真正的 更好的方法是将 to_string 包装在 lambda 中并传递:

invoke_function([](auto x){ return std::to_string(x); }, 42);

并且只需调整invoke_function 以获取任意可调用而不是特定的函数。该 lambda 包装概括为:

#define FWD(x) static_cast<decltype(x)&&>(x)
#define LIFT(name) [&](auto&&... args) noexcept(noexcept(name(FWD(args)...))) -> decltype(name(FWD(args)...)) { return name(FWD(args)...); }

invoke_function(LIFT(std::to_string), 42);

【讨论】:

  • 两个编译器给出不同的结果,为什么?看起来 gcc 按顺序推导参数,第一个参数首先使用刚刚获得的结果,然后是第二个;而 clang 不会那样做。
  • @n.'pronouns'm.不同的结果是什么?
  • 一个编译带有交换参数的函数,另一个不编译。
  • 但是没有尝试形成指向std::to_string的指针...
  • @n.'pronouns'm。很确定 gcc 接受您的代码是错误的 - 只有当它从第一个参数推导出 T 然后使用该推导的结果来帮助推导 R 时才有效。但这就是规定的规则 - 它谈到独立推导所有参数。无论如何,这很复杂,推导F 更容易:-)
猜你喜欢
  • 1970-01-01
  • 2022-08-03
  • 2023-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-22
  • 1970-01-01
相关资源
最近更新 更多