{"id":32232,"date":"2023-05-16T17:33:57","date_gmt":"2023-05-16T17:33:57","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/cppblog\/?p=32232"},"modified":"2023-05-16T17:33:57","modified_gmt":"2023-05-16T17:33:57","slug":"cpp20-support-comes-to-cpp-cli","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/cppblog\/cpp20-support-comes-to-cpp-cli\/","title":{"rendered":"C++20 Support Comes To C++\/CLI"},"content":{"rendered":"<p>We&#8217;re pleased to announce the availability of C++20 support for C++\/CLI\nin Visual Studio 2022 v17.6. This update to MSVC was in response to\nfeedback received from many customers via votes on <a href=\"https:\/\/developercommunity.microsoft.com\/t\/VS-2019-CCLI-and-C-standard-version\/435513\">Developer\nCommunity<\/a>\nand otherwise. Thank you!<\/p>\n<p>In Visual Studio 2022 v17.6, the use of <code>\/clr \/std:c++20<\/code> will no\nlonger cause the compiler to emit a diagnostic warning that the compiler\nwill implicitly downgrade to <code>\/std:c++17<\/code>. Most C++20 features are\nsupported with the exception of the following:<\/p>\n<ul>\n<li>Two-phase name lookup support for <em>managed<\/em> templates. This is\ntemporarily on hold pending a bugfix.<\/li>\n<li>Support for module import under <code>\/clr<\/code>.<\/li>\n<\/ul>\n<p>Both the above are expected to be fixed in a near-future release of\nMSVC.<\/p>\n<p>The remainder of this blog post will discuss the background details,\nlimitations, and caveats of C++20 support for C++\/CLI.<\/p>\n<h2>Brief history and background of C++\/CLI<\/h2>\n<p>C++\/CLI was first introduced as an extension to C++98. It was specified\nas a superset of C++98 but soon with the introduction of C++11, some\nincompatibilities appeared between C++\/CLI and ISO C++, some of which\nexist to this day. [Aside: <code>nullptr<\/code> and <code>enum class<\/code> were originally\nC++\/CLI inventions that were migrated to ISO C++ and standardized.]\nWith the further introduction of C++14 and C++17 standards, it became\nincreasingly challenging to support the newer language standards and\neventually due to the effort required to implement C++20, we decided\nto temporarily limit standard support to C++17 in <code>\/clr<\/code> mode. A\nprime reason for this was the pause in the evolution of the C++\/CLI\nspecification, which has not been updated since its introduction and\ntherefore could not guide the interaction of C++\/CLI features with the\nnew features being introduced in ISO C++.<\/p>\n<p>The design rationale for C++\/CLI is spelled out in <a href=\"http:\/\/www.gotw.ca\/publications\/C++CLIRationale.pdf\">this\ndocument<\/a>.<\/p>\n<p>Originally envisioned as a first-class language for <code>.NET<\/code>, C++\/CLI&#8217;s\nuse has primarily fallen into the category of <em>interop<\/em>, which includes\nboth directions from managed to native and vice versa. The demise of\nC++\/CLI has been predicted many times, but it continues to thrive in\nWindows applications primarily because of its strength in interop, where\nit is very easy to use and hard to beat in performance. The original\ngoals spelled out in the C++\/CLI Rationale were:<\/p>\n<ol>\n<li>Enable C++\/CLI as a first-class programming language on <code>.NET<\/code>.<\/li>\n<li>Use the fewest possible extensions. ISO C++ code &#8220;just works&#8221; and\nconforming extensions are added to ISO C++ to allow working with\n<code>.NET<\/code> types.<\/li>\n<li>Be as orthogonal as possible: if feature X works on ISO C++ types,\nit should also work on C++\/CLI types.<\/li>\n<\/ol>\n<p>With the specialization of C++\/CLI as an interop language, the ECMA\nspecification for it was not updated to keep up with fast evolving\n<code>.NET<\/code> features with the result that it can no longer be called a\nfirst-class language on <code>.NET<\/code>. While it can consume most of the\nfundamental types in the <code>.NET<\/code> base-class library, not all features are\navailable due to lack of support from C++\/CLI. This has resulted in both\n<code>\/clr:safe<\/code> (allow only the &#8220;safe&#8221; CLS subset of CLI, disallowing native\ncode) and <code>\/clr:pure<\/code> (compile everything to pure MSIL code) to be\ndeprecated. The only options supported currently are <code>\/clr<\/code>, which\ntargets the <code>.NET<\/code> Framework, and <code>\/clr:netcore<\/code> which targets NetCore.\nIn both cases, compilation of native types and code, and interop are\nsupported.<\/p>\n<p>Goal (2) was originally achieved nearly perfectly in C++98 but newer\nversions of ISO C++ caused MSVC to deviate from this goal.<\/p>\n<p>With regard to goal (3), C++\/CLI was never specified or implemented to\nthe required level of full generality to satisfy this goal. While some\nfeatures such as templates were made orthogonal to ISO C++ and managed\nC++\/CLI types, the full generality of features such as allocating\nmanaged types on the native heap with <code>new<\/code>, allowing managed types to\nembed in native types, etc., were not specified by the ECMA\nspecification. This lack of support turns out to be fortuitous and\nallows us to move forward with implementing support for newer ISO C++\nStandards.<\/p>\n<h2>C++20 support for C++\/CLI<\/h2>\n<p>While C++14 and C++17 were mostly incremental updates to C++11, as far\nas the core language is concerned, C++20 is a large change because of\nfeatures like <em>concepts<\/em>, <em>modules<\/em>, and <em>coroutines<\/em>. While coroutines\naren&#8217;t yet pervasive, concepts and modules are already in use in the ISO\nC++ Standard Library.<\/p>\n<p>Generally speaking, we need support from the <code>.NET<\/code> runtime whenever the\nlanguage introduces a new feature which has a runtime impact in the area\nof <a href=\"https:\/\/learn.microsoft.com\/cpp\/dotnet\/using-cpp-interop-implicit-pinvoke\">implicit P\/Invoke\ninterop<\/a>.\nTwo examples from C++11 are move constructors and <code>noexcept<\/code>. The <code>.NET<\/code>\nruntime&#8217;s P\/Invoke engine already knew how to call copy constructors\nwhen objects were copied across the managed\/native boundary. With the\nintroduction of move constructors, types like <code>std::unique_ptr<\/code> were\nhandled incorrectly in interop because they have a move constructor\ninstead of a copy constructor. Handling this correctly required adding\nfunctionality to the P\/Invoke engine on the <code>.NET<\/code> side and generating\nthe code and metadata to make sure it was called appropriately. For the\n<code>noexcept<\/code> case, we still don&#8217;t have a correct implementation available.\nWhile we handle <code>noexcept<\/code> correctly in the type system, at runtime an\nexception crossing a function with a <code>noexcept<\/code> specification does not\nresult in program termination. Implementing this would, again, require\nteaching the <code>.NET<\/code> runtime how to handle such cases but due to no user\ndemand to handle this correctly, it has been left unimplemented.<\/p>\n<p>We wanted to avoid requiring new functionality in the <code>.NET<\/code> runtime\nsince doing so is time consuming and requires expensive updates, so to\nadd support for C++20 to C++\/CLI, we followed this general principle:<\/p>\n<blockquote><p><em>Separate C++\/CLI types from new C++20 features but allow all possible\nC++20 features with native types in a <code>\/clr<\/code> compilation.<\/em><\/p><\/blockquote>\n<p>To achieve this, the implementation of C++20 support in C++\/CLI follows\nthis scheme:<\/p>\n<ul>\n<li>All native code in a translation unit is compiled to managed MSIL,\nwith the exception of code having these constructs in it:<\/p>\n<ol>\n<li>aligned data types<\/li>\n<li>inline assembly<\/li>\n<li>calls to <a href=\"https:\/\/learn.microsoft.com\/cpp\/cpp\/naked-cpp\">functions declared\n<code>__declspec(naked)<\/code><\/a><\/li>\n<li>references to <code>__ImageBase<\/code><\/li>\n<li>functions with vararg (<code>...<\/code>) arguments<\/li>\n<li><code>__ptr32<\/code> or <code>__ptr64<\/code> modifiers on pointer types<\/li>\n<li>CPU intrinsics or other intrinsic functions<\/li>\n<li>virtual call thunks to virtual functions not declared\n<code>__clrcall<\/code><\/li>\n<li><code>setjmp<\/code> or <code>longjmp<\/code><\/li>\n<li>coroutines<\/li>\n<\/ol>\n<p>With the exception of coroutines, the above list has already been\nthe case for compilation to native in prior versions of MSVC. All\nsemantics conform to ISO C++ semantics, as before, the only\ndifference being that the compiler emits managed instructions.\nNative types are emitted out to metadata as empty value classes with\na given size, just to provide tokens for type names when they&#8217;re\nused in function signatures. Otherwise, they remain a black-box to\nthe runtime and are handled entirely at the byte level by the\ngenerated managed code.<\/li>\n<li>Conformant (two-phase) name lookup (<code>[temp.res]<\/code> in ISO C++) is\nenabled in <em>native<\/em> templates in <code>\/clr<\/code> compilations. Previous\nversions of MSVC forced the user to specify <code>\/Zc:twoPhase-<\/code> when\nusing <code>\/clr<\/code> and any flag that implied ISO C++ name lookup\nsemantics.<\/li>\n<li>Coroutines are implemented by compiling all coroutines to native\ncode. This requires no new support from the <code>.NET<\/code> runtime and uses\nthe native runtime support. The disadvantage is that all calls to\ncoroutines are interop calls that have the transition and\nmarshalling overhead.<\/li>\n<li>Allow concepts to interact only with native types. This is another\nviolation of the &#8220;orthogonality&#8221; goal mentioned above. The exception\nis C++\/CLI types that have a 1-1 mapping with native types such as\n<code>System::Int32<\/code>, etc.<\/li>\n<li>Allow <em>import<\/em> of modules but not export from translation units\ncompiled with <code>\/clr<\/code>. In a similar vein, module header units cannot\nbe generated in a <code>\/clr<\/code> compilation but may be used in one. This\nrestriction is because the module metadata format is based on the\n<a href=\"https:\/\/github.com\/microsoft\/ifc-spec\">IFC specification<\/a>, which has no support for C++\/CLI types.<\/li>\n<li>Allow <em>all<\/em> Standard Library headers to compile with <code>\/clr<\/code> and\nC++20. Some headers had previously been blocked off from being\ncompiled as managed because they included ConcRT parallel\nprogramming headers as a dependency, while some, like <code>&lt;atomic&gt;<\/code>,\nhad no support in <code>.NET<\/code>. The dependency on ConcRT is now removed\nand headers previously forbidden from inclusion with <code>\/clr<\/code> have\nbeen updated. Note: some of these headers are still forbidden from\ninclusion in <code>\/clr:pure<\/code> mode.<\/li>\n<\/ul>\n<p>No attempt is being made to fix the below pre-existing issues. If there\nis user demand, these can be handled separately in the future.<\/p>\n<ul>\n<li>Lack of <code>noexcept<\/code> support from <code>.NET<\/code>, as explained above.<\/li>\n<li><code>enum class<\/code> has differing meanings in C++\/CLI and ISO C++. This is\ncurrently resolved by treating such declarations as native enums\nexcept when preceded by an access specifier (as C++\/CLI allows).<\/li>\n<li><code>nullptr<\/code> has differing meanings in C++\/CLI and ISO C++. In cases\nwhere this matters, <code>__nullptr<\/code> is provided to mean the ISO\nC++ <code>nullptr<\/code> value.<\/li>\n<\/ul>\n<p>Going forward, we plan to use the same strategy to support future ISO\nC++ versions: compile constructs that have no support from <code>.NET<\/code> to\nnative code and keep the C++\/CLI and ISO C++ type universes separate. In\nthe rare case where a new mechanism is required for marshalling types\nacross managed\/native boundaries, we shall require new support from\n<code>.NET<\/code>. Historically, this has not happened since C++11.<\/p>\n<h2>Examples<\/h2>\n<p>The below examples illustrate how C++20 constructs are being handled in\nC++\/CLI.<\/p>\n<h3>Coroutines<\/h3>\n<p>There are no restrictions on coroutines. They may be used in their full\ngenerality with the understanding that the coroutines themselves are\nalways compiled to native code.<\/p>\n<p>Consider the below program fragment:<\/p>\n<pre><code class=\"language-c++\">generator&lt;move_only&gt; answer()\r\n{\r\n    co_yield move_only(1);\r\n    co_yield move_only(2);\r\n    co_yield move_only(3);\r\n    move_only m(4);\r\n    co_return m; \/\/ Move constructor should be used here when present\r\n}\r\n\r\nint main()\r\n{\r\n    int sum = 0; \r\n    auto g = answer();\r\n    for (move_only&amp;&amp; m : g)\r\n    {\r\n        sum += m.val;\r\n    }\r\n    return sum == 6 ? 0 : 42+sum;\r\n}<\/code><\/pre>\n<p>Inspecting the generated IL for this, we can see this IL sequence:<\/p>\n<pre><code class=\"language-plaintext\">IL_0000:  ldc.i4.0\r\nIL_0001:  stloc.2\r\nIL_0002:  ldc.i4.0\r\nIL_0003:  stloc.0\r\nIL_0004:  ldloca.s   V_8\r\nIL_0006:  call       valuetype 'generator&lt;move_only&gt;'*\r\n              modreq([mscorlib]System.Runtime.CompilerServices.IsUdtReturn)\r\n              modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl)\r\n              answer(valuetype 'generator&lt;move_only&gt;'*)\r\nIL_000b:  pop<\/code><\/pre>\n<p>together with:<\/p>\n<pre><code class=\"language-plaintext\">method assembly static pinvokeimpl(\/* No map *\/) \r\n        valuetype 'generator&lt;move_only&gt;'*\r\n        modreq([mscorlib]System.Runtime.CompilerServices.IsUdtReturn)\r\n        modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl) \r\n        answer(valuetype 'generator&lt;move_only&gt;'* A_0)\r\n                           native unmanaged preservesig\r\n{\r\n  \/\/ Embedded native code\r\n} \/\/ end of method 'Global Functions'::answer<\/code><\/pre>\n<p>showing that <code>answer()<\/code> is a native method and the call to it in the\nabove MSIL disassembly fragment is an interop call. This is shown only\nfor exposition and the user has to do absolutely nothing to make it\nwork.<\/p>\n<h3>Concepts<\/h3>\n<p>Since concepts are a mechanism to perform computations on types at\ncompile time, there is no runtime component to be supported by <code>.NET<\/code>.\nFurther, concepts &#8220;disappear&#8221; once templates are specialized and\ntemplates have been supported for C++\/CLI from the outset. There are two\nkinds of templates supported in C++\/CLI, ISO C++ templates and <em>managed\ntemplates<\/em> whose specialization results in managed types. We have made\nthe choice to keep all managed types separate from concepts and this\nincludes managed templates. Any attempt to mix managed types and\nconcepts results in a failed compilation with diagnostics. Note that\nthis excludes types like <code>System::Int32<\/code> which can be mapped directly to\nnative types, but boxing of such types is also excluded from interaction\nwith concepts.<\/p>\n<pre><code class=\"language-c++\">#include &lt;concepts&gt;\r\n#include &lt;utility&gt;\r\ntemplate&lt;std::swappable Swappable&gt;\r\nvoid Swap(Swappable&amp; s1, Swappable&amp; s2)\r\n{\r\n    s1.swap(s2);\r\n}\r\nstruct SwapMe\r\n{\r\n    int i;\r\n    void swap(SwapMe&amp; other) { std::swap(this-&gt;i, other.i); }\r\n};\r\nvalue struct SwapMeV\r\n{\r\n    int i;\r\n    void swap(SwapMeV% other) { auto tmp = i; i = other.i; other.i = tmp; }\r\n};\r\nint main()\r\n{\r\n    SwapMe s1, s2;\r\n    Swap(s1, s2);\r\n    SwapMeV s1v, s2v;\r\n    Swap(s1v, s2v);   \/\/ error C7694: managed type 'SwapMeV' \r\n                      \/\/ used in a constraint definition or evaluation\r\n                      \/\/ or in an entity that uses constraints\r\n    \/\/ Boxed value types\r\n    int ^b1 = 1;      \r\n    int ^b2 = 2;\r\n    Swap(b1, b2);     \/\/ error C7694: managed type 'System::Int32 ^'\r\n                      \/\/ used in a constraint definition or evaluation\r\n                      \/\/ or in an entity that uses constraints\r\n}<\/code><\/pre>\n<p>In the above example, the native types work exactly as for ISO C++\ncompilation without <code>\/clr<\/code> but attempting to use concepts with C++\/CLI\ntypes generates a diagnostic. It is possible to widen concepts to allow\na carefully chosen subset of the C++\/CLI type universe, but for this\nversion of MSVC, we have chosen to keep them separate. Removing the line\nwith the diagnostic and inspecting the disassembly shows us<\/p>\n<pre><code class=\"language-plaintext\">IL_0009:  ldloca.s   V_3\r\nIL_000b:  ldloca.s   V_2\r\nIL_000d:  call       void\r\nmodopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl)\r\n  'Swap&lt;struct SwapMe&gt;'(\r\n    valuetype SwapMe* modopt([mscorlib]System.Runtime.CompilerServices.IsImplicitlyDereferenced),\r\n    valuetype SwapMe* modopt([mscorlib]System.Runtime.CompilerServices.IsImplicitlyDereferenced))<\/code><\/pre>\n<p>Note that the parameter types of the function are <code>SwapMe*<\/code> and the\nconcept <code>std::swappable<\/code> does not appear.<\/p>\n<h3>Modules<\/h3>\n<p>As mentioned above, in C++\/CLI modules can only be imported, either as\nregular ISO C++ modules, or header units, created from headers that\ncontain no C++\/CLI code. Thus, we have these restrictions on modules:<\/p>\n<pre><code class=\"language-c++\">module m;               \/\/ error under \/clr\r\nexport module m;        \/\/ error under \/clr\r\nexport class X;\r\nexport import m;        \/\/ error under \/clr\r\nimport m;               \/\/ OK with \/clr\r\nimport &lt;vector&gt;;        \/\/ header units OK with \/clr<\/code><\/pre>\n<p>From the command-line, certain flag combinations will produce errors:<\/p>\n<pre><code class=\"language-plaintext\">cl \/exportHeader \/clr ...       # error\r\ncl \/ifcOutput \/clr ...          # error\r\ncl \/ifcOnly \/clr ...            # error<\/code><\/pre>\n<h4>Interop and module import<\/h4>\n<p>Since, currently we don&#8217;t allow module export under <code>\/clr<\/code>, and since\nthe modules can have code in associated <code>.obj<\/code> files, unless built with\n<code>\/ifcOnly<\/code>, it follows that a call to any imported non-inline function\nhas to be an interop call to native code. For templates, this\nrestriction is not required and hence importing, say the class template\n<code>std::vector<\/code>, can result in its member functions being compiled to\nmanaged code. This is an important consideration for performance since\ninterop calls will inhibit inlining. We shall provide more guidance in a\nfuture blog article, when support for modules under <code>\/clr<\/code> ships.<\/p>\n<h2>Conclusion and call to action<\/h2>\n<p>C++20 support is being added to C++\/CLI with very few restrictions as\nfar as native types are concerned. The general principle followed is\nthat of least surprise: if a particular feature is valid in a native\ncompilation, there is a high chance it is also valid under <code>\/clr<\/code>.<\/p>\n<p>If you have C++\/CLI code, we encourage you to turn on C++20 compilation,\ntry the product and report bugs via Visual Studio Feedback. If you have\nspecific need for modules to be used in conjunction with C++\/CLI, again,\nwe would appreciate feedback.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Details of C++20 support for C++\/CLI introduced in VS 2022 17.6<\/p>\n","protected":false},"author":67691,"featured_media":35994,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1,512],"tags":[],"class_list":["post-32232","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cplusplus","category-general-cpp-series"],"acf":[],"blog_post_summary":"<p>Details of C++20 support for C++\/CLI introduced in VS 2022 17.6<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/posts\/32232","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/users\/67691"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/comments?post=32232"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/posts\/32232\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/media\/35994"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/media?parent=32232"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/categories?post=32232"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/tags?post=32232"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}