C++20中Concepts与CRTP结合的编译错误解决方法问询
在项目中尝试使用C++20的Concepts与CRTP模式,但在gcc-12和MSVC 2022环境下均出现编译错误。简化后的代码如下:
#include <iostream> namespace Test { namespace concepts { template<typename T> concept SupportsFn = requires(T t, int k) { { t.fn(k) }; }; } template<concepts::SupportsFn D> class CD { static_assert(concepts::SupportsFn<D>); public: void call(int k) { this->fn(k); } }; template<concepts::SupportsFn T> class BD : public CD<T> { static_assert(concepts::SupportsFn<T>); public: void call2(int k) { this->fn(k + 2); } }; } class ED : public Test::BD<ED> { public: void fn(int k) { std::cout << "FN " << k << " \n"; } }; int main() { ED ed{}; ed.call(10); return 0; }
GCC和MSVC均提示因ED类型不完整导致模板约束不满足,MSVC错误信息如下:
C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.38.33130\include\expected(11): warning STL4038: The contents of
are available only with C++23 or later.
1> C:\temp\memfnexpt\memfnexpt\memfnexpt.cpp(377,25): error C7602: 'Test::BD': the associated constraints are not satisfied
1> C:\temp\memfnexpt\memfnexpt\memfnexpt.cpp(368,2):
1> see declaration of 'Test::BD'
1> C:\temp\memfnexpt\memfnexpt\memfnexpt.cpp(367,11):
1> the concept 'Test::concepts::SupportsFn' evaluated to false
1> C:\temp\memfnexpt\memfnexpt\memfnexpt.cpp(353,5):
1> use of undefined type 'ED'
1> C:\temp\memfnexpt\memfnexpt\memfnexpt.cpp(377,7):
1> see declaration of 'ED'
1> C:\temp\memfnexpt\memfnexpt\memfnexpt.cpp(353,7):
错误核心在于CRTP基类实例化时,派生类ED仍属于不完整类型——此时编译器还未读取到ED的fn成员定义,直接验证SupportsFn<ED>必然失败。以下是三种可行的解决方式:
方法1:修改概念为指针访问成员
调整SupportsFn概念,通过指针而非对象实例来检查成员函数。指针访问仅需要类的声明,不需要完整定义,适配CRTP的不完整类型场景:
#include <iostream> namespace Test { namespace concepts { // 改用指针检查成员函数,支持不完整类型 template<typename T> concept SupportsFn = requires(T* t, int k) { { t->fn(k) }; }; } template<concepts::SupportsFn D> class CD { static_assert(concepts::SupportsFn<D>); public: void call(int k) { // CRTP推荐显式向下转型,避免依赖查找问题 static_cast<D*>(this)->fn(k); } }; template<concepts::SupportsFn T> class BD : public CD<T> { static_assert(concepts::SupportsFn<T>); public: void call2(int k) { static_cast<T*>(this)->fn(k + 2); } }; } class ED : public Test::BD<ED> { public: void fn(int k) { std::cout << "FN " << k << " \n"; } }; int main() { ED ed{}; ed.call(10); ed.call2(10); return 0; }
方法2:延迟约束检查到成员函数实例化
移除类模板的直接约束,将概念检查放在成员函数层面。此时约束会延迟到成员函数被调用时验证,而调用时派生类已完成定义:
#include <iostream> namespace Test { namespace concepts { template<typename T> concept SupportsFn = requires(T t, int k) { { t.fn(k) }; }; } // 类模板不直接加约束,转而在成员函数上声明 template<typename D> class CD { public: void call(int k) requires concepts::SupportsFn<D> { static_cast<D*>(this)->fn(k); } }; template<typename T> class BD : public CD<T> { public: void call2(int k) requires concepts::SupportsFn<T> { static_cast<T*>(this)->fn(k + 2); } }; } class ED : public Test::BD<ED> { public: void fn(int k) { std::cout << "FN " << k << " \n"; } }; int main() { ED ed{}; ed.call(10); // 此时ED为完整类型,概念检查通过 return 0; }
方法3:两步继承+事后静态断言
提前声明派生类,基类不做约束,待派生类完整定义后用静态断言验证概念:
#include <iostream> // 提前声明ED,让基类能识别该类型 class ED; namespace Test { namespace concepts { template<typename T> concept SupportsFn = requires(T t, int k) { { t.fn(k) }; }; } template<typename D> class CD { public: void call(int k) { static_cast<D*>(this)->fn(k); } }; template<typename T> class BD : public CD<T> { public: void call2(int k) { static_cast<T*>(this)->fn(k + 2); } }; } // 完整定义ED class ED : public Test::BD<ED> { public: void fn(int k) { std::cout << "FN " << k << " \n"; } }; // 事后验证ED满足概念要求 static_assert(Test::concepts::SupportsFn<ED>); int main() { ED ed{}; ed.call(10); return 0; }
内容的提问来源于stack exchange,提问作者user1751716

