C++ 文字列リテラルとvoid *型とコンパイラ

VC++2008では文字列リテラルのアドレスをvoid *に暗黙の内に変換し渡すことができるけど、
GCC4.4.0ではエラーになったので、簡単にメモ。


何でこんな事になったというと、汎用型みたいな感じで、こんなクラスを作ってみたことから。

main.cpp

#include <iostream>

class Test{
private:
	union{
		int Int;
		double Double;
		void *Pointer;
	};
public:
	Test(int n){ Int = n; }
	Test(double n){ Double = n; }
//	Test(const char *n){ Pointer = static_cast<void *>(const_cast<char *>(n)); }
	Test(void *n){ Pointer = n; }
	
	int getInt(){ return Int; }
	double getDouble(){ return Double; }
	const char * getConstData(){ return static_cast<const char *>(Pointer); }
	template<class C> operator C(){ return static_cast<C>(Pointer); }
};

int main(void){
	Test a(1);
	Test b(3.14);
	Test c("abcdefg");
	Test d(new Test("abcd"));
	Test *e = d;
	
	int i = a.getInt();
	double pi= b.getDouble();
	const char *str = c;

	std::cout << i << std::endl;
	std::cout << pi << std::endl;
	std::cout << str << std::endl;
	
	std::cout << e->getConstData() << std::endl;
	return 0;
}

結果

1
3.14
abcdefg
abcd

値はintとdoubleとか決められた値以外は、ほとんどはポインタで扱えます。
配列もクラス型もnewで作ればポインタになるので、それをvoid *で受け取ることで対応します。
値を返すときは、getIntなどのアクセサから受け取れますが、
このとき整数型やクラス型などのポインタを持つ事を考えると、
いちいち受け取ったvoidポインタをキャストしなくてはならなくなるので、めんどくさいです。
そのためにテンプレートを使えばいいんですが、
関数のオーバーロードは戻り値の型が違うだけでは出来ないので、変換演算子を使って、

template<class C> operator C(){return static_cast<C>(Pointer);}

とかやって、voidポインタを各クラスの型にあわせて変換します。
代入には=を使って行うことになるので、少し違和感があります。どうにかしたい。

問題点

このクラス自体の問題もあるし、まだテストが足りて無いので他の問題がありそうだけど、とりあえず今、特に問題視している点について。


まず、上のコードのままコンパイルすると、こんなエラーを出した。

main.cpp: In function 'int main()':
main.cpp:25: error: call of overloaded 'Test(const char [8])' is ambiguous
main.cpp:14: note: candidates are: Test::Test(void*)
main.cpp:11: note: Test::Test(int)
main.cpp:3: note: Test::Test(const Test&)
main.cpp:26: error: call of overloaded 'Test(const char [5])' is ambiguous
main.cpp:14: note: candidates are: Test::Test(void*)
main.cpp:11: note: Test::Test(int)
main.cpp:3: note: Test::Test(const Test&)

文字列リテラルをコンストラクタの引数にしている行が"曖昧な理由"によるエラーで、
const char ではそれぞれのコンストラクタの引数型にマッチして困ってるらしい。


文字列リテラルコンパイラはconst char 型として捕らえてるらしい。
[]は用は配列でしょ?配列の先頭のアドレスでしょ?ならconst char *に置き換えればいいよね?
ということで、単純にコンストラクタを増やしてみた。

	Test(const char *n){ Pointer = n; }

main.cpp:15: error: invalid conversion from 'const void*' to 'void*'

エラーが出るのはわかってたが、いつconst char *がconst void *になったんだ?
文字列リテラルを書き換えられたら困るから、const外すとか絶対に許さないよ!
という意味なんだろうか。でもそれではこっちも困る。
ということで、強引に通させるためのコードが以下。(main.cppにコメントアウトしていた行)

	Test(const char *n){ Pointer = static_cast<void *>(const_cast<char *>(n)); }

やってることは、受け取った文字列へのポインタを、無理やりconstをはずして、void*型へキャストしてるだけなんですが。
失敗すればコンパイルエラーになるはずなので、少しは安心?
これでVC++2008でもGCC4.4.0でもコンパイルが通るようになりました。

const_castでconst外した文字列をいじったらどうなるの、っと

代入時にconst外してるんだから、もしchar*で受け取った後に改ざんされる可能性があるんじゃね?
と思った。
文字列を定義するによく使うものでは以下の3つあります。
char a[]= "aaa";
char *b = "aaa";
"aaa"


これらをサンプルとして、こんなかんじに。

#include <iostream>

int main(void){
	const char string[]="abcdefg";
	const char *string_p="abcdefg";

	char *str_a= const_cast<char *>("abcdefg");
	char *str_b= const_cast<char *>(string);
	char *str_c= const_cast<char *>(string_p);

	str_a[0]='A'; // 実体は文字列リテラル "abcdefg"
	str_b[0]='A'; // 実体はconst char string[] "abcdefg"
	str_c[0]='A'; // 実体はconst char *string "abcdefg"
	
	std::cout << "str_a: " << str_a << std::endl;
	std::cout << "str_b: " << str_b << std::endl;
	std::cout << "str_c: " << str_c << std::endl;
	
	return 0;
}

結果は、最適化オプション-O2をつけると、コンパイル出来るし実行も出来るが、str_aとstr_cの変更は行われない。
最適化オプションなしでコンパイルは通るが、実行でエラー。(str_a[0]='A';とstr_c[0]='A';でひっかかる)
const charでも[]と*で文字列を定義するのは、どうやらちゃんと区別されてるらしい。
str_bの改変はちゃんとうまくいってます。
最適化オプションなしでもエラー部分もコメントアウトすれば実行でき、str_bの改変をしてくれます。
const外しの前ではconstのアイデンティティも台無しだな!!
でも本来使うべきじゃないんだろうなぁ。