无法推断函数 return 类型

Cannot deduce function return type

这不适用于 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 就可以了。然而,这需要为每个重载集创建一个单独的函数对象。

有没有更好的方法?

It isn't clear why this code is rejected. Since T is deduced, the overload of std::to_string can be determined.

实际情况并非如此。模板推导首先推导每个 parameter/argument 对 独立 - 然后我们将所有推导放在一起并确保它们是一致的。所以我们从 42 推导出 T,然后,我们分别从 std::to_string 推导出 R(&)(T)。但是 std::to_string 的每个重载都匹配该模式,所以我们不知道该选择哪个。

但只有当我们可以独立地推导出每一对时,以上内容才成立。如果一个参数是non-deducible,我们跳过它,然后尝试返回并稍后填充它。这就是这里的关键 - 我们重组推导,这样我们 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(&)(int)。现在有效,因为只有一个重载匹配该模式。


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

Let F denote a standard library function ([global.functions]), a standard library static member function, or an instantiation of a standard library function template. Unless F is designated an addressable function, the behavior of a C++ program is unspecified (possibly ill-formed) if it explicitly or implicitly attempts to form a pointer to 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);