带Concept约束方法的类显式实例化:编译器行为合规性判定与适配方案问询
先贴出相关代码:
#include <concepts> using namespace std; struct A { void op(); }; struct B { void op( bool ); }; template<typename T> concept concept_C = is_same<T, A>::value || is_same<T, B>::value; template<typename T> requires concept_C<T> struct C { void opA() requires is_same<T, A>::value; void opB( bool f ) requires is_same<T, B>::value; T *pt; }; template<typename T> requires concept_C<T> void C<T>::opA() requires is_same<T, A>::value { pt->op(); } template<typename T> requires concept_C<T> void C<T>::opB( bool f ) requires is_same<T, B>::value { pt->op( f ); } template struct C<A>; template struct C<B>;
遇到的编译器行为差异
- 旧版本MSVC:会为
C<A>实例化opA和opB,为C<B>实例化opA和opB,进而触发参数不匹配的编译错误:(42,10): error : too many arguments to function call, expected 0, have 1
(46,8): message : in instantiation of member function 'C::opB' requested here
(7,7): message : 'op' declared here
(34,9): error : too few arguments to function call, expected 1, have 0
(49,8): message : in instantiation of member function 'C::opA' requested here
(12,7): message : 'op' declared here - Clang(包括Visual Studio中12.0版本的clang):出现类似上述的错误
- GCC 12及更新版本:会通过Concept过滤不恰当的函数编译,仅为
C<A>实例化opA,为C<B>实例化opB,代码可正常编译 - 补充:更新至最新官方版本的Visual Studio后,MSVC已修复该问题,代码可正常编译
核心疑问
- 在此类带Concept约束方法的类显式实例化场景中,哪个编译器的行为符合C++标准?
- 针对仍存在问题的编译器(如Visual Studio中的clang 12.0),是否有可行的适配方案?
问题1:哪个编译器的行为符合C++标准?
GCC和修复后的MSVC的行为是符合C++20标准的,而旧MSVC、Clang 12的行为不符合。
根据C++20标准的规定,当你显式实例化一个类模板特化时,只有那些满足自身约束条件的成员函数才会被实例化。对于C<A>来说,opB的约束is_same<T, B>::value不满足,所以编译器不应该尝试实例化这个成员函数;同理C<B>的opA约束不满足,也不应该被实例化。
旧版本的编译器(包括Clang 12)没有正确处理这种成员级别的约束过滤,错误地尝试实例化所有成员函数,导致了参数不匹配的编译错误。
问题2:针对有问题的编译器的适配方案
如果必须兼容像Clang 12这类尚未正确支持该特性的编译器,有几种可行的适配思路:
方案1:使用类模板特化拆分功能
把不同类型对应的成员函数拆分到类的特化版本中,避免在同一个主模板中放置受约束的成员函数:
#include <concepts> using namespace std; struct A { void op(); }; struct B { void op( bool ); }; // 主模板只声明通用部分 template<typename T> struct C { T *pt; }; // 特化C<A>,只包含opA template<> struct C<A> { void opA() { pt->op(); } A *pt; }; // 特化C<B>,只包含opB template<> struct C<B> { void opB( bool f ) { pt->op( f ); } B *pt; }; template struct C<A>; template struct C<B>;
这种方式完全避开了成员函数的约束问题,兼容性最好,缺点是如果通用逻辑较多,会有代码重复。
方案2:显式实例化单个成员函数而非整个类
放弃显式实例化整个类模板,改为只显式实例化需要的成员函数:
// 去掉原来的template struct C<A>;和template struct C<B>; // 替换为: template void C<A>::opA(); template void C<B>::opB(bool);
这样编译器只会实例化你指定的成员函数,不会去碰那些不满足约束的函数,自然不会触发错误。不过这种方式需要你明确列出所有需要实例化的成员函数,如果成员较多会比较繁琐。
方案3:用SFINAE替代成员函数的requires约束
对于不支持C20成员函数requires的旧编译器,可以用C17的enable_if实现类似的效果:
#include <type_traits> using namespace std; struct A { void op(); }; struct B { void op( bool ); }; template<typename T> concept concept_C = is_same<T, A>::value || is_same<T, B>::value; template<typename T> requires concept_C<T> struct C { template<typename U = T> enable_if_t<is_same_v<U, A>> opA() { pt->op(); } template<typename U = T> enable_if_t<is_same_v<U, B>> opB( bool f ) { pt->op( f ); } T *pt; }; // 成员函数的定义也需要调整为模板形式 template<typename T> requires concept_C<T> template<typename U> enable_if_t<is_same_v<U, A>> C<T>::opA() { pt->op(); } template<typename T> requires concept_C<T> template<typename U> enable_if_t<is_same_v<U, B>> C<T>::opB( bool f ) { pt->op( f ); } template struct C<A>; template struct C<B>;
这种方式利用SFINAE在实例化时过滤掉不符合条件的成员函数,能兼容大部分旧编译器,缺点是代码会变得稍微复杂一些。
内容的提问来源于stack exchange,提问作者Bonita Montero

