Parameter pack
0個以上の可変長の複数の変数をまとめて扱い展開していく時に使う。
Reference
Parameter pack(since C++11) - cppreference.com
Example1
template<typename T, typename... Targs> void tprintf(const char* format, T value, Targs... Fargs) { for (; *format != '0\n'; format++) { if (*format == '%') { tprintf(format+1, Fargs); return; } } } int main() { tprintf("% world% %\n","Hello",'!',123); }
出力
Hello world! 123
動作イメージ
コメント
型としては必ずテンプレートを使わないといけない。例えば以下のような使い方はできない。
void value_print(int value, int... values) { std::cout << value << std::endl; value_print(values); }
Example2
デバッグモードの時のみ値をprintする関数
/// @brief デバッグモードの時のみ値をprintする関数 template <typename... Targs> void print(Targs... args) { # if DEBUG printf(args...); // 引数に...をつけることに注意 # endif return; }
テンプレートと非テンプレートで関数の引数につけられた&&の挙動の違い
非テンプレートの場合、&&がついた引数は右辺値しか受け取ることができない。一方テンプレートの場合は&&がついた引数は右辺値のみではなく左辺値も受け取ることができる。
例
class Hoge { }; template <typename T> void templae_func(T && h){ (void)h; } void nontemplae_func(Hoge && h){ (void)h; } int main() { Hoge h; templae_func(h); nontemplae_func(h); // これはエラーが }
Reference
std::forward
上で書いたように、テンプレート関数の引数に&&がついていると、その引数は右辺値と左辺値の両方の値がくる可能性がある。ただ、それをそのまま別の関数に渡してしまうと仮に引数が右辺値型で来ていたとしても右辺値を受け取った引数は左辺値として扱われるため別の関数に左辺値として渡ってしまうという問題がある。例えば以下のようにすると右辺値引数の関数func2を呼べずエラーになる。
class Hoge { }; void func2(Hoge && h) { std::cout << "func2 double and" << std::endl; } template <typename T> void func(T && h){ func2(h); } int main() { func(Hoge()); }
そこでstd::forward
の出番である。これは左辺値で来たものを左辺値のまま、右辺値で来たものを右辺値のままにキャストするものであり、func内でこれを使うとこれがエラーではなくなる。
class Hoge { }; void func2(Hoge && h) { std::cout << "func2 double and" << std::endl; } template <typename T> void func(T && h){ func2(std::forward<T>(h)); } int main() { func(Hoge()); }
マクロの可変長引数展開
やりたいこと
MACRO(3, 4, 5);
とやったらある処理を3、4、5の3ケースについてループしてくれるようなことや
MACRO(add, subt, multiple);
とやったらある処理をadd
、subt
、multiple
の3関数についてループしてくれるようなこと。
マクロループ展開のやり方
マクロループは(a)(b)(c)
のようなマクロ形式の集合の形で渡す必要がある。例えば
#include <boost/preprocessor/cat.hpp> #include <boost/preprocessor/seq/for_each_i.hpp> #include <boost/preprocessor/variadic/to_seq.hpp> // dataの取り扱いは不明 #define MACRO(r, data, i, elem) std::cout << "r="<<r<<",i="<<i <<",elem="<<elem<< std::endl; // 第二引数は使わなくてよさようだ #define MACRO2() BOOST_PP_SEQ_FOR_EACH_I(MACRO, _, (3)(4)(5)); int main() { MACRO2(); return 0; }
とやると
r=1,i=0,elem=3 r=2,i=1,elem=4 r=3,i=2,elem=5
が出力される。
マクロ形式の集合の形の作り方
BOOST_PP_VARIADIC_TO_SEQ
を使うと (a, b, c)
と渡した引数を(a)(b)(c)
と変換できる。またマクロの引数を...
としそのマクロ内で__VA_ARGS__
を使うと可変長引数を受けられる。例えば
#include <boost/preprocessor/cat.hpp> #include <boost/preprocessor/seq/for_each_i.hpp> #include <boost/preprocessor/variadic/to_seq.hpp> #define MACRO(r, data, i, elem) std::cout << "r="<<r<<",i="<<i <<",elem="<<elem<< std::endl; #define MACRO2(...) BOOST_PP_SEQ_FOR_EACH_I(MACRO, _, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)); int main() { MACRO2("3", "4", "5"); return 0; }
とやると
r=1,i=0,elem=3 r=2,i=1,elem=4 r=3,i=2,elem=5
と出力される。
マクロに関数を渡す。
マクロには数値や文字だけでなく関数も渡すことができる。
#include <boost/preprocessor/cat.hpp> #include <boost/preprocessor/seq/for_each_i.hpp> #include <boost/preprocessor/variadic/to_seq.hpp> int add(int i) { return i+1; } int subt(int i) { return i-1; } int multiple(int i) { return i*2; } #define MACRO(r, data, i, elem) std::cout << "i="<<i<<",elem="<<elem(3) << std::endl; #define MACRO2(...) BOOST_PP_SEQ_FOR_EACH_I(MACRO, _, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)); int main() { MACRO2(add, subt, multiple) return 0; }
出力結果は
i=0,elem=4 i=1,elem=2 i=2,elem=6
headerの引数にconstをつけない
// hpp void f(int v); // cpp void f(const int v) { }
のように、値で渡す引数については、headerの方はconstをつけずcppの方にはconstをつける。
ポリモーフィズムの基底クラスのデストラクタにはvirtualをつける
ポリモーフィズムとは
int main() { Base *b = new Derived(); delete b; return 0; }
のように、Base型のポインタの変数にDerived型のポインタを割り当てることである。このようなポリモーフィズムの使い方をされる場合は、基底クラスのデストラクタにvirtual
をつけないといけない。
virtual
をつけた場合
class Base { public: Base() = default; virtual ~Base() = default; }; class Derived : public Base { public: Derived() { std::cout<<"Derived constructor called" << std::endl;} ~Derived() { std::cout<<"Derived destructor called" << std::endl;} };
出力は
Derived constructor called Derived destructor called
virtual
をつけなかった場合
class Base { public: Base() = default; ~Base() = default; }; class Derived : public Base { public: Derived() { std::cout<<"Derived constructor called" << std::endl;} ~Derived() { std::cout<<"Derived destructor called" << std::endl;} };
出力は
Derived constructor called
となりデストラクタが呼ばれなくなってしまう。
無理やりキャスト
普通は違うクラスへのポインタを変換できない
Class1* p1 = new Class1(); Class2 * p2 = p1;
以下のようなエラーが出る。
error: cannot initialize a variable of type 'Class2 *' with an lvalue of type 'Class1 *'
reinterpret_cast
を使うと無理やりキャストすることができる。
Class1* p1 = new Class1(); Class2* p2 = reinterpret_cast<Class2*>(p1);
矢印を使った関数の定義のしかた
void b(int v) { (void)v; }
と全く同じ関数定義を
auto b(int v) -> void { (void)v; }
のような書き方で記述することができる。これが使われるケースは、引数の型によって返り値の型を変更したい時。
例えば
#include <iostream> template <typename T> auto func(T v) -> decltype(v) { return v; } int main() { std::cout << func(3) << std::endl; std::cout << func(4.2) << std::endl; return 0; }
のようなことができる。 通常のように
decltype(v) func(T v) { ... }
と記載してしまうとv
の定義の前にdecltype(v)
が呼ばれてしまいエラーとなってしまう。
参考
c++ - Arrow operator (->) in function heading - Stack Overflow
条件に応じた型設定
条件に応じて変数の型を変えたい場合はstd::conditional
を使えば良い。
#include <iostream> int main() { std::conditional<true, int, double>::type a = 4.2; std::conditional<false, int, double>::type b = 4.2; std::cout << "a=" << a << std::endl; std::cout << "b=" << b << std::endl; return 0; }
とすると、a
はint
型、b
はdouble
型として定義される。
出力は
a=4 b=4.2
具体的な条件を設定してみると
#include <iostream> // 引数がint型ならint型の値を出力し、それ以外ならdouble型の値を出力する。 template <typename T> void func(const T v) { typename std::conditional<std::is_same<T, int>::value, int, double>::type c = 4.2; std::cout << c << std::endl; } int main() { func(3); func(2.4); return 0; }
ポイントとしては、std::conditional
の前にtypename
をつけないとエラーになる。
出力は
4 4.2
となる。
条件に応じてテンプレート関数を使い分ける。
decltype
を使ったやり方
#include <iostream> #include <vector> // 引数がbegin()を持つデータ構造の時だけ定義される関数 template <typename T> auto func(const T v) -> decltype(v.begin(), void()){ std::cout << "begin() defined" << std::endl; } // 引数が+1の演算が定義できるデータ構造の時だけ定義される関数 template <typename T> auto func(const T v) -> decltype(v + 1, void()){ std::cout << "+1 defined" << std::endl; } // こういう関数を定義してもエラーにならない template <typename T> auto func(const T v) -> decltype(v.bbbegin(), void()){ std::cout << "+1 defined" << std::endl; } int main() { func(3); std::vector<int > a{3, 4, 5}; func(a); //func({3, 4, 5}); // これはエラー return 0; }
ポイントはdecltype(v + 1, void())
の部分で、,
は演算子としてみなされ、,
の左側の返り値は無視されて,
の右側の返り値が代入される。またvoid
などの型に()
をつける必要があるようだ。すなわち、decltype(v + 1, void())
の部分は 「v+1
が定義されているのであればvoid
型」となる。
出力は
+1 defined begin() defined
enable_if
を使ったやり方
decltype
を使うと少し冗長な記述になるので、enable_if
を使った記述方法についてまとめた。
#include <iostream> #include <vector> #include <type_traits> // fundamental型の引数のときのみ使えるテンプレート関数 template <typename T> typename std::enable_if<std::is_fundamental<T>::value, void>::type func(const T v) { std::cout << "fundamental" << std::endl; } int main() { func(3); // func("hoge"); // 未定義エラー return 0; }
ここで、C++14以降であればenable_if_t
を使うことができる。これはcppreference.comを読むと
template< bool B, class T = void > using enable_if_t = typename enable_if<B,T>::type;
とあり、defaultでvoid
の型が選択されるようだ。
// C++14以降の別の書き方 template <typename T> typename std::enable_if_t<std::is_fundamental<T>::value> func(const T v) { std::cout << "fundamental" << std::endl; }
また、fundamental型以外の型が引数として与えられた時に呼ばれる関数を定義したい時はfunc(...)
とすれば良い。すなわち
#include <iostream> #include <vector> #include <type_traits> // fundamental型の引数のときのみ使えるテンプレート関数 template <typename T> typename std::enable_if<std::is_fundamental<T>::value, void>::type func(const T v) { std::cout << "fundamental" << std::endl; } void func(...) { std::cout << "others" << std::endl; } int main() { func(3); func("hoge"); return 0; }
とすると
fundamental others
と出力される。
インライン
関数の呼び出しにはコストがかかるので、何度も呼ばれる短い関数はインラインにすると定義をそのまま呼び出し元にコピーするので高速化できることがある。
インラインの仕方は、cppとhppのように定義と実装を分けず、hppに定義実装をしてinline
をつけること。ただしこのように定義してもコンパイラがインラインにすることは保証されない。
As-if ルール
プログラムの観測結果を変えない限りはどんなコード変換も許されるというルール。 copy/move elisionのときだけ例外がある。