- ベストアンサー
C++ template コンパイルできないパターン
度々お世話になります。 以下のソースがコンパイルできません。 ご存知の方がいらっしゃれば教えて下さい。 #include <iostream> #include <exception> template<typename T, T C> inline T check(T x) { if (x == C) { throw(std::exception()); } return x; } int main() { using namespace std; int x; try { char * p = "abc"; check<char*, 0>(p); // コレがコンパイルできない int i = 2; check<int, 4>(i); // コレはコンパイルできる } catch (...) { cerr << "err" << endl; } return 0; } 手元の環境だと % g++ foo.cc foo.cc: In function 'int main()': foo.cc:18: error: no matching function for call to 'check(char*&)' となります。 関係するのか分からないのですが char* をテンプレートの引数にしているのに コンパイラのエラーメッセージは char*& となっているのがよく分かりません。 目的としては、エラーチェックをして エラーをであれば、例外を投げるというものです。
- みんなの回答 (7)
- 専門家の回答
質問者が選んだベストアンサー
> 多分、他に書き方がないんでしょうね 原文ですが(JISはしらない)、例えば同列の「pointer to member ..."」などと異なり、 こちらの規定では"the address of ..."なあたりもポイントかと。 # 私も当初、ML側で説明がある程度の認識だったのですが、 # ちゃんと読むと、この部分の規定はポインタではなくアドレス。 > これだと, NULL と比較できないので 私も昨日少し考えましたが、多分、綺麗なNULL比較はパラメータでは無理なんじゃないかと。 > char * xa[] = {0}; xaをnull_exprとか命名すると多少はましになるかもしれませんが、 私なら多分テンプレートの第二引数を省略した版を作ります(↓)。 > という事で、やっぱり似たような専用の記述を複数書くのが > 無難なような気がしてきました。 template<typename T, T c> // 本当はT c=0にしたいが0はintなので… // [0比較用のオーバロード] cを省略すると、最も頻度の高そうな0と比較 // オーバロードでなくcheck_zeroとかそんな名でもいいですが…。 template<typename T> inline T check(T x) { if (x == 0) { throw std::exception(); } // できれば、この用途にstd::exceptionは派生させておくべきかと。 // でないと受け側catchで原因が特定できない check<int, 4>(i); check<char*>(p); // 0比較 // もしかするとこんなのもありか。template<typename T*, int p> // でもcheck<int*, 0x80000000>とか書くとunsigned longだなぁ。 > なんだか、実用というより自分の興味本位な内容になってしまってすみません。 私も趣味/興味で調査してますから(C++が好きなんです) # cppllに反応すれば、私なら素直にRaiiで例外出すか、 # Raiiの「ポリシー」にする気がします。(このcheckがポリシー用?)
その他の回答 (6)
- MrBan
- ベストアンサー率53% (331/615)
# cppllも(別ハンドルですが)入ってますのでそちらの流れも把握しています。 JIS X3014でも章番号は一緒ですので、それを頼りにしてもらえれば、 ここで見るべきは以下ですね。 14.3.2 Template non-type arguments ・"& 識別式" 形式(※)で表され外部リンケージを持つオブジェクトまたは関数のアドレス ※もし名前(識別式)が関数/配列をさすか、テンプレートパラメータが参照の場合には'&'は省略可能 なお、[--snip--]としていた部分は例外規定で、 「関数テンプレートと関数テンプレート識別子は含まれるが非静的なクラスメンバは含まない」 と書いてあります。 また、上記のポインタ規定以外でテンプレート引数として認められているのは、 ・整数型または列挙型の整数定数式 ・型でないテンプレート引数の名前 ・5.3.1章で規定のメンバへのポインタ だけで、「上記のいずれかでなければならない(shall)」と書いてあります。 また、その後に変換規定があり、それらに合致しない場合もill-formedとのことです。 > 1.関数と配列の & は省略できる。(アドレスの為の &) > 2.テンプレートに関数、配列を使う場合はリファレンスになる > ということでしょうか? ポイントはそこではなく、「"& 識別式" の形式で表され」の部分かと思います。 単に"x"ではだめで、"&x"になるような形でなければならないとなっており、 (多分コンパイル時定数の保証のための規定?) ここで、xはchar*であって'&'を省略できる条件を満たしませんので、 リンケージだけを外部にしてもダメということになります。 # 規格書の例では、文字列リテラル("Vivisectionist")すらchar配列に格納しないとエラー。 # MinGW3.2で試してみると、グローバル変数で確かに # char x[] = "foo";だと配列なので単に"x"でもコンパイルが通ります。 # char* x = "foo";にしたら通らなくなりました。
補足
何度も本当にありがとうございます。 > ポイントはそこではなく、「"& 識別式" の形式で表され」の部分かと思います。 > 単に"x"ではだめで、"&x"になるような形でなければならないとなっており、 > (多分コンパイル時定数の保証のための規定?) > ここで、xはchar*であって'&'を省略できる条件を満たしませんので、 > リンケージだけを外部にしてもダメということになります。 MinGWの挙動が正しいと言われていた理由が理解できました。 規格のこの部分の表現は少しトリッキーに感じました。 「"& 識別式" の形式で表され」でも &を省略できる場合があるよ というような部分に、 多分、他に書き方がないんでしょうね > # char x[] = "foo";だと配列なので単に"x"でもコンパイルが通ります。 こっちはg++もコンパイルできました。 これだと, NULL と比較できないので template<typename T, T * C> inline T check(T x) { if (x == *C) { throw std::exception(); } return x; } char * xa[] = {0}; int main() { ... check<char*, xa>(p); ... } などとやってみました。 ただこれだと、checkがintにも使える汎用性もなくなりますし、 check の中では NULL を使ってるのではなくて xa を使っているので 最適化の視点からは微妙ですね。(こういう事に、無意味にこだわっている自分の頭の方がおかしいとは思っているんですが。。。。) また、 char * const xa[] = {0}; とすれば、最適化の可能性も出てくるかと思ったのですが テンプレートの引数のところをいろいろ試してみても、 コンパイルできませんでした。 コンパイル通ってもいろいろ微妙なので、これはできなくてもいいかなと思います。 char * xa[] = {0}; の方法を考えなおすと 1. check で int を使えなくなるので微妙 2. 最適化できない。 3. xaを書換えられるかもしれない 4. 外部リンケージなので static char * xa[] = {0}; はダメで xa がグローバルになる。(namespaceはできましたが) という事で、やっぱり似たような専用の記述を複数書くのが 無難なような気がしてきました。 なんだか、実用というより自分の興味本位な内容になってしまってすみません。
- MrBan
- ベストアンサー率53% (331/615)
# 指摘を受けて「お?」と思い再確認したら、テストコードがバグってました…orz # 突貫作業はダメだ…すみません。 MinGW3.2では、外部リンケージにしてもエラーになりました。 Bccの方は、外部リンケージにすれば通るようです。 ただ、規格を厳格に読む限りMinGWの挙動の方が正しそうです(expressed~以降) <ISO/IEC 14882> the address of an object or function with external linkage, [--snip--], expressed as & id-expression where the & is optional if the name refers to a function or array, or if the corresponding template-parameter is a reference </ISO/IEC 14882> # 規格書は、凄く見にくい/微妙に内容が違う日本版(JIS X3014)でよければ、 # オンラインで閲覧可能です(ISOの方は売り物)。見にくいので私はISOの方しか見てませんが。 # cppllでも以前話題になってます(こちらは私も見てます)。
補足
ありがとうございます。 体調悪かったのでしばらく寝てました。 cppllの方にも質問してみました。 http://ml.tietew.jp/cppll/cppll/article/12933 > # 指摘を受けて「お?」と思い再確認したら、テストコードがバグってました…orz > # 突貫作業はダメだ…すみません。 gcc 3.2 の方は、ダメだったというより コンパイルできませんでした。 4.1.1 で 3.2 をコンパイルするのが変なのかもしれません。 あまりはまっても仕方ないので諦めました。 > <ISO/IEC 14882> 英語はあまり分からないのですが、 1.関数と配列の & は省略できる。(アドレスの為の &) 2.テンプレートに関数、配列を使う場合はリファレンスになる ということでしょうか? だとすると、Bcc が正しいような気がします。 > # 規格書は、凄く見にくい/微妙に内容が違う日本版(JIS X3014)でよければ、 > # オンラインで閲覧可能です(ISOの方は売り物)。見にくいので私はISOの方しか見てませんが。 見にくい方しか見てないので、なんとも言えないですが 文章の構成が頭に入ってないと見るところの、的が絞れそうにありませんでした。 目次もないみたいでしたし。
- MrBan
- ベストアンサー率53% (331/615)
「templateの非タイプパラメータにアドレスを渡す場合、 外部リンケージを持っていないといけない」規定(ISO/IEC14882 $14.3.2 1)に 抵触してて、(char*)0ではダメなんですね。 # VCの引数解釈で0特別扱いはまずいとして、(char*)0もまずいのか…。 # 単純に「型不一致」と「コンパイル定数か否か」の問題かと誤認してました>自分…orz > char * p = "abc"; > char * const x = 0; > check<char*, x>(p); というわけで、これは、xをグローバルに宣言すると通りました(MinGW)
補足
何度もありがとうございます。 > というわけで、これは、xをグローバルに宣言すると通りました(MinGW) おぉ、そうなのですか! 実は、これもやってはいたのですがこっちではダメでした。 そういえば、gccは3.3.6 も持っていたのですが こっちもダメでした。 とりあえず、gcc 3.2 をコンパイルしています。 MinGWのバージョンと互換性があるのかは分からないですが。 > 「templateの非タイプパラメータにアドレスを渡す場合、 > 外部リンケージを持っていないといけない」規定(ISO/IEC14882 $14.3.2 1)に > 抵触してて、(char*)0ではダメなんですね。 なるほど、規格は持ってないのですが 3.2でダメなら、gccのバグ(<-良く分かってない人が使いがちなフレーズ) かもしれないので、 cppllとgcc本家にあたってみようかと思います。
- colder
- ベストアンサー率43% (30/69)
規格書14.3.2/5により、不適格なプログラムです。 > 0 は、汎整数型の非型のテンプレート仮引数に対する正当な《テンプレート実引数》であるが、 > ポインタ型の非型のテンプレート仮引数に対する正当な《テンプレート実引数》ではない。
お礼
回答ありがとうございます。 不適格なプログラムですかぁ。 テンプレートでのやり方はなさそうですね。 あとはマクロですかね。 一応直接 0 でなくて char * p = "abc"; char * const x = 0; check<char*, x>(p); ともやってみたのですが % g++ hoge.cc hoge.cc: In function 'int main()': hoge.cc:37: error: 'x' cannot appear in a constant-expression hoge.cc:37: error: no matching function for call to 'check(char*&)' となるようでした。 intのconstはコンパイルできるようでしたが int i = 2; int const x = 4; check<int, x>(i);
- MrBan
- ベストアンサー率53% (331/615)
GCC(g++)のバージョンはいくつですか? VC7.1SP1では、提示のままのソースでコンパイル通りました。 VC8も通ります。 MinGW(g++)3.2ではエラー。 ・no matching function for call to check(char*&) Bcc5.6でもエラー。 ・check<T,C>(T)からテンプレート特化できない ・check<T,C>(char*)に一致するものが見つからない 但し、Bccはcheck<char*, (char*)0>(p);に直すと通ります。 MinGWは相変わらずで、以下のエラーになります。 ・'0' is not a valid template argument ・it must be the address of an object with external linkage ・no matching function for call to check(char*&) C++でNULLに相当する0は、型としてはintです。 単にcheck<char*, 0>と書くと、型はcheck<char*,int>と扱われるため、 0(int)⇒char* Cが暗黙で変換できない(reinterpret_castが必要なはず)ので、 合致しないと言われているかと思います。 (ちなみに、VC7.1においても、0⇒1にする(check<char*,1>)はエラーなので、 ・型キャスト' : 'int' から 'char *const ' に変換できません。 ・テンプレート引数 'int' が無効です。 0(NULL)だけ特別扱いするような特殊則があると思われます。 GCC(g++)でキャストしてもエラーになる理由は不明です。 # 3.2は古い代物なので、GCCのバージョン問題かも…。
お礼
いろいろ調べてもらってありがとうございます。 > GCC(g++)のバージョンはいくつですか? 4.1.1 です。 (リリースの最新より1つ前みたいです) > C++でNULLに相当する0は、型としてはintです。 自分もこの点が気になったので、char* にキャストしてみたんですが % g++ hoge.cc hoge.cc: In function 'int main()': hoge.cc:32: error: a cast to a type other than an integral or enumeration type cannot appear in a constant-expression hoge.cc:32: error: no matching function for call to 'check(char*&)' となりました。 エラーメッセージを見る限り、 整数かenum以外へのキャストは constantな文(=>テンプレートの引数)に入れられない。 ということみたいですね。 gcc以外では通るようなので、 規格としてダメなのか、gccだけダメなのかは分からないですが(もしくは未定義) reinterpret_castは、関数で実行時なのかもしれないと、 思ったのですが、先に inline展開 するのかもしれないですが 同じエラーでした。 > ・型キャスト' : 'int' から 'char *const ' に変換できません。 上に書いたgccの 整数かenumへのキャストの件もこの事をいってるのかもしれないですね。 このキャストはコンパイル時でなくて、実行時での取り扱いなのかもしれませんね
- neKo_deux
- ベストアンサー率44% (5541/12319)
> char* をテンプレートの引数にしているのに いえ。 > template<typename T, T C> > inline T check(T x) { > if (x == C) { > throw(std::exception()); > } > return x; > } 実体の引数を受け取るものしか準備されていません。 char*を渡したいのなら、char*を受け取れる関数を準備しておく必要があります。
補足
回答ありがとうございます。 せっかく回答を頂いのですが、回答の意味を理解しきれていません。 すみません。 > 実体の引数を受け取るものしか準備されていません。 > char*を渡したいのなら、char*を受け取れる関数を準備しておく必要があります。 char* 専用に以下のようなものは作ってみたのですが、状況は変化しませんでした。 template<char *, char * C> inline char * check(char * x) { if (x == C) { throw(std::exception()); } return x; } char *& でも、コンパイルできませんでした。 template<char *&, char *& C> inline char *& check(char *& x) { if (x == C) { throw(std::exception()); } return x; } ... int main() { ... check<char*&, 0>(p); ... } 多分これでは、char* を受けとれないということなのですよね。 この部分の書き方を教えて頂けないでしょうか? さすがにtemplateを止めるとコンパイルできました。 inline char * check(char * x) { if (x == 0) { throw(std::exception()); } return x; }
補足
ありがとうございます。 > template<typename T> > inline T check(T x) { > if (x == 0) { > throw std::exception(); > } そうですね。 こういう感じですね。 // できれば、この用途にstd::exceptionは派生させておくべきかと。 // でないと受け側catchで原因が特定できない あぁこれは、もともと自分もそうしようとしていて template<typename E, typename T> ... throw E(); ... という感じにしようかと思っていました。 ただ、あまり元々質問の本質と関係ないと思ったので 混乱されたくなかったので省略していました。 > // もしかするとこんなのもありか。template<typename T*, int p> > // でもcheck<int*, 0x80000000>とか書くとunsigned longだなぁ。 ここは、よく分かりませんでした。 > # cppllに反応すれば、私なら素直にRaiiで例外出すか、 > # Raiiの「ポリシー」にする気がします。(このcheckがポリシー用?) Raiiの「ポリシー」というのが、イメージ沸かなかったのですが。 例外については、Kent.N さんも書かれていますが、 一旦 Raii のコンストラクタに入る前に、 止めたいというのがあります。 デストラクタで気を使う必要が出そうなので。 > 私も趣味/興味で調査してますから(C++が好きなんです) それは安心しました。