Visual Studio 中(基于字符的)STL(流)容器的编译错误

Compile error for (char based) STL (stream) containers in Visual Studio

这基本上是与 [SO]: C2491: 'std::numpunct<_Elem>::id' : definition of dllimport static data member not allowed [closed] 相同的问题,但考虑到以下事实:

请不要关闭它或将其标记为重复(至少,不要在没有仔细阅读和理解它的情况下)。

main.cpp:

#include <sstream>


//#define THROW_C2491
#if defined(THROW_C2491)
typedef int CharType;
#else
typedef char CharType;
#endif


int main() {
    std::basic_stringstream<CharType> stream;
    CharType c = 0x41;
    stream << c;
    return 0;
}

代码略有修改(简化),编译失败if THROW_C2491定义为:

xlocnum(294): error C2491: 'std::numpunct<_Elem>::id': definition of dllimport static data member not allowed

输出:

E:\Work\Dev\Whosebug\q048716223>"c:\Install\x86\Microsoft\Visual Studio Community15\vc\vcvarsall.bat" amd64

E:\Work\Dev\Whosebug\q048716223>
E:\Work\Dev\Whosebug\q048716223>"c:\Install\x86\Microsoft\Visual Studio Community15\vc\bin\amd64\cl.exe" /GS /W3 /Zc:wchar_t /ZI /Gm /Od /Zc:inline /fp:precise /D "_DEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /RTC1 /Gd /MDd /EHsc /nologo -c "src\main.cpp"
main.cpp

E:\Work\Dev\Whosebug\q048716223>echo %errorlevel%
0

E:\Work\Dev\Whosebug\q048716223>
E:\Work\Dev\Whosebug\q048716223>"c:\Install\x86\Microsoft\Visual Studio Community15\vc\bin\amd64\cl.exe" /GS /W3 /Zc:wchar_t /ZI /Gm /Od /Zc:inline /fp:precise /D "_DEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /RTC1 /Gd /MDd /EHsc /nologo -c "src\main.cpp" /D "THROW_C2491"
main.cpp
c:\install\x86\microsoft\visual studio community15\vc\include\xlocnum(294): warning C4273: 'id': inconsistent dll linkage
c:\install\x86\microsoft\visual studio community15\vc\include\xlocnum(120): note: see previous definition of 'public: static std::locale::id std::numpunct<int>::id'
c:\install\x86\microsoft\visual studio community15\vc\include\xlocnum(120): note: while compiling class template static data member 'std::locale::id std::numpunct<_Elem>::id'
        with
        [
            _Elem=CharType
        ]
c:\install\x86\microsoft\visual studio community15\vc\include\xlocnum(1261): note: see reference to function template instantiation 'const _Facet &std::use_facet<std::numpunct<_Elem>>(const std::locale &)' being compiled
        with
        [
            _Facet=std::numpunct<CharType>,
            _Elem=CharType
        ]
c:\install\x86\microsoft\visual studio community15\vc\include\xlocnum(1255): note: while compiling class template member function 'std::ostreambuf_iterator<_Elem,_Traits> std::num_put<_Elem,std::ostreambuf_iterator<_Elem,_Traits>>::do_put(_OutIt,std::ios_base &,_Elem,bool) const'
        with
        [
            _Elem=CharType,
            _Traits=std::char_traits<CharType>,
            _OutIt=std::ostreambuf_iterator<CharType,std::char_traits<CharType>>
        ]
c:\install\x86\microsoft\visual studio community15\vc\include\ostream(305): note: see reference to class template instantiation 'std::num_put<_Elem,std::ostreambuf_iterator<_Elem,_Traits>>' being compiled
        with
        [
            _Elem=CharType,
            _Traits=std::char_traits<CharType>
        ]
c:\install\x86\microsoft\visual studio community15\vc\include\ostream(291): note: while compiling class template member function 'std::basic_ostream<_Elem,_Traits> &std::basic_ostream<_Elem,_Traits>::operator <<(int)'
        with
        [
            _Elem=CharType,
            _Traits=std::char_traits<CharType>
        ]
e:\work\dev\Whosebug\q048716223\src\main.cpp(16): note: see reference to function template instantiation 'std::basic_ostream<_Elem,_Traits> &std::basic_ostream<_Elem,_Traits>::operator <<(int)' being compiled
        with
        [
            _Elem=CharType,
            _Traits=std::char_traits<CharType>
        ]
c:\install\x86\microsoft\visual studio community15\vc\include\istream(939): note: see reference to class template instantiation 'std::basic_ostream<_Elem,_Traits>' being compiled
        with
        [
            _Elem=CharType,
            _Traits=std::char_traits<CharType>
        ]
c:\install\x86\microsoft\visual studio community15\vc\include\sstream(574): note: see reference to class template instantiation 'std::basic_iostream<_Elem,_Traits>' being compiled
        with
        [
            _Elem=CharType,
            _Traits=std::char_traits<CharType>
        ]
e:\work\dev\Whosebug\q048716223\src\main.cpp(14): note: see reference to class template instantiation 'std::basic_stringstream<CharType,std::char_traits<_Elem>,std::allocator<_Elem>>' being compiled
        with
        [
            _Elem=CharType
        ]
c:\install\x86\microsoft\visual studio community15\vc\include\xlocnum(294): error C2491: 'std::numpunct<_Elem>::id': definition of dllimport static data member not allowed
        with
        [
            _Elem=CharType
        ]

E:\Work\Dev\Whosebug\q048716223>echo %errorlevel%
2

备注:

[MS.Docs]: Compiler Error C2491 is pretty straightforward, I am familiar with it, there are some answers (e.g. , ) 支持我。

考虑到上面的注释,我知道这与 Win 处理 char[=106= 的方式(限制?)有关]s,但是 我没有看到代码和错误之间的任何(直接)联系 [MS.Docs]: basic_stringstream Class 上没有关于此行为的信息。我(粗浅地)浏览了涉及的标准include headers,但至今没有深究。我是不是漏掉了非常明显的东西?

其他参考文献(相同或相似的错误,但 none 包含 有效 修正:


值得一提的是,最终目标是构建一些 3rd-party 软件实例化一些32 位 char 基于流。

开始笔记:

方法

  1. 快速(浅)调查

    VStudio IDE上双击,在Output[=中的第2nd条上193=] window(在尝试编译文件后),并从那里在相关宏上重复 RClicks,并从上下文菜单中选择 Go To定义 (F12):

    • xlocnum (#120):(评论是原文的一部分file/line)

      __PURE_APPDOMAIN_GLOBAL _CRTIMP2_PURE static locale::id id; // unique facet id
      
    • yvals.h: (#494):

           #define _CRTIMP2_PURE _CRTIMP2
      
    • crtdefs.h (#29+):

      #ifndef _CRTIMP2
          #if defined CRTDLL2 && defined _CRTBLD
              #define _CRTIMP2 __declspec(dllexport)
          #else
              #if defined _DLL && !defined _STATIC_CPPLIB
                  #define _CRTIMP2 __declspec(dllimport)  // @TODO - cfati: line #34: Here is the definition
              #else
                  #define _CRTIMP2
              #endif
          #endif
      #endif
      

    如上所示,__declspec(dllimport) 是在第 34 行定义的。在 _DLL 宏上重复该过程,没有产生任何结果。发现于 [MSDN]: Predefined Macros:

    _DLL Defined as 1 when the /MD or /MDd (Multithreaded DLL) compiler option is set. Otherwise, undefined.

    我想到了 2 种可能的方法(都可以成功构建):

    • 使用 static 版本的 CRT 运行时[MSDN]: /MD, /MT, /LD (Use Run-Time Library)). I don't consider it a viable option, especially when the project consists of .dlls (and it does): bad things can happen (e.g. ,甚至在运行时可能出现更糟糕的版本)
    • 手动 #undef _DLL(在 main.cpp之前 任何 #include)。这是一个蹩脚的解决方法 (gainarie)。它构建良好,但篡改这些东西可能(并且很可能会)在运行时触发 未定义的行为

    None 这两个选项完全令人满意,所以:

  2. 再深入一点

    试图进一步简化事情 (main.cpp):

    #include <sstream>
    
    
    //typedef unsigned short CharType;  // wchar_t  unsigned short
    #define CharType unsigned short
    
    
    int main() {
        std::basic_stringstream<CharType> stream;
        CharType c = 0x41;
        stream << c;
        return 0;
    }
    

    备注:

    • typedef 替换为 #define(去除新类型定义的复杂性)
    • 切换到 unsigned short 这是 wchar_t 的定义 (/Zc:wchar_t-) 以避免任何可能的 类型大小 / 对齐差异


    "Compiled" 上面的代码加上 [MSDN]: /E (Preprocess to stdout) and [MSDN]: /EP (Preprocess to stdout Without #line Directives)(这样 warnings/errors 只引用当前文件中的行号):

    • 生成的预处理文件(使用上面的每个标志):~1MB+~56.5k 行)
    • 文件的唯一区别是 #definewchar_t vs. unsigned short)在最后的某个地方
    • 编译文件(令人震惊 :))产生了相同的结果:wchar_t 编译了一个文件,而 unsigned short 编译失败并出现相同的错误
    • 在失败的文件中添加了一些 #pragma message 语句(是的,它们由预处理器处理,但仍然)(在每个 warning/note 之前),注意到 2 #defines,但至今无法弄清楚为什么 1
    • 在浏览生成的文件时,注意到一个 template<> struct char_traits<char32_t> 定义,所以我试了一下,它起作用了(至少当前程序编译了) 1(而且,正如预期的那样,sizeof(char32_t) 是 4)。然后,找到[MSDN]: char, wchar_t, char16_t, char32_t


    备注:

    • 虽然这解决了我的当前问题(仍然不知道为什么),但必须试一试最终目标
    • 1 虽然我查看了文件,但我没有看到任何仅针对“”的模板定义特权”类型(例如,我没有看到任何可以区分 wchar_tsigned charchar32_tunsigned short 的东西),所以我 不知道(还)为什么它对某些类型有效但对其他类型无效。 这是一个开放的话题,每当我有新的更新时,我都会分享它们

底线:

根据经验发现,在使用基于charSTL容器[=193]时,允许使用以下类型=]:

  • char
  • unsigned char
  • signed char
  • wchar_t
  • char16_t
  • char32_t
  • unsigned short (/Zc:wchar_t- )

最后的注释:

  • 我会在答案中加入任何有用的东西(例如评论

@EDIT0:

问题的发生是因为在模板化的 class std::numpunct 中,public 变量 id 是用 _CRTIMP2_PURE_IMPORT 声明的。我没有追查这个的定义,但它大概设置了这个变量从 C 运行时 dll 导入。

解决此问题的方法是将 std::numpunct class 专门化为您希望使用的字符类型,但声明 id 变量而不声明 _CRTIMP2_PURE_IMPORT

由于 OP 指的是 Visual Studio 的一个现已过时的版本,我不会尝试引用特定的文件或行号,因为它们可能会因版本而异。我也不会提供可以使用的特定专业化,因为这可能完全取决于 OP 想要什么(如果他们只想使用他们的语言环境可能很容易,或者如果他们想使用 other/any 语言环境).

不是答案,而是说明三年后情况并没有好转。这是我在 VS2019 v16.9.4

中得到的
C:\Program Files (x86)\Microsoft Visual Studio19\Community\VC\Tools\MSVC.28.29910\include\xlocnum(263,1): error C2491: 'std::numpunct<_Elem>::id': definition of dllimport static data member not allowed

尝试使用 uint64_t 作为字符类型(编写 NCurses C++ 库,我的 PDCurses 版本使用 64 位 chtypes...