带默认模板实参的类模板与模板模板形参的匹配

首先解释一下这个看起来很奇怪的标题。

“带默认模板参数的类模板”是指 Class template with default template-parameter,即一个类模板的部分或全部模板参数有默认值,比如:

template<typename T1, typename T2 = int>
struct Foo { };

有个很典型的例子是 std::vector,它其实有两个模板参数:

template<class T, class Allocator = std::allocator<T>>
class vector;

只不过绝大多数情况下,我们并不需要指定第二个模板参数,感觉上好像它就只有一个模板参数,比如:std::vector<int> vec;

“模板模板参数”即 Template template parameter,也就是带模板的模板参数,比如:

template <template <typename> class C, typename T>
void bar(const C<T>& c) { }

这里面的 C 就是一个模板模板参数。

现在,问题来了,在已有上述类 Foo 和函数 bar() 定义的情况下,下面这个代码能够通过编译吗?

bar(Foo<int>{});

这个问题其实是在问,template<typename T1, typename T2 = int> 能不能与 template <typename> class C 进行匹配。

一方面,C 这个类模板只能有一个模板参数,而 Foo 是有两个模板参数的,所以看起来两者应该无法匹配。但另一方面,Foo 第二个模板参数有默认值,它在实际使用中完全可以看作只有一个模板参数,所以似乎可以匹配 C

该问题答案是:取决于编译器!

参见 cppreference,C++17 引入了一个新的特性 Matching template template parameters to compatible arguments (模板模板形参与可兼容实参匹配,貌似没有更简单的名字,暂且叫它 MTTP2CA 吧) 来解决 CWG 150 问题,使得以下代码能够编译:

template<class T> class A { /* ... */ };
template<class T, class U = T> class B { /* ... */ };
template <class ...Types> class C { /* ... */ };
 
template<template<class> class P> class X { /* ... */ };
X<A> xa; // OK
X<B> xb; // OK in C++17 after CWG 150
         // Error earlier: not an exact match
X<C> xc; // OK in C++17 after CWG 150
         // Error earlier: not an exact match

所以,不支持 C++17 的编译器是不能编译 bar(Foo<int>{}); 的。但问题并不仅限于此,即使是目前最新的 Clang 12 和 MSVC v19,也都不行,只有 GCC 能够编译这样的代码,这是为什么呢?

原因是 MTTP2CA 特性其实有一个缺陷,假设上述 bar() 函数新增一个定义:

template <template <typename, typename> class C, typename T1, typename T2>
void bar(const C<T1, T2>& c) { }

此时 bar(Foo<int>{}); 匹配的是 void bar(const C<T>& c); 还是 void bar(const C<T1, T2>& c); 呢?

类似的,如果加入一个新的类模板:

template <class>
struct Bar;

template <template <class> class X, class T>
struct Bar<X<T>> { };

template <template <class, class> class X, class T, class U>
struct Bar<X<T, U>> { 
};

那么 Bar<Foo<int>> a; 匹配的是 Bar<X<T>> 还是 Bar<X<T, U>> 呢?

事实上这两种情况在 MTTP2CA 中是没有定义的,也就是说 C++ 标准并没有规定此时应该匹配前者(一个模板形参)还是后者(两个模板形参)。我的测试结果是 GCC 会选择匹配后者。

因为这个未定义的情况,Clang 和 MSVC 在默认情况下隐藏了这一特性。Clang 可以通过 -frelaxed-template-template-args 编译选项来打开该特性,MSVC 我还没找到开启方法。


GCC 和 Clang 在该问题上的不一致还带来了一个比较神奇的现象:如果你 IDE 用的是 QtCreator,并开启了 ClangCodeModel 插件(自动分析代码问题,高版本 QtCreator 默认开启),那么在编译前面提到的代码时,QtCreator 会报错(ClangCodeModel 基于 Clang 对代码进行分析),但同时又能够编译成功(Linux 上的 QtCreator 默认使用 GCC 作为编译器)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值