列挙型のサブカテゴリをvariantで作ろう

列挙型のサブカテゴリをvariantで作ろう

目的

c++で列挙型を使っていると列挙しているものが増えてきて 管理に苦しむことはないだろうか。 そこで増えてきた要素をカテゴライズできるサブグループをvariantを使い、 分類する方法を記載する。

nlohmann Jsonのcmake設定

はじめに

C++でJsonを読み書きするライブラリであるnlohmann-jsonを cmakeで取り込む方法を記載する。

方法

基本的にnlohmann-jsonのreadmeに 書かれている通りに記載する。 ただし、そのままではnlohmann/json.hppで取り込めないためinclude_directoriesで ヘッダーファイルの場所を指定する。

# use FetchContent to download
include(FetchContent)

# fetch library
FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz)
FetchContent_MakeAvailable(json)
include_directories(${json_SOURCE_DIR}/include) # append to include header

# link
target_include_directories(hoge PUBLIC nlohmann_json::nlohmann_json)

すると次のようにインクルードできる。

#include <nlohmann/json.hpp>

Magic EnumをCmakeに取り込む方法

はじめに

magic-enumは、header onlyで動くc++の標準では手が届かないenumの機能に 関するライブラリです。 magic-enumを使うことで明らかに自明なenumから文字列、 文字列からenumといった変換が可能になります。

debian系の環境ならaptで導入することもできますが、 windows環境ではvcpkgやconanを使うなどの方法もありますが、 導入が面倒です。

そこでビルドに使うcmakeを用いてfetchして 組み込んでしまおうというアイディアです。

方法

cmakeのスプリクトに次のような記述をします。

include(FetchContent)
FetchContent_Populate(
  magic_enum
  URL https://github.com/Neargye/magic_enum/releases/download/v0.9.7/magic_enum-v0.9.7.tar.gz
)
include_directories(${magic_enum_SOURCE_DIR}/include)

すると#include<magic_enum/magic_enum.hpp>で取り込めるようになります。

Factorioサーバーの構築

Factorioサーバーの構築

概要

Factorio Version 2と追加modのSpace Ageが2024年10月21日についにリリースされた。1 すでにシングルでプレイされている工場長もいるだろうが、 本稿ではマルチプレイで楽しみたい工場長のために、 私の環境でのFactorioサーバーの構築手順を共有する。

C言語でのState Machine実装

C言語でステートマシンを実装する場合、 どのような実装が考えられるだろうか。 文法a(bc|d)を受理するステートマシンについて switch case文と構造体を使う実装を比較してみよう。

switch case文を使う実装

最も原始的な実装としてswitch caseとenumを組み合わせた方法が挙げられる。

typedef enum state{STATE_INIT,STATE_A,STATE_B,STATE_COMPLETE,STATE_ERROR}state_t;

state_t state_next(state_t s, char c){
  switch (c){
    case STATE_INIT:
      return c=='a'?STATE_A:STATE_ERROR;
    case STATE_A:
      return c=='b'?STATE_B:
             c=='d'?STATE_COMPLETE:STATE_ERROR;
    case STATE_B:
      return c=='c'?STATE_COMPLETE:STATE_ERROR;
    default:
      return STATE_ERROR:
  }
}

これの呼び出し例は次のようになる。

void demo(){
  const char *str="abc";
  state_t s=STATE_INIT;
  for (;*str;str++){
    s=state_next(*str);
    if (s==STATE_ERROR)break;
    if (s==STATE_COMPLETE){
      puts("complete!!");
      break;
    }
  }
}

この方法では、状態が少ないうちは容易な記述で比較的わかりやすいが 状態が増えてくるとswitch case文が肥大化してしまう。 更によくステートマシンの実装で使われるaction,enter,exitも実装すると 肥大化したswitch case文が散逸してしまう。

そして、状態遷移を示す関数を実装するときに状態が既知でないと実装できない。

構造体を使った実装

構造体を用いた実装を考えてみよう。 まず、状態を保存する構造体は次のように書ける。

struct state{
  const char *name;//デバック用に名前をつけておくことをおすすめする。
  void (*action)(void*context);//今回は使わない
  state_t* (*next)(void*context,char);//次のstateを返す(nullable)
  // NOTE: 今回は文字を受理するステートマシンとして書いているため
  // 第二引数をcharにしているがより汎用的に書くならばvoid*にするとよい。
  void (*enter)(void*context);//stateが開始するときに呼び出される(nullable)
  void (*exit)(void*context);//stateが終了するときに呼び出される(nullable)
  void *context;//これをいれることでstate machineの入れ子を実現できる(nullable)
}state_t;

次に、文法a(bc|d)を受理するステートマシンの状態遷移を示す。

C言語でもdeferを使いたい #2

c言語でdeferを実装してみよう。

まず、前回示したようにdeferを実装する構造体は次のようになる。

#include <stddef.h>
typedef struct defer_item{
  void (*deleter)(void* context);
  void *context;
}defer_item_t;

typedef struct defer_impl{
  size_t len,cap;
  defer_item_t *items;
}defer_impl_t;

そして、初期化・解消処理は次のように書ける。

#include <stdlib.h>
#include <assert.h>

// コンパイルオプション(gccなら-DDEFER_DEFAULT_CAP=XX)で
// 変更できるようにマクロを定義する。
#ifndef DEFER_DEFAULT_CAP
#define DEFER_DEFAULT_CAP 16
#endif/*DEFER_DEFAULT_CAP*/

defer_handle defer_open(){
  defer_impl_t*const impl = malloc(sizeof(defer_impl_t));
  defer_item_t*const items = malloc(DEFER_DEFAULT_CAP*sizeof(defer_item_t));
  if (!impl||!items){
    //early returnですぐに制御を戻す
    free(impl);
    free(items);
    return NULL;
  }
  // implに設定を書き込みんでいく
  impl->cap=DEFER_DEFAULT_CAP;
  impl->len=0;
  impl->items=items;//move ownership into impl
  return impl;
}

void defer_close(defer_handle d){
  assert(d);//d==NULLは明らかなコーディングミスなので落とす
  defer_item_t *const rbegin=&d->items[d->len-1];
  defer_item_t *const rend=&d->items[-1];
  for (defer_item_t* iter=rbegin;iter!=rend;iter--){
    iter->deleter(iter->context);
  }
  free(d->items);
  free(d);
}

次に開放処理を登録する関数を示す。

C言語でもdeferを使いたい #1

C言語でファイル操作やmutexを複数使っていると go言語のようにdeferを使いたくなることがある。

仮に3つ開放処理が必要な操作do_somethingをするとした場合、 C言語で書くと次のようになる。

int hoge(){
  int lock1=lock();
  if (!lock1){
    return 1;
  }

  int lock2=lock();
  if (!lock2){
    unlock1(lock1);
    return 1;
  }

  int lock3=lock();
  if (!lock3){
    unlock2(lock2);
    unlock1(lock1);
    return 1;
  }

  do_something();

  unlock(lock3);
  unlock(lock2);
  unlock(lock1);
  return 0;
}

Note

ブログを始めました

ブログを始めました

遅々としてブログを建てられていなかったのでブログを立ててみました。

これから徒然と技術記事を書いていくつもりです。