- PR -

テンプレート内this使用によるコンパイルエラーC2664

投稿者投稿内容
やっぷ
会議室デビュー日: 2007/05/07
投稿数: 17
投稿日時: 2007-10-15 00:45
やっぷと申します。WindowsXP、VisualStudio2005にて、C++開発を行っております。今回はテンプレート内でthisを使用した際におきるコンパイルエラーの対処法について、相談させて頂きたく、投稿させて頂きます。
まずは下記ソースをご覧頂きたく、思います。
========================================================
template< class You > class Me
{
public:
 Me( void ){}
 ~Me( void ){}
 void SetYou( You* y ){ m_you = y; }
 void SetYouOfYours_ItsMe( You* y ){
  y->SetYou( this ); // ★★C2664
 }
private:
 You* m_you;
};

// クラスインスタンセズの作成
class A;
class B;
class A : public Me< B >{};
class B : public Me< A >{};

int main( void )
{
 A* a = new A;
 B* b = new B;
 {
  b->SetYou( a );
  b->SetYouOfYours_ItsMe( a );// ★
 }
 delete a;
 delete b;
 return 0;
}
========================================================
上記の例では、★を記述した途端、「// ★★C2664」 の行で、一番目の引数において、this(Me<You>*)を、B*に変換できないなどと言い出し、コンパイルエラーとなってしまいます。現象だけ見ると、ソースの同箇所におけるthis の型のリゾルブが、ちゃんと解決できてないために起きているような気がします。
このエラーは、実は、テンプレートパラメータ引数に、自分自身の型を追加し、問題のthis を、自分自身の型でキャスト(普通のCのキャストでOK)すると解決します。マイクロソフトのサイトなど調べても、どんぴしゃの事例はないのですが、C2664は何やらコンパイラの仕様のようなのですが、問題の本質が今ひとつよくわかりません。
今回のようなケースでC2664が出てしまう本質的な理由および、他のより良い解決方法などをご存知の方がいらっしゃれば、ご教示頂ければと思います。


[ メッセージ編集済み 編集者: やっぷ 編集日時 2007-10-16 23:10 ]
囚人
ぬし
会議室デビュー日: 2005/08/13
投稿数: 1019
投稿日時: 2007-10-15 16:34
Me<A>、Me<B> template で生成されるクラスを便宜上、Me_A、Me_B とします。

継承関係は
A → Me_B
B → Me_A
です。

Me_A と Me_B は継承ツリー上では全くの赤の他人です。

class B の SetYou は A か A の派生クラスのポインタしか渡せませんが、
y->SetYou( this ); // ★★C2664
の部分では、B のポインタを渡そうとしています。A と B は継承ツリー上では全く赤の他人ですので C2664 となります。

サンプルだと、したい事がよくわからないので解決策は分かりません。具体的には何をしたいのでしょうか?

_________________
囚人のジレンマな日々
やっぷ
会議室デビュー日: 2007/05/07
投稿数: 17
投稿日時: 2007-10-15 23:30
囚人さん、ご返答どうもありがとうございます。

> の部分では、B のポインタを渡そうとしています。A と B は
> 継承ツリー上では全く赤の他人ですので C2664 となります。

(↑??)恐れながら、これは納得できかねます。main 関数の中では、
b->SetYouOfYours_ItsMe( a );  
という呼び方をしているので、SetYouOfYours_ItsMe() 関数の中の、

y->SetYou( this ); // ★★C2664   
における、y は、Me_A であり、y->SetYouは、つまり Me_A の SetYou()
を呼び出しているだけです。this は、Me_Bとなります。

つまり、class A の SetYou に Bのポインタを渡そうとしているだけです。
class A のSetYouは、BかBの派生クラスのポインタを受け取ることに分には
問題ないはずですので、どうしてこれでエラーとなるのかがわからないのです。

したいことは、別にあります。それは、互いに参照関係のある複数の異種オブジェクト一式の
基本動作を、テンプレートとして実装したいのです。
その中で、C2664にぶつかりました。本サンプルはそれを再現する一つのミニマムコードを
本投稿用に作成したものです。

本サンプルでどうなって欲しいのかと言いますと、main関数の中の、
b->SetYou( a );
b->SetYouOfYours_ItsMe( a );// ★
の1行目では、Me_bのm_youにはMe_aが、そして、2行目では、
Me_a のm_you には、Me_B が入ることを狙ったものです。

いかがでしょうか?質問の主旨はお分かり頂けましたでしょうか?
やっぷ
会議室デビュー日: 2007/05/07
投稿数: 17
投稿日時: 2007-10-16 06:57
囚人さん、、、自分は何か勘違いしているようです。
すみません、ちょっと考えてみます。先のレスはなかったことにして下さい。
申し訳ありませんでした。
AOFG
会議室デビュー日: 2007/09/07
投稿数: 11
投稿日時: 2007-10-16 10:11
はっきりとは分からないですが、
b->SetYouOfYours_ItsMe( a );// ★
のところで
bが基底クラスのMe<A>に自動でキャスト(アップキャスト)される。

その後、
y->SetYou( this ); // ★★C2664
では、
(これはAの基底クラスであるMe<B>のメソッド呼び出しで、)
引数としてはBのインスタンスを要求するが、
this はキャストされているのでMe<A>である。
これをBにするにはダウンキャストが必要だが、それは自動では行われない。
という考えはどうでしょうか?

RedHat Linux 上の gcc 3.4.3 でもやっぱり駄目で、代わりに
class B : public Me< A >{public: void SetYouOfYours_ItsMe(A* y){y->SetYou(this);}
と定義したら上手く動きました。
一郎
ぬし
会議室デビュー日: 2002/10/11
投稿数: 1081
投稿日時: 2007-10-16 11:07
テンプレートというのは.NETのジェネリクスと違い、指定された型で型引数の部分を置き換えたクラスをコンパイル時に自動で定義してくれる機能ですよね。
ですので、やっぷさんのソースは
コード:
class Me_A
{
public:
	void SetYou( A* y ){ 
		m_you = y; 
	} 
	void SetYouOfYours_ItsMe( A* y ){ 
		y->SetYou( this );
	} 
private: 
	A* m_you;
};
class Me_B
{
public:
	void SetYou( B* y ){ 
		m_you = y; 
	} 
	void SetYouOfYours_ItsMe( B* y ){ 
		y->SetYou( this );
	} 
private: 
	B* m_you;
};

class A : public Me_B{}; 
class B : public Me_A{}; 


という意味合いになると思います。
(コンパイル通らないですけど意味は分かりますよね)

Me_AクラスのSetYouOfYours_ItsMe()を見てください。
引数yはA型ですので、そのSetYou()の引数はB型でなければなりません。
しかし渡しているのはthis、つまりMe_Aです。
BはMe_Aを継承していますので、Me_Aが要求されている所にBを渡すことはできますが、Bが要求されている所にMe_Aを渡すのは無理ですよね。
囚人
ぬし
会議室デビュー日: 2005/08/13
投稿数: 1019
投稿日時: 2007-10-16 17:06
あー。完全に読み違いました。すみません。

平たく言ったら以下みたいなもんでしょうね。

コード:
class Dev;

class Base{
public:
	void Func1(Dev* d){}
	void Func2(Base* b){
		b->Func1(this);
	}
};

class Dev : public Base{
};




ちなみに
引用:

上記の例では、★を記述した途端


使った途端コンパイルエラーになるのは、多分、呼び出している箇所がなければコンパイルしない(template 展開しない)という C++ の特性があるからでしょう(Modern C++ Design に書いてあった覚えが)。

_________________
囚人のジレンマな日々
やっぷ
会議室デビュー日: 2007/05/07
投稿数: 17
投稿日時: 2007-10-16 23:06
AOFGさん、一郎さん、囚人さん、皆様どうもありがとうございました。
お蔭様で、C2664の出る原因は、基本クラスの中でアップキャストされてしまっているthisポインタ
を、その継承クラスとして無理矢理渡そうとしていたことによるものであるということが、理解できました。
AOFGさんのソリューションは、納得ですが、これだとテンプレートのユーザーに、
SetYou() の実装を任せることになってしまい、ライブラリとしての使い勝手が落ちてしまいます。
そこで私が考えた解決策は以下のようなものです(main関数はそのままです)
これでコンパイルも通り、問題なく動作しました。(★をつけた行だけを書き換えています。)
=============================
template< class Myself, class You > class Me // ★
{
public:
 Me( void ){}
 ~Me( void ){}
 void SetYou( You* y ){ m_you = y; }
 void SetYouOfYours_ItsMe( You* y ){ y->SetYou( (Myself*)this ); // ★C2664は消える。
 }
private:
 You* m_you;
};
// クラスインスタンセズの作成
class A;
class B;
class A : public Me< A, B >{};// ★自分の型を必ず最初のテンプレート引数に渡す
class B : public Me< B, A >{};// ★自分の型を必ず最初のテンプレート引数に渡す
=====================================
私の目的は、先にも述べましたように、互いに参照し合って連携して機能する、
複数の異種基本クラスの基本動作を、テンプレートクラスのセットとして記述することです。
実際の具現化して使うインスタンスはテンプレートが具現化されてできた基本クラス(Me<B>, Me<A>)を継承した、拡張クラス(A:public Me<B>, B:public Me<A>)を使用したいわけです。
なので、本件のようなC2664はどうしても避けなくてはなりません。つまり、thisポインタは、
テンプレートのユーザーにしてみればわかりきった派生クラスで強制的にダウンキャストしてやる必要が
あり、それで十分なのでした。
もしこのコーディング方針に問題・ご意見などありましたらば、拝聴させて頂ければ幸いです。

[ メッセージ編集済み 編集者: やっぷ 編集日時 2007-10-16 23:09 ]

スキルアップ/キャリアアップ(JOB@IT)