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()のどちらを参照するべきか、曖昧であるとエラーになります。