背景
UE4 Mac构建编译报错 no template named “is_void_v” in namespace “std” ,但WINDOWS没有遇到错误。
分析过程
1、is_void_v 是否属于C++17
从语法上看,AI说不只是 C++17 才有。早在C++14 就有 is_void_v 的用法了。
2、WINDOWS中is_void_v的定义
这段代码在WINDOWS下定义在
C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.40.33807\include\type_traits
3、Mac中is_void_v的定义
在WINDOWS中,is_void_v 的定义是在: C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.40.33807\include\type_traits ,那么在Mac机器中,它可能定义在哪个路径下?
我找到了它在Mac机器上的定义,位于:/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__type_traits/is_void.h ,具体定义是:
//代码1:
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef _LIBCPP___TYPE_TRAITS_IS_VOID_H
#define _LIBCPP___TYPE_TRAITS_IS_VOID_H
#include <__config>
#include <__type_traits/integral_constant.h>
#include <__type_traits/is_same.h>
#include <__type_traits/remove_cv.h>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
#endif
_LIBCPP_BEGIN_NAMESPACE_STD
#if __has_builtin(__is_void)
template <class _Tp>
struct _LIBCPP_TEMPLATE_VIS is_void : _BoolConstant<__is_void(_Tp)> {};
# if _LIBCPP_STD_VER >= 17
template <class _Tp>
inline constexpr bool is_void_v = __is_void(_Tp);
# endif
#else
template <class _Tp>
struct _LIBCPP_TEMPLATE_VIS is_void : public is_same<__remove_cv_t<_Tp>, void> {};
# if _LIBCPP_STD_VER >= 17
template <class _Tp>
inline constexpr bool is_void_v = is_void<_Tp>::value;
# endif
#endif // __has_builtin(__is_void)
_LIBCPP_END_NAMESPACE_STD
#endif // _LIBCPP___TYPE_TRAITS_IS_VOID_H
4、_LIBCPP_STD_VER的含义
_LIBCPP_STD_VER是 libc++(LLVM 的 C++ 标准库实现)内部用来表示其当前所支持的 C++ 标准版本的一个宏。它的值是一个整数,对应了不同的 C++ 标准发布年份。
这个宏的主要作用是让 libc++ 的代码能够根据不同的 C++ 标准版本条件地编译某些特性或提供特定版本的 API。以下是 _LIBCPP_STD_VER常见值及其对应的 C++ 标准的梳理:
# if _LIBCPP_STD_VER >= 17
template <class _Tp>
inline constexpr bool is_void_v = __is_void(_Tp); // 或者 = is_void<_Tp>::value;
# endif
上述代码意味着 is_void_v这个变量模板只有在 C++17 或更高版本下才会被定义和提供。这很好地解释了你最初的问题:is_void_v确实是 C++17 引入的特性。
5、理解 _LIBCPP_STD_VER与 __cplusplus的区别
你可能会想到另一个预定义宏 __cplusplus,它由编译器直接定义,用于指示编译器所遵循的 C++ 标准版本。
- __cplusplus:由 编译器 定义,表示编译器当前正在编译所遵循的 C++ 标准模式。它的值也是年份,例如 C++17 模式下其值通常为 201703L。
- _LIBCPP_STD_VER:由 libc++ 标准库头文件 定义,表示 libc++ 库本身实现所支持的最高或当前选择的 C++ 标准版本。
简单来说:
- __cplusplus告诉你编译器在用什么模式。
- _LIBCPP_STD_VER告诉你标准库在用什么模式。
在大多数正常配置下,_LIBCPP_STD_VER的值会和 __cplusplus的值保持一致(经过某种映射,例如 __cplusplus == 201703L时 _LIBCPP_STD_VER被定义为 17),因为它们需要协同工作。这个映射逻辑通常隐藏在 libc++ 的内部配置头文件(如 <__config>)中。
6、Mac中决定_LIBCPP_STD_VER 的地方
//代码2:
public CppStandardVersion CppStandard = CppStandardVersion.Default;
static string GetCppStandardCompileArgument(CppCompileEnvironment CompileEnvironment)
{
var Mapping = new Dictionary<CppStandardVersion, string>
{
{ CppStandardVersion.Cpp14, " -std=c++14" },
{ CppStandardVersion.Cpp17, " -std=c++17" },
{ CppStandardVersion.Latest, " -std=c++17" },
{ CppStandardVersion.Default, " -std=c++14" }
};
return Mapping[CompileEnvironment.CppStandard];
}
在我的构建机中,该默认配置,读取的就是c++14,也就是Mac机用的也就是c++14.
7、WINDOWS是否是 c++14 ?
是的,在我的构建机中,WINDOWS 也是c++14。论述如下。在UE4中,我看到下面变量决定了c++的标准:
public class ModuleRules
{
……
public CppStandardVersion CppStandard = CppStandardVersion.Default;
……
}
然而在WINDOWS的构建机中,我观察到UBT产生的编译命令中,没有 -std=c++14 或 -std=c++17 或 /std:c++latest 或 /Qstd=c++14 或 /std:c++17 或 /std:c++14 中的任何一个。我调试的代码位于 UnrealBuildTool.VCToolChain.AppendCLArguments_CPP() ,UnrealBuildTool.VCToolChain里的 "VC" 字面上指的是 Visual C++,这是微软(Microsoft)提供的 C++ 编译工具链 。最终观察到下面这些参数,它们记录在EngineSource\Engine\Intermediate\Build\Win64\UE4Editor\Development\Launch\Module.Launch.cpp.obj.response :
/Zc:inline
/nologo
/Oi
/c
/Gw
/Gy
/Zm1000
/wd4819
/D_CRT_STDIO_LEGACY_WIDE_SPECIFIERS=1
/D_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS=1
/D_DISABLE_EXTENDED_ALIGNED_STORAGE
/source-charset:utf-8
/execution-charset:utf-8
/Ox
/Ot
/GF
/Ob2
/errorReport:prompt
/EHsc
/Z7
/MD
/bigobj
/fp:fast
/Zo
/Zp8
/we4456
/we4458
/we4459
/wd4463
/wd4838
/we4668
也就是说,没有显式声明 C++版本。那么,WINDOWS下UE4最终会以什么C++标准来编译呢?
在 Windows 下观察到的 UE4 编译命令中没有显式指定 C++ 标准(如 /std:c++17),这通常意味着它依赖于编译器的默认标准。结合你提到的 ModuleRules中的 CppStandard = CppStandardVersion.Default设置,分析如下:
8、总结
在Mac构建机和WINDOWS构建机中,构建机编译器都正在用着C++14,而非C++17 。
前面我们知道,mac中 要用 C++17,才能编译到 is_void_v,但 为什么WINDOWS用 c++14 却也能编译 is_void_v ?
因为,在 WINDOWS的定义( C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.40.33807\include\type_traits )中,没有C++版本限制;
_EXPORT_STD template <class _Ty>
_INLINE_VAR constexpr bool is_void_v = is_same_v<remove_cv_t<_Ty>, void>;
_EXPORT_STD template <class _Ty>
struct is_void : bool_constant<is_void_v<_Ty>> {};
而在 Mac的定义( /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__type_traits/is_void.h )中,有C++版本限制;
# if _LIBCPP_STD_VER >= 17
template <class _Tp>
inline constexpr bool is_void_v = __is_void(_Tp);
# endif
修复方法
因此,我们将所有的 std::([^<]+)_v< 都替换为 std::($1)::value ,例如: