C++模板Args&&...万能引用引发std::conditional C2975编译错误
C2975编译错误:std::conditional要求编译期常量表达式问题排查
环境信息
- 操作系统:Windows
- 编译器环境:Visual Studio 2017
- 业务自定义逻辑:定义
TSTRING宏,WIN32环境下映射为std::string,x64环境下映射为std::wstring - 开发目标:编写模板实现类字符串类型到
std::wstring的自动转换,支撑不同字符串类型自动转为TSTRING类型
错误信息
编码过程中触发C2975编译错误:
Error C2975 '_Test': invalid template argument for 'std::conditional', expected compile-time constant expression
复现代码
#include <codecvt> #include <iostream> std::wstring to_wide_string(const std::string& input) { std::wstring_convert<std::codecvt_utf8<wchar_t> > converter; return converter.from_bytes(input); } std::string to_byte_string(const std::wstring& input) { std::wstring_convert<std::codecvt_utf8<wchar_t> > converter; return converter.to_bytes(input); } // 检测Args是否可用于F的构造函数,例:check<std::string>(3, 'a').value == true template <class F> struct check { template <class... Args> constexpr check(Args&&... args) : value( std::is_void< decltype(t2<F, Args...>(0, std::forward<Args>(args)...)) >::type::value) {} bool value; struct nat {}; template <class G, class... GArgs> static auto t2(int, GArgs&&... args) -> decltype(G(std::forward<GArgs>(args)...), void()); template <class, class...> static auto t2(...)->nat; }; // 非宽字符串场景转换为std::string(未完成) template <typename T, bool is_wstring = std::is_same<std::wstring, std::decay_t<T>>::value> struct to_tstring : std::string { }; // 宽字符串场景转换为std::wstring template <typename T> struct to_tstring<T, true> : std::wstring { template <typename... Args> // 将Args&&...修改为Args...即可编译通过 to_tstring(Args&&... args) : to_tstring<T, true>( std::conditional< check<std::string>(std::forward<Args>(args)...).value, std::true_type, std::false_type >::type(), std::forward<Args>(args)... ) { std::cout << "to_tstring<T, true>::ctor_1" << std::endl; } private: template <typename... Args> to_tstring(std::false_type, Args&&... args) : std::wstring(std::forward<Args>(args)...) { std::cout << "to_tstring<T, true>::ctor_2 false_type" << std::endl; } template <typename... Args> to_tstring(std::true_type, Args&&... args) : std::wstring(to_wide_string(std::string(std::forward<Args>(args)...))) { std::cout << "to_tstring<T, true>::ctor_3 true_type" << std::endl; } }; int main() { to_tstring<std::wstring> a("asdf"); // 触发编译错误 return 0; }
异常现象
- 单独在
main函数中使用check类获取value时,可正常作为constexpr编译期常量使用,测试代码如下:int main() { constexpr int i = check<std::string>(3,'j').value; int s[i] = {}; // 编译正常 } - 将
to_tstring构造函数签名从to_tstring(Args&&... args)修改为to_tstring(Args... args)后,编译可正常通过,原理未知。
根本原因
std::conditional的模板参数要求必须是编译期常量表达式,错误触发的核心是在转发引用的构造函数初始化列表语境下,VS2017无法将check<std::string>(std::forward<Args>(args)...).value识别为编译期常量:
构造函数的形参args属于函数作用域的运行期实体,哪怕传入的实参是编译期已知的字面量,在转发引用(Args&&万能引用)的推导逻辑下,编译器会将形参视为运行期变量,导致check的constexpr构造函数无法在编译期完成求值,返回的value自然不满足模板参数的常量要求。- 单独在
main函数中调用check<std::string>(3,'j').value可以正常编译,是因为该语境下直接将编译期常量作为实参传入,没有经过函数形参中转,编译器可以直接在编译期完成constexpr构造的全流程求值,得到的value是合法的编译期常量。 - 将构造函数参数改为
Args... args(值传递)后可编译通过,是因为值传递场景下,VS2017对传入的编译期常量实参做常量求值时,不会将形参判定为运行期实体,check的constexpr构造可以正常在编译期完成计算,value满足std::conditional的参数要求。
注:该行为属于VS2017对C++17 constexpr规则的旧实现特性,更高版本的MSVC以及GCC/Clang对该场景的常量求值支持更完善,不会触发同类错误。
内容的提问来源于stack exchange,提问作者Outcast
相关产品推荐
相关产品推荐

