为何移除构造函数参数的const会阻止类实例化?
移除Heck类中构造函数参数的const修饰符后,模板类What<int>在编译阶段不再被实例化。原本以为无论是否带有const,What类都不会被实例化,且clang、gcc、MSVC表现一致,说明这符合C++标准。即使将构造函数标记为explicit也无法阻止实例化,疑惑为何const修饰符会造成这种差异?
示例代码
template <typename T> constexpr bool just_false() { return false; } template<typename T> class What { static_assert(just_false<T>(), "Why was this class instantiated?"); }; struct The {}; struct Heck { Heck(The) {} Heck(const What<int>&); // 移除此行的'const'会阻止What<int>被实例化 }; int main() { The the; Heck{the}; }
注:just_false的作用是避免static_assert在任何情况下触发编译错误。
核心原因在于重载解析规则与引用绑定的限制:
带
const的情况(const What<int>&)
当执行Heck{the}的列表初始化时,编译器会把Heck(const What<int>&)作为候选构造函数之一。为了判断是否能将The类型对象转换为const What<int>&,编译器需要检查What<int>是否存在可接受The的构造函数——因为临时What<int>对象可以从The转换而来,再绑定到const引用。
要检查What<int>的构造函数,编译器必须实例化What<int>的类定义,这就触发了模板类的实例化(虽然static_assert因just_false返回false未报错,但实例化确实发生了)。不带
const的情况(What<int>&)
非const左值引用只能绑定到可修改的左值,而The对象无法转换为What<int>类型的可修改左值(既没有直接转换路径,临时对象也不能绑定到非const引用)。
编译器在重载解析阶段会直接排除这个构造函数,无需检查What<int>的构造函数,因此不会实例化What<int>的类定义。标记
explicit仍无法阻止实例化的原因
若将构造函数标记为explicit,Heck{the}这类列表初始化不会将其作为候选(explicit构造函数不参与隐式转换),但如果代码中存在直接初始化(如Heck(the);),编译器仍会将其纳入重载解析候选,此时仍需检查转换可行性,进而触发What<int>的实例化。如果是严格的列表初始化场景,explicit应该能阻止实例化,可能是测试场景存在差异。
内容的提问来源于stack exchange,提问作者Dave Magnets

