プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

C++の相互参照ってどう作るのが良いの?

スマポと相互参照

 GCを持っていない以上、根本的にC++のスマポと循環参照は相性が悪いけど、私が問題にしたいのはソコではないです。

 例えば、前回公開したSVDライブラリだと以下の様に、木構造の情報を構成します。

  1. デバイスはペリフェラルを複数保持する
  2. ペリフェラルはレジスターを複数保持する
  3. レジスタ―はフィールドを複数保持する

 ただ保持しているだけなら、vector<レジスタ>か、vector<unique_ptr<レジスタ>>でいいでしょう。 ですが、SVDのデータ構造は、フィールドの情報を確定するにはレジスターの情報が必要である、という様に、子→親への参照も必須なのです。

 (むろん、木構造の構築時に確定してしまっても良いけど。実際、最初に作ったときは、どうせ編集するつもりがないので確定して作った。この場合、子が親を必要としないので、単にvectorで困ることはないないばーですね。)

   で、今更生ポインタ(ナマポ?)で管理ってのもあれなので、スマポ使いましょう。相互参照するので、shared_ptrとweak_ptrを使えばいいのでしょうか?

 しかし、これだと問題が発生します。それは親ポインタ(オヤポ?)をいつ確定するのってことです。

struct device
{
  vector< share_ptr< peripheral> >  peripheral;

  device():list{}
  {
    peripheral . emplace_back(  new peripheral(this)  );
  }
}

struct peripheral
{
   week_ptr< device > parent;  // オヤポ
   vector< share_ptr <register>  >  registers;

  peripheral(  week_ptr< device>  オヤポ? ) 
    parent( オヤポ? ),
    registers{}
  {
    registers . emplace_back(  new field(this) );
  }
};

struct register
{
   week_ptr< peripheral > parent;  // オヤポ
   vector< share_ptr <field>  >  fields;
};

 

 オヤポ?とか書いてるところがネックです。

 ナマポだったら、コンストラクタにthisを渡せば済むことが、スマポだと、コンストラクタで完結できないなさそうです。次の様な関数を定義するなりして、後で呼び出すしかない。

struct device
{
  void set_parent (  std::share_ptr< device > ptr)
  {
    for(auto p: peripherals)  p-> set_parent( ptr, p);
  }
};

struct peripheral
{
  void set_parent (  std::share_ptr< device > ptr, std::shared_ptr<peripheral> me)
  {
    parent = ptr;
    for(auto p: registers)  p-> set_parent( me, p);
  }
};

 

 

 もしくは、諦めて、親への参照はナマポにするか。

 今回の場合なんかは、そもそも、親への参照が壊れたら正常に動かないので、ソコを気にしない(ユーザー責任)というのは一つの答えとして悪くない気がします。

struct device
{
  vector< unique_ptr< peripheral> >  peripheral;

  device():list{}
  {
    peripheral . emplace_back(  new periphera( *this ) );
  }
}

struct peripheral
{
private:
   device& parent;  // オヤポ

public:
   vector< unique_ptr <register>  >  registers;

  peripheral(  device&  d ) 
    parent( d ),
    registers{}
  {
    registers . emplace_back(  new register(*this) );
  }
};

struct register
{
private:
   peripheral& parent;  // オヤポ
public:
   vector< unique_ptr <field>  >  fields;
};

 うーむ………。Java何かだと一切悩まない様なところで、変に悩むのがC++ですな。

 一般にはどうするのが良い手段なんだろうね。

C++のCMSIS5 SVD読み込みライブラリ作った

 ブーメランという言葉をご存じだろうか。そう、今は無き民主党のあれである。

C++ header only library for CMSIS SVD

github.com

 はい、というわけで、私が必要になりました。C++のSVD解析器。畜生。

 その成果を公開しておきます。

 

ざっくばらんな使い方

 使い方は大体こんな感じ。

#include "nodamushi/svd.hpp"
#include "nodamushi/svd/boost.hpp"
#include "nodamushi/svd/normalized.hpp"

void read(std::string& filename){
   namespace svd = nodamushi::svd;
   svd::boost_svd_reader reader(filename);
   svd::Device<> device(reader);
   auto ptr = svd::normalize(device); // derivedFromやdimの解決
}

 これだけでさくっと読み込めて、derivedFromの面倒な解決もしてくれるので、まぁまぁ便利なんちゃう?

 Visitor パターンも用意してあるので、各要素にアクセスするプログラムも割とサクッと書けます。

//visitor パターン
struct visitor{
  //お作法
  using this_t = visitor;
#undef VISITOR_MEMBER
#define VISITOR_MEMBER static
  NORM_VISITOR_INIT;
  //お作法 ここまで
  
  NORM_Visit_Register(r){
    std::cout << "Register:" <<r.get_path() << std::endl;
    return CONTINUE;
  }

  NORM_Visit_Interrupt(i){
     std::cout << "Interrupt:" << i.name << "," << i.value<<std::endl;
     return CONTINUE;
  }
};

visitor::accept(ptr) ;  //visitorパターンに投げる

 実際に私が必要になった機能とかも大凡作ってあるので、まぁまぁ実用に耐えられるんじゃないかな。これ、誰か使ってるの?っていう様な仕様も、それなりにきちんと解析してます。

 cluster周りはあんまりテストしてないけど………。

 エラー処理は甘いので、その内追々修正するかな。

 

C++ワカラン…

 ちなみに、これを最初から作ったのではなく、一回別に作った物を、腹立ち紛れに作り直しました。

 C++の魔窟メタプログラミングに私がどれだけ耐えられるのか、試してみたかったのもあります。 気が向いたらソースコードを見て貰えると良いかもしれませんが、なんでここまで複雑に作ったのって言うぐらい複雑です。 実務でこんなコード書くやついたら、人格を否定しても許されると思います。

 そして、私、よく分かった。 C++ゼッンゼンワカラン。 とても辛い。

 Rustが良くコンパイラとの格闘があるとか何とか言うじゃない? 私はあまりRustやりこんでないから分からんけど、C++のはコンパイラとの死闘だったよ。

コンパイラちゃん「ここ、ここがおかしいの!」

私「………ソコ、ナニモオカシクナイヨ」

 もうこんなことがしょっちゅう。

 あくまでコンパイラがエラーに気がついた場所で会って、実際のエラー原因は遙か遠いところにあったりするの。 何度コンパイラにブレークポイントぶち込みたいと思ったことか。

 ひたすら長大なエラーレポートを追っていって、どこで型が化けたのかを解析するんだけど、結局は大体の原因は以下の5つに分類されると思う。

  • いつの間にかconstがついてた・消えてた
  • いつの間にか参照がついてた
  • いつの間にかポインタが消えてた
  • いつの間にか暗黙に変換されてた
  • 勘違いによりそもそも型・変数が違う(タイポ含む)

 こういう、なんか思ってたんと違う型の化けにより、本来私が進むと思ってるルートを外れて、別な関数コールを走り始め、問題にぶち当たったところでエラーが出される訳です。

 もう、ホント辛いよ。二度とやりたくないね。

 私はC++上級者になれなくていいや!

helm-swoop--editが動かない

久しぶりにHelm Swoop Editを使おうとしたら

apply: No such action ‘helm-swoop--edit’ for this source

って言うて動かない。どうやら、Helm本体の仕様が変わったらしく、1年放置されているhelm-swoopが仕様変更に対応してないのが問題っぽい。

github.com

で、このイシューのashiklom氏の言う様に~/.emacs.d/elpa/helm-swoop-20180215.1154/helm-swoop.elのhelm-c-source-swoop関数のaction(547行目あたり)とhelm-multi-swoop--exec(1126行目あたり)に

("Edit" . helm-swoop--edit)

を追加して、byte-reconpile-fileして、file-loadをしたら、動きました。マジありがたい!

ashiklom氏のパッチ↓ github.com

組み込み屋さんのC++学習は普通にやっちゃ駄目って話

 周囲の組み込み屋さんを闇のC++プログラマに堕落させたいと目論む訳ですが、組み込み屋さんが普通のC++の教科書を片手にお勉強しちゃ駄目だよねって話。特に取り纏めもないメモ書きなんだけど。

 私はもう流石にC++の入門書は手元にないのですが、例えば、私もかつては書籍を持っていたロベールのC++教室などは、第一章をすっ飛ばすと第二章でクラスの話に入っていく訳です。

 普通それでいいんですけど、組み込みのリソース少ない世界において、オブジェクト指向は不要とまで言い切りはしませんが、必須じゃありません。

 それどころか、new,delete,virtual辺りは害悪と言っても良いでしょう。コンストラクタとかも、場合によっては危険ですね。staticだったり、グローバルな変数の初期化順序とか絡むので。なによりvolatileが絡むとクッソ面倒くさい。

 メモリ小さいから殆どスタックに変数を配置することもないですし、極論を言ってしまえば、組み込みのC++で有効なオブジェクト指向は、デストラクタ以外に特に見当たりません。ステップ数増えるからポインタによる間接参照は極力したくないしね。その辺はコードサイズとの兼ね合いもあるけど。

 

 C言語からの差分でC++を勉強するんだったら、大体、以下の内容を勉強すれば良いんじゃないかなぁ。

 

  1. 組み込みC++で安易に使ってはならない機能
    1. オブジェクト指向
    2. 予約語:new,delete,virtual
    3. STL : string,vector,mapなど、ていうかstd::arrayとstd::tuple以外の殆どのクラス。
    4. staticローカル変数、グローバル変数でコンストラクタを持つデータ使う
  2. extern "C"
  3. 関数
    1. 参照
    2. デフォルト引数
    3. inline関数
    4. constexpr
  4. static_assert
  5. 構造体
    1. staticな変数
    2. staticな関数
    3. 構造体の中で構造体を定義する
    4. デストラクタによるRAII
  6. namespace
    1. using namespace
    2. inline namespace
    3. typedef,using
  7. templateメタプログラミング
    1. 特殊化とか
    2. パック・アンパック
      1. sizeof...
      2. swallow
    3. using
      1. typename,templateの使い方
    4. 型(struct)を用いた静的なポリモーフィズム
    5. tuple,type_traits,utility,initializer_listヘッダ
  8. マクロ
    1. 可変長引数マクロ

 

 ま、何にせよ、普通に学習してstd::coutだの、string、vectorだの、クラスだのを勉強するのは、無駄とは言わないけど、後で良いと思います。

組み込み屋こそC++を使うべきなのです

 やーやーやー。なんとワタクシ、遂に脱8bitしました!ワーワー、ドンドンパチパチ!

 さて、組み込みでもRustがアツかったり色々しますね。ただまぁ、Rustってサポートや車載対応等が無いので、政治的ごにょごにょでちょっと使いにくいですよね。

 だから、今まで通りC言語………って決めてしまう前に、ちょっと待って、まだ選択肢有るじゃない。

 そう、C++ですよ!

 

新しい言語とか無理

 安心してください。C++は昭和の言語です(1983年)。昭和ですよ、ショーワ。世界大戦があった時代の言語です。今は令和ですよ。断言しますが、この記事書いてる現在、令和生まれの若い子って、昭和とかもう知らないですよ。古すぎるといって過言ではありません。

 

そうじゃない オブジェクト指向とか無理

 安心してください。C++はオブジェクト指向言語ではありません。C++は 魔留血原太武言語 組み込み屋向けに嬉しい機能がいっぱいある言語です。

 単に組み込みって言ってしまうと、かなり範囲が広いので一概には言えませんが、特にRTOSも使わない様な環境では、実はオブジェクト指向ってしっくりこないんです。staticを理解していない人のコードを見ると、いちいちインスタンス宣言しているので笑ってしまう。

 オブジェクト指向なんて、メモリとポインタ使ってなんぼです。リソースも計算時間も無い系な組み込みには向いてません。オブジェクト指向なんて、組み込み屋が使えなくても問題ありません。newとか組み込みでは死語です。vector、string?使わねぇって。

 組み込み屋が理解すべきは、C++の 魔境 templateとconstexprと型システムです。

 え、潤沢に富豪プログラムができる環境だって?JavaとかPythonとか使えば良いんじゃないかな。

 

気分はtemplate constexpr!

 C言語で、あるペリフェラルのレジスタのビットに書き込みたいとき、どう書くでしょうか?こう?

PIYO->CTRL = 0x10;
FOO->EN     = 1 << 15 | 1 << 20 | 1 << 1;

 

 あ、ちなみに、場合によりけりだけど、以下の様にビットフィールドを使うのは駄目です。

 FOO->EN_b.EN15=1;  FOO->EN_b.EN20=1; FOO->EN_b.EN1=1;

 通常、ペリフェラルのレジスタはvolatileだから、上記のコードはread-modify-writeが3回走ります。一括で初期化したい場合には向いてないですね。

 C++なら、こんな感じでかけます。

// ----------- どこかのヘッダに書いておく -------------------
// ビットを立てない場合は0
constexpr uint32_t bit(){return 0;}

// ビットの計算をする関数
template<typename... ARGS> //←ここは今は気にしない
constexpr uint32_t bit(size_t a,ARGS... args){ //ARGS...も気にしない
  return (1 << a) | bit(args...); //bit(args...)も気にしない
}
//-----------------------------------------------------------------

FOO->EN = bit(1,15,20);

 1,15,20を引数とするbit関数の呼び出しになっていますが、C言語と違って、最適化ビルドをしなくても、この関数の計算をコンパイル時にさせてしまう機能がC++にはあります。この関数の実行時の計算コストは0で、C言語で直値で書いたのと同じです。恐ろしいですね。

 

   

constexprな変数を使ってみよう

 constexprは定数式と言います。C言語では定数を#defineで名前を付けていました。C++では、これをconstexprで記述します。

#define            FOO_MODE1_CONFIG   1 << 1 | 1 << 21
constexpr uint32_t FOO_MODE1_CONFIG = 1 << 1 | 1 << 21;

 もはや理解が追いつかない!と言う程の大した差は無いですね。

 

 組み込み屋にとって、一番の大きな違いは、マクロ特有のバグが入らないことです。

 例えば、以下のコードは、#defineとconstexprでは結果が変わります。

FOO->CONFIG = FOO_MODE1_CONFIG + 0x1;

 define文はあくまで文字列置換なので、「1 << 1 | 1 << 21 + 0x1」となり、結果的に「1 << 1 | 1 << 22」と同じになりますね。このバグを避ける為に、defineを書くときはヒステリックな程に()で式を囲います。

 constexprでは特に()で囲ったりしていませんが、正しく「FOO_MODE1_CONFIGの値と、0x1の加算」となります。

 

constexprな変数?定数?

 ところで、constexprを外すと、普通の変数宣言と同じになります。

constexpr uint32_t FOO_MODE1_CONFIG = 1 << 1 | 1 << 21;
          uint32_t FOO_MODE1_CONFIG = 1 << 1 | 1 << 21;

 ということは、どこかに変数用の領域が用意されていて、それを読み出しているのだろうと思われるかも知れません。

 対象デバイスの命令セットに依存しますが、今回のFOO_MODE1_CONFIGは大きな値なので、命令の直値にはならないでしょう。多分、普通の定数と同じく、関数の後ろに値が配置されるかと思われます。FOO_MODE1_CONFIGが非常に大きな配列やら構造体なら、Flashのどこかに1箇所だけ置かれるかも知れません。

 しかし、逆に、FOO_MODE1_CONFIGが1とか100みたいな小さな数であれば、命令の直値に変換されることでしょう。

 constexprな変数は、書き換え不可能な変数であり、かつ、直値にもなり得えます。直値として埋め込まれ、誰も実体を必要としなければ、最終的なバイナリに残りません。

 マクロと違って安全で、それでいて、マクロと同じく直値にもなり得る。こんな素晴らしい機能があるのに組み込みで使わないなんて信じられません。さぁ、C++を使いましょう。

 

constexprな関数を使ってみよう

 constexprな変数FOO_MODE1_CONFIGには問題があります。

 MODEが10個くらいあったらどうするというのだろうか。全部書くのか? FOO_MODE A_X_1 CONFIGとか、プロパティ増えたらどーすんの!?

 これを解決してくれるのがconstexprな関数です。

 例えば、FOO_MODEx_CONFIGは「1 << x | 1 << 21」としよう。

constexpr uint32_t FOO_MODE_CONFIG(int x){  return 1 <<  x  |  1 << 21; }

//使う場合
FOO->CONFIG = FOO_MODE_CONFIG(1) + 0x1;

 

 関数なので、全部のモードについて定義をしなくても、勝手に値が求まります。

 そして、C++コンパイラはconstexprな関数を定数だけで呼び出している場合、勝手に計算して、定数にしてくれます。 実行時には関数呼び出しが行われませんので、計算コストはタダです。

 なお、実行時に値が決まる変数などで呼び出すと、実行時に関数を呼び出します。constexprな関数は、定数としても、関数としても使うことができます

 文法も単純で、ふつーの関数定義にconstexprがついているだけです。非常に簡単ですね。

 

 と、言いたいのですが、constexprな関数は場合によってはちょっと面倒くさくなります。

 例えば、 \sum_{n = 0}^x nを愚直に0+1+2+3....と計算するconstexprな関数はこう書きます。なお、愚直じゃない計算は  \frac{x(x+1)}{2}ですね。

constexpr uint32_t sum(uint32_t x)
{
  return 
    x == 0? 0 :   
    x == 1? 1 :
            2 * (sum(x/2) + sum((x-1)/2)) + (x +1)/2;
}

 ちょっと何書いてるのか意味が分からないですね。これ書いた私は、非常に頭が悪いですね。私にもさっぱり分かりません。

 こんな頭の悪いクソコードを読み解く必要はなくて、重要なのは、return文しかないという所です。実はconstexprな関数には、return文しか書けませんでした。

 でした、って述べた様に、この書き方はC++11っていうちょびっと古いバージョンで、凡骨向けの書き方です。 私の様なポンコツには想像も及ばない世界です。

 でも大丈夫。C++14でだいぶポンコツフレンドリーになりました。C++14ではfor文もif文も変数宣言も解禁され、以下の様に書けます。

constexpr uint32_t sum(uint32_t x)
{
  uint32_t s = 0;
  for(uint32_t i =0 ; i < x; i++)
    s += i;
  return s;
}

 読める、読めるぞぉ!

 C++11のconstexprは、どうしても再帰関数の形になって、最適化しないとスタック消費が激しいですが、C++14なら、constexpr用のsumと実行時用のsumを別個に実装なんてしなくても良いのです。ビバC++14!

 

 さて、constexprな関数がどれぐらい便利かは、お使いのコンパイラがどのC++のバージョンにまで対応しているかによります。

 constexprが使えるC++のバージョンはC++11、C++14があります。正確には「ISO/IEC 14882:2011」、「ISO/IEC 14882:2014」といいます。要するに2011年規格と、2014年規格ってことです。1998年版はC++98です。2098年?さぁ……?

 ARM系なら、基本的には最新のコンパイラはC++14に正式に対応しています。 KailのARM Compiler Version6、IARのコンパイラ、arm-none-eabi-gcc 8辺りは全部対応しています。

 

 本当は更にポンコツ迎合したC++17ってのもあるんだけど、今のところ組み込みで正式に対応してるのはないんじゃないかな。 GCCは先月公開された9.1で正式にC++17に対応しています。組み込みも、後2,3年ぐらいしたらC++17対応してくるかと思いますが。 C++20対応はあと6,7年ぐらいしたら出てくるんじゃないの。

 

static_assertでテストを簡略化しよう

 組み込みって、チープな環境でのテストで嫌になりますよね。テスト走らそうにも、メモリが足りないしね。

 しかし、どれだけ面倒だろうが、C言語では、関数の実装が本当に正しいかは、実機やシミュレータで走らせて確認するしかありませんでした。

 C++はこれをコンパイル時に確認するstatic_assertを提供してくれます。

#include <cstdint>  // uint32_t
#include <cassert> // static_assertを使う為にinclude

constexpr uint32_t sum(uint32_t x)
{
  uint32_t s = 0;
  for(uint32_t i =0 ; i < x; i++)
    s += i;
  return s;
}

static_assert(   sum(0) == 0 ,"sum(0) == 0");
static_assert(   sum(1) == 1 ,"sum(1) == 1"); 
static_assert(   sum(10) == 55 ,"sum(10) == 55");
static_assert(  sum(1000) == 1000*1001/2, "sum(1000) = 1000*1001/2");

 static_assert ( 成立するべき式 , "エラーメッセージ" ) と記述します。C++17ではエラーメッセージの省略ができます。うらやましい。

 これをコンパイルすると、例えばこんな感じになります。

test.cpp:13:25: error: static assertion failed: sum(1) == 1
 static_assert(   sum(1) == 1 ,"sum(1) == 1");
                  ~~~~~~~^~~~

 sum(1)が1にならない!と言っていますね。原因は i < x; の部分ですね。正確には、i <= xです。バグってのはこういうしょーもない所に入り込む物です。

 このように、実機に持ち込まなくても、コンパイル時にconstexprな関数の動作を確認することができます。言い換えれば、ある入力に対する結果が確定する処理は、コンパイル時にテスト可能です。

 組み込みはコンパイル時に動作が決まらないんだよ、糞野郎!という魂の叫びが聞こえますが、果たして本当にそうでしょうか?

 無論、組み込みである以上、ペリフェラルの状態に依存することも多いでしょう。しかし、動作の全てがコンパイル時に未定義なのでしょうか?

 例えば、UARTから値を受け取って、コマンドを実行する場面を考えましょう。確かに、通信相手とUARTペリフェラル次第で、動きは確定できません。UARTから値を受け取る処理は、実機検証するしかないでしょう。

 しかし、「コマンドの実行」まで動作未定義でしょうか?むろん、ペリフェラル依存のコマンドもあるかも知れませんが、コンパイル時に確定する動作もあるのではないでしょうか?

 全体としてはコンパイル時に決まらない動作でも、部分的にはコンパイル時に確定するはずです。そう言った内容は、constexprな関数で抜き出して、先にC++コンパイラの上でテストすることができます。

 

 (ペリフェラルの動作をmocで作っちゃえば、static_assertでテストできそうとか思いましたが、実機って思った様に動かないので、やめた方がいいでしょうね。)

 

template constexprな関数を使おう

 先に挙げたbit関数の例は、複数ビットを指定することができます。C++14版も載せておきましょう。

// C++ 11用
constexpr uint32_t bit(){return 0;}
template<typename... ARGS> constexpr uint32_t bit(size_t a,ARGS... args){
  return (1 << a) | bit(args...);
}

// C++ 14用
template<typename... ARGS>
constexpr uint32_t bit(ARGS... args){
  uint32_t v = 0;
  for(const auto a: {args...})  // 鉞投げても良いのよ
    v |= 1 << a;

  //  意味の分からない書き方をするなら、こう
  //  (void)std::initializer_list<int>{(void(v |= 1 << (int)args),0)...};

  return v;
}

FOO->EN = bit(1,15,20);

 

 さて、templateという謎の機能が出てきました。

 テンプレートやスケルトンと言ってなんとなーくイメージが湧いてくれれば良いのですが、これを簡単に語れというのは、ぶっちゃけ無理です。

 ざっくり言ってしまえば、関数の設計書で、template<typename... ARGS>のARGSを型を指定することで、実際の関数を作る機能です。

 色々探してみて、読みやすいかなーと思った記事を紹介しておきます。以下の記事で理解できなければ、「C++ 関数テンプレート」「C++ テンプレート アンパック」とかで検索してください。

 

template<数値> constexprな関数を使おう

 先ほどは、「型で」templateを指定していましたが、実は型意外にも整数でtemplateを作ることができます。

//Vに100を足す
template<int V>
constexpr int add100(){ return V + 100;}

static_assert( add100<1>() == 101 ,"");

 これを使って、bit関数を再定義すると以下の様になります。

template<unsigned int... ARGS>
constexpr uint32_t bit(){
  uint32_t v = 0;
  for(const auto a: {ARGS...})  v |= 1 << a;
  return v;
}

FOO->EN = bit<1,15,20>();

 これのメリットは変数を渡せなくなったことです。

 このbit関数って、組み込み的に考えたら、高確率で定数ですよね。 しかし、間違えてbit関数に変数を受け入れてしまうと、そこがbit関数呼び出しになってしまう。しかし、コンパイルエラーになる訳じゃないから、そのもったいないを見抜けない。

 この書き方だと、bit<変数>とは書けませんから、コンパイル時にミスに気がつきます。なお、constexprな変数=定数は入れることができます。

 

template constexprな変数を使おう

 先ほどの「bit<1,15,20>();」ですが………、面倒くさくないっすか?「()」書くの。だってさー、()っていらないじゃーん。ぶっちゃけ。

 そういう場合は、template constexprな変数を使いましょう。

template<unsigned int... ARGS>
constexpr uint32_t __bit(){   // __を追加
  uint32_t v = 0;
  for(const auto a: {ARGS...})  v |= 1 << a;
  return v;
}

template<unsigned int... ARGS>
constexpr uint32_t bit = __bit<ARGS...>(); //template constexprな変数bit

FOO->EN = bit<1,15,20>;

 無駄な「()」が必要なくなりましたね!

 

気分はstatic!

 templateとconstexprを理解した皆さんは、C++がいかにCより組み込み屋にとって便利か、理解できたかと思います。

 しかし、まだ、使いこなしているとは言いがたいです。

 最後のキーワード、それはstaticです。C言語にも、ファイルスコープを表すstaticがありましたが、ここでのstaticはクラスの中で使うstaticです。

 staticはC++のclassやstructを名前空間に変えてしまう、魔法の単語です。

 例えば、各ペリフェラルの電力供給管理をするペリフェラルのPOWレジスタがある、という場面を考えてみましょう。各ペリフェラルの電力は、POWレジスタの適当なビットに対応してるとします。例えば、以下の様な情報として記述しておくとしましょうか。

struct DMA
{
  static constexpr uint32_t POW = bit<3>;
};

template<unsigned int N> struct PWM
{
  static constexpr uint32_t POW = bit<6 + N>;
};
using PWM0 = PWM<0>;
using PWM1 = PWM<1>;


template<unsigned int N>  struct UART
{
  static constexpr uint32_t POW = bit<12 + N>;
};
using UART0 = UART<0>;
using UART1 = UART<1>;
using UART2 = UART<2>;
using UART3 = UART<3>;

 ここで重要なのは、DMA,PWM,UART全ての構造体が、「static constexpr POW」を持つという所です。これを利用すると、POWレジスタを設定する関数は以下の様に作れます。

template<typename... ARGS>
constexpr uint32_t  __get_POW(){
  uint32_t v = 0;
  (void)std::initializer_list<int>{(void( v |= ARGS::POW),0)...};
  return v;
}

template<typename... ARGS>
inline void powerOn(){
  FOO-> POW = FOO-> POW | __get_POW<ARGS...>();
}

//使い方
powerOn<PWM0,UART2,UART3>();

 「(void)std::initializer_list{(void( v |= ARGS::POW),0)...};」ここがミソですね。swallowと呼ばれるテクニックを使っていますが、それを可能にしているのが、全ての構造体が「static constexpr POW」を持つという条件です。

 確かにこの構造体を先に定義するのは面倒ですが、一度定義してしまえば、別プロジェクトでも使い回せます。あくまでconstexprな定数でしかないので、余計なバイナリを生成する心配はありません。

 ビットフィールド指定と違って、constexprな関数の__get_POWでビットを計算するので、read-modify-writeも1回しか発生しません。それでいて、ソースコードを後から読んだときでも、PWM0、UART2、UART3の3モジュールの電源を入れてるんだなと、理解しやすいですよね。可読性もバッチリです。

 

まとめ

 どうでしたでしょうか、C++が理解できたでしょうか? うん、知ってる、 わからんよね!!

 ぶっちゃけると、C++は深淵を覗けば、何処までも深みにはまります。他人のコードを見ると、魔法みたいなことをしてることが多々あります。

 私もC++なんて全然分かりません。最近になってinline namespaceとか知ったぐらいだしね。C++は謎の記号が多くて、検索しにくく、何の文法かも分からんことがあります。C++の全てを理解してる人間なんて、この世にいないんじゃないかとすら思います。理解してるのは、魔人か何かです。

 しかし、「Cで書けるんだから、C++なんて価値もないクソ」ではなく、「C++はよく分からないけど、組み込みに便利らしい」と、少しでも歩み寄って頂けたなら本望です。

 今回は紹介しませんでしたが、RAIIなど、Cでは使えない組み込みで有用なテクニックが他にも色々あるので、調べてみると良いでしょう。

基礎からしっかり学ぶC++の教科書 C++14対応

基礎からしっかり学ぶC++の教科書 C++14対応

ロベールのC++入門講座

ロベールのC++入門講座

  • 作者:ロベール
  • 発売日: 2007/11/15
  • メディア: 単行本(ソフトカバー)

Effective C++ 第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTI)

Effective C++ 第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTI)

今更ながら、文字列を型にしたい

 やーやーやー

 なんで私、最近こんな黒魔術に手を出してるんだろうね。最近、相当変態的な方向性でC++を悪用 利用しようと企ててるから、こんな羽目になってますよ。

 と言うのもですね、こう、色々と面倒くさい諸問題を回避しようとしてたら、黒い方法を思いついてしまったんだ。いや、私は悪くない。Goサインを出した人が悪いんだ。

 

 しかし、C++11、C++14で相当敷居が下がったって言っても、型だとか、constexprってやっぱり、こう、闇の軍団か中3女子的なイメージあるじゃない。タイトルみたいなことは、Sprout使ったらすぐできそうなんだけど、政治的ごにょごにょにより…

 ポンコツ…いや、豚骨な私が手を出して良い領域じゃないと思うんですよ。私はC++を完全に理解した日は来るのだろうか。

 闇の軍団的にはできて当たり前なのかも知れないけど、こう、ファイアをやっと唱えられましたレベルの私だと中々ね。

 

やりたいこと

 以下の型を宣言したい。

template<char... chars>struct char_type
{
  static void print(){
    (void)std::initializer_list<int>{(void(std::cout << chars),0)...};
    std::cout << std::endl;
  }
};

constexprな関数から作る場合

 C++14なら豚骨にも簡単にできた。

 関数から作ることができるので、constexprな配列の名前が定義されてたら、同じように作ることが可能。

template<int N> struct make_alphabet_type
{
private:
  static constexpr char get(int n){return (char)('a'+n);}
  template<typename Seq>struct make_char_type{};
  template<typename I,I... seq>struct make_char_type<std::integer_sequence<I,seq...>>{
    using type = char_type<get(seq)...>;
  };
public:
  using type =typename make_char_type<std::make_integer_sequence<size_t,N>>::type;
};

int main(int argc, char *argv[])
{
  using a5_t = make_alphabet_type<5>::type;
  a5_t::print(); // abcdeと出力される
  return 0;
}

 実体は何処にもないけど、constexprの計算で作ったchar配列の型を作ることができたよ!

 

文字列リテラルから作りたい

 GCC拡張のtemplateユーザー文字列リテラルが使えるなら簡単なんだけど、これどうすれば良いんだろ。一度どこかに名前を作れれたりすればいいんだけど。ラムダで何とかならないかと思ったけど無理でした。

 BOOST_PP_ENUMとか使えばできるっぽいけど。

 BOOST_PP_ENUMと同じことを手でやってしまえば、固定長だけど、出来ないことは無くて、しかも私の目的としてはこれで十分なんだけど。

//GCCの場合はこれ
template<char... chars>
constexpr char_type<chars...> operator ""_chartype(){return char_type<chars...>();}

template<char... chars>struct _char_type{
private:
  static constexpr char str[] = {chars...};
  static constexpr size_t array_size = sizeof...(chars);
  static constexpr size_t count(size_t i){
    return i == array_size? array_size:
      str[i] == '\0' ? i : count(i+1);
  }
  
  static constexpr size_t length = count(0);
  
  template<typename seq>struct make_char_type{};
  template<typename I,I... seq>struct make_char_type<std::integer_sequence<I,seq...>>{
    using type = char_type<str[seq]...>;
  };
public:
  using type = typename make_char_type<std::make_integer_sequence<int, length>>::type;
};
template<int N>
constexpr char get_char(const char (&array)[N],int index){
  return index < N? array[index] : '\0';
}

#define GET_CHAR_TYPE(x) _char_type<get_char(x,0),get_char(x,1),get_char(x,2),get_char(x,3),get_char(x,4),get_char(x,5),get_char(x,6)>::type

int main(int argc, char *argv[])
{
  using abc_t = GET_CHAR_TYPE("abc");
  abc_t::print();
  return 0;
}

constexprでfloatのバイナリ値を取得したい

 constexprで計算したfloatの値を、バイナリをそのままにuint32_tに変換して配列の一部に保存したいという場面に出くわしました。

 templateの構造体で定義すれば良いようにも思えますが、floatが入る位置や数は未定で、やりにくいのです。

 というわけで、float→uint32_tというバイナリ解釈の変更をしたいのですが、なんと、constexprでunion型って、アクティブでない要素を計算に使えないんですね。

union b32_t{
  uint32_t i;
  float f;
  constexpr b32_t(float v):f{v}{}
  constexpr b32_t(uint32_t v):i{v}{}
};

constexpr uint32_t fbToUi32(float f)
{
    b32_t b(f);
    return b.i; // iがアクティブでないのでエラー
}

 んー。まじか。

 リトル/ビッグエンディアンぐらいは考慮しないといけないかなーとか思ってたけど、それ以前の問題だった。たぶん、C++コンパイラのconstexpr処理系では、unionは普通に二つの要素を持つオブジェクトとして処理されてるんだろうね。

 C++20からは制限が解除されるとからしいけど、C++11とかC++14なので関係ない。

 GCCだと、cmathの関数がconstexprになってるから、logbとかsignbitとかを使えば再構築できるけど、残念ながら私が使っている処理系では無理。 中3女子ことBolero MURAKAMIさんのSproutライブラリを使えれば良いのですが、政治的諸事情により使いにくい。困った。

 

 あ、なお、この辺もC++20でconstexprがつく様になるらしいっすよ。今の私にゃ関係ないけどね。

 

 

float→uint32_tを自前で実装

 諦めて自分でfloatの値に対応するバイナリを手動で書いてしまえばいいのだが、残念ながら私はクソニートだ。ここで意志を曲げて部屋から出て行く様では、老後が心配になってしまう。親に立ち向かう勇気を持って、この問題に立ち向かう義務がある。

 さて、こうなってしまっては自分でfloatの値を解析して、uint32_tを再構築するしかないだろう。

 float(IEEE 754準拠)は符号、仮数部、指数部の3つからなる。仮数部は2のn乗の部分で、実際にはn+127の値が入ります。指数部は1から2未満の値です。

 まぁ、そんなに難しかぁないさ。単純にそれぞれについて解析していけば良いでしょう。

 ただし、幾つかfloatには特殊な値が存在があります。+0、-0、∞、-∞、NaN、非正規化数である。これらは別個に処理…

constexpr bool is_pos0(float f){ return f ==  .0f; }
constexpr bool is_neg0(float f){ return f == -.0f; }

int main(int argc, char *argv[])
{
  static_assert(is_pos0(.0f));
  static_assert(!is_pos0(-.0f)); // エラー
  static_assert(!is_neg0(.0f));  // エラー
  static_assert(is_neg0(-.0f));
  static_assert(!is_pos0(1.0f));
  static_assert(!is_neg0(-1.0f));
  return 0;
}

 

 あるぇえ?-0と+0の区別が付かない………。あれ、バイナリ同じ物が生成されるの?いや、b32_tで出力してみたらちゃんと符号ビットが違うし………

 ………

 あははは!そうだよね、0は0だよね!誰だよ、0に正と負があるとか言った奴!ねぇよ、んなもん!

 よし、見なかったことにしよう。

 ただし、幾つかfloatには特殊な値が存在があります。0、∞、-∞、NaN、非正規化数である。これらは別個に処理する必要があります。

 一先ずはC++14で実装。

#include <limits>
#include <cstdint>

using float_limits = std::numeric_limits<float>;

constexpr float FLOAT_POS_INFINITY = float_limits::infinity();
constexpr float FLOAT_NEG_INFINITY = -float_limits::infinity();

constexpr bool is_zero(float f){ return f == .0f; }
constexpr bool is_posinf(float f){ return f == FLOAT_POS_INFINITY;}
constexpr bool is_neginf(float f){ return f == FLOAT_NEG_INFINITY;}
constexpr bool is_nan(float f){ return f != f;}
constexpr bool is_denormal(float f){
  //こんなんでいいのかな?
  return f != 0.f 
      && f < float_limits:: min_exponent 
      && f > -float_limits:: min_exponent;
}

constexpr uint32_t fbToUi32(float f){
  if(is_zero(f)){
    return 0;
  }else if(is_posinf(f)){
    return 2139095040;
  }else if(is_neginf(f)){
    return 4286578688;
  }else if(is_nan(f)){
    return 2143289344;
  }else if(is_denormal(f)){
    //デバイスの制限上0にしています。エラーでも良いかも
    return 0;
  }

  const uint32_t sign = f < 0? 0x80000000:0;
  if(f < 0){ f = -f; }

  uint32_t exp = 127;
  if(f < 1){
    while(f < 1){
      f *=2;
      exp--;
    }
  }else{
    while(f >=2){
      f *=0.5;
      exp++;
    }
  }

  uint32_t frac =0;
  f -=1;
  uint32_t _frac = 1 << 22;
  float sub = .5;
  while(f != 0.0 && _frac != 0){
    if(f >= sub){
      frac |= _frac;
      f -= sub;
    }
    sub *= .5;
    _frac = _frac >> 1;
  }
  
  return sign | (exp << 23) | frac;
}

 どうせコンパイル時にしか使わないんだし、仮数部の探索とかを二分探索とかにする必要はないでしょう。

 一応何個かテストしてみたら、それっぽく正しい値が出てるっぽいよ。とりあえずはこれでいっかな。後は必要ならC++11にすっか。