汎用出力関数(?)

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
chaemon

汎用出力関数(?)

#1

投稿記事 by chaemon » 12年前

こんにちは。先ほど、unordered_set関連のトピックにてテンプレート特殊化について教わった者です。
どうもありがとうございました。
続けての投稿失礼します。

実は前々からpythonのprint関数のように、
1. printの中身が整数ならそのまま出力、
2. vector<int>なら[]とカンマで区切って順番に出力
3. vector<vector<int> >なら[[, ],[, ] ,[, ] ]のように入れ子にして出力
といった関数がほしいと思っておりました。

テンプレート特殊化を用いることで以下のようにひとまずは実現できました。
カンマが最後についてしまってやや見苦しいですが、これは大した問題ではないので、ご容赦ください。
ついでに以下の関数はvector<int>であれ、set<int>であれ、list<vector<int> >みたいな形であれ出力してくれることでしょう!

しかしながら、未だに解決できないことがありまして、それは以下であります。
1. 「cout<<v」のようにostreamを指定することで出力されるようにしたい。
 (下のコードの#define COMPROMISEをコメントアウトしていただければそれっぽいコードになりますが、コンパイルできません。。。)
2. 1. に関係しておりますが、「ostream &operator<<(ostream &os, T &x)」がすでにiostreamなどで定義されていればそれを使い、
 そうでなければテンプレート関数から定義するようにしたい。
 テンプレート特殊化と似ていますが、最初に定義されているテンプレートは除くという点で違いそれが解決できません。
3. vector<int>の出力の場合は"vector[1, 2, 3, 4]"とset<int>の場合は"set[1, 2, 3, 4]"といったように、
 []の前に型名を表示させたい。
 __typeof指定子をstring型に渡すような関数があれば実現できると思ったのですが・・・

この3点のうち一つでも構いませんのでご存知の方はご教示いただけたら幸いです。

コード:

#include<iostream>
#include<vector>

using namespace std;

#define COMPROMISE
#ifndef COMPROMISE
template<class T>
ostream &operator<<(ostream &os, T &x) {
	os<<"[";
	for(__typeof(x.begin()) it=x.begin();it!=x.end();it++){
		os<<*it<<",";
	}
	os<<"]";
	return os;
}

template<>
ostream &operator<<(ostream &os, int &x) {
	os<<x;
	return os;
}
#else
template<class T>
void print(T &x) {
	cout<<"[";
	for(__typeof(x.begin()) it=x.begin();it!=x.end();it++){
		print(*it);
		cout<<",";
	}
	cout<<"]";
}

template<>
void print(int &x) { cout<<x;}
#endif

int main(){
	vector<vector<int> > v(3,vector<int>());
	v[0].push_back(1);v[0].push_back(2);v[0].push_back(3);
	v[1].push_back(4);v[1].push_back(5);v[1].push_back(6); v[1].push_back(7);v[2].push_back(8);v[2].push_back(9);
#ifndef COMPROMISE
	cout<<v;
#else
	print(v);
#endif
	return 0;
}

アバター
a5ua
記事: 199
登録日時: 13年前

Re: 汎用出力関数(?)

#2

投稿記事 by a5ua » 12年前

特殊化にこだわらずとも、必要な分だけオーバーロードすれば良いのではないでしょうか

コード:

#include <iostream>
#include <vector>
#include <list>
#include <set>

template <typename InIt>
std::ostream & print(std::ostream &os, InIt first, InIt last)
{
	os << "[";
	for ( ; first != last; ++first) {
		os << *first << ",";
	}
	os << "]";
	return os;
}

template <class T>
std::ostream &operator<<(std::ostream &os, const std::vector<T> &x)
{
	os << "vector";
	return print(os, x.begin(), x.end());
}

template <class T>
std::ostream &operator<<(std::ostream &os, const std::list<T> &x)
{
	os << "list";
	return print(os, x.begin(), x.end());
}

template <class T>
std::ostream &operator<<(std::ostream &os, const std::set<T> &x)
{
	os << "set";
	return print(os, x.begin(), x.end());
}

int main()
{
	const int x[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
	std::vector< std::list<int> > v(3);
	v[0].assign(x    , x + 3);
	v[1].assign(x + 3, x + 7);
	v[2].assign(x + 7, x + 9);
	std::cout << v << std::endl;
    return 0;
}

アバター
tk-xleader
記事: 158
登録日時: 13年前
連絡を取る:

Re: 汎用出力関数(?)

#3

投稿記事 by tk-xleader » 12年前

コンテナかどうかを判別して処理を分けるということですよね?

コード:

#include<iostream>
#include<vector>
#include<string>
#include<type_traits>
#include<utility>
#include<algorithm>

namespace my{
	template<typename T>
	struct identity{
		typedef T type;
	};
	template<typename T>
	class is_container{
		template<typename U>
		static std::true_type check1(U*u,decltype(u->begin())* = nullptr);
		static std::false_type check1(...);
		template<typename U>
		static std::true_type check2(U*u,decltype(u->end())* = nullptr);
		static std::false_type check2(...);
	public:
		static const bool value = identity<decltype(check1((T*)0))>::type::value && identity<decltype(check2((T*)0))>::type::value;
		typedef typename std::integral_constant<bool,value>::type type;
	};
	template<typename T>
	typename std::enable_if<!my::is_container<T>::value,std::ostream&>::type print(std::ostream& stream,const T& t){
		return stream<<t;
	}
	template<typename T>
	typename std::enable_if<my::is_container<T>::value,std::ostream&>::type print(std::ostream& stream,const T& container){
		stream<<"[";
		std::for_each(container.begin(),container.end()
			,[&stream](typename T::value_type const& value)->void{
			print(stream,value)<<",";
		});
		return stream<<"]";
	}
	std::ostream& print(std::ostream& stream,const std::string& s){
		return stream<<s;
	}
}

template<typename C>
typename std::enable_if<my::is_container<C>::value,std::ostream&>::type operator<<(std::ostream& stream,const C& c){
	return my::print(stream,c);
}

int main(){
	std::vector<std::vector<int> > v;
	for(int i=0; i < 3; i++){
		v.push_back(std::vector<int>());
		for(int j=0; j < 5; j++){
			v[i].push_back(i*j);
		}
	}
	std::cout<<v<<std::endl;
}
ただ、最後にあるコンテナ名を表示するとこまではできませんでした。

たかぎ
記事: 328
登録日時: 13年前
住所: 大阪
連絡を取る:

Re: 汎用出力関数(?)

#4

投稿記事 by たかぎ » 12年前

型名の出力についてですが...
GCCの場合、方法は二種類あります。

ひとつは、__PRETTY_FUNCTION__から型名を取り出す方法です。
解析のためのコードを自分で書かないといけないのが難点です。

もうひとつは、std::type_info::name()を使う方法です。
こちらは簡単ですが、そのままではマングルされた名前が出てきます。
abi::__cxa_demangleを使うなどして、読みやすい名前に変換するとよいでしょう。
あと、型名に国際文字名を使った場合(-fextended-identifiersを使用)、そのままではUTF-8になります。
必要なら文字コードを変換してください(-fexec-charsetオプションでは変換できません)。

chaemon

Re: 汎用出力関数(?)

#5

投稿記事 by chaemon » 12年前

皆様、アドバイスありがとうございます!

>>a5uaさん

私も同様のことを考えたのですが、新しくコンテナを追加するたびに定義しなおさなくてはならないという点で汎用性がなく、さらなる汎用化を模索しておりました。

>>tkmakwins15さん

ありがとうございます。難しいコードですね。。。あとでがんばって解析します♪

>>たかぎさん

__PRETTY_FUNCTION__を使う方針で考えてみました。

お三方のアドバイスを反映させて以下のコードを書きました。皆様のおかげで、あと一歩のところまできています。

現在の問題は、enable_if関数の中程にコメントアウトしてあるprint(stream,t)が実行するとエラーを起こすという点です。とりあえず、printfでごまかしています。

ついでに、最後にカンマが出力される問題を修正しました。

型名解析関数などの私が書き加えた部分のみ汚い書き方になってしまった感があるので、もう少しよい書き方があるといったご意見もいただけると嬉しいです。

よろしくお願いします。

コード:

#include<iostream>
#include<vector>
#include<string>
#include<type_traits>
#include<utility>
#include<algorithm>
#include<set>
#include<list>

using namespace std;

//{{{ Output Function

template<typename T>
string get_container_name(const T &a){
	const char *c=__PRETTY_FUNCTION__;
	string str(c);
	string::size_type i=str.find("with T = std::");
	i+=14;
	string res="";
	while(str[i]!='<')res+=str[i],i++;
	return res;
}

namespace my{
	template<typename T>
		struct identity{
			typedef T type;
		};
	template<typename T>
		class is_container{
			template<typename U>
				static std::true_type check1(U*u,decltype(u->begin())* = nullptr);
			static std::false_type check1(...);
			template<typename U>
				static std::true_type check2(U*u,decltype(u->end())* = nullptr);
			static std::false_type check2(...);
		public:
			static const bool value = identity<decltype(check1((T*)0))>::type::value && identity<decltype(check2((T*)0))>::type::value;
			typedef typename std::integral_constant<bool,value>::type type;
		};
	template<typename T>
		typename std::enable_if<!my::is_container<T>::value,std::ostream&>::type print(std::ostream& stream,const T& t){
			return stream<<t;
		}
	template<typename T>
		typename std::enable_if<my::is_container<T>::value,std::ostream&>::type print(std::ostream& stream,const T& container){
			const string t=get_container_name(container);
			printf("%s",t.c_str());
//			print(stream,t);
			stream<<"[";
			for(__typeof(container.begin()) it=container.begin();it!=container.end();it++,stream<<((it==container.end())?"":",")){
					print(stream,*it);
					};
			return stream<<"]";
		}
	std::ostream& print(std::ostream& stream,const std::string& s){
		return stream<<s;
	}
}

template<typename C>
typename std::enable_if<my::is_container<C>::value,std::ostream&>::type operator<<(std::ostream& stream,const C& c){
	return my::print(stream,c);
}
//}}}

int main(){
	const int x[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
	std::vector< std::list<int> > v(3);
	v[0].assign(x    , x + 3);
	v[1].assign(x + 3, x + 7);
	v[2].assign(x + 7, x + 9);
	std::cout << v << std::endl;
	return 0;
}

アバター
tk-xleader
記事: 158
登録日時: 13年前
連絡を取る:

Re: 汎用出力関数(?)

#6

投稿記事 by tk-xleader » 12年前

chaemon さんが書きました:現在の問題は、enable_if関数の中程にコメントアウトしてあるprint(stream,t)が実行するとエラーを起こすという点です。とりあえず、printfでごまかしています。
printfを使うとまずいです。

stream<<t;

でないと、printfを使っていた場合、streamがstd::coutでない場合バグになります(型名だけが標準出力に出力されてしまう。)

あと、is_containerテンプレートクラスの中では、SFINAEというテンプレート実体化の特性を用いています。

chaemon

Re: 汎用出力関数(?)

#7

投稿記事 by chaemon » 12年前

printfを使うとまずいです。
はい。分かっております。stream<<tを使うとコンパイルエラーになり、print(stream,t)を使うと実行時にクラッシュするので、
ダミーで書いておきました。

SFINAEとはどういったものなのでしょうか。実体化の部分はどこに該当するのでしょうか。
ネットで調べてみたのですが、メンバ関数を持っているかどうかを判定するみたいなものですか?

アバター
tk-xleader
記事: 158
登録日時: 13年前
連絡を取る:

Re: 汎用出力関数(?)

#8

投稿記事 by tk-xleader » 12年前

エラーの原因は、おそらくstd::stringがis_containerを真にしてしまうことだと思います。そこで、コンテナかどうかに加えて、std::basic_stringではないかというのも条件に加えて見ると、成功するようです。

コード:

#include<iostream>
#include<vector>
#include<string>
#include<type_traits>
#include<utility>
#include<algorithm>

namespace my{
	template<typename T>
	struct identity{
		typedef T type;
	};
	template<typename T>
	class is_container{
		template<typename U>
		static std::true_type check1(U*u,decltype(u->begin())* = nullptr);
		static std::false_type check1(...);
		template<typename U>
		static std::true_type check2(U*u,decltype(u->end())* = nullptr);
		static std::false_type check2(...);
	public:
		static const bool value = identity<decltype(check1((T*)0))>::type::value && identity<decltype(check2((T*)0))>::type::value;
		typedef typename std::integral_constant<bool,value>::type type;
	};
	template<typename T>
	class is_string:public std::false_type{
	};
	template<typename CHART,typename TRAITST,typename ALLOCT>
	class is_string<std::basic_string<CHART,TRAITST,ALLOCT> >:public std::true_type{
	};
	template<typename T>
	typename std::enable_if<!my::is_container<T>::value||is_string<T>::value,std::ostream&>::type print(std::ostream& stream,const T& t){
		return stream<<t;
	}
	template<typename T>
	typename std::enable_if<my::is_container<T>::value&&!is_string<T>::value,std::ostream&>::type print(std::ostream& stream,const T& container){
		stream<<"[";
		std::for_each(container.begin(),container.end()
			,[&stream](typename T::value_type const& value)->void{
			print(stream,value)<<",";
		});
		return stream<<"]";
	}
}

template<typename C>
typename std::enable_if<my::is_container<C>::value&&!my::is_string<C>::value,std::ostream&>::type operator<<(std::ostream& stream,const C& c){
	return my::print(stream,c);
}

int main(){
	std::vector<std::vector<int> > v;
	for(int i=0; i < 3; i++){
		v.push_back(std::vector<int>());
		for(int j=0; j < 5; j++){
			v.at(i).push_back(i*j);
		}
	}
	std::cout<<v<<std::endl;
	my::print(std::cout,std::string("test"));
}
自分のコードに修正を加えているのは、申し訳ないのですが環境が異なるからです(こちらはVC10です。)

SFINAE - Wikipedia(英語) というのは、テンプレートを実体化した際に、テンプレート実引数が原因でエラーが生じる場合、それをコンパイルエラーとしなくてもよい。というルールのことで、特に関数オーバーロードがかかわる場合が多いです(それが全てではありませんが…)
たとえば、次のようなテンプレート関数を定義したとします。

コード:

/*
ここでは、int型は4バイトあるものと仮定する,だから、このテンプレートはTがintならvalueは偽値を示す。
それと、std::stringはポインタ(これも4バイトと仮定)と文字列の長さ(unsigned intで保持していると仮定)の計8バイトと仮定する。
*/
template<typename T>
struct less_than_4{
	static const bool value = sizeof(T) < 4;
};

//Tがiteratorという型を持っていなければエラーになる(はず)
template<typename T>
void print(T t,typename T::iterator* = 0){
	std::cout<<"[T may be container or std::string] "<<t<<std::endl;
}//1番
//Tの型の大きさが4以上だと要素数0の配列型が生成されてしまいエラーになる(はず)
template<typename T>
void print(T t,char(*)[less_than_4<T>::value?1:0] = 0){
	std::cout<<"[T may be small : sizeof(T)<4] "<<t<<std::endl;
}//2番
//どんな型でも受け付ける。
void print(...){
	std::cout<<"[T may be sizeof(T)>=4 && not have type iterator!]"<<std::endl;
}//3番
で、これをこのように呼び出したとします。

コード:

int main(){
	print('a');//呼び出しA
	print(std::string("test"));//呼び出しB
	print(100);//呼び出しC
}
print関数はオーバーロードされていますから、呼び出しの実引数からどの関数が呼ばれるかが決定されますが、そのプロセスは…

まず、呼び出しABC全てで、printという関数が呼ばれるので、コンパイラは呼ばれる関数の候補として1~3番のprint関数を考慮します。
まず、呼び出しAですが、まず、1番のprint関数はテンプレートなので、テンプレート引数Tをcharとして実体化します。すると、char::iteratorという存在しない型が生成されてしまうため、テンプレート引数の置き換えに失敗しますが、これはコンパイルエラーにならず、print関数1番を呼ばれる関数の候補から外します。
次に、2番のprint関数もテンプレートなので、1と同様実体化します。すると、sizeof(char)==1で、less_than_4::valueは真になりますから、配列型は要素数1で不正になりません。よって、呼ばれる関数の候補に残ります。print関数3番はどのような引数でも取れるので、当然候補に残ります。
そして、候補に残った2番と3番のprint関数のうち、2番のほうがより当てはまる(申し訳ありません、いい表現が思いつきませんでした)ので呼ばれる関数は2番に決まる。ということになります。
呼び出しBは、1番で実体化すると、std::stringはiteratorという型を持ちますから、不正にならず、呼び出し関数の候補に残ります。2番で実体化すると、sizeof(std::string)==8で、配列型は要素数0となり不正な型となり、呼び出し関数の候補から外れます。3番は呼び出しAと同様です。
そして、候補に残った1番と3番のうち、呼び出しAと同じ理由で1番が選ばれることになります。
呼び出しCは、1番も2番も実体化すると失敗(int::iteratorなんて型は存在しないし、sizeof(int)==4より配列型は要素数0の不正な型になる)しますので、どっちも候補から外れ、残った3番が呼ばれる関数になります。

SFINAEは、テンプレートを実体化するとchar::iteratorという存在しない型や、char[0]という不正な型が生成されるときに、これがエラーにならず、なかったことになる(つまり候補から外される)ときに起こっています。

chaemon
記事: 5
登録日時: 12年前
住所: 川崎

Re: 汎用出力関数(?)

#9

投稿記事 by chaemon » 12年前

私の環境でもコンパイルでき、正常に動作しました!したがって、解決といたします。

また、SFINAEに関する詳しい説明ありがとうございます。なんとなくわかりましたが、使いこなすにはまだまだ難しそうです〜とりあえず、check1, check2あたりがその処理なんですね!

そうなりますと、次は構造体やクラスの内容を出力してくれる関数の構築に興味が出てきました。考えてみます。

例えば、

コード:

struct{
  int a, b;//2
  vector<int> v;//[5,4,3]
} s;

int main(){
  cout<<v<<endl;
}
みたいな状態で実行すると、

s(a=2,b=2,v=vector[5,4,3])

みたいに出力されるものでしょうかね。私はデバッグなどに役立つと思うのですが、そういった関数の需要はどのくらいあるのでしょうか?

もし、需要が大きいとしたらboostあたりでどなたかがすでに作っていたりしないのでしょうか?


chaemon
記事: 5
登録日時: 12年前
住所: 川崎

Re: 汎用出力関数(?)

#11

投稿記事 by chaemon » 12年前

ありがとうございます。xmlファイルに出力されるのですね〜

閉鎖

“C言語何でも質問掲示板” へ戻る