Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enh(cpp) Improve C++ class template headers #2728

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 55 additions & 9 deletions src/languages/c-like.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export default function(hljs) {
'atomic_bool atomic_char atomic_schar ' +
'atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong ' +
'atomic_ullong new throw return ' +
'class struct' +
joshgoebel marked this conversation as resolved.
Show resolved Hide resolved
'and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq',
built_in: 'std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream ' +
'auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set ' +
Expand Down Expand Up @@ -200,6 +201,58 @@ export default function(hljs) {
]
};

var TEMPLATE_USE = {
begin: /</, end: />/,
keywords: CPP_KEYWORDS,
contains: EXPRESSION_CONTAINS.concat([
// Match left-shift to prevent it from creating a nested `TEMPLATE_USE`.
klmr marked this conversation as resolved.
Show resolved Hide resolved
{ begin: /<</ },
'self'
klmr marked this conversation as resolved.
Show resolved Hide resolved
])
};

var CLASS_DECLARATION = {
className: 'class',
beginKeywords: 'class struct union', end: /[{;:]/,
contains: [
{ beginKeywords: "final" },
TEMPLATE_USE,
TITLE_MODE
]
};

var TEMPLATE_DECLARATION = {
beginKeywords: 'template',
// Add additional stops to stop runaway template declaration, e.g.
// `template <bool b = 1 < 2> void f();`
end: /[>;{]/,
contains: [
{
begin: /</, end: />/,
endsWithParent: true,
Copy link
Member

@joshgoebel joshgoebel Oct 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just so you know this endsWithParent does not auto-recurse... it simply means that within the scope of the <...> rule (line 230/231) that it will attempt to match not only the contains rules on line 235, but ALSO the end rule on line 228. But it doesn't go any deeper than that unless you added endsWithParent at every level...

I'm not sure if this has implications for your runaway guard on line 227 or not...

Copy link
Contributor Author

@klmr klmr Oct 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! So, if I understand you correctly, I could equivalently remove endsWithParent, and change the end rule to /[>;{]/ (i.e. the same as the parent end)? In this case I should probably do that instead, and add the same end to the other contained rules.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well if the desire is that they end with the parent you'd probably want to just use that... that's what it does - pull the parent "end" down into the children... I was just pointing out that it only goes one layer deep... so if it got "stuck" in a child of a child then your "guard" would not save you... so that needs to be considered and might result in adding some more "false positive" edge case tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that works now. I needed to add a nested {begin: />/} to prevent a > inside a (…) from terminating the template declaration mode prematurely. That said, the behaviour on my unit tests is actually the same as before, I need to think of some example which works now and would previously have failed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Damn, I think I’ve just kicked the can down the road now. I think this fundamentally doesn’t work because I would need to nest the parent mode inside the mode that handles (…). With my newest change, the the two first lines parse correctly, but the template declaration in the third line ends prematurely, and the “class title” starts at class T rather than at class C:

template <int A = (B<C>), class T> class C;
template <int A = B<(c > D<E>)>, class T> class C;
template <int A = B<(c > D<(e > f)>)>, class T> class C;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  var TEMPLATE_USE = {};
  Object.assign(TEMPLATE_USE, {
    // .. can now use TEMPLATE_USE recursively

All you need is a reference. :-) Though I'm not sure we do this anywhere else and not sure it's a pattern we should start using now though. ...

I'm guessing the nesting here could be unlimited but is there some practical limits for MOST C++ code as far as what you'd see in the real world?

endsParent: true,
keywords: CPP_KEYWORDS,
contains: EXPRESSION_CONTAINS.concat([
// Match left-shift to prevent it from creating a nested `TEMPLATE_USE`.
{ begin: /<</ },
TEMPLATE_USE,
{
begin: /\(/, end: /\)/,
keywords: CPP_KEYWORDS,
contains: [
// Match left-shift to prevent it from creating a nested `TEMPLATE_USE`.
{ begin: /<</ },
TEMPLATE_USE
]
},
]),
relevance: 10
},
// FIXME: Set relevance of class declarations at this point to 10!
klmr marked this conversation as resolved.
Show resolved Hide resolved
CLASS_DECLARATION,
]
};

return {
aliases: ['c', 'cc', 'h', 'c++', 'h++', 'hpp', 'hh', 'hxx', 'cxx'],
keywords: CPP_KEYWORDS,
Expand All @@ -222,15 +275,8 @@ export default function(hljs) {
begin: hljs.IDENT_RE + '::',
keywords: CPP_KEYWORDS
},
{
className: 'class',
beginKeywords: 'class struct', end: /[{;:]/,
contains: [
{ beginKeywords: "final" },
{begin: /</, end: />/, contains: ['self']}, // skip generic stuff
hljs.TITLE_MODE
]
}
TEMPLATE_DECLARATION,
CLASS_DECLARATION,
]),
exports: {
preprocessor: PREPROCESSOR,
Expand Down
104 changes: 104 additions & 0 deletions test/markup/cpp/templates.expect.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<span class="hljs-keyword">template</span> &lt;<span class="hljs-keyword">template</span> &lt;<span class="hljs-keyword">typename</span> X&gt; <span class="hljs-keyword">class</span> T&gt; <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">f</span><span class="hljs-params">()</span></span>;

<span class="hljs-keyword">template</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">A::B</span>&lt;<span class="hljs-number">1</span>, <span class="hljs-number">2</span>&gt; {</span>};

<span class="hljs-function"><span class="hljs-keyword">template</span> <span class="hljs-keyword">void</span> <span class="hljs-title">A::f</span><span class="hljs-params">(<span class="hljs-keyword">int</span>)</span></span>;

<span class="hljs-keyword">template</span> &lt;<span class="hljs-keyword">class</span> T, <span class="hljs-built_in">std</span>::<span class="hljs-keyword">size_t</span> N = <span class="hljs-keyword">sizeof</span>(T), <span class="hljs-keyword">class</span> K = T&gt;
ostream&amp; <span class="hljs-keyword">operator</span>&lt;&lt; (ostream&amp; os, <span class="hljs-keyword">const</span> skg::Triplet&lt;T&gt; pt) ;
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">other_stuff_that_isnt_colored</span><span class="hljs-params">()</span></span>;


<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">class</span> T&gt;
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">My_vector</span> {</span> <span class="hljs-comment">/* ... */</span> };

<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">class</span> T = <span class="hljs-keyword">void</span>&gt;
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">My_op_functor</span> {</span> <span class="hljs-comment">/* ... */</span> };

<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span>... Ts&gt;
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">My_tuple</span> {</span> <span class="hljs-comment">/* ... */</span> };


<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">class</span>&gt; <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">My_vector</span>;</span>
<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">class</span> = <span class="hljs-keyword">void</span>&gt; <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">My_op_functor</span>;</span>
<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span>...&gt; <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">My_tuple</span>;</span>


<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T&gt; <span class="hljs-keyword">concept</span> C1 = <span class="hljs-literal">true</span>;
<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span>... Ts&gt; <span class="hljs-keyword">concept</span> C2 = <span class="hljs-literal">true</span>; <span class="hljs-comment">// variadic concept</span>
<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T, <span class="hljs-keyword">typename</span> U&gt; <span class="hljs-keyword">concept</span> C3 = <span class="hljs-literal">true</span>;

<span class="hljs-keyword">template</span>&lt;C1 T&gt; <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">s1</span>;</span> <span class="hljs-comment">// constraint-expression is C1&lt;T&gt;</span>
<span class="hljs-keyword">template</span>&lt;C1... T&gt; <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">s2</span>;</span> <span class="hljs-comment">// constraint-expression is (C1&lt;T&gt; &amp;&amp; ...)</span>
<span class="hljs-keyword">template</span>&lt;C2... T&gt; <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">s3</span>;</span> <span class="hljs-comment">// constraint-expression is (C2&lt;T&gt; &amp;&amp; ...)</span>
<span class="hljs-keyword">template</span>&lt;C3&lt;<span class="hljs-keyword">int</span>&gt; T&gt; <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">s4</span>;</span> <span class="hljs-comment">// constraint-expression is C3&lt;T, int&gt;</span>
<span class="hljs-keyword">template</span>&lt;C3&lt;<span class="hljs-keyword">int</span>&gt;... T&gt; <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">s5</span>;</span> <span class="hljs-comment">// constraint-expression is (C3&lt;T, int&gt; &amp;&amp; ...)</span>


<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> K, <span class="hljs-keyword">typename</span> V, <span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span>&gt; <span class="hljs-keyword">typename</span> C = my_array&gt;
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Map</span> {</span>};


<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">class</span> B&gt;
<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">class</span> C&gt;
<span class="hljs-keyword">void</span> A&lt;B&gt;::g(C) {}


<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">g</span><span class="hljs-params">()</span> </span>{
f&lt;<span class="hljs-keyword">int</span>()&gt;();
}


<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">int</span> (&amp;pa)[<span class="hljs-number">5</span>]&gt; <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">W</span> {</span>};
<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">void</span> (*pf)(<span class="hljs-keyword">int</span>)&gt; <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">A</span> {</span>};


<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">class</span> T, <span class="hljs-keyword">const</span> <span class="hljs-keyword">char</span>* p&gt; <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">X</span> {</span>};


<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T&gt; <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">A</span> {</span> <span class="hljs-keyword">int</span> x; }; <span class="hljs-comment">// primary template</span>
<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T&gt; <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">A</span>&lt;T*&gt; {</span> <span class="hljs-keyword">long</span> x; }; <span class="hljs-comment">// partial specialization</span>


<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T&gt; <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">eval</span>;</span> <span class="hljs-comment">// primary template </span>

<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span>, <span class="hljs-keyword">typename</span>...&gt; <span class="hljs-keyword">class</span> TT, <span class="hljs-keyword">typename</span> T1, <span class="hljs-keyword">typename</span>... Rest&gt;
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">eval</span>&lt;TT&lt;T1, Rest...&gt;&gt; {</span>}; <span class="hljs-comment">// partial specialization of eval</span>


eval&lt;A&lt;<span class="hljs-keyword">int</span>&gt;&gt; eA;
eval&lt;B&lt;<span class="hljs-keyword">int</span>, <span class="hljs-keyword">float</span>&gt;&gt; eB;
eval&lt;C&lt;<span class="hljs-number">17</span>&gt;&gt; eC;
eval&lt;D&lt;<span class="hljs-keyword">int</span>, <span class="hljs-number">17</span>&gt;&gt; eD;
eval&lt;E&lt;<span class="hljs-keyword">int</span>, <span class="hljs-keyword">float</span>&gt;&gt; eE;


<span class="hljs-keyword">template</span> &lt;<span class="hljs-keyword">template</span> &lt;<span class="hljs-keyword">auto</span>&gt; <span class="hljs-keyword">class</span>&gt; <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">FA</span><span class="hljs-params">()</span></span>; <span class="hljs-comment">// note: C++17</span>


<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> TT = <span class="hljs-keyword">char</span>&gt; <span class="hljs-keyword">class</span> T&gt;
<span class="hljs-keyword">void</span> A&lt;T&gt;::g()
{
T&lt;&gt; t; <span class="hljs-comment">// ok: t is T&lt;char&gt;</span>
}

s.<span class="hljs-keyword">template</span> foo&lt;T&gt;();
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">C</span>;</span>


<span class="hljs-comment">// Hard mode</span>

<span class="hljs-keyword">template</span> &lt;<span class="hljs-keyword">int</span> a, <span class="hljs-keyword">int</span> b, <span class="hljs-keyword">bool</span> c = (a &gt; b), <span class="hljs-keyword">class</span> T&gt; <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">f1</span><span class="hljs-params">()</span></span>;
<span class="hljs-keyword">template</span> &lt;<span class="hljs-keyword">int</span> a, <span class="hljs-keyword">int</span> b, <span class="hljs-keyword">bool</span> c = (a &gt;&gt; b), <span class="hljs-keyword">class</span> T&gt; <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">f2</span><span class="hljs-params">()</span></span>;

<span class="hljs-keyword">template</span> &lt;<span class="hljs-keyword">int</span> a, <span class="hljs-keyword">int</span> b, <span class="hljs-keyword">bool</span> c = (a &lt; b), <span class="hljs-keyword">class</span> T&gt; <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">g1</span><span class="hljs-params">()</span></span>;
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">C</span>;</span>

<span class="hljs-keyword">template</span> &lt;<span class="hljs-keyword">int</span> a, <span class="hljs-keyword">int</span> b, <span class="hljs-keyword">bool</span> c = a &lt; b, <span class="hljs-keyword">class</span> T&gt; <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">g1</span><span class="hljs-params">()</span></span>;
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">D</span>;</span>

<span class="hljs-keyword">template</span> &lt;<span class="hljs-keyword">int</span> a, <span class="hljs-keyword">int</span> b, <span class="hljs-keyword">bool</span> c = (a &lt;&lt; b), <span class="hljs-keyword">class</span> T&gt; <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">g2</span><span class="hljs-params">()</span></span>;
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">E</span>;</span>

<span class="hljs-keyword">template</span> &lt;<span class="hljs-keyword">int</span> a, <span class="hljs-keyword">int</span> b, <span class="hljs-keyword">bool</span> c = a &lt;&lt; b, <span class="hljs-keyword">class</span> T&gt; <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">g2</span><span class="hljs-params">()</span></span>;
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">F</span>;</span>
104 changes: 104 additions & 0 deletions test/markup/cpp/templates.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
template <template <typename X> class T> void f();

template class A::B<1, 2> {};

template void A::f(int);

template <class T, std::size_t N = sizeof(T), class K = T>
ostream& operator<< (ostream& os, const skg::Triplet<T> pt) ;
void other_stuff_that_isnt_colored();


template<class T>
class My_vector { /* ... */ };

template<class T = void>
struct My_op_functor { /* ... */ };

template<typename... Ts>
class My_tuple { /* ... */ };


template<class> class My_vector;
template<class = void> struct My_op_functor;
template<typename...> class My_tuple;


template<typename T> concept C1 = true;
template<typename... Ts> concept C2 = true; // variadic concept
template<typename T, typename U> concept C3 = true;

template<C1 T> struct s1; // constraint-expression is C1<T>
template<C1... T> struct s2; // constraint-expression is (C1<T> && ...)
template<C2... T> struct s3; // constraint-expression is (C2<T> && ...)
template<C3<int> T> struct s4; // constraint-expression is C3<T, int>
template<C3<int>... T> struct s5; // constraint-expression is (C3<T, int> && ...)


template<typename K, typename V, template<typename> typename C = my_array>
class Map {};


template<class B>
template<class C>
void A<B>::g(C) {}


void g() {
f<int()>();
}


template<int (&pa)[5]> struct W {};
template<void (*pf)(int)> struct A {};


template<class T, const char* p> class X {};


template<typename T> class A { int x; }; // primary template
template<typename T> class A<T*> { long x; }; // partial specialization


template<typename T> struct eval; // primary template

template<template<typename, typename...> class TT, typename T1, typename... Rest>
struct eval<TT<T1, Rest...>> {}; // partial specialization of eval


eval<A<int>> eA;
eval<B<int, float>> eB;
eval<C<17>> eC;
eval<D<int, 17>> eD;
eval<E<int, float>> eE;


template <template <auto> class> void FA(); // note: C++17
klmr marked this conversation as resolved.
Show resolved Hide resolved


template<template<typename TT = char> class T>
void A<T>::g()
{
T<> t; // ok: t is T<char>
}

s.template foo<T>();
class C;


// Hard mode

template <int a, int b, bool c = (a > b), class T> void f1();
template <int a, int b, bool c = (a >> b), class T> void f2();

template <int a, int b, bool c = (a < b), class T> void g1();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is failing, and I don’t know why: I thought my rules would handle the nested parenthesised expression — and this works for the corresponding test case above, i.e. for (a > b).

Copy link
Member

@joshgoebel joshgoebel Oct 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd guess the < in a < b is starting a new TEMPLATE, messing everything up. I'm not sure I see anywhere in the code where you handle this possibility.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, of course! I actually don’t know how to handle this: I thought about adding ) to the end of the TEMPLATE_USE, mode; this works, but then the mode started by ( won’t terminate, even when I also set excludeEnd: true.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the following to the TEMPLATE_USE mode seems to work:

    'on:begin': (matchData, response) => {
      const rest = matchData.input.slice(matchData.index + 1);
      let parens = 0;
      let brackets = 1; // start with `<`
      for (let i = 0; i < rest.length; i++) {
        const c = rest.charAt(i);
        switch (c) {
          case '(': parens++; break;
          case ')': parens--; break;
          case '<': brackets++; break;
          case '>': brackets--; break;
        }
        if (brackets == 0) return;
        if (parens == -1) {
          response.ignoreMatch();
          return;
        }
      }
    },

However, this feels like overkill (potentially also inefficient?); it also only works in cases where the comparison expression is nested inside parentheses, i.e. it works for this test case but not the next one.

If this approach is taken at all, we’d probably want to add some emergency stop characters (;, {) to the switch.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is too much... and definitely has performance implications.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the best maybe we can do here is liberal use of endsWithParent to make sure we escape if we hit a {}, line end, etc... and try to return to normal matching...

class C;

template <int a, int b, bool c = a < b, class T> void g1();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is also failing, and there’s probably no fix, since it’s context sensitive: a<b, class T> could be a valid template parameter, if a were a template template parameter (i.e. if we had template <typename> typename a instead of int a). Of course in that case we’d be missing an additional terminating >, but I can’t see how to make the parser aware of this since it comes (potentially much) later.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could do someone like this with a callback that looks ahead and does code analysis, but that's definitely crossing over into "parsing" territory I think so I don't think we'd want to do that.

class D;

template <int a, int b, bool c = (a << b), class T> void g2();
class E;

template <int a, int b, bool c = a << b, class T> void g2();
class F;