我最近偶然发现了“为什么模板参数推导在这里不起作用? ”,答案可以总结为“这是一个非推导的上下文”。
具体来说,第一个说它是这样的,然后重定向到“详细信息”的标准,而第二个引用标准,至少可以说是神秘的。
有人可以向像我这样的凡人解释什么是非推断上下文,它何时发生以及为什么会发生?
我最近偶然发现了“为什么模板参数推导在这里不起作用? ”,答案可以总结为“这是一个非推导的上下文”。
具体来说,第一个说它是这样的,然后重定向到“详细信息”的标准,而第二个引用标准,至少可以说是神秘的。
有人可以向像我这样的凡人解释什么是非推断上下文,它何时发生以及为什么会发生?
推导是指根据给定参数确定模板参数类型的过程。它适用于函数模板、auto
和其他一些情况(例如部分特化)。例如,考虑:
template <typename T> void f(std::vector<T>);
现在,如果您说f(x)
,您在哪里声明std::vector<int> x;
,则T
推断为,int
并且您获得了专业化f<int>
。
为了使推导起作用,要推导的模板参数类型必须出现在可推导的上下文中。在这个例子中,函数参数 off
就是这样一个可演绎的上下文。也就是说,函数调用表达式中的参数允许我们确定模板参数T
应该是什么,以便调用表达式有效。
但是,也有非演绎的上下文,其中不可能进行演绎。典型示例是“出现在 a 左侧的模板参数::
:
template <typename> struct Foo;
template <typename T> void g(typename Foo<T>::type);
在此函数模板中,T
函数参数列表中的 处于非推导上下文中。因此你不能说g(x)
和推断T
。原因是任意类型和成员 Foo<T>::type
之间没有“向后对应” 。例如,您可能有专业化:
template <> struct Foo<int> { using type = double; };
template <> struct Foo<char> { using type = double; };
template <> struct Foo<float> { using type = bool; };
template <> struct Foo<long> { int type = 10; };
template <> struct Foo<unsigned> { };
如果您打电话g(double{})
,则有两种可能的答案T
,如果您打电话g(int{})
,则没有答案。一般来说,类模板参数和类成员之间没有关系,所以你不能进行任何合理的参数推导。
有时,明确地禁止论证推导是有用的。例如std::forward
. 另一个例子是当你有从Foo<U>
to的转换Foo<T>
,比如说,或其他转换(想想std::string
和char const *
)。现在假设您有一个免费功能:
template <typename T> bool binary_function(Foo<T> lhs, Foo<T> rhs);
如果您调用binary_function(t, u)
,则扣除可能不明确并因此失败。但是只推断一个论点而不推断另一个论点是合理的,因此允许隐式转换。现在需要一个明确的非推导上下文,例如:
template <typename T>
struct type_identity {
using type = T;
};
template <typename T>
bool binary_function(Foo<T> lhs, typename type_identity<Foo<T>>::type rhs)
{
return binary_function(lhs, rhs);
}
(您可能遇到过类似的扣除问题std::min(1U, 2L)
。)