<!DOCTYPE html>
<html>

<head>

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>ModulesAtScale</title>


<style type="text/css">
/**
 * prism.js default theme for JavaScript, CSS and HTML
 * Based on dabblet (http://dabblet.com)
 * @author Lea Verou
 */

code[class*="language-"],
pre[class*="language-"] {
	color: black;
	background: none;
	text-shadow: 0 1px white;
	font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
	text-align: left;
	white-space: pre;
	word-spacing: normal;
	word-break: normal;
	word-wrap: normal;
	line-height: 1.5;

	-moz-tab-size: 4;
	-o-tab-size: 4;
	tab-size: 4;

	-webkit-hyphens: none;
	-moz-hyphens: none;
	-ms-hyphens: none;
	hyphens: none;
}

pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
	text-shadow: none;
	background: #b3d4fc;
}

pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
	text-shadow: none;
	background: #b3d4fc;
}

@media print {
	code[class*="language-"],
	pre[class*="language-"] {
		text-shadow: none;
	}
}

/* Code blocks */
pre[class*="language-"] {
	padding: 1em;
	margin: .5em 0;
	overflow: auto;
}

:not(pre) > code[class*="language-"],
pre[class*="language-"] {
	background: #f5f2f0;
}

/* Inline code */
:not(pre) > code[class*="language-"] {
	padding: .1em;
	border-radius: .3em;
	white-space: normal;
}

.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
	color: slategray;
}

.token.punctuation {
	color: #999;
}

.namespace {
	opacity: .7;
}

.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
	color: #905;
}

.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
	color: #690;
}

.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
	color: #a67f59;
	background: hsla(0, 0%, 100%, .5);
}

.token.atrule,
.token.attr-value,
.token.keyword {
	color: #07a;
}

.token.function {
	color: #DD4A68;
}

.token.regex,
.token.important,
.token.variable {
	color: #e90;
}

.token.important,
.token.bold {
	font-weight: bold;
}
.token.italic {
	font-style: italic;
}

.token.entity {
	cursor: help;
}
</style>


</head>

<body>

<h1 id="toc_0">Modules at scale</h1>

<p><em>Document number: P0841R0</em>
</br>
<em>Audience: EWG</em>
</br>
<em><a href="mailto:blopes@apple.com">Bruno Cardoso Lopes</a>,
<a href="mailto:aprantl@apple.com">Adrian Prantl</a>,
<a href="mailto:dexonsmith@apple.com">Duncan P. N. Exon Smith</a></em>
</br>
<em>2017-10-16</em></p>

<hr>

<p>This paper discusses Apple&#39;s experience with modules on its software ecosystem and how the current state of the <a href="http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/n4681.pdf">Modules TS</a> lacks the necessary expressiveness to reflect Apple&#39;s requirements.</p>

<h2 id="toc_1">Background</h2>

<p>Apple currently uses <a href="https://clang.llvm.org/docs/Modules.html">Clang Modules</a>, a <em>Modules</em> design and implementation that&#39;s specific to clang and contains support for different C-based languages; C, Objective-C, Objective-C++ and C++. The intent of this paper is not to sell <em>Clang Modules</em> but to provide insights based on the current requirements of Apple&#39;s libraries. Ideally we would like to incorporate changes into the <a href="http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/n4681.pdf">Modules TS</a> that would allow Apple to support C++ Modules in its shipping library interface.</p>

<h2 id="toc_2">Modules at Apple</h2>

<p>Clang&#39;s modules implementation predates the <a href="http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/n4681.pdf">Modules TS</a> and was first introduced in 2012. Since then Apple has been shipping modularized library interfaces (headers) in its SDKs, enabled the use of modules by default for all new macOS/iOS projects, and facilitated the widespread use of modules by third-party libraries on Apple platforms.  We also support modules internally for hundreds of internal projects.</p>

<p>The majority of these projects are written in Objective-C or Objective-C++, and although not strictly C++, they export C++ or C++-compatible interfaces, which are consumed by internal or external C++ projects; Clang and Webkit are examples of such projects.</p>

<h3 id="toc_3">Interoperability through modules</h3>

<p>The <em>Swift</em> language supports interoperation with C and Objective-C, which relies on Modules for bridging the languages. Apple&#39;s developer tools provide transparent generation of modules for C and Objective-C libraries, meaning that the majority of Swift developers targeting Apple&#39;s platforms indirectly use Modules, because:</p>

<ul>
<li>Swift access to Apple APIs is done through the C and Objective-C headers coming from SDKs.</li>
<li>The primary way to access third party library APIs written in C and Objective-C in Swift is through a modular interface.</li>
</ul>

<h2 id="toc_4">Header Modules</h2>

<p>To better explain Apple&#39;s use of <em>Clang Modules</em> in <a href="http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/n4681.pdf">Modules TS</a> terms, Apple ships and provides customers the equivalent of the <em>module interface unit</em> part of a library, meaning that Apple provides one module for each library, where the exported content comes from a set of headers. The set of headers in a library is entirely described in one module, which may or may not contain associated submodules.</p>

<p>Different libraries in Apple&#39;s SDKs contain dependencies between each other. Apple&#39;s SDK is almost entirely modularized bottom-up, meaning that the relationship between all library dependencies is a chain of module imports and few non-modular includes. This allows customers to consume modules at almost every single point where a header from the SDK is used.</p>

<p>Modularization of headers are described in a special textual file (<em>modulemap</em>) that maps headers to <em>modules</em> and <em>submodules</em>. Clang performs a module import transparently: parsing a <code>#include</code> directive triggers a module import if a <em>modulemap</em> is found for the header.</p>

<p>Export control is done at module granularity level, for each module one can specify if the entire module, a few selected submodules or nothing at all is exported - see Clang Modules <a href="https://clang.llvm.org/docs/Modules.html">documentation</a> for details.</p>

<p>For instance, two possible ways of mapping are:</p>

<ol>
<li>A main header (<em>umbrella header</em>) that includes all other headers that are part of the library interface. Each header in the umbrella is automatically assigned to a submodule.</li>
<li>The explicit list of headers that are part of the module, with possible inline declaration of submodules and their sub-sequential explicit list of headers or an umbrella header.</li>
</ol>

<p>To illustrate, take library <code>Foo</code> with umbrella header <code>Foo.h</code>, including <code>FooA.h</code> and <code>FooB.h</code> headers. This corresponds to module <code>Foo</code> with submodules <code>Foo.FooA</code> and <code>Foo.FooB</code>.</p>

<p>In order to properly modularize bottom-up, Apple ships <em>modulemaps</em> in its standard C++ library (libcxx) and C library (Darwin&#39;s <code>PATH_TO_SDK/usr/include</code>). </p>

<h3 id="toc_5">Problems with the current form of Modules TS</h3>

<p>The <a href="http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/n4681.pdf">Modules TS (10.7.2)</a> states that there can only be one <em>module interface unit</em> file in a module:</p>

<blockquote>
<p>A module interface unit is a module unit whose module-declaration contains the export keyword; any other module unit is a module implementation unit. A named module shall contain exactly one module interface unit.</p>
</blockquote>

<p>This is a showstopper for us, because as mentioned in the previous section, Apple&#39;s libraries consists of one or more headers, where any of them can contribute content to be exported in the final module. Additionally, the headers are shared among other supported C-based languages, and we strongly believe it&#39;s a reasonable model to continue shipping.</p>

<p>Similar concerns have been mentioned before, see the <em>Module partitions</em> section from <a href="http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0273r0.pdf">P0273R0</a>.</p>

<h2 id="toc_6">Macros</h2>

<p>Given that Apple library interfaces support different C-based languages, we rely on macros in order to correctly gather availability information, heterogeneous platform support and to reason on top of features. Because Apple&#39;s SDK is entirely modularized, these macros are available for consumption at any library interface level, being critical in Apple&#39;s chain of module imports.</p>

<p>For example, as one can see in <code>LinearAlgebra/base.h</code> shipped with the SDK in macOS 10.13, the header use macros to control availability information for a library:</p>

<div><pre><code class="language-cpp">/*  Define abstractions for a number of attributes that we wish to be able to 
concisely attach to functions in the LinearAlgebra library.             */
#define LA_AVAILABILITY  __OSX_AVAILABLE_STARTING(__MAC_10_10,__IPHONE_8_0)
...
#define LA_FUNCTION      OS_EXPORT OS_NOTHROW
#define LA_CONST         OS_CONST</code></pre></div>

<p>Note that <code>__MAC_10_10</code> is available through another module that provides macros from <code>Availability.h</code>. Apple&#39;s customers depend on macros with modules like these.</p>

<h3 id="toc_7">Problems with the lack of Macros in Modules TS</h3>

<p>The <a href="http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/n4681.pdf">Modules TS</a> lacks support for macros. According to Section 3.2 in <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0142r0.pdf">p0142r0</a>:</p>

<blockquote>
<p>... because the preprocessor is largely independent of the core language, it is impossible for a tool to understand (even grammatically) source code in header files without knowing the set of macros and configurations that a source file including the header file will activate. It is regrettably far too easy and far too common to under-appreciate how much macros are (and have been) stifling development of semantics-aware programming tools and how much of drag they constitute for C++, compared to alternatives...</p>
</blockquote>

<p>Using Modules as a way to break away from macros seems fair but we believe a transition path is necessary, since no support for macros blocks Apple&#39;s path to adopting C++ Modules. Additionally, concerns from others were already outlined in <a href="http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0273r1.pdf">P0273R1</a>.</p>

<p>We propose support for macros with the intent of helping with migration. To achieve it, we suggest adding extra syntax that:</p>

<ul>
<li>Adds on top of existing <a href="http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/n4681.pdf">Modules TS</a> syntax.</li>
<li>Can easily be later deprecated and removed, without polluting the reserved keyword namespace.</li>
</ul>

<h3 id="toc_8">Proposed macros syntax</h3>

<p>To export macros defined in a module, we propose augmenting the <em>module-declaration</em> in a <em>module interface unit</em> with a submodule-like specification:</p>

<div><pre><code class="language-cpp">export module M; // declare module M
...
export module M.__macros; // M export all macros</code></pre></div>

<p>In the code snippet above, all macros in <code>M</code>&#39;s <em>module interface unit</em> are exported. To select macros to export, a macro identifier can be explicitly specified with <code>export module M.__macros.&lt;MACRO_NAME&gt;</code> as if accessing a reserved submodule:</p>

<div><pre><code class="language-cpp">export module M.__macros.INFINITY; // M exports macro INFINITY
...
export module M.__macros.HUGE_VAL; // M exports macro HUGE_VAL</code></pre></div>

<p>On the module consumer side, a similar mechanism is used. To import the complete set of macros exported by module <code>M</code>:</p>

<div><pre><code class="language-cpp">import M.__macros; // import all macros exported in module M</code></pre></div>

<p>Similarly, for importing specific known macros from module <code>M</code>:</p>

<div><pre><code class="language-cpp">import M.__macros.INFINITY; // import macro INFINITY from module M
import M.__macros.HUGE_VAL; // import macro HUGE_VAL from module M</code></pre></div>

<p>Note that exporting all macros in module <code>M</code> doesn&#39;t by default make the macros visible to importers of <code>M</code>. Importers must explicitly <code>import M._macros</code> or <code>import M._macros.&lt;MACRO_NAME&gt;</code> to make macros defined in <code>M</code> visible. This enforces judicious use of macros in a modular world.</p>

<p>The use of <code>import</code> for macros is only allowed <em>after</em> a <em>module-declaration</em> in the same file. Likewise <code>export</code> is only valid after a <em>module-import-declaration</em> in the same file.</p>

<p>Representing macros with syntactic sugar allows for deprecation of the mechanism later on, without pollution of the top-level reserved keyword space.</p>

<h2 id="toc_9">The <code>module</code> keyword</h2>

<p>It is not clear in the <a href="http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/n4681.pdf">Modules TS</a> whether <code>module</code> is an always reserved or context-sensitive reserved keyword. </p>

<p>Apple has been shipping for several years a kernel extension interface in C++, where the identifier <code>module</code> is used to name members describing a kernel module, as one example in <code>/usr/include/mach/host_priv.h</code>:</p>

<div><pre><code class="language-cpp">...
kern_return_t kmod_create
(
    host_priv_t host_priv,
    vm_address_t info,
    kmod_t *module
);</code></pre></div>

<p>For instance, some versions of Clang would <a href="https://bugs.llvm.org/show_bug.cgi?id=33801">complain</a> and error out when including the code above because of the keyword conflict in Clang&#39;s implementation of the <a href="http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/n4681.pdf">Modules TS</a>.</p>

<p>We propose that <code>module</code> becomes a context-sensitive reserved keyword allowing existing libraries and and interfaces to continue using it in contexts where no semantic conflict with modules is possible.</p>



<script type="text/javascript">
var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(\w+)\b/i,t=0,n=_self.Prism={util:{encode:function(e){return e instanceof a?new a(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++t}),e.__id},clone:function(e){var t=n.util.type(e);switch(t){case"Object":var a={};for(var r in e)e.hasOwnProperty(r)&&(a[r]=n.util.clone(e[r]));return a;case"Array":return e.map&&e.map(function(e){return n.util.clone(e)})}return e}},languages:{extend:function(e,t){var a=n.util.clone(n.languages[e]);for(var r in t)a[r]=t[r];return a},insertBefore:function(e,t,a,r){r=r||n.languages;var l=r[e];if(2==arguments.length){a=arguments[1];for(var i in a)a.hasOwnProperty(i)&&(l[i]=a[i]);return l}var o={};for(var s in l)if(l.hasOwnProperty(s)){if(s==t)for(var i in a)a.hasOwnProperty(i)&&(o[i]=a[i]);o[s]=l[s]}return n.languages.DFS(n.languages,function(t,n){n===r[e]&&t!=e&&(this[t]=o)}),r[e]=o},DFS:function(e,t,a,r){r=r||{};for(var l in e)e.hasOwnProperty(l)&&(t.call(e,l,e[l],a||l),"Object"!==n.util.type(e[l])||r[n.util.objId(e[l])]?"Array"!==n.util.type(e[l])||r[n.util.objId(e[l])]||(r[n.util.objId(e[l])]=!0,n.languages.DFS(e[l],t,l,r)):(r[n.util.objId(e[l])]=!0,n.languages.DFS(e[l],t,null,r)))}},plugins:{},highlightAll:function(e,t){var a={callback:t,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};n.hooks.run("before-highlightall",a);for(var r,l=a.elements||document.querySelectorAll(a.selector),i=0;r=l[i++];)n.highlightElement(r,e===!0,a.callback)},highlightElement:function(t,a,r){for(var l,i,o=t;o&&!e.test(o.className);)o=o.parentNode;o&&(l=(o.className.match(e)||[,""])[1],i=n.languages[l]),t.className=t.className.replace(e,"").replace(/\s+/g," ")+" language-"+l,o=t.parentNode,/pre/i.test(o.nodeName)&&(o.className=o.className.replace(e,"").replace(/\s+/g," ")+" language-"+l);var s=t.textContent,u={element:t,language:l,grammar:i,code:s};if(!s||!i)return n.hooks.run("complete",u),void 0;if(n.hooks.run("before-highlight",u),a&&_self.Worker){var c=new Worker(n.filename);c.onmessage=function(e){u.highlightedCode=e.data,n.hooks.run("before-insert",u),u.element.innerHTML=u.highlightedCode,r&&r.call(u.element),n.hooks.run("after-highlight",u),n.hooks.run("complete",u)},c.postMessage(JSON.stringify({language:u.language,code:u.code,immediateClose:!0}))}else u.highlightedCode=n.highlight(u.code,u.grammar,u.language),n.hooks.run("before-insert",u),u.element.innerHTML=u.highlightedCode,r&&r.call(t),n.hooks.run("after-highlight",u),n.hooks.run("complete",u)},highlight:function(e,t,r){var l=n.tokenize(e,t);return a.stringify(n.util.encode(l),r)},tokenize:function(e,t){var a=n.Token,r=[e],l=t.rest;if(l){for(var i in l)t[i]=l[i];delete t.rest}e:for(var i in t)if(t.hasOwnProperty(i)&&t[i]){var o=t[i];o="Array"===n.util.type(o)?o:[o];for(var s=0;s<o.length;++s){var u=o[s],c=u.inside,g=!!u.lookbehind,h=!!u.greedy,f=0,d=u.alias;u=u.pattern||u;for(var p=0;p<r.length;p++){var m=r[p];if(r.length>e.length)break e;if(!(m instanceof a)){u.lastIndex=0;var y=u.exec(m),v=1;if(!y&&h&&p!=r.length-1){var b=r[p+1].matchedStr||r[p+1],k=m+b;if(p<r.length-2&&(k+=r[p+2].matchedStr||r[p+2]),u.lastIndex=0,y=u.exec(k),!y)continue;var w=y.index+(g?y[1].length:0);if(w>=m.length)continue;var _=y.index+y[0].length,P=m.length+b.length;if(v=3,P>=_){if(r[p+1].greedy)continue;v=2,k=k.slice(0,P)}m=k}if(y){g&&(f=y[1].length);var w=y.index+f,y=y[0].slice(f),_=w+y.length,S=m.slice(0,w),O=m.slice(_),j=[p,v];S&&j.push(S);var A=new a(i,c?n.tokenize(y,c):y,d,y,h);j.push(A),O&&j.push(O),Array.prototype.splice.apply(r,j)}}}}}return r},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r,l=0;r=a[l++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.matchedStr=a||null,this.greedy=!!r};if(a.stringify=function(e,t,r){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return a.stringify(n,t,e)}).join("");var l={type:e.type,content:a.stringify(e.content,t,r),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:r};if("comment"==l.type&&(l.attributes.spellcheck="true"),e.alias){var i="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(l.classes,i)}n.hooks.run("wrap",l);var o="";for(var s in l.attributes)o+=(o?" ":"")+s+'="'+(l.attributes[s]||"")+'"';return"<"+l.tag+' class="'+l.classes.join(" ")+'" '+o+">"+l.content+"</"+l.tag+">"},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var t=JSON.parse(e.data),a=t.language,r=t.code,l=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),l&&_self.close()},!1),_self.Prism):_self.Prism;var r=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return r&&(n.filename=r.src,document.addEventListener&&!r.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",n.highlightAll)),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
</script>

<script type="text/javascript">
Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:{pattern:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/};
</script>

<script type="text/javascript">
Prism.languages.c=Prism.languages.extend("clike",{keyword:/\b(asm|typeof|inline|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while)\b/,operator:/\-[>-]?|\+\+?|!=?|<<?=?|>>?=?|==?|&&?|\|?\||[~^%?*\/]/,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)[ful]*\b/i}),Prism.languages.insertBefore("c","string",{macro:{pattern:/(^\s*)#\s*[a-z]+([^\r\n\\]|\\.|\\(?:\r\n?|\n))*/im,lookbehind:!0,alias:"property",inside:{string:{pattern:/(#\s*include\s*)(<.+?>|("|')(\\?.)+?\3)/,lookbehind:!0},directive:{pattern:/(#\s*)\b(define|elif|else|endif|error|ifdef|ifndef|if|import|include|line|pragma|undef|using)\b/,lookbehind:!0,alias:"keyword"}}},constant:/\b(__FILE__|__LINE__|__DATE__|__TIME__|__TIMESTAMP__|__func__|EOF|NULL|stdin|stdout|stderr)\b/}),delete Prism.languages.c["class-name"],delete Prism.languages.c["boolean"];
</script>

<script type="text/javascript">
Prism.languages.cpp=Prism.languages.extend("c",{keyword:/\b(alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|class|compl|const|constexpr|const_cast|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|float|for|friend|goto|if|inline|int|long|mutable|namespace|new|noexcept|nullptr|operator|private|protected|public|register|reinterpret_cast|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,"boolean":/\b(true|false)\b/,operator:/[-+]{1,2}|!=?|<{1,2}=?|>{1,2}=?|\->|:{1,2}|={1,2}|\^|~|%|&{1,2}|\|?\||\?|\*|\/|\b(and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/}),Prism.languages.insertBefore("cpp","keyword",{"class-name":{pattern:/(class\s+)[a-z0-9_]+/i,lookbehind:!0}});
</script>


</body>

</html>
