All Posts

全ての記事をそのまま表示している。

とても重くなってしまっているかもしれない。

C++のconstexpr

概要

C++では関数や式に constexpr をつけることで、その値をコンパイル時に計算するようにコンパイラに指示できる。 コンパイル時に計算された constexpr 式の値はリテラルやテンプレート変数と同じように使用できる。例えば、

  • 他のテンプレートに渡す
  • 配列の大きさの宣言に使う
  • 他のconstexprを作る

が行える。

以下は確認例。

#include <array>
#include <iostream>

// この関数は(可能ならば)コンパイル時に計算が行われる
constexpr int add(int x, int y) {
  return x + y;
}

int main() {
  // constexprはテンプレートの引数に使える
  // add()の宣言からconstexprを削除するとコンパイルエラーになる
  std::array<int, add(1, 1)> arr1;
  // 配列も確保できる
  int arr2[add(1, 1)];

  // constexprを使って別のconstexprを定義できる
  constexpr auto x = 0;
  constexpr auto y = add(x, 1);
  std::array<int, y> arr4;

  std::cout << "OK" << std::endl;
  return 0;
}
OK

上の例では add() 関数が簡単なので constexpr によるありがたみが薄く、 テンプレート変数を使っても同じことができそうに見えるが、 constexpr を使ったほう簡潔で、通常の関数と同じ記法で書くことができる分うれしい。 関数が複雑になった場合(例えば fact() pow() log2() など) にはありがたみが増す。

使用例

いい使用例を思いついたので書く。

仕様

浮動小数点数のビット表現を表現するクラスを作りたい。 そのクラスは floatdouble のビット表現について学ぶためのクラスで、 以下の機能を持たせたい。

  • floatdouble に対応するクラスがある。
  • floatdouble の値を受け取るコンストラクタがある。
  • 指数部、仮数部の長さを返す関数がある。
  • 格納されている値をビット列として返す関数がある。
  • 格納されている値の指数部をビット列として返す関数がある。
  • 格納されている値の仮数部をビット列として返す関数がある。
  • 上の3つの関数の返り値として std::bitset<N> を使う。

この機能を持たせるには floatdouble に対応するクラスで 違う長さの std::bitset<N> をメンバ変数として持つ必要がある。 クラスを2つ作ることも可能だが、長さ以外の部分がほぼ同じになるので無駄になる。

実装

テンプレートと constexpr を使うといい感じにこの要求を満たすクラスを作れる。

Code Snippet 1: floating_point.hpp

#include <bitset>

// 浮動小数点数を格納するためのクラス
template <typename T,            // 格納される値の型 (floatかdouble)
          unsigned wExponent,    // 指数部の長さ
          unsigned wSignificand> // 仮数部の長さ
class FloatingPoint {
 public:
  // 浮動小数点数の各部分の長さを返す
  // constexprなのがミソ
  // コンパイル時に他の変数の宣言に使うのでクラスの先頭に書く
  constexpr static unsigned w_exp() { return wExponent; };
  constexpr static unsigned w_sig() { return wSignificand; };
  constexpr static unsigned w_all() { return 1 + wExponent + wSignificand; };

 private:
  T m_val;
  // 浮動小数点数の各部分のビット列を保持する
  // 各部分の長さを取得する関数がconstexprなのでここでテンプレート引数として使える
  std::bitset<w_all()> m_bits_all;
  std::bitset<w_exp()> m_bits_exp;
  std::bitset<w_sig()> m_bits_sig;

  // 指数部、仮数部の第iビットが全体の第何ビットかを返す
  static unsigned index_exp(unsigned i) { return w_sig() + i; }
  static unsigned index_sig(unsigned i) { return i; }

  // m_bits_allが初期化された状態から、m_bits_expとm_bits_sigを初期化する
  void init_bits() {
    for (unsigned i = 0; i < w_exp(); ++i) {
      m_bits_exp[i] = m_bits_all[index_exp(i)];
    }

    for (unsigned i = 0; i < w_sig(); ++i) {
      m_bits_sig[i] = m_bits_all[index_sig(i)];
    }
  }

 public:
  explicit FloatingPoint(T val);
  // ビット列を取り出す
  std::bitset<w_all()> bits_all() const { return m_bits_all; }
  std::bitset<w_exp()> bits_exp() const { return m_bits_exp; }
  std::bitset<w_sig()> bits_sig() const { return m_bits_sig; }
  bool bit_sign() const { return {m_bits_all[w_all() - 1]}; }
};

// IEEE-754に従うように型を定義
using Single = FloatingPoint<float, 8, 23>;
using Double = FloatingPoint<double, 11, 52>;

// コンストラクタだけは特殊に定義する
template <>
Single::FloatingPoint(float val)
    : m_val(val), m_bits_all(*reinterpret_cast<int32_t *>(&val)) {
  init_bits();
}

template <>
Double::FloatingPoint(double val)
    : m_val(val), m_bits_all(*reinterpret_cast<int64_t *>(&val)) {
  init_bits();
}

テンプレートを使ってクラスを定義し、それを floatdouble 用に特殊化することでコードの重複を防いだ。 やろうと思えば、 floatdouble でない 浮動小数点数の表現にも対応することができる。

以下のように使う。

#include "floating_point.hpp"
#include <cassert>
#include <iostream>

int main() {
  // 実数に対応するビット列は適当に求める
  Single f(1234.567);
  assert(f.bit_sign() == false);
  // to_string()をしてから比較をしてもいいが、Single::w_all()などを使うためにあえてそのまま比較する
  assert(f.bits_all() == std::bitset<Single::w_all()>("01000100100110100101001000100101"));
  assert(f.bits_exp() == std::bitset<Single::w_exp()>("10001001"));
  assert(f.bits_sig() == std::bitset<Single::w_sig()>("00110100101001000100101"));

  Double d(-12345678.90123456);
  assert(d.bit_sign() == true);
  assert(d.bits_all() ==
         std::bitset<Double::w_all()>("1100000101100111100011000010100111011100110101101110100111011100"));
  assert(d.bits_exp() ==
         std::bitset<Double::w_exp()>("10000010110"));
  assert(d.bits_sig() ==
         std::bitset<Double::w_sig()>("0111100011000010100111011100110101101110100111011100"));

  std::cout << "OK" << std::endl;
  return 0;
}
OK


Ubuntu 17.10 で MDR-1000X を使う

MDR-1000X を bluetooth で Ubuntu に接続しても音が聞こえなかったが、 pavucontrol を使ったらできるようになった。

sudo apt install pavucontrol

としてから

  1. pavucontrol で起動
  2. 設定タブからプロファイルを High Fidelity Playback (A2DP Sink) に変更
  3. Ubuntuのサウンド設定からサウンドの出力デバイスをMDR-1000Xに変える

とするとできる。 上手く行かないときは一度サウンド設定を他のデバイスにしてからMDR-1000Xに戻したり、音量を変えたりすると できるようになることがある。

自動化するのは面倒くさいのでパス。



Ubuntu 17.10 で Kindle for PC を使う

Wine を使うと Kindle for PC をUbuntuで使えると聞いたのでやってみたがエラーがたくさん出て手間取った。

結論からいうと、Kindle for PCの最新版はインストールできない。そこで”Kindle for PC older version”などのワードでググって出てくる(オフィシャルでない)サイトから古いものを落とす必要がある。自分の環境では version 1.14.1 (build 43029) のインストーラを使ったらうまくいった。

エラーの原因が分からなかったときにいろいろ試したので、次の要素も関係があるのかもしれない。自分はもう動くようになったので詳しく調べていない。

  • Wine は最新版 (3.0) ではなくデフォルトで入っているppaのもの (2.0.2) を使った。
  • Wine PREFIX を32ビットのもに変えた (参考)
  • winetricks allfonts を実行した

Kindle Cloud Reader がまともに使えるならこんなこともしなくて済むんだけどなぁ。



Python風Rangeの実装 in C++

範囲forについて学んだので実装した。

普通の for

STLコンテナはイテレーターを持つので、要素を順番に取る処理がfor文で書ける。

#include <iostream>
#include <set>
#include <string>
#include <vector>

int main() {
  std::vector<int> vec{1, 2, 3};
  // begin()とend()でイテレーターを取得し、for文の条件として使う
  // itrを定義するときの型はautoと書くのが普通
  for (std::vector<int>::iterator itr = vec.begin(); itr != vec.end(); ++itr) {
    // イテレーターはコンテナの成分のポインタっぽく使える
    std::cout << *itr << std::endl;
  }
  std::cout << std::endl;

  // vectorだけでなく他のコンテナでもほとんど同じように書ける
  std::set<std::string> set{"a", "b", "c"};
  for (std::set<std::string>::iterator itr = set.begin(); itr != set.end(); ++itr) {
    std::cout << *itr << std::endl;
  }
}

範囲 for

C++11では同じ意味のfor文を次のように書ける。

#include <iostream>
#include <set>
#include <string>
#include <vector>

int main() {
  std::vector<int> vec{1, 2, 3};
  // 範囲 for
  for (int i : vec) {
    std::cout << i << std::endl;
  }
  std::cout << std::endl;

  std::set<std::string> set{"a", "b", "c"};
  // strを参照にしてstd::stringのコピーが起こらないようにする
  // さらにconstをつけてループ内でsetの内容が変わらないことを保証する
  for (const std::string &str : set) {
    std::cout << str << std::endl;
  }
}
1
2
3

a
b
c

pythonのrange

pythonには range() 関数があり、以下のように使える。

for i in range(3, 10, 2):
  print(i)
3
5
7
9

これをC++で実装する。

範囲 for の展開

リファレンスによると、

for ( range_declaration : range_expression )
  loop_statement

は以下のように展開される。

{
  auto && __range = range_expression;

  for (auto __begin = begin(__range), __end = end(__range); __begin != __end; ++__begin) {
    range_declaration = *__begin;
    loop_statement
  }
}

範囲 for に対応したクラス

展開式を見ると、クラスを範囲 for に対応させるには適当な型のついた

  • begin()
  • end()
  • operator!=
  • operator++
  • operator*

があればいいことが分かる。

C++で実装

とりあえず最低限のものを実装する。

#include <iostream>
#include <vector>
#include <cassert>
#include <cmath>

template <typename T>
class Range {
 private:
  T m_value;        // 現在値
  const T m_end;    // 終了値
  const T m_stride; // ループ一回ごとに進む値

 public:
  // コンストラクタ
  Range(T begin, T end, T stride)
      : m_value(begin), m_end(end), m_stride(stride) {}
  // ゲッタ
  const T &value() const { return m_value; }
  // begin()の戻り値がforループの中で編集される
  Range<T> begin() const { return *this; }
  // end()の返り値との比較によってループから抜けるかどうかが判定される
  // begin()とend()は同じ型を持つ必要がある
  const Range<T> end() const { return Range(m_end, m_end, m_end); }
  // operator!=(rhs) はforループから抜けてよいかどうかを返す
  // Rangeオブジェクトが異なるかどうかではない
  bool operator!=(const Range<T> &rhs) const {
    return m_stride > 0 ? m_value < rhs.value() : m_value > rhs.value();
  }
  // m_valueを進める
  void operator++() { m_value += m_stride; }
  // 現在の値を返す
  const T &operator*() const { return m_value; }
};


// 実際に使う
int main() {
  int sum = 0;
  for (int i : Range<int>{0, 20, 3}) {
    sum += i; // i = 0,3,6,9,12,15,18
  }
  assert(sum == 63);


// Rangeの使い回し
  float sumd = 0;
  const Range<double> r{0.1, 5.3, 1.1};
  for (double d : r) {
    for (double d : r) {
      sumd += d;
    }
    sumd -= d * 5;
  }
  assert(fabs(sumd) < 1.0e-5);

  std::cout << "OK";
  return 0;
}
OK

とりあえずはこれで使える。

改善

この実装にはいくつか問題がある。

  • 同じRangeオブジェクトのend()は常に同じ値しか返さないので、const参照を返したほうがいい。
  • Rangeオブジェクトを作るたびに型を明示的に書かなければいけないのは面倒くさい。

これを直す。

#include <iostream>
#include <vector>
#include <cassert>
#include <cmath>

template <typename T>
class Range {
 private:
  // Rangeとは別にクラスを作って、begin(), end()はこのクラスのオブジェクトを返すことにする
  class Iterator {
   // 変数/メソッドは同じ
   private:
    T m_value;
    const T m_end;
    const T m_stride;

   public:
    Iterator(T begin, T end, T stride)
        : m_value(begin), m_end(end), m_stride(stride) {}
    const T &value() { return m_value; }
    void operator++() { m_value += m_stride; }
    T operator*() const { return m_value; }
    bool operator!=(const Iterator &end) const {
      return m_stride > 0 ? m_value < end.m_end : m_value > end.m_end;
    }

  };

 private:
  // Rangeが begin(), end() で返すのは
  // 常に同じIteratorオブジェクトなのでどちらもconstで問題ない
  const Iterator m_begin, m_end;

 public:
  Range(T begin, T end, T stride)
      : m_begin{begin, end, stride}, m_end{end, end, end} {}

  // begin() の戻り値は常に同じだが、for文の中で変更されるので値で帰す
  Iterator begin() const { return m_begin; }
  // end() の返り値は常に同じでfor文中で変更されることもないのでconst参照で返す
  const Iterator &end() const { return m_end; }

};

// 関数テンプレートを使って型の明示を不要にする
template<typename T>
Range<T> range(T begin, T end, T stride) {
  return {begin, end, stride};
}

// 実際に使う (一つ前のプログラムと同じ)
int main() {
  int sum = 0;
  for (int i : range(0, 20, 3)) {
    sum += i; // i = 0,3,6,9,12,15,18
  }
  assert(sum == 63);

  // Rangeの使い回し
  float sumd = 0;
  auto r = range(0.1, 5.3, 1.1);
  for (double d : r) {
    for (double d : r) {
      sumd += d;
    }
    sumd -= d * 5;
  }
  assert(fabs(sumd) < 1.0e-5);

  std::cout << "OK";
  return 0;
}
OK

だいぶpythonっぽくできる。 range() の引数を省略できるようにするのは簡単なので省略。

C++17

C++17 では範囲forの展開方法が変わり、begin()とend()の返り値の型が一致しなくても良くなっている。具体的には、C++11では

{
    auto && __range = range_expression ;
    for (auto __begin = begin_expr, __end = end_expr;
            __begin != __end; ++__begin) {
        range_declaration = *__begin;
        loop_statement
    }
}

だったものが

{
    auto && __range = range_expression ;
    auto __begin = begin_expr ;
    auto __end = end_expr ;
    for ( ; __begin != __end; ++__begin) {
        range_declaration = *__begin;
        loop_statement
    }
}

に変わっている(リファレンス)。

そのため、python風のrangeも簡潔に書ける。

#include <iostream>
#include <vector>
#include <cassert>
#include <cmath>

// python風range (C++17)
template <typename T>
class Range {
 private:
  T m_value;
  const T m_end;
  const T m_stride;

 public:
  Range(T begin, T end, T stride)
      :  m_value(begin), m_end(end), m_stride(stride) {}
  const T &value() const { return m_value; }
  Range<T> begin() const { return *this; }
  // end()の返り値の型が変わる
  T end() const { return m_end; }
  // operator!=の引数の型が変わる
  bool operator!=(const T &value) const {
    return m_stride > 0 ? m_value < value : m_value > value;
  }
  void operator++() { m_value += m_stride; }
  const T &operator*() const { return m_value; }
};

template<typename T>
Range<T> range(T begin, T end, T stride) {
  return {begin, end, stride};
}

// 実際に使う
int main() {
  int sum = 0;
  for (int i : range(0, 20, 3)) {
    sum += i;
  }
  assert(sum == 63);

  // Rangeの使い回し
  float sumd = 0;
  auto r = range(0.1, 5.3, 1.1);
  for (double d : r) {
    for (double d : r) {
      sumd += d;
    }
    sumd -= d * 5;
  }
  assert(fabs(sumd) < 1.0e-5);

  std::cout << "OK";
  return 0;
}
OK