ページ 11

テンプレートを利用した関数の書き方

Posted: 2014年12月13日(土) 10:42
by ChainSmoker
はじめまして。

OS:Windows7
開発用アプリ:VisualStudio2010 VC++

上記の環境にてちまちまとプログラムを組んでいまして、
今回返り値と引数の型だけが違う処理関数をテンプレートでまとめてみようとしましたが
理解が全くないまま使おうとしたのが災いし、エラーでコンパイルが通りませんでした。

以下、単純化したソースとエラー文です。

コード:

#include <string>
#include <vector>

struct Sa {
	int no;
	std::string str;
};
typedef std::vector<Sa> SaArray;

struct Sb {
	int no;
	int sub_no;
	std::string str;
};
typedef std::vector<Sb> SbArray;


template <typename S_Ret, typename SArray_Arg>
S_Ret* GetOne(int request_no, SArray_Arg& array) {
	for(int i = 0;i < array.size();i++) {
		if(array[i].no == request_no) {
			return array[i];
		}
	}
	return NULL;
}

//------------------------------------------------

int main(int argc, char** argv) {
	SaArray sa_array;
	Sa* a = GetOne(0, sa_array);
	
	SbArray sb_array;
	Sb* b = GetOne(0, sb_array);
	
	return 0;
}
error C2783: 'S_Ret *GetOne(int,SArray_Arg&)' : テンプレート 引数を 'S_Ret' に対して減少できませんでした
error C2783: 'S_Ret *GetOne(int,SArray_Arg &)' : テンプレート 引数を 'S_Ret' に対して減少できませんでした

どういった理由でコンパイルが通らないのか、
正しくはどう記述するべきかもわからないです。


コード:

Sa* Sa_GetOne(int request_no, SaArray& array) {
	for(int i = 0;i < array.size();i++) {
		if(array[i].no == request_no) {
			return &array[i];
		}
	}
	return NULL;
}

Sb* Sb_GetOne(int request_no, SbArray& array) {
	for(int i = 0;i < array.size();i++) {
		if(array[i].no == request_no) {
			return &array[i];
		}
	}
	return NULL;
}
とすればこの話はなかったことにできるのですが、
テンプレートを使ってみたいというささやかな希望を叶えるためには
どのようにすればよいでしょうか。

Re: テンプレートを利用した関数の書き方

Posted: 2014年12月13日(土) 11:05
by みけCAT
この定義だと、引数の型を見ただけでは戻り値の型がわからないので、明示的に指定する必要があるようです。
また、arrayの型と戻り値の型が合っていないのも悪いです。

コード:

#include <string>
#include <vector>

struct Sa {
	int no;
	std::string str;
};
typedef std::vector<Sa> SaArray;

struct Sb {
	int no;
	int sub_no;
	std::string str;
};
typedef std::vector<Sb> SbArray;


template <typename S_Ret, typename SArray_Arg>
S_Ret* GetOne(int request_no, SArray_Arg& array) {
	for(size_t i = 0;i < array.size();i++) { // 符号付き整数と符号なし整数を比較すると警告が出る
		if(array[i].no == request_no) {
			return &array[i]; // 返り値の型はポインタなので、ポインタを返さないといけない
		}
	}
	return NULL;
}

//------------------------------------------------

int main(int argc, char** argv) {
	SaArray sa_array;
	Sa* a = GetOne<Sa, SaArray>(0, sa_array); // 型を指定しないと、戻り値の型がわからない
	
	SbArray sb_array;
	Sb* b = GetOne<Sb, SbArray>(0, sb_array); // 同上

	// unused警告避け
	(void)a;
	(void)b;
	(void)argc;
	(void)argv;

	return 0;
}
試しに戻り値を消してこうすると、型を明示的に指定しなくてもWandbox(gcc 4.6.4)でコンパイルが通りました。

コード:

#include <string>
#include <vector>

struct Sa {
	int no;
	std::string str;
};
typedef std::vector<Sa> SaArray;

struct Sb {
	int no;
	int sub_no;
	std::string str;
};
typedef std::vector<Sb> SbArray;


template <typename SArray_Arg>
void GetOne(int request_no, SArray_Arg& array) {
	for(size_t i = 0;i < array.size();i++) { // 符号付き整数と符号なし整数を比較すると警告が出る
		if(array[i].no == request_no) {
			return;
		}
	}
	return;
}

//------------------------------------------------

int main(int argc, char** argv) {
	SaArray sa_array;
	GetOne(0, sa_array);
	
	SbArray sb_array;
	GetOne(0, sb_array);

	// unused警告避け
	(void)argc;
	(void)argv;

	return 0;
}

Re: テンプレートを利用した関数の書き方

Posted: 2014年12月13日(土) 14:27
by hoge
みけCATさんの書いたとおり、呼出時に戻り値の型を指定するのも一つの方法ですが、
他の方法としては、引数の型から戻り値の型が分かるようにしておくことも可能です。

コード:

template <typename SArray_Arg>
typename SArray_Arg::value_type* GetOne(int request_no, SArray_Arg& array) {
    ...
}

Re: テンプレートを利用した関数の書き方

Posted: 2014年12月14日(日) 00:51
by ChainSmoker
みけCATさんにご提示いただいた

コード:

    Sa* a = GetOne<Sa, SaArray>(0, sa_array); // 型を指定しないと、戻り値の型がわからない
こちらに呼び出し側を変えたらコンパイラが通してくれるようになりました。
「a」も「sa」も宣言してるから明示的な指定がすでにされているじゃん?、というわけではないのですね。

hogeさんにご呈示いただいたコードについて最初、Sa*が返ってくるのか疑問でしたが
実際にいくつかテストしてみて期待通りのものが返ってきたため、value_typeとはそういう意味だったのだな、と
納得できました。

どちらもまだ理解と言うにはほど遠いものではありますが目下の問題は解決できましたので、
テンプレートの理解・習得は今後の課題として残し、作業の継続に戻りたいと思います。
みけCATさん、hogeさん、ありがとうございました。
オフトピック
実は以前にC++テンプレートメタプログラミングなる本を図書館で借りたことがあるんですが、
まえがきの半ページほど読んでそのまま返却まで放置したという記憶がよみがえりました。
目次にすらたどりつかなかった……。
どうでもいいですね、すみません。