C++11 SFINAE实现constexpr构造类型分支错误排查
问题:C++11 SFINAE实现constexpr/非constexpr单例分支失效
原C17代码通过if constexpr结合std::is_literal_type<T>,为可constexpr构造的类型返回static constexpr单例,为其他类型返回static const单例。由于需要兼容C11/14,且std::is_literal_type已废弃,尝试用SFINAE实现分支逻辑,但所有类型均走非constexpr分支,请求排查错误。
原C++17代码
/** This function returns a reference to a read-only, default-constructed * static singleton object of type T. */ template <typename T> const T & GetDefaultObjectForType() { if constexpr (std::is_literal_type<T>::value) { static constexpr T _defaultObject = T(); return _defaultObject; } else { static const T _defaultObject; return _defaultObject; } }
自行编写的C++11 SFINAE代码
#include <string> #include <type_traits> namespace ugly_constexpr_sfinae_details { template<int> struct sfinae_true : std::true_type{}; template<class T> sfinae_true<(T::T(), 0)> is_constexpr(int); template<class> std::false_type is_constexpr(...); template<class T> struct has_constexpr_f : decltype(is_constexpr<T>(0)){}; // constexpr version template<typename T, typename std::enable_if<true == has_constexpr_f<T>::value, T>::type* = nullptr> const T & GetDefaultObjectForType() { printf("constexpr method called!\n"); static constexpr T _defaultObject = T(); return _defaultObject; } // non-constexpr version template<typename T, typename std::enable_if<false == has_constexpr_f<T>::value, T>::type* = nullptr> const T & GetDefaultObjectForType() { printf("const method called!\n"); static const T _defaultObject = T(); return _defaultObject; } } /** Returns a read-only reference to a default-constructed singleton object of the given type */ template<typename T> const T & GetDefaultObjectForType() { return ugly_constexpr_sfinae_details::GetDefaultObjectForType<T>(); } int main(int, char **) { const int & defaultInt = GetDefaultObjectForType<int>(); // should call the constexpr function in the namespace const float & defaultFloat = GetDefaultObjectForType<float>(); // should call the constexpr function in the namespace const std::string & defaultString = GetDefaultObjectForType<std::string>(); // should call the non-constexpr function in the namespace return 0; }
实际输出
$ ./a.out const method called! const method called! const method called!
期望输出
$ ./a.out constexpr method called! constexpr method called! const method called!
错误原因分析
核心问题在于has_constexpr_f的判断逻辑存在语法错误:
T::T()是非法语法,构造函数不能通过类名::构造函数名()的方式直接调用(无论内置类型还是自定义类型,这种写法都会触发编译错误)。- 由于上述语法错误,
is_constexpr<T>(0)永远无法匹配第一个重载,只能匹配可变参数的std::false_type版本,导致has_constexpr_f<T>::value始终为false,所有类型都进入非constexpr分支。
修正后的C++11 SFINAE代码
我们需要修改类型判断逻辑,正确检测类型是否支持constexpr默认构造:
#include <cstdio> #include <string> #include <type_traits> namespace ugly_constexpr_sfinae_details { template<int> struct sfinae_true : std::true_type {}; // 尝试在constexpr上下文中构造T,若可行则匹配此重载 template<typename T> sfinae_true<0> is_constexpr_default_constructible(int, decltype( (void) constexpr T(), 0 )* = nullptr); // 兜底匹配 template<typename> std::false_type is_constexpr_default_constructible(...); // 提取判断结果 template<typename T> struct has_constexpr_default_ctor : decltype(is_constexpr_default_constructible<T>(0)) {}; // constexpr版本分支 template<typename T, typename std::enable_if<has_constexpr_default_ctor<T>::value, int>::type = 0> const T& GetDefaultObjectForType() { printf("constexpr method called!\n"); static constexpr T _defaultObject = T(); return _defaultObject; } // 非constexpr版本分支 template<typename T, typename std::enable_if<!has_constexpr_default_ctor<T>::value, int>::type = 0> const T& GetDefaultObjectForType() { printf("const method called!\n"); static const T _defaultObject = T(); return _defaultObject; } } template<typename T> const T & GetDefaultObjectForType() { return ugly_constexpr_sfinae_details::GetDefaultObjectForType<T>(); } int main(int, char **) { const int & defaultInt = GetDefaultObjectForType<int>(); const float & defaultFloat = GetDefaultObjectForType<float>(); const std::string & defaultString = GetDefaultObjectForType<std::string>(); return 0; }
关键修正点
- 替换了错误的构造函数调用方式,改用
constexpr T()在SFINAE的参数推导中验证是否能进行constexpr默认构造。 - 简化了
std::enable_if的写法,用int = 0替代指针类型默认参数,代码更简洁。 - 修正后的判断逻辑能正确识别支持constexpr默认构造的类型(如
int、float)和不支持的类型(如std::string)。
内容的提问来源于stack exchange,提问作者Jeremy Friesner
相关产品推荐
相关产品推荐

