LoginSignup
0
0

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

Last updated at Posted at 2023-09-25

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

#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;

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0