GCC7下带类型不匹配默认值的构造函数无编译错误原因
B<int, int>(14)在C17编译通过但C14报错,而foo<int, int>(14)始终报错? 这个问题的核心是C++17对类模板构造函数默认参数的实例化规则做了调整,再加上类模板和函数模板在处理默认参数时的本质差异,才导致了这种看似矛盾的现象。咱们一步步来理清楚:
1. C++14及更早的逻辑:默认参数必须被实例化
在C++14里,当你显式指定类模板的所有参数(比如B<int, int>)并调用构造函数时,编译器会把构造函数的整个签名(包括默认参数)都实例化出来。
对于B<int, int>来说,构造函数会被展开成这样:
explicit B(const int s, int n = A());
这时候编译器必须检查默认参数A()能不能隐式转成int——但A就是个空结构体,根本没有转int的路径,所以直接报错,这和你预期的一致,也和模板函数foo的表现同步。
而模板函数foo不管在哪个C++标准下,只要你显式指定<int, int>,函数签名会被实例化为:
void foo(const int s, int nx = A());
函数模板的默认参数属于函数签名的一部分,只要实例化这个函数,就必须检查所有默认参数的合法性,所以不管你传不传第二个参数,A()转int的错误都会触发。
2. C++17的优化:默认参数只用才实例化
C++17给类模板的构造函数加了个实用优化:如果你调用构造函数时没用到某个默认参数,编译器就不会去实例化它。
回到你的代码,B<int, int>(14)只传了第一个参数,编译器只需要匹配第一个const int参数就行,第二个参数的默认值A()根本没被用到,所以编译器完全不会去检查它能不能转成int——自然就不会报错了。
但函数模板的规则没改,foo<int, int>(14)还是会实例化整个函数签名,包括那个有问题的默认参数,所以依然报错。
3. 验证一下:用到默认参数时C++17也会报错
要是你在C++17下故意触发默认参数的使用,比如写:
B<int, int> b(14, {}); // 试图用默认参数的类型推导
或者直接传A()当第二个参数:
B<int, int> b(14, A());
这时候编译器就会检查A()到int的转换,照样会报错,这也印证了咱们的结论——只有当默认参数被实际使用时,C++17才会去实例化并检查它。
内容的提问来源于stack exchange,提问作者R zu

