2012年1月26日木曜日

戻り値型の後方宣言

関数の戻り値型を後方で宣言する

関数の宣言は、次のように2通りの記述が可能です。

// 1) 戻り値の宣言を、前方に記述する。
int add(int a, int b) {
  return a + b;
}
// 2) 戻り値の宣言を、後方に記述する。
auto add(int a, int b) -> int {
  return a + b;
}

なぜ2通りの記述方法があるのか不思議に思うかもしれません。1)の記述方法は、C言語から踏襲している記述方法です。2)の記述方法は1)では表現できない関数宣言を解消するためにC++で追加されました。

例えば、型が不明な2つの引数を取り、その加算結果を返す関数の場合、その戻り値の方が引数の型に依存してしまいます。なぜ戻り値の型が引数の型に依存するかというと、C++は演算子のオーバーライドができ、オーバーライドの定義によって戻り値の型を自由に指定できるからです。

そのような状況では、2つの引数の型と戻り値の型をテンプレート引数の指定するよりも、コンパイラに戻り値の型を解決させるほうが、その関数を簡単に使用することができます。

// テンプレート引数で戻り値の型を指定する場合
// P1とP2を加算した結果、戻り値の型が何になるかを関数を使用する人が考慮しなければならない
template <typename R, typename P1, typename P2>
R add(P1 a, P2 b) {
  return a + b;
}
// 戻り値の型をコンパイラに解決させるため、decltypeを使用する
template <typename P1, typename P2>
auto add(P1 a, P2 b) -> decltype(a + b) {
  return a + b;
}
// decltype(a + b)の引数a, bが解決できないため、以下のようには記述できない
template <typename P1, typename P2>
decltype(a + b) add(P1 a, P2 b) {
  return a + b;
}

戻り値型の後方宣言は、ラムダ関数でも使用する。詳しくはlambda式を参照して下さい。

2012年1月14日土曜日

lambda式

lambda(ラムダ)式とは

式とステートメントを含めた匿名関数オブジェクトを生成するための式のことです。

あるロジックを特定の関数などに渡したい場合は、関数オブジェクトを生成して渡したり関数ポインタを使用したりすることができます。しかし、関数オブジェクトや関数ポインタを使用するためにはクラスや関数をあらかじめ定義する必要があり、簡単にロジックだけを渡すことができません。そこでlambda式を使用することで、記述がシンプルになります。具体的な使い方について説明します。

lambda式を定義する

lambda式は次のように定義します。

// [capture] mutable (引数) -> 戻り値型 { 式 };
auto add = [](int a, int b) -> int { return a + b; };
std::cout << add(1, 2) << std::endl;

captureについては後述します。

引数は、通常の関数と同じように引数を指定できます。ただし、デフォルト値の指定、可変長引数、名前のない引数は指定できません。

戻り型も通常の関数と同じように指定できますが、引数の後ろに置く記述方法になります。戻り値がない場合や式の内容がreturn文1つだけの場合は省略することができます。

式には、通常のC++の式を記述することができます。

lambda式を引数に取る

lambda式を関数の引数に取るには、次のようにテンプレートを使用します。

template <typename PRINT>
void func(PRINT print) {
  print();
}

int main() {
  auto add = [](int a, int b) -> int { return a + b; };
  std::cout << add(1, 2) << std::endl;
  func([add](){ std::cout << add(1, 2) << std::endl;});
}

上記の例で示したとおり、captureに指定することでlambda式をlambda式内で使用することも可能です。

capture

lambda式を引数に取る例でも触れたように、特定のオブジェクト(変数など)をcapture(キャプチャ)として指定することでlambda式内で使用する事が可能になります。

captureで指定した変数は、lambda式内で変更することができません。そのような式を書くとコンパイルエラーになります。ただし、captureで指定するときに"&"をつけて指定すると、オブジェクトの参照としてlambda式内で使用することができ、値を変更することができます。"&"をつけた変数を参照キャプチャと呼び、"&"がついていない変数を値キャプチャと呼びます。

  int a = 0, b = 0;
  auto p = [a, &b](){
    std::cout << "a=" << a << std::endl;
    std::cout << "b=" << b++ << std::endl;
  };
  p(); // a=0, b=0
  std::cout << "a=" << a << std::endl; // a=0
  std::cout << "b=" << b << std::endl; // b=1
  a = 10;
  b = 20;
  p(); // a=0, b=20

上記の例のとおり、値キャプチャはlambda式の外で値を変更しても、それがlambda式に影響することはありません。参照キャプチャはlambda式の外の処理がlambda式に影響しており、更にその逆のlambda式の変更がlambda式の外にも影響しています。

captureに"&"のみを指定した場合は、すべての外部変数を参照キャプチャとして参照することができます。また、"="のみを指定した場合は、すべての外部変数を値キャプチャとして参照することができます。

  [&](){}; // すべての外部変数を参照キャプチャとして参照する。
  [=](){}; // すべての外部変数を値キャプチャとして参照する。
  [&, a](){}; // 変数aは値キャプチャ、それ以外の外部変数を参照キャプチャとして参照する。
  [=, &a](){}; // 変数aは参照キャプチャ、それ以外の外部変数を値キャプチャとして参照する。

TODO

lambdaの構文に、mutable,throwを指定できるか確認する。

2012年1月9日月曜日

range based for

使いやすいfor文

リストのすべての要素を捜査するときには、for文を次のように記述します。

// 配列の場合
int array[] = {1, 2, 3, 4};
for (int val : array) {
  std::cout << val << std::endl;
}
// 配列以外の場合
std:vector<int> vec = {1, 2, 3, 4};
for (int val : vec) {
  std::cout << val << std::endl;
}

range based forは、配列と配列以外の場合それぞれ次のように展開され処理されます。

// 配列の場合
int array[] = {1, 2, 3, 4};
for (int i = 0; i < sizeof(array) / sizeof(decltype(*array)); ++i) {
  auto val = *(array + i);
  std::cout << val << std::endl;
}
// 配列以外の場合
std:vector<int> vec = {1, 2, 3, 4};
for (auto i = begin(vec); i != end(vec); ++i) {
  auto val = *i;
  std::cout << val << std::endl;
}

begin/end関数

配列以外のオブジェクトをrange based forに指定した場合、前述したようにbegin/end関数を使用して、最初の要素のイテレータと最後の要素のイテレータを取得しています。このbegin/end関数が何者かというと、<iterator>に次の関数が定義されています。

template<typename C> auto begin(C& c) -> decltype(c.begin());
template<typename C> auto begin(const C& c) -> decltype(c.begin());
template<typename C> auto end(C& c) -> decltype(c.end());
template<typename C> auto end(const C& c) -> decltype(c.end());
template<typename T, size_t N> T* begin(T (&array)[N]);
template<typename T, size_t N> T* end(T (&array)[N]);

上の4つが配列以外のオブジェクト用、下の2つが配列用のbegin/end関数になっています。配列以外のオブジェクト用のbegin/end関数は、宣言を見て分かる通りそのオブジェクトにbegin/endメンバ関数が存在していることが前提となっています。<iterator>をインクルードして、全要素の捜査をしたいクラスにbegin/end関数を定義することで、そのクラスをrange based forで使用することが可能になります。

もし、range based forに使用したいクラスにbegin/end関数がなく、追加することもできない場合は、グローバル関数としてbegin/end関数を定義することで、どのようなクラスであってもrange based forの恩恵をうけることが可能です。

ただし、begin/end関数は、ADLによって呼び出されるため、定義が特定できずにコンパイルエラーになったり、想定外の関数が呼び出されて思わぬ挙動になる場合があるので注意が必要です。

2012年1月2日月曜日

ADL

ADLとは

Argument Dependent name Lookupの略で、引数によって関数を探索する仕組みのことです。ADLは、あるオブジェクトを非メンバー関数で使用する場合に、メンバー関数と同じように簡単に使用できるようにするために存在します。

次に例を示します。名前空間Aに存在するクラスBには加算演算子の非メンバー関数が定義されています。

namespace A {
  class B {
    // class Bの定義
  };
  B operator+(const B&, const B&);
  void func(const B&);
}

この加算演算子を別の名前空間で使用する場合、ADLが役に立ちます。

namespace C {
  void func() {
    B b1, b2;
    // ADLが使える場合。
    B b3 = b1 + b2;
    // ADLが使えない場合。
    B b4 = A::operator+(b1, b2);
    func(b4); // A::func(const B&)が呼び出されます。
  }
}

ADLの厄介なところ

とても便利なADLですが、非常に厄介な面もあります。意図しない関数が呼び出されるケースがあるからです。

namespace A {
  template<typename T>
  class B {
  };
}
namespace C {
  class D {
  };
  template<typename T> void print(T& t) {
    std::cout << "C::print()" << std::endl;
  }
}
int main() {
  A::B<C::D> b;
  print(b);
}

上記のサンプルを実行すると、"C::print()"が出力されます。テンプレートと名前空間が組み合わさると、ADLは非常にわかりづらいバグを生み出します。

また、名前空間Aにprint関数が存在すると、コンパイル時にA::print()かC::print()のどちらを参照するべきか、曖昧であるとエラーになります。

2011年12月26日月曜日

型推論

型推論とは

簡単に言えば、関数の戻り値型などが自明である場合、それを受ける変数の型などをコンパイラに判断させる作業のことです。

型推論をコンパイラが行うことで、ソースコードが簡潔になり可読性が良くなります。

C++でコンパイラに型推論を行わせる方法は、autoキーワードを使用する方法と、decltypeキーワードを使用する方法の二種類あります。

autoによる型推論

関数の戻り値を受け取る変数の型を指定するとき、関数の戻り値の型はコンパイラにとっては自明であるため、autoキーワードを変数の型として指定することができます。

void func() {
  std:vector<int> values {1, 2, 3, 4};
  auto sum = 0;
  for (auto i = values.begin(); i != values.end(); ++i) {
    sum += *i;
  }
  std::cout << "sum = " << sum << std::endl;
}

autoを使用しない場合は、次のようにかなり冗長になります。

void func() {
  std:vector<int> values {1, 2, 3, 4};
  int sum = 0;
  for (std::vector<int>::const_iterator i = values.begin(); i != values.end(); ++i) {
    sum += *i;
  }
  std::cout << "Sum = " << sum << std::endl;
}

decltypeによる型推論

変数を定義する時点でその変数の型を明確に定義できない場合、decltypeキーワードを使用してその問題を解決することができます。

int add(int a, int b) {
  return a + b;
}
long add(long a, long b) {
  return a + b;
}
template<typename T>
void func(T v1, T v2) {
  decltype(add(v1, v2)) result;
  result = add(v1, v2);
  std::cout << "Result = " << result << std::endl;
}

ただし、上記の場合はautoキーワードを使ったほうが簡潔になります。

int add(int a, int b) {
  return a + b;
}
long add(long a, long b) {
  return a + b;
}
template<typename T>
void func(T v1, T v2) {
  auto result = add(v1, v2);
  std::cout << "Result = " << result << std::endl;
}

autoとdecltypeをどのように使い分けるか

変数の型を指定する場合は、autoキーワードを指定すればよいかと思います。decltypeを使用するケースとしては、関数の戻り値の型を指定する場合になります。

template<typename T1, typename T2>
auto add(T1 v1, T2 v2) -> decltype(v1 + v2) {
  return v1 + v2;
}

decltypeの読み方

"でくるたいぷ"と読むらしい。

2011年12月22日木曜日

初期化リスト

オブジェクトの初期化方法いろいろ

配列を初期化する場合、次のような初期化リストを使用します。

int[] array = {1, 2, 3, 4, 5};

クラスや構造体を初期化する場合もこの書式を使うことができます。

ClassA a = {1, 2, 3, 4, 5};
StructB b = {1, 2, 3, 4, 5};

このように初期化する場合は、対象のクラスや構造体がPODであるか、次のようにコンストラクタを定義する必要があります。

class ClassA {
public:
  ClassA(std::initializer_list<int> list);
};
struct StructB {
  int a, b, c, d, e;
};

このように、std::initialize_list<T>をコンストラクタの引数に指定することで、初期化リストを使用してオブジェクトを初期化することができます。

関数の引数に初期化リストを使用する

std::initializer_list<T>を関数の引数に指定することができます

int sum(std::initializer_list<int> list) {
  int result = 0;
  for (auto i = list.begin(); i != list.end(); ++i) {
    result += *i;
  }
  return result;
}
int main() {
  int result = sum({1, 2, 3, 4, 5});
}

どんなときに使うか

任意の数の引数を受け取る方法として、可変長引数テンプレートやCの可変長引数があります。それらと比べて引数リストを使用する利点として、「型が指定できる」「配列と同じ書式で初期化できる」と言った店が挙げられます。

とくに、「配列と同じ書式で初期化できる」というのは、可読性が良くなるので積極的に利用したいところです。が、見当違いな使い方をすると、逆に解りづらくなるので使い方には注意が必要です。

2011年12月19日月曜日

外部テンプレート

templateにexternを指定する

ある翻訳単位で完全に引数が特定されたテンプレートが見つかると、コンパイラはそのテンプレートを実体化します。このテンプレートの実体化によりコンパイル時間を増加させます。同じ引数を指定したテンプレートが複数の翻訳単位で実体化されるときは、非常に時間を取られます。

これを解消するために、特定の翻訳単位でテンプレートの実体化をしないように指定することができます。次のサンプルのようにtemplateにexternを指定することで、コンパイル時にtemplateを実体化しません。

extern template class std::vector<ClassA>;

問題点

ただし、当然コンパイル・リンクされたバイナリ内に一つ以上の実態が存在している必要があります。その実態をどこで定義するかをコーディング規約などで取り決める必要があります。

本当は・・・

このぐらいの最適化、コンパイラやリンカで自動的に行なってくれればいいのですが、作りの問題でできないのでしょうか。

TODO

テンプレートの実態をどこで定義するかをどのように取り決めればいいのだろうか?