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する関数
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について
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>
#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をつけない
void f(int v);
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>
template <typename T>
auto func(const T v) -> decltype(v.begin(), void()){
std::cout << "begin() defined" << std::endl;
}
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);
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>
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);
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
の型が選択されるようだ。
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>
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のときだけ例外がある。
The as-if rule - cppreference.com