[迷宮入りか？]MSVCが見せるtemplateクラス内でのfriend関数定義への謎of謎な取り扱い、explicit instantaionを添えて

まず次のコードを見てほしい。

```cpp
#include <iostream>
template<class T> class Foo
{
	friend int f(T*) { return 1; }
public:
	static int g() { return f(static_cast<T*>(nullptr)); }
};
template<class T> class Bar
{
	friend int f(T*);
public:
	static int g() { return f(static_cast<T*>(nullptr)); }
};

template<typename T>
int bar()
{
	return Bar<T>::g();
}

template<typename T>
class Dest1
{
	using F = Foo<T>;
};
class Dest2
	: Dest1<Dest2>
{};
template Dest2::F;

int main()
{
	const auto ret = bar<Dest2>();
	std::cout << ret << std::endl;
}
```

さて、これを各種コンパイラでコンパイルしてみる。

```
prog.cc:10:24: warning: friend declaration 'int f(T*)' declares a non-template function [-Wnon-template-friend]
   10 |         friend int f(T*);
      |                        ^
prog.cc:10:24: note: (if this is not what you intended, make sure the function template has already been declared and add '<>' after the function name here)
prog.cc:29:18: error: expected unqualified-id before ';' token
   29 | template Dest2::F;
      |                  ^
prog.cc: In instantiation of 'static int Bar<T>::g() [with T = Dest2]':
prog.cc:18:18:   required from 'int bar() [with T = Dest2]'
prog.cc:33:29:   required from here
prog.cc:12:34: error: 'f' was not declared in this scope
   12 |         static int g() { return f(static_cast<T*>(nullptr)); }
      |                                 ~^~~~~~~~~~~~~~~~~~~~~~~~~~
```

https://wandbox.org/permlink/ltupX27Y00J2pV42


```
prog.cc:29:10: error: declaration does not declare anything
template Dest2::F;
         ^~~~~~~~
prog.cc:12:26: error: use of undeclared identifier 'f'
        static int g() { return f(static_cast<T*>(nullptr)); }
                                ^
prog.cc:18:17: note: in instantiation of member function 'Bar<Dest2>::g' requested here
        return Bar<T>::g();
                       ^
prog.cc:33:19: note: in instantiation of function template specialization 'bar<Dest2>' requested here
        const auto ret = bar<Dest2>();
                         ^
2 errors generated.
```
https://wandbox.org/permlink/plVDIn12GzbyUMKl

ところでこれをVisual Studioでビルドするとコンパイルになぜか成功する。なんでMSVCはこれを通すのか、それがわからない。ちなみに手元でVS2005を引っ張り出してきたが同じくこのコードを通す(using aliasとかautoとかnullptrは書き換えたけども)

https://godbolt.org/#g:!((g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:14,fontUsePx:'0',j:1,lang:c%2B%2B,selection:(endColumn:2,endLineNumber:35,positionColumn:2,positionLineNumber:35,selectionStartColumn:2,selectionStartLineNumber:35,startColumn:2,startLineNumber:35),source:'%23include+%3Ciostream%3E%0Atemplate%3Cclass+T%3E+class+Foo%0A%7B%0A%09friend+int+f(T*)+%7B+return+1%3B+%7D%0Apublic:%0A%09static+int+g()+%7B+return+f(static_cast%3CT*%3E(nullptr))%3B+%7D%0A%7D%3B%0Atemplate%3Cclass+T%3E+class+Bar%0A%7B%0A%09friend+int+f(T*)%3B%0Apublic:%0A%09static+int+g()+%7B+return+f(static_cast%3CT*%3E(nullptr))%3B+%7D%0A%7D%3B%0A%0Atemplate%3Ctypename+T%3E%0Aint+bar()%0A%7B%0A%09return+Bar%3CT%3E::g()%3B%0A%7D%0A%0Atemplate%3Ctypename+T%3E%0Aclass+Dest1%0A%7B%0A%09using+F+%3D+Foo%3CT%3E%3B%0A%7D%3B%0Aclass+Dest2%0A%09:+Dest1%3CDest2%3E%0A%7B%7D%3B%0Atemplate+Dest2::F%3B%0A%0Aint+main()%0A%7B%0A%09const+auto+ret+%3D+bar%3Cint%3E()%3B%0A%09std::cout+%3C%3C+ret+%3C%3C+std::endl%3B%0A%7D'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:vcpp_v19_14_x86,deviceViewOpen:'1',filters:(b:'0',binary:'1',binaryObject:'1',commentOnly:'0',debugCalls:'1',demangle:'0',directives:'0',execute:'1',intel:'0',libraryCode:'0',trim:'1'),flagsViewOpen:'1',fontScale:14,fontUsePx:'0',j:1,lang:c%2B%2B,libs:!(),options:'/std:c%2B%2B17+/permissive',overrides:!(),selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1),l:'5',n:'0',o:'+x86+msvc+v19.14+(Editor+%231)',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',m:50,n:'0',o:'',t:'0'),(g:!((h:output,i:(compilerName:'x86+msvc+v19.14',editorid:1,fontScale:14,fontUsePx:'0',j:1,wrap:'1'),l:'5',n:'0',o:'Output+of+x86+msvc+v19.14+(Compiler+%231)',t:'0')),header:(),l:'4',m:50,n:'0',o:'',s:0,t:'0')),l:'3',n:'0',o:'',t:'0')),version:4

ちなみに`//template Dest2::F;`のようにコメントアウトするとつぎのようにリンクエラーになる。

```
1>  main..cpp
1>main..obj : error LNK2019: 未解決の外部シンボル "int __cdecl f(class Dest2 *)" (?f@@YAHPAVDest2@@@Z) が関数 "public: static int __cdecl Bar<class Dest2>::g(void)" (?g@?$Bar@VDest2@@@@SAHXZ) で参照されました。
1>c:\users\21-0042\documents\visual studio 2015\Projects\friend_test2\Debug\friend_test2.exe : fatal error LNK1120: 1 件の未解決の外部参照
```

また、`const auto ret = bar<int>();`のように書き換えるとつぎのようにリンクエラーになる。

```
1>  main..cpp
1>main..obj : error LNK2019: 未解決の外部シンボル "int __cdecl f(int *)" (?f@@YAHPAH@Z) が関数 "public: static int __cdecl Bar<int>::g(void)" (?g@?$Bar@H@@SAHXZ) で参照されました。
1>c:\users\21-0042\documents\visual studio 2015\Projects\friend_test2\Debug\friend_test2.exe : fatal error LNK1120: 1 件の未解決の外部参照
```



## templateクラス内でのfriend関数定義

そもそもtemplate classの中でfriend関数定義する技法自体は

https://hightoro.hatenablog.com/entry/2014/04/06/223712

で紹介されている。

C++規格書で関連するところがないか探したが、あまりクリティカルな記述を見つけられなかった。探し方が悪そうではある。

>https://timsong-cpp.github.io/cppwp/n4861/class.friend#6
>
>6 A function can be defined in a friend declaration of a class if and only if the class is a non-local class ([class.local]), the function name is unqualified, and the function has namespace scope.
[ Example:
>
>```cpp
>class M {
>  friend void f() { }           // definition of global f, a friend of M,
>                                // not the definition of a member function
>};
>```
>— end example
> ]

>https://timsong-cpp.github.io/cppwp/n4861/class.friend#7
>
>7 Such a function is implicitly an inline ([dcl.inline]) function if it is attached to the global module.
A friend function defined in a class is in the (lexical) scope of the class in which it is defined.
A friend function defined outside the class is not ([basic.lookup.unqual]).


## explicit_instantaionとtypedef

`template Dest2::F;`についてもう少し深く考える。

要するに下のように書いたとき、`Base_Derived1`はtypedefで作られた名前だが、`template Base_Derived1`のようにexplicit instantaionできるのはなぜかということ。

`main.cpp`

```cpp
#include "foo.h"
#include <iostream>
int main()
{
	Derived1 d;
	std::cout << d.bar() << std::endl;
}
```

`foo.h`

```cpp
template<typename Derived>
class Base {
public:
	Base(int a);
	int bar() { return n; }
	int n;
};

class Derived1 : public Base<Derived1> {
public:
	Derived1() : Base<Derived1>(4) {}
};
typedef Base<Derived1> Base_Derived1;
```

`foo.cpp`

```cpp
#include "foo.h"
#include <iostream>
template<typename Derived>
Base<Derived>::Base(int a) : n(a) { std::cout << "base" << std::endl; }

template Base_Derived1;
```

### 各コンパイラでのコンパイル結果

msvcは通すがgcc/clangはエラーとなった。

| コンパイラ                                    | 結果                                                                                             |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| Visual Studio 2005                            | pass                                                                                             |
| Visual Studio 2022 (/std:c++20, /permissive-) | pass                                                                                             |
| gcc 12.1.0                                    | error: expected unqualified-id before ';' token<br>https://wandbox.org/permlink/vVKGAONfUQq9amB8 |
| clang 16.0.0 HEAD                             | error: declaration does not declare anything<br>https://wandbox.org/permlink/bZ515NugCwER8quI    |

### 規格書読解

C++20の規格書を参照する。

https://timsong-cpp.github.io/cppwp/n4861/temp.explicit


> 1
>
> A class, function, variable, or member template specialization can be explicitly instantiated from its template.
> A member function, member class or static data member of a class template can be explicitly instantiated from the member definition associated with its class template.

まずここでは明示的インスタンス化をするにあたって、可能なものを列挙している。

- クラス
- 関数
- 変数
- member template specialization
- テンプレートクラスの
  - メンバー関数
  - メンバークラス
  - staticメンバ変数

>2
>
>The syntax for explicit instantiation is:<dl>
    <dt>explicit-instantiation:</dt>
    <dd>$extern_{opt}$ template _declaration_</dd>
</dl>

>4
>
>If the explicit instantiation is for a class or member class, the elaborated-type-specifier in the declaration shall include a simple-template-id; otherwise, the declaration shall be a simple-declaration whose init-declarator-list comprises a single init-declarator that does not have an initializer.

2と4を組み合わせて読む。

クラスもしくはメンバークラスの明示的か初期化において、declaration中のelaborated-type-specifierはsimple-template-idを含むか、declarationはinitializerを持たない単一init-declaratorを含むinit-declarator-listのsimple-declarationでなければならない

### declaration中のelaborated-type-specifierはsimple-template-idを含む

https://timsong-cpp.github.io/cppwp/n4861/dcl.type.elab#nt:elaborated-type-specifier

```
elaborated-type-specifier:
	class-key attribute-specifier-seqopt nested-name-specifieropt identifier
	class-key simple-template-id
	class-key nested-name-specifier templateopt simple-template-id
	elaborated-enum-specifier
elaborated-enum-specifier:
	enum nested-name-specifieropt identifier
```

https://timsong-cpp.github.io/cppwp/n4861/class.pre#nt:class-key

```
class-key:
	class
	struct
	union
```

つまりこれらを合算すると

- `class-key simple-template-id`
- `class-key nested-name-specifier templateopt simple-template-id`

のいずれかということでしょう。もう少し用語を追いかけたのが下記です。

https://timsong-cpp.github.io/cppwp/n4861/temp.names#nt:simple-template-id

```
simple-template-id:
	template-name < template-argument-listopt >

template-name:
	identifier
```

https://timsong-cpp.github.io/cppwp/n4861/expr.prim.id.qual#nt:nested-name-specifier

```
nested-name-specifier:
	::
	type-name ::
	namespace-name ::
	decltype-specifier ::
	nested-name-specifier identifier ::
	nested-name-specifier templateopt simple-template-id ::
```

今回のお題では一旦nested-name-specifierは関係ないので忘れて`class-key simple-template-id`を考えます。書き換えると`class-key template-name < template-argument-listopt >`です。

これの例は下が挙げられます。

```cpp
template<typename T>
class Foo {};

template class Foo<int>;
// or
explicit template class Foo<int>;
```

### declarationはinitializerを持たない単一init-declaratorを含むinit-declarator-listのsimple-declaration

https://timsong-cpp.github.io/cppwp/n4861/dcl.pre#nt:simple-declaration

```
simple-declaration:
	decl-specifier-seq init-declarator-listopt ;
	attribute-specifier-seq decl-specifier-seq init-declarator-list ;
	attribute-specifier-seqopt decl-specifier-seq ref-qualifieropt [ identifier-list ] initializer ;
```

https://timsong-cpp.github.io/cppwp/n4861/dcl.decl#nt:init-declarator-list

```
init-declarator-list:
	init-declarator
	init-declarator-list , init-declarator

init-declarator:
	declarator initializeropt
	declarator requires-clause
```

https://timsong-cpp.github.io/cppwp/n4861/dcl.spec#nt:decl-specifier-seq

```
decl-specifier:
	storage-class-specifier
	defining-type-specifier
	function-specifier
	friend
	typedef
	constexpr
	consteval
	constinit
	inline

decl-specifier-seq:
	decl-specifier attribute-specifier-seqopt
	decl-specifier decl-specifier-seq
```

一旦attributeとconceptのことを考えず、上記を統合すると  
`decl-specifier-seq declarator`  
となります。

・・・ぜんぜんわからない。

### 関連するVSへのfeedback

[Explicit template instantiation via typedef should fail with /permissive- - Developer Community](https://developercommunity.visualstudio.com/t/explicit-template-instantiation-via-typedef-should/537093)

気になるfeedbackは見つかる。

>As far as I know this code should not compile:

```cpp
template<typename T>
struct Test{
    void foo() {
    }
};


template<typename T>
struct Trait {
    using type = Test<T>;
};

template struct Trait<int>::type;
```

ただこれが直接影響するのかはよくわからない。