C++11 CRTP检测派生类size方法:两段代码为何结果不同?
CRTP检测成员方法:两段代码的差异与错误原因
为什么第一段代码可行?
第一段代码中,IsContainer的has_size是成员模板函数,核心在于模板的延迟实例化和**SFINAE(替换失败并非错误)**机制:
- 当定义
struct Container : IsContainer<Container>时,基类IsContainer<Container>会被隐式实例化,但仅会实例化类的框架,不会实例化成员模板函数has_size。 - 静态常量
value的初始化表达式has_size<Derive>(nullptr),其实际的函数重载解析和模板实例化会延迟到value被实际使用时(即main函数中访问obj.value的时刻)。 - 此时
Container已完成完整定义,编译器可正确解析&Container::size,因此第一个has_size重载被选中,返回true;对于NonContainer,第一个重载的decltype(&NonContainer::size)会触发SFINAE,编译器自动选择...版本的重载,返回false。
对应的第一段代码:
#include <iostream> template<typename Derive> struct IsContainer { template<typename Der> static constexpr bool has_size( decltype(&Der::size) ) { return true; } template<typename Der> static constexpr bool has_size(...) { return false; } static constexpr bool value = has_size<Derive>(nullptr); }; struct Container : IsContainer<Container> { const unsigned int size(); }; struct NonContainer : IsContainer<NonContainer> {}; int main() { Container obj; std::cout << obj.value << std::endl; }
为什么第二段代码编译错误?
第二段代码移除了has_size的模板参数,使其成为普通成员函数:
#include <iostream> template<typename Derive> struct IsContainer { static constexpr bool has_size( decltype(&Derive::size) ) { return true; } static constexpr bool has_size(...) { return false; } static constexpr bool value = has_size(nullptr); }; struct Container : IsContainer<Container> { const unsigned int size(); }; struct NonContainer : IsContainer<NonContainer> {}; int main() { Container obj; std::cout << obj.value << std::endl; }
错误原因在于:
- 普通成员函数的参数类型必须在类模板
IsContainer<Derive>实例化时就确定。当定义Container : IsContainer<Container>时,IsContainer<Container>被立即实例化,此时Container是不完全类型——编译器还未处理完它的成员声明,无法解析&Container::size,因此触发incomplete type 'Container' used in nested name specifier错误。 - 核心区别是:普通函数的类型检查是即时的,而模板函数的类型检查是延迟到实例化时进行的。
修复第二段代码的提示
要修复这个问题,核心是让&Derive::size的解析延迟到Derive成为完整类型之后,即利用模板的延迟实例化特性。最简单的修复方式是恢复has_size的模板参数(回到第一段代码的写法);如果想保留类似第二段的结构,也可以将value的初始化改为依赖模板的函数调用,比如:
template<typename Derive> struct IsContainer { template<typename Der> static constexpr bool has_size(decltype(&Der::size)) { return true; } template<typename Der> static constexpr bool has_size(...) { return false; } // 用模板lambda包裹,延迟实例化(C++14及以上支持) static constexpr bool value = []{ return has_size<Derive>(nullptr); }(); };
若需兼容C++11,可改用辅助模板函数实现延迟:
template<typename Derive> struct IsContainer { template<typename Der> static constexpr bool has_size(decltype(&Der::size)) { return true; } template<typename Der> static constexpr bool has_size(...) { return false; } template<typename D> static constexpr bool get_value() { return has_size<D>(nullptr); } static constexpr bool value = get_value<Derive>(); };
本质都是利用模板只有在被使用时才会实例化的特性,避免在基类实例化时就解析不完全类型的成员。
内容的提问来源于stack exchange,提问作者Iman Abdollahzadeh
相关产品推荐
相关产品推荐

