まず次のコードを見てほしい。
#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)); }
| ~^~~~~~~~~~~~~~~~~~~~~~~~~~
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.
ところでこれをVisual Studioでビルドするとコンパイルになぜか成功する。なんでMSVCはこれを通すのか、それがわからない。ちなみに手元でVS2005を引っ張り出してきたが同じくこのコードを通す(using aliasとかautoとかnullptrは書き換えたけども)
ちなみに//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関数定義する技法自体は
で紹介されている。
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: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
#include "foo.h"
#include <iostream>
int main()
{
Derived1 d;
std::cout << d.bar() << std::endl;
}
foo.h
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
#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 https://wandbox.org/permlink/vVKGAONfUQq9amB8 |
clang 16.0.0 HEAD | error: declaration does not declare anything https://wandbox.org/permlink/bZ515NugCwER8quI |
規格書読解
C++20の規格書を参照する。
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:
- explicit-instantiation:
- $extern_{opt}$ template declaration
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を含む
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
class-key:
class
struct
union
つまりこれらを合算すると
class-key simple-template-id
class-key nested-name-specifier templateopt simple-template-id
のいずれかということでしょう。もう少し用語を追いかけたのが下記です。
simple-template-id:
template-name < template-argument-listopt >
template-name:
identifier
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 >
です。
これの例は下が挙げられます。
template<typename T>
class Foo {};
template class Foo<int>;
// or
explicit template class Foo<int>;
declarationはinitializerを持たない単一init-declaratorを含むinit-declarator-listの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 ;
init-declarator-list:
init-declarator
init-declarator-list , init-declarator
init-declarator:
declarator initializeropt
declarator requires-clause
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
気になるfeedbackは見つかる。
As far as I know this code should not compile:
template<typename T>
struct Test{
void foo() {
}
};
template<typename T>
struct Trait {
using type = Test<T>;
};
template struct Trait<int>::type;
ただこれが直接影響するのかはよくわからない。