Some Notes about C++

non-deduced context

Definition

为了方便理解,此处仅讨论如下形式的函数模板:
template <typename T> void foo(P t) (此处 P 为可能包含 T 的类型表达式)
即仅有一个模板参数且为类型参数的单参数函数。

在我们进行模板参数推导的时候,我们实质上是在做一个类似解方程的过程。对于一个函数调用 foo(a), 它给出了一个类型间的相等关系,即 a 的类型(设为 U)与 P 相同: U = P。要推导出模板参数的类型,就需要对这一方程进行求解。当这一方程可以被解出时,它就被称为一个 deducible context;当这一方程无法被解出时,它就是一个 non-deduced context。用一个例子来说明:

1
2
3
4
5
6
7
8
9
10
template <typename T>
void foo(std::vector<T> a) { }

template <typename T>
T bar(std::type_identity_t<T> b) { return b; }

int main() {
foo(std::vector<int>{1});
// bar(1); // error: couldn't infer template argument 'T'
}

在这个例子中,对 foo(std::vector<int>{1}) 进行模板参数推导的时候,我们可以得到方程 std::vector<T> = std::vector<int> (a = {1}),由此解出 T = int 完成推导,这是一个 deducible context;对 bar(1) 进行模板参数推导的时候,我们可以得到方程 std::type_identity<T>::type = int (b = 1),这是一个无法求解的方程(因为 type_identity 可能有无穷多种特化满足这一方程,无法一一枚举求解),于是会产生编译错误,所以这是一个 non-deduced context。(事实上,在所有的 non-deduced context 中,最常见的一种就是模板参数 T 出现在 :: 的左边。这种方程无法求解的原因可参见上文。)

Application

知道了 non-deduced context 的概念以后,我们就可以用它来解决一些问题(或者 play tricks):

1
2
3
4
5
6
7
8
9
10
template <typename T>
T foo(T a, T b) { return a + b; }

template <typename T>
T bar(T a, std::type_identity_t<T> b) { return a + b; }

int main() {
// foo(4.2, 1); // error: deduced conflicting types for parameter 'T' ('double' vs. 'int')
bar(4.2, 1); // only 4.2 gives useful info for deduction here, no conflict
}

在上述代码中,对 foo(4.2, 1) 进行模板参数推导的时候,我们可以得到两个方程: T = double (a = 4.2), T = int (b = 1)。这两个方程分别给出了两个不同的解,它们的地位是平等的,所以发生了冲突:编译器不知道应该选择哪一个解,导致编译错误。但在对 bar(4.2, 1) 进行模板参数推导的时候就不会出现这个问题,因为 std::type_identity_t<T> b 是一个 non-deduced context,不会给出任何解。此时对于 T 编译器只会得到 T = double 的唯一解,并不会像 foo 一样产生冲突,从而解决了编译错误。

Reference

What is a nondeduced context? - SoF
type_identity - cppreference
Template Argument Deduction - cppreference