プログラムdeタマゴ

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

JavaのOptionalは現状失敗作

 チマチマとは書いていたんですが、1年ぶりぐらいに真面目にJava書いています。

 前からずっと思っていて、実際何度か口にしたことがあるんですが、やっぱ言いたい。

 JavaのOptionalは失敗作

 

 

JavaのOptionalの何が悪いの?

 言語機能じゃないから。完全にここに集約されると私は思っています。

 Java8で登場したOptionalですが、この時にコンパイラを拡張しなかったのがそもそもの問題だと思っています。

 

AutoBoxing

 例えばKotlinのnullableのような機能の導入、というのは慎重になるべきでしょうし、すぐできるとは思えません。

 しかし、Optionalへのオートボクシングぐらいは実現できたのではないでしょうか?int→Integerというオートボクシングは既に存在しているわけで、最初から導入すべきだったと思います。

public OptionalInt  intMethod(){
     if(何か条件) return 1;  // -> OptionalInt.of(1)に
     if(何か条件) return OptionalInt.of(1);  // そのまま
     return null;// -> OptionalInt.empty()に
}

public Optional<Object> objMethod(){
    Object obj = null;
    if(何か条件)   return new Object(); //-> Optional.of(new Object())に
    if(何か条件)   return obj; // -> Optional.ofNullable(obj)に
    return null; // -> Optional.empty()に
}

 

 実に鬱陶しいofやらemptyやらofNullableが省略できる上、Optionalの癖にnullが返せるという謎の状況も生まれにくいでしょう。(※上の例だと、OptionalIntをそのまま返すとしてるけど、ここでnull挿入できる)

 むろん、色々な議論があったのでしょうが、導入が許されるのは、最初だけなのです。オートボクシングはもはや、実現不可能です。

 なぜなら、現在、nullが返せるという謎の状況が合法だからです。(どれほどおかしいとしても!)

public Optional<Object> objMethod(){
    return null;
}

 オートボクシングを認めてしまうと、後方互換を破壊してしまいます。

 今から可能なのは、Kotlinの様に言語機能を拡張するか、後方互換の破壊か、nilとかnullに変わる単語を導入する(ミスる自信があるぞ!)か、アノテーションで処理変えるか、Optionalに暗黙に変換されるOptional2見たいな型でも用意するかぐらいでしょうか。

 

Auto-Unboxing

 Optionalのオートボクシングを認めるなら、当然、アンボクシングもあるべきです。Optional<T>→Tへのアンボクシング………の前にOptional→booleanへのアンボクシングです。アンボクシングと言うよりは、暗黙の変換ですね。

 例えば、以下の様な状況を考えると、二つ目のif文の方が読みやすいと思います。isPresentって長いし面倒くさいよ。

if(intMethod().isPresent()){
  //戻り値は特に使わない処理
}else{
}

if( intMethod() ){
  //戻り値は特に使わない処理
}else{
}

 

 ifPresentOrElseがあるだろ、この馬鹿たれ!というお叱りの声が聞こえますが、本気でそれが読みやすいと思っています?

 ifPresentOrElse(()->{},()->{})より、単純にif文の方が、私には読みやすいです。

 そして、条件式がOptionalだったら、trueの場合は_にアンボクシングした結果が格納される、みたいな構文でアンボクシングされると良いかなーとか思います。

int value = intMethod() ? _  :  0 ;
if( intMethod() ) {
  int x = _ + value;
}

 なるべくキーワードを増やさない様に_ってかいたけど、$1とか$2でもいいかもね。

 ifPresentOrElseなんかよりずっと読みやすいし、ラムダ式の変数のキャプチャとかも発生しない。

 更に推し進めて、存在演算子が導入されても良いと思います。

int value =  intMethod()? 0;

 

まとめ

 JavaもプリミティブのラッパーやString、列挙体、Cloneable、AutoCloseableなど、幾つかのクラスでは言語的に特殊処理しているのだから、Optionalは何が何でも絶対駄目という理由はないと思います。むしろ言語として特殊扱いすべきだったと思います。

 C++は難しすぎる、Javaの方が簡単と言われますが、OptionalはC++の方が簡単ですね。オートボクシングやbooleanへの変換は、C++では暗黙の型変換で普通に使えます。

 最近のJavaは言語機能の拡張に積極的ですから、早いところ、言語機能に組み込んで欲しいな。

組み込みでラムダは使って良いけど、std::functionは駄目っぽい

 例えば、組み込みでウォッチドックタイマー(WDT)を使う場面を想定しましょう。

void main()
{
  クロック設定とか;
  WDT->Prescaler = プリスケーラの設定;
  WDT->Clear     = クリア;
  WDT->Control = イネーブル;

  for(;;){
     WDT->Clear = クリア;

     何か処理;
  }
}

 無茶苦茶長い処理をする場合は処理中にクリアを挟むことはあるだろうけど、基本形はこれでいいでしょう。

 さて、どうだろう?なんとも思わない?よし、君は素晴らしい人格者だ。幸せに生きて欲しい。

 さぁ、無駄な時間を過ごす前に、ブラウザバックするなり、タブを今すぐ閉じるんだ。

 モヤモヤした何かを感じるかい?奇遇だ、私もだ。さぁ、私と一緒に地獄に堕ちようよ。

 

こう書きたい

 さて、上記の様なコードを見ると、私としては、例えばこう書きたくなります。

void main()
{
  クロック設定とか;
  WDT.period( 1ms, 100_MHz ) // WDTの基準クロックと、オーバーフロー周期
     .loop( [](){
    何か処理;
   });
}

 

 クリアだなのなんだのは隠蔽して、勝手にやって欲しい。一回のループでするべき処理だけ定義して、後は自動でやって欲しいのです。

 つまり、処理の「意味」だけに注力したいのです。レジスタとか後から読んで読めないゴミクズを、一々触りたくないのです。

(なお、1msはstd::chrono_literalsに定義してあります。 100_MHz は、ユーザー定義リテラルで自作できます。)

 

std::functionで関数を渡す

 このように、何か勝手にやって欲しい処理の間に、ユーザー定義の処理を挟むには大凡以下の方法があります。

  1. 関数を定義して、関数ポインタを渡す
  2. 関数オブジェクトを渡す
  3. ラムダ式を渡す

 先に書いた例ではラムダ式を使っていますね。なお、実際にはラムダ式は2の糖衣構文なので、基本的には二つですね。

 「C++は関数ポインタよりも、関数オブジェクトの方が最適化が効きやすい」という話もありますが、最近のコンパイラは賢いので、関数ポインタでも最適化がいけてる気がします。実際、手元にあるIARのコンパイラでリリースビルドすると、関数ポインタの最適化ができています。

 どれを使うにせよ、受け取る側(loopメソッド)はC++11以上では、std::functionで簡単に同じように受け取ることができます。

#include <functional>

void loop(std::function< void () > func);

 

 これをビルドするとどうなるのでしょうか?

 以降はWDTの例を引きずるとコードが長くなるので、以下の様な単純なコードで考えてみましょう。

#include <functional>

volatile int a=0,b=1;//レジスタを想定

void foo(std::function<void()> f)
{
  while(1)
  {
    a++;//ループの最適化をさせない
    f();
  }
}


void main()
{
  foo([](){
    int bb = b;
    b = bb * (bb + 1); // volatileなので最適化はされない
  });
}

 

 IARのコンパイラで上記のコードをデバッグビルドすると、マップファイルは以下の様になります。

Entry                       Address  Size  Type      Object
-----                       -------  ----  ----      ------
.iar.init_table$$Base         0x478         --   Gb  - Linker created -
.iar.init_table$$Limit        0x488         --   Gb  - Linker created -
?main                         0x489        Code  Gb  cmain.o [4]

(略)
std::allocator<int>::allocator(const std::allocator<int>&) [subobject]
                              0x3a1   0x8  Code  Gb  main.o [1]
(略)
operator delete (void *)
                              0x41d   0xa  Code  Gb  delop_0.o [3]
operator new (unsigned int, void *)
                              0x339   0x4  Code  Gb  main.o [1]

(略)

 newとかdeleteとかallocatorとか、組み込みの敵なメソッドがずらり。

 最適化してないからしょうが無いのかと思ったら、リリースビルドにしても、deleteや仮想テーブルが残る結果になりました。

 これでは、組み込み的にはちょっと使えません。

 ラムダ式がインライン化されて、もっとがっつり消えてくれるのかと思ってたんですけど、少なくともIAR Embedded Workbench for ARM8.40のコンパイラでは消えませんでした。

 

 そこで、もうちょっと最適化させやすくしてみます。

inline void foo(std::function<void()> f)

 fooにinlineを付けてみました。これでリリースビルドをすると、デリータ関連が消えました。fooも消えたので、fooがinline展開されて、デリータがいらなくなったのでしょう。(というか、C++において、inlineは基本的にヘッダーに関数をかける以上の意味は無いと思っていたのですが、消えてびっくり)

 しかし、仮想テーブルは残りました。std::functionという情報を消せてないようですね。(無論コンパイラによっても違うでしょうが)

 ということは、どうも組み込み的にはstd::functionが敵のようです。そうか、君もstd::vectorとかと同類の禁止薬物だったか。

 

sdt::functionを使わず、templateにしてみる

 functionがだめならば、使わなければいいのだろう?と単純にtemplate化してみました。

template<typename F>  void foo(F f)

   これを、デバッグビルドしたときのマップファイルが以下です。

Entry                       Address  Size  Type      Object
-----                       -------  ----  ----      ------
.iar.init_table$$Base          0xa4         --   Gb  - Linker created -
.iar.init_table$$Limit         0xb4         --   Gb  - Linker created -
?main                          0xb5        Code  Gb  cmain.o [3]
CSTACK$$Base            0x2000'0008         --   Gb  - Linker created -
CSTACK$$Limit           0x2000'0408         --   Gb  - Linker created -
Region$$Table$$Base            0xa4         --   Gb  - Linker created -
Region$$Table$$Limit           0xb4         --   Gb  - Linker created -
__cmain                        0xb5        Code  Gb  cmain.o [3]
__exit                        0x119  0x14  Code  Gb  exit.o [4]
__iar_data_init3               0x7d  0x28  Code  Gb  data_init.o [3]
__iar_debug_exceptions         0xd6   0x1  Data  Gb  unwind_debug.o [4]
__iar_program_start           0x12d        Code  Gb  cstartup_M.o [3]
__iar_zero_init3               0x41  0x3a  Code  Gb  zero_init3.o [3]
__low_level_init               0xd3   0x4  Code  Gb  low_level_init.o [2]
__vector_table                  0x0        Data  Gb  vector_table_M.o [3]
_call_main                     0xc1        Code  Gb  cmain.o [3]
_exit                         0x10d        Code  Gb  cexit.o [3]
_main                          0xcf        Code  Gb  cmain.o [3]
a                       0x2000'0000   0x4  Data  Gb  main.o [1]
b                       0x2000'0004   0x4  Data  Gb  main.o [1]
exit                           0xf1   0x4  Code  Gb  exit.o [2]
main                           0xe9   0x8  Code  Gb  main.o [1]
main::[lambda() (instance 1)]::operator ()() const
                               0xd9   0xc  Code  Lc  main.o [1]
void foo<main::[lambda() (instance 1)]>(main::[lambda() (instance 1)])
                               0xf5  0x18  Code  Gb  main.o [1]

 おぉ、単にmain::[lambda()(instance 1)]という型の関数オブジェクトになったお陰で、デバッグビルドだというのに、newだとかallocとかdeleteとか仮想テーブルが一切なくなっています。

 ちなみに、最適化すると、fooもラムダ式もインライン展開されて、全部消えてました。

 これなら、組み込みでもニッコリですね。

 

関数ポインタ

 テンプレートでもニッコリですが、関数ポインタにもしてみましょう。

 例の様に、キャプチャしていないラムダ式は関数ポインタに暗黙に変換できます。

volatile int a=0,b=1;//単に最適化させない為だけ

void foo(void (*f) ())
{
  while(1)
  {
    a++;// レジスタを想定して、ループの最適化をさせない
    f();
  }
}

int main()
{
  foo([](){
    int bb = b;
    b = bb * (bb + 1); // volatileなので変に最適化はされない
  });
  return 0;
}

 これを同様にIARでデバッグビルドするとこんなんができてて、微妙に8byteほどサイズが大きい。

main::[lambda() (instance 1)]::_FUN()
                               0x71   0xa  Code  Lc  main.o [1]
main::[lambda() (instance 1)]::operator ()() const
                               0x41   0xc  Code  Lc  main.o [1]

 この_FUNってなんじゃらほい?というわけで、逆アセンブル。

   0x70: 0xb580         PUSH    {R7, LR}
   0x72: 0x2000         MOVS    R0, #0
   0x74: 0xf7ff 0xffe4  BL      _ZZ4mainENKUlvE_clEv   ; 0x40
                                main::[lambda() (instance 1)]::operator ()() const
   0x78: 0xbd01         POP     {R0, PC}

 単にインスタンスを置いてラムダ式をコールしてreturnしてるだけですね。暗黙変換されるとき、こいつが渡されるようです。こういう処理が入る分、8byteほどデバッグビルドのサイズが大きくなった様子。

 なお、最適するとどっちも同じ結果になりました。

   

まとめ

 組み込みにおいては、肥大化しても良いならテンプレートを、そうでないなら関数ポインタを使いましょう。近年のコンパイラは賢いので、関数ポインタでもちゃんと最適化してくれます。

 

 無論、これは2019年現在の話であって、コンパイラが賢くなれば、std::functionでも良くなるかも知れませんが。

static_assertで比較した値を表示したい

「C++のconstexprの値がおかしいんだけど、static_assertで間違った値表示できないの?」と聞かれました。

  できねぇんだな、これが

 C++11からstatic_assertはあるというのに、何故static_assert_eqやstatic_assert_ltとかがないのだろう。謎で仕方がないです。

#include <cassert>

constexpr int x = 10;
static_assert(x == 1, "x?");

 これをg++でコンパイルすると、以下の様なエラーになります。

 xが何だかは全然分からないですね。

static assertion failed: x?
    4 | static_assert(x == 1, "x?");
      |               ~~^~~~~

 

 

 static_assertのメッセージには、ストリングリテラルしか入れられないので、動的に生成することもできないです。 C++20でconstexprにおけるメモリ確保までできる様になるんだから、そこら辺も緩和して欲しいところです。

 

組み込みの定数計算ぐらいならなんとかなるかも

 

 表示できないと言いましたが、整数値なら、裏技的に値を表示する方法があります。

constexpr int x = 10;

template<int NUMBER>
struct test
{
  static constexpr bool value=true;
  static_assert(NUMBER == 1,"test<NUMBER>");
};

static_assert(test<x>::value,"");

 

 これのg++のエラーは以下の様になります。

foo.cpp: In instantiation of 'struct test<10>':
foo.cpp:15:22:   required from here
foo.cpp:12:24: error: static assertion failed: test<NUMBER>
   12 |   static_assert(NUMBER == 1,"test<NUMBER>");
      |                 ~~~~~~~^~~~~

 そう、一度テンプレートパラメータに入れてしまうと、「foo.cpp: In instantiation of 'struct test<10>':」ここ(赤字)に値が出るんです。

 ちなみに、clang++だと、NUMBER==1じゃなくて、10==1と表示してくれます。凄いね。

 

 複雑なクラスなど使わない組み込みで、整数を計算するだけのconstexprなら、これで十分な場合も多々あると思います。

 

というわけで、まとめておいた

 一々書くのも面倒くさいと言うことで、まとめておきました。

github.com

 こんな感じで使えます。

#include "sassert.h"

// @return x * x
constexpr int pow(int x){return x  +  x;}

SASSERT_EQ(pow(10),100); // 10*10 == 100
SASSERT_NE(pow(10),20);  // 10*10 != 20
SASSERT_LT(pow(1),2);    // 1*1 < 2
SASSERT_GT(pow(10),50);  // 10*10 > 50
SASSERT_LTE(pow(10),100);// 10*10 <= 100
SASSERT_GTE(pow(10),100);// 10*10 >= 100

 エラー時の値は「nds::assert_○<型, 値, 期待値, ファイル行 >」という形で表示されるので、その言葉を探してください。IAR Embedded Workbench等で使う場合は、エラーログを右クリックし、ログのフィルタリングを無し(全て)にして表示してください。

 g++だとこんな感じ。

include/sassert.h: In instantiation of 'struct nds::assert_eq<int, 20, 100, nds::line<6> >':
foo.cpp:6:1:   required from here
include/sassert.h:54:17: error: static assertion failed: Value == Expected
   54 |   static_assert(value,"Value == Expected");
      |                 ^~~~~
include/sassert.h:31:45: error: static assertion failed: pow(10) == 100
   30 |   static_assert(::nds::assert_##op <::nds::assert_type_t<decltype(x)>,\
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   31 |                 x,y,::nds::line<__LINE__>>::value,#x message #y)
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~
include/sassert.h:22:27: note: in expansion of macro '__NDS__ASSERT_BASE'
   22 | #  define SASSERT_EQ(x,y) __NDS__ASSERT_BASE(eq," == ",x,y)
      |                           ^~~~~~~~~~~~~~~~~~
foo.cpp:6:1: note: in expansion of macro 'SASSERT_EQ'
    6 | SASSERT_EQ(pow(10),100); // 10*10 == 100
      | ^~~~~~~~~~

 「include/sassert.h: In instantiation of 'struct nds::assert_eq<int, 20, 100, nds::line<6> >':」の所に、pow(10)の間違った結果が出ています。

 ライセンスはCC0なので(Unlicenseとどっちがいいの?)、適当に改変して結構です。 

 ………static_assert_eqとかC++で対応してくれないかなぁ。

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だの、クラスだのを勉強するのは、無駄とは言わないけど、後で良いと思います。