SFINAEやテンプレートの部分特殊化で気をつけたいこと
はじめに
久々にSFINAE(Substitution Failure Is Not An Error)やテンプレートの部分特殊化(Partial Specialization)を久々に使ったら見事にハマったので、まとめときます。
ただし、用語の間違えについては自分の中でもあやふやなんで気づいた人は指摘お願いします。
環境について
多分、どの環境でも同じだけれども、ボクはVisual Studio 2015 Update2を使ってるんで他の環境ついては知りません。
本題
主に気をつけたいことは以下の3つです。いずれもコンパイルエラーの原因です。
- 解決可能なオーバーロードが複数ある。
- 実は lvalue もしくは rvalue な参照がテンプレート引数になっている(のに気が付いていない)
- 配列を受け取りたいのに何故かエラーになる
どれも解決までに結構悩んだものです。
解決可能なオーバーロードが複数ある
これは最もやらかしやすいと思う。
まあ、コンパイルエラーを読めば、そのまんま書いてあるからその通りに直せば良いのだけれども、量が増えるとそうは行かなくなる。
以下は実例。
#include <iostream> #include <type_traits> template <class Tp, class = void> struct type_impl { static void print() { std::cout << "default" << std::endl; } }; // 本来はポインタ型だけを受け取りたい特殊化 template <class Pointer> struct type_impl<Pointer, std::enable_if_t<std::is_pointer<Pointer>::value>> { static void print() { std::cout << "Pointer" << std::endl; } }; // 本来はPOD型を受け取りたい特殊化 template <class POD> struct type_impl<POD, std::enable_if_t<std::is_pod<POD>::value> > { static void print() { std::cout << "POD" << std::endl; } }; template <class Tp> struct type : public type_impl<Tp> {}; int main() { type<int*>::print(); type<int>::print(); return 0; }
これ、エラーになります。
見ればわかるんですけど、is_podはポインタを受け取ってもtrueになってしまうからですね。
当然、解決策の検討が必要になるんだけれども、最も安直な方法はこれ。
16行目にポインタを受け取らないようにチェックを足してやる
template <class POD> struct type_impl<POD, std::enable_if_t< std::is_pod<POD>::value && !std::is_pointer<POD>::value // ポインタは受け取りませんと明示する > > { static void print() { std::cout << "Literal" << std::endl; } };
実は lvalue もしくは rvalue な参照がテンプレート引数になっている(のに気が付いていない)
以下がその例。っていうかさっきと同じコードのmain関数に1行だけ足した
#include <iostream> #include <type_traits> template <class Tp, class = void> struct type_impl { static void print() { std::cout << "default" << std::endl; } }; template <class Pointer> struct type_impl<Pointer, std::enable_if_t<std::is_pointer<Pointer>::value>> { static void print() { std::cout << "Pointer" << std::endl; } }; template <class POD> struct type_impl<POD, std::enable_if_t< std::is_pod<POD>::value && !std::is_pointer<POD>::value> > { static void print() { std::cout << "POD" << std::endl; } }; template <class Tp> struct type : public type_impl<Tp> {}; int main() { type<int*>::print(); type<int>::print(); type<int&>::print(); // 足した部分 return 0; }
さてこれの出力はどうなるでしょうか?
まあ、当たり前なんだけど
Pointer POD default
になります。これがどういう時に困るかというと、以下の様な関数を足した時。
template <class Tp, class Traits = type<Tp>> void print(Tp&& value) { Traits::print(/* std::forward<Tp>(value) */); // 実際にはvalueを渡すことが多かろう } // in int main() int hoge = 0; print(10); // 多分 lvalue reference print(std::move(hoge)); // 間違いなく rvalue reference print(&hoge); // 間違いなくポインタ print(hoge); // ?
で、これの出力が以下のようになって欲しいのに、
POD POD Pointer POD
以下のようになってしまいます。
POD default Pointer default
一瞬うーんと成るかも知れませんが、参照はPOD型ではありません。
それ故、これを解決するためには remove_reference を使い以下のようにPOD型の判定を行う際に参照を外す必要があります。
template <class POD> struct type_impl<POD, std::enable_if_t< std::is_pod<std::remove_reference_t<POD>>::value && !std::is_pointer<POD>::value> > { static void print() { std::cout << "POD" << std::endl; } };
配列を受け取りたいのに何故かエラーになる
これが一番、厄介、というか一番気づくまでに時間がかかった。
さて、ここで言う配列というのは一次元、それも固定長の配列だ。
この配列だが一般的に以下の方法で配列のサイズを取得することが可能とされている
template <class Array, std::size_t Size> constexpr std::size_t size(Array(&)[Size]) { return Size; } int hoge[5] = {}; std::cout << size(hoge) << std::endl; // 5
この方法を応用して以下の様なコードを考える。
#include <iostream> #include <type_traits> template <class Tp, class = void> struct type_impl { static void print(Tp&&) { std::cout << "default" << std::endl; } }; template <class Array, std::size_t Size> struct type_impl<Array[Size], std::enable_if_t<std::is_array<Array>::value>> { static void print(Array (&& value)[Size]) { std::cout << "Array:(" << value << "," << Size << ")" << std::endl; } }; template <class POD> struct type_impl<POD, std::enable_if_t< std::is_pod<std::remove_reference_t<POD>>::value && !std::is_array<POD>::value> > { static void print(POD&& p) { std::cout << "POD:" << p << std::endl; } }; template <class Tp> struct type : public type_impl<Tp> {}; template <class Tp, class Traits = type<Tp>> void print(Tp&& value) { Traits::print(std::forward<Tp>(value)); } int main() { int hoge = 20; int array[10] = {}; print(10); print(hoge); print(array); return 0; }
これを実行すると期待的には以下のとおりにしたいのだが、
POD:10 POD:20 Array:(<arrayのアドレス>,10)
実際は、
POD:10 POD:20 POD:<arrayのアドレス>
となる。
実は、ここで犯しているのはテンプレートの置き換え云々の問題だけではない。
以下がその問題と修正したコードだ。
- 配列は rvalue referenceで受け取れない。
- 特殊化するのはArrayだけではなく、Array[Size]。
#include <iostream> #include <type_traits> template <class Tp, class = void> struct type_impl { static void print(Tp&&) { std::cout << "default" << std::endl; } }; template <class Array, std::size_t Size> struct type_impl<Array[Size], std::enable_if_t<std::is_array<Array[Size]>::value>> // 型の渡し方を変える { // Array (&&)[Size] ではなく Array(&)[Size] static void print(Array (&value)[Size]) { std::cout << "Array:(" << value << "," << Size << ")" << std::endl; } }; template <class POD> struct type_impl<POD, std::enable_if_t< std::is_pod<std::remove_reference_t<POD>>::value && !std::is_array<POD>::value> > { static void print(POD&& p) { std::cout << "POD:" << p << std::endl; } }; template <class Tp> struct type : public type_impl<Tp> {}; template <class Tp, class Traits = type<Tp>> void print(Tp&& value) { Traits::print(std::forward<Tp>(value)); } // オーバーロードする template <class Tp, std::size_t Size, class Traits = type<Tp[Size]>> void print(Tp (&value)[Size]) { Traits::print(value); } int main() { int hoge = 20; int array[10] = {}; print(10); print(hoge); print(array); return 0; }
こうすることで期待通りの結果得られる筈だ。
おわりに
未だにSFINAE機構を使うとやらかすことが多いので、メモを兼ねて記した。
もし、SFINAEやテンプレートの特殊化で困ったことがあれば、この話を思い出して欲しい。
どうでもいいのだけれども
かなりどうでもいい話です。
過去の僕が書いた記事を見ていて、ふと、眼気眼ってなんだと思って。
google検索にかけてみると、なぜか以下のページがトップに出てきた。
なぜだなぜだ、と見返してみると、眠気眼と書いていたらしい。
実際、Google IMEで「ねむけまなこ」で変換すると眠気眼が出てくる。
が、ここで気になるのが、「ねむけまなこ」と「ねぼけまなこ」の違い。
まあ、結果だけ言うと、「ねむけまなこ」なんて言葉は存在しないらしい。
笑われる
なんとまあ、そんな誤用、変換させるなやとGoogleIMEに八つ当たりしたい所だ。
PythonでDLLを使う時の話
DLLを作ろう
まずは、適当な方法でDLLを作ろう。
個人的には何で作ってもいいと思うけど今回はマルチプラットフォームを意識してcmakeを使ってみます。
使うソースは以下3つ。
config.hpp
#ifndef CONFIG_HPP #define CONFIG_HPP #ifdef Test_EXPORTS # define DLL_API extern "C" __declspec(dllexport) #else # define DLL_API extern "C" __declspec(dllexport) #endif #endif CONFIG_HPP
test.hpp
#include "config.hpp" DLL_API void print(char const* str); DLL_API int sum(int lhs, int rhs);
test.cpp
#include "test.hpp" #include <iostream> DLL_API void print(char const* str) { std::cout << str << std::endl; } DLL_API int sum(int lhs, int rhs) { return lhs + rhs; }
CMakeLists.txt
cmake_minimum_required(VERSION 3.3) project(Test) add_executable(Test SHARED ${CMAKE_CURRENT_LIST_DIR}/test.cpp ${CMAKE_CURRENT_LIST_DIR}/test.hpp ${CMAKE_CURRENT_LIST_DIR}/config.hpp )
今回のポイントとしては、DLL_APIマクロに __declspec(dllexport) だけではなく extern "C" も含んでいる所。これがないと、C++の関数として扱われて後述する問題にぶちあたります。
それと、cmakeでDLLを作った経験がない人にとっては馴染みがないと思いますが、cmakeでビルドする場合、謎のWinMainを定義する必要も、(モジュール名)_EXPORTSマクロを自分で定義する必要はありません。cmakeが勝手にやってくれます。
ついでに言うと、ぶっちゃっけヘッダーはadd_executableには不要なんだけど、VisualStdio信者な僕には必須です。
DLLの中身を確認しよう
ここはWindows限定(Linuxとかでもできるかもだけれども、やり方は知らん)。
まず、VisualStudioのコマンドツールを起動しよう。
それからビルドしたDLLに対して以下のコマンドを実行。
$ dumpbin /EXPORT Test.dll
すると、自分で定義した関数の一覧が出るはず。
これのname欄に@Xsdwsda@見たいな関数名以外のシグネチャが含まれていたらおそらく、extern "C"が抜けているのだと思う。
extern "C"なしのC++の関数は呼び出せないという訳ではないのだけれども、今回の説明では扱わないのでとりあえずextern "C" を付けましょう。
Dump of file Test.dll File Type: DLL Section contains the following exports for Test.dll 00000000 characteristics 577278AE time date stamp Tue Jun 28 22:16:30 2016 0.00 version 1 ordinal base 2 number of functions 2 number of names ordinal hint RVA name 1 0 00001020 print 2 1 00001010 add Summary 1000 .data 1000 .gfids 1000 .rdata 1000 .reloc 1000 .rsrc 2000 .text
Pythonからの呼び出し
後は簡単、ctypesモジュールを使えばちゃちゃっとできます。
# coding:utf-8 import ctypes def main(): dll = ctypes.cdll.LoadLibrary('Test.dll') c_print = dll.print c_print.argtypes = [ctypes.c_char_p] c_print.restype = None c_sum= dll.sum c_sum.argtypes = [ctypes.c_int, ctypes.c_int] c_sum.restype = ctypes.c_int c_print(ctypes.c_char_p('utf-8'.encode('utf-8'))) print(int(c_sum(ctypes.c_int(10), ctypes.c_int(20)))) if __name__ == '__main__': main()
これを実行すると出力は以下のようになるはずです.
utf-8 30
まず、LoadLibraryで読み込んで、その後は、dll.(関数名)でdll内の関数オブジェクトを取得することができます。
その後、戻り値(restype)と引数の型(argtypes)を設定しやりましょう。
その時の注意事項は以下の4つ
- 文字列はencodeでbyte型に変換してからc_char_p型に変換する
- 引数にポインタ型を使いたいときは ctypes.POINTER(ctypes.何らかの型) という風にしてやりましょう。
- 同様にアドレスを渡す時もctypes.addressofでアドレスを渡すことができる。
- たまに、アドレスの型変換をしたい時もあると思うけど、そのときは、ctypes.cast を使いましょう
おわりに
もっと知りたければ、Pythonの公式ドキュメントを見よう。
もし僕に間違いがあっても多分書いてるだろう。
16.16. ctypes — Pythonのための外部関数ライブラリ — Python 3.5.1 ドキュメント
なんとなく
いつのまにやら、ブログをほったらかしにして、2年が立ってました。
改めて自分の書いた、未熟な過去(今も大概ですが)を見てみるとニマーとすることも多いですが、更新を再開しようかなと思います。
動機はなんとなくなので長く続くとは限りませんがねー
ちなみにブログ名を変えた理由は心機一転というわけではなく、単純に書く内容をプログラミングに絞らずに趣味のことも書きたいなと思ったからです。
プログラミングだけだと、ブログ書いてる暇あったらプログラム書こうかなとか思って、ブログの更新をしなくなるしね。
latexmkのメモ
latexmkを使う時の自分用のメモです
前提として今回利用するlatexmkはWindows版であってcygwin版ではないということです
以下のページを参考にしつつlatexmkの設定ファイルを書いてみたけど、動かない
天地有情 latexmk と ptex2pdf
latexmkのドキュメントを読んでみると、どうやらWindowsでlatexmkがプロファイルを読む順番は以下の通りらしい
http://users.phys.psu.edu/~collins/software/latexmk-jcc/latexmk-439.txt
- "C:\latexmk\LatexMk"(LatexMkがプロファイルを書き込んだファイル)
- "%HOME%/.latexmkrc"
- "%USER_PROFILE%/.latexmkrc"
- latexmkの実行ディレクトリ
- -rオプションで与えられたrcファイル
ここで何が問題かというと、cygwinを使用している環境では%HOME%が既に使用されているためWindowsのホームディレクトリが指定されないという点です。
そのためUnix形式のパスである"/home/(ユーザー名)"とか指定しているともうどうしようもない。
とりあえず、手っ取り早い解決策として、1番の方法で解決することに
自分が思うに-rで与えられるプロファイルの順位がもっと高ければなあとは思うが、まあ何かしらの歴史があったのだろう。
Linuxレビュー
色々忙しくしていて書きたいことも山ほど…あると思ってたけどたいしてなかったのでとりあえず、OSの入れ替えを激しく行っていたのでその時に使っていたディストロの感想
1. OpenSUSE
とにかくよくハングアップする。原因はおそらくハードとKDEの相性が悪いのであろう
それに、CUIからパッケージマネージャを使うのがすごく難しいという印象。
ただ、それ以外は結構快適で、リポジトリんパッケージも比較的新しく動作もいい感じ。
ハードとの相性が良ければ採用しても良いレベル
2.debian 7.0 stable
GNOME3版を入れてしまったというミスさえなければすごく快適だった。
リポジトリも当時は最低限ではあれど十分使える新しさで、ffmpegなど、オーディオ系のパッケージを拾ってきてもデフォルトでmp3などの他のパッケージ(liblameだっけか)に依存したものもリンクされたものが手に入ったので手間がかからずに良かった。
3.Linux mint 17.0
パッケージが新しく、デスクトップ環境も綺麗で非常に快適
ただ、debian程パッケージに安定性はない模様
現在愛用中
ざっと書いたが相変わらずディスク容量は厳し目な模様
お久しぶりです
生活環境の変化から当初は週1で更新すると言っていたのに気づいたら100日ほど経過していました。。。が今日から復活します!