如何仅在模板类成员函数合法时对其进行ODR使用(C++11)
问题:C++11中条件式ODR使用类模板成员函数
我需要在类模板foo_t上定义成员函数__say_hi,该函数不会被任何地方调用,但必须被基于Clang的前端保留。为此添加了持有该函数地址的constexpr静态成员,但如果模板参数不满足条件(如T不可拷贝),__say_hi的函数体无法编译,导致整个模板实例化失败。
要求仅当__say_hi可正常编译时,才定义这个持有其地址的静态成员;且foo_t是库组件,无法提前为所有可能的实例化类型编写特化。
示例代码(存在编译问题):
template<typename T> struct foo_t { const char * __say_hi() const { T copy(m_t); // 需要T具备拷贝构造函数 return "hi"; } // 该函数不会被调用,但不能从AST中移除 static constexpr auto __ODR_use = &foo_t::__say_hi; // ODR使用以保留函数 T m_t; }; struct non_copyable_t { non_copyable_t(non_copyable_t const &) = delete; }; int main() { foo_t<int> f1; // 正常 // foo_t<non_copyable_t> f2; // 编译错误 }
解决方案
核心思路是通过SFINAE检测__say_hi是否可合法实例化,再基于检测结果通过继承条件基类的方式,选择性定义ODR使用的静态成员。这样既避免非法实例化时的编译错误,又能在合法时保留函数。
完整代码:
#include <type_traits> #include <utility> template<typename T> struct foo_t; // 检测foo_t<T>::__say_hi是否可合法实例化的traits template<typename T> struct has_valid_say_hi { private: // 尝试构造__say_hi的调用表达式,若合法则返回std::true_type template<typename U> static auto test(int) -> decltype(std::declval<const foo_t<U>&>().__say_hi(), std::true_type{}); // 匹配所有不满足条件的情况,返回std::false_type template<typename U> static std::false_type test(...); public: static constexpr bool value = decltype(test<T>(0))::value; }; // 条件基类:仅当检测通过时提供__ODR_use成员 template<typename T, bool = has_valid_say_hi<T>::value> struct odr_use_base {}; template<typename T> struct odr_use_base<T, true> { static constexpr auto __ODR_use = &foo_t<T>::__say_hi; }; template<typename T> struct foo_t : odr_use_base<T> { const char * __say_hi() const { T copy(m_t); return "hi"; } T m_t; }; struct non_copyable_t { non_copyable_t(non_copyable_t const &) = delete; }; int main() { foo_t<int> f1; // 正常,__ODR_use被定义,__say_hi被保留 foo_t<non_copyable_t> f2; // 正常,无__ODR_use,__say_hi不会被实例化 }
原理说明
- traits检测:
has_valid_say_hi通过SFINAE机制,尝试在编译时构造__say_hi的调用表达式。若表达式合法(即__say_hi的函数体可正常编译),则test<int>重载被选中,返回std::true_type;否则匹配test(...)重载,返回std::false_type。 - 条件继承:
odr_use_base根据检测结果做模板特化:当检测通过时,特化版本定义__ODR_use静态成员,对__say_hi进行ODR使用;否则基类为空,不定义该成员。 - 避免不必要实例化:当
__ODR_use未定义时,__say_hi不会被ODR使用,其函数体也不会被编译器实例化,从而避免非法模板参数带来的编译错误。
内容的提问来源于stack exchange,提问作者Dorian
相关产品推荐
相关产品推荐

