无法推断函数的返回类型

时间:2020-08-10 12:10:03

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

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

有更好的方法吗?

1 个答案:

答案 0 :(得分:2)

目前尚不清楚为什么拒绝此代码。由于推导了T,因此可以确定std::to_string的重载。

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

但是,只有当我们可以独立推导每对时,上述情况才成立。如果参数不可推导,我们将其跳过,然后尝试返回并稍后再填写。这就是这里的关键-我们重新构造推论,以便仅从T推论42

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

在这里,我们推导Tint,现在我们从std::string(&)(int)推论std::to_string。现在可以使用,因为只有一个重载会匹配该模式。


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

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

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

因此, real 更好的方法是将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);