C言語で関数のオーバーロード

これはAmusement Creators Advent Calendar (ACAC) の6日目の記事です.

adventar.org

C++C#にあってC言語にはない機能の一つにオーバーロード (overloading) というのがあります. これはC言語でもCPP(CPreProcessor)を使えば限定的ではあるければもオーバーロードミミックできる!という内容の記事です.

オーバーロードって何?

オーバーロードってなんなんでしょう?とりあえずC++でどう定義されているのか見てみます.
現行C++ (C++17) のdraftは多分 n4659*1 なのでこれを読んでみると16章で大々的にoverloadingについて言及されているのが見つかります.
冒頭の部分を訳すと"同じスコープにある一つの名前に対して2つ以上の異なる宣言が指定されているとき,その名前はオーバーロードされているという. 付け加えて,同じスコープで同じ名前に対する型の違う宣言が2つあったときそれらはオーバーロード宣言と呼ばれる.関数と関数テンプレートの宣言だけがオーバーロードできて変数や型の宣言はオーバーロードできない" でしょうか.
多分このあたりは他の言語でもだいたい同じなんじゃないかなーと思います.例を出すと

#include <iostream>
void hoge(int) { std::cout << "neko" << std::endl; }
void hoge(char) { std::cout << "inu" << std::endl; } // valid
void hoge(int, int) { std::cout << "syounen" << std::endl; } // valid
// void hoge (int) { std::cout << "piyopiyo" << std::endl; }  ! invalid ! 型が同じ宣言はダメ

int main() {
    hoge(42); // neko
    hoge('a'); // inu
    hoge(42, 'c'); // syounen
}

ってかんじです.
別のことをする関数に同じ名前を付けるのは良くないとかオーバーロードがあるような言語は型推論が決定不能になるといった理由から単純なオーバーロードという機能はあまり好まれていないフシがありますが,まぁこれはオアソビですしC言語には型推論なんて無いのであんまり気にせずいきましょう.

本題

CPPを使ってC言語でもオーバーロードみたいなのを書けるようにしたいんですが,CPPはトークン列しか見れないので型によるオーバーロードはできません.なので引数の違いによるオーバーロードで勘弁して下さい.残念.

実際のコードはこんなかんじです

void hoge1(int n) { printf("neko %d\n", n); }
void hoge2(int n, char c) { printf("inu %d%c\n", n, c); }

#define SELECTER(ONE_, TWO_, SELECT, ...) SELECT
#define HOGE(...) SELECTER(__VA_ARGS__, hoge2, hoge1) (__VA_ARGS__)

int main() {
    HOGE(42);  // neko 42
    HOGE(42, 'a'); // inu 42a
}

マクロHOGEに渡した引数の数が1つならhoge1が,2つならhoge2が呼ばれていることがわかります.
どうしてこのような挙動をするのでしょう?
引数が1つのときを考えるとHOGE(42)SELECTER(42, hoge2, hoge1) (42)に展開されて次にSELECTER(42, hoge2, hoge1)の部分がhoge1に展開されるので最終的にhoge1(42)になります.
引数が2つのときを考えるとHOGE(42, 'a')SELECTER(42, 'a', hoge2, hoge1) (42, 'a')に展開されて次にSELECTER(42, 'a', hoge2, hoge1)の部分がhoge2に展開されるので最終的にhoge2(42, 'a')になります.
つまりSELECTERは前から3番目を取り出すマクロで,そこにHOGE__VA_ARGS__を渡しすことでHOGEの引数の数に応じてSELECTERが選ぶトークンがずれるようになってるんですね.
同じテクニックを使えば与えられた引数の数を計算するマクロも作れそうですね.つまりこうです

#include <stdio.h>

#define SELECTER(ONE, TWO, THREE, FOUR, N, ...) N
#define N_ARGS(...) SELECTER(__VA_ARGS__, 4, 3, 2, 1)

int main() {
    printf("%d\n", N_ARGS(neko));  // 1
    printf("%d\n", N_ARGS(neko, inu, syounen));  // 3
}

この例では4つまでしか引数の数を計算できませんがもっと数を増やせば増やした分だけ計算できる引数の数を大きくすることができます.

参考にしたもの

XeLaTeXやLuaLaTeXでascmac.styのitemboxやscreenを使うとき

覚え書き
XeLaTeXやLuaLaTeXでは\tbaselineshift(縦組の和欧文のペースラインの位置調整)が定義されていないのでascmac.styのitemboxやscreenを使おうとすると

! Undefined control sequence.
\screen ->\@savetbaselineshift \tbaselineshift
                                               \tbaselineshift \z@ \@ifnextc...

と怒られる.

なので\usepackage{ascmac}の時に

\usepackage{ascmac}
\newdimen\tbaselineshift

とする.

参考: http://oku.edu.mie-u.ac.jp/tex/mod/forum/discuss.php?d=1454

OCamlのannotファイルの読み方

ocamlcやocamloptでnyan.mlを-annotオプションを付けてコンパイルするとnyan.annotファイルが生成される。 このファイルにはコンパイルするときに得られた識別子のスコープとか式の型とか関数呼び出しが末尾再帰かそうでないかとかの情報が含まれています。例えばテキストエディタが末尾再帰をハイライトする時にこのファイルを使えます。
annotファイルのフォーマットは中身を見れば大体分かるだろうけど、取り敢えず使いそうな事をメモしておきます。
この記事は雰囲気を感じ取って書いています。正式なフォーマットの解説とかあれば教えてください。

*はdon’t careだと思ってください。

annotファイルがどうとか以前に僕はOCaml自体についても初心者なので変なこと書いてるかもです。指摘していただけるとありがたいです。

全体

情報はそれぞれの式や識別子に対して

"ファイル名1" * * 開始位置 "ファイル名2" * * 終了位置
情報名1 (
  情報の詳細
)
情報名2 (
  情報の詳細
)
...

という形式で記述されています。
“ファイル名1"の"開始位置"から"ファイル名2"の"終了位置"までがその式や識別子の書かれている場所です。普通は"ファイル名1"と"ファイル名2"は同じになると思います。
情報名1や情報名2は型とか識別子とかそういうのです。 例えば

"main.ml" * * 8 "main.ml" * * 11
type(
  int -> int
)

って書いてあると先頭から8〜11の位置に書かれてあるものの型がintからintへの関数なんだなーって分かります。

識別子

識別子の宣言に対しては

ident(
  def 識別子 "ファイル名" * * 開始位置 "ファイル名" * * 終了位置
)

という情報がつきます。これは指している文字列は宣言された識別子であり、そのスコープは開始位置から終了位置であるということを示します。

また変数の使用については

ident(
  *_ref 名前 "ファイル名" * * 開始位置 "ファイル名" * * 終了位置
)

という情報がつきます。これは指している識別子が"名前"という名前の変数であり、その変数は開始位置から終了位置で宣言されていることを示します。
この"名前"はモジュール名込みの名前っぽいです。

関数呼び出し

関数呼び出しが末尾再帰であれば

call(
  tail
)

非末尾再帰であれば

call(
  stack
)

という情報がつきます。

WebAssembly手書き (wast) で簡単なSTGを作った

WebAssembly (wasm) のテキスト表現でwastというものがある。
僕の所属しているAmusementCreatorsの48時間ゲームジャムでこのwastをがりがり書いて簡単なSTGを作った。
デモはこちら https://akitsu-sanae.github.io/works/wast-game/index.html
ソースコードはここのgame.wastを見てください https://github.com/akitsu-sanae/wast-game

Web周りの知識に疎いので有名なブラウザで動かないようなコードを書いてたらごめんなさい。指摘してくれると嬉しいです。

wastを書いた感想は記述量の多い昔のC言語って感じだった。キーボードを打つのが疲れるけど思ってたよりマゾマゾしくない。

今回で得られた知見をまとめて一つの記事にする予定。WebAssembly手書きマン増えてくれ。

ゲームを作るにあたって参考になったものを上げておく

C/C++の可変長引数マクロで引数の数でオーバーロードしたい

#define SELECTER(_1, _2, SELECT, ...) SELECT
#define FOO(...) SELECTER(__VA_ARGS__, NYAN, WAN) (__VA_ARGS__)

FOO(neko)       // expanded to `NYAN (neko)`
FOO(inu, dog)   // expanded to `WAN (inu, dog)`

参考

c - Overloading Macro on Number of Arguments - Stack Overflow

c++ - Can macros be overloaded by number of arguments? - Stack Overflow

RustでArrayにcollectしたい

Rustで以下のコードは違法

(0 .. 9).map(|i| i*i).collect::<[i32; 10]>::()

これは配列がFromIteratorを実装していないため.

こういう時はArrayVecを使う.

取り敢えずCargo.tomldependencies

arrayvec = "0.3.20"

を書き加えて

extern crate arrayvec;
use arrayvec::ArrayVec;

let result: ArrayVec<[_, 10]> = (0..9).map(|i| i*i).collect();
result.into_inner().unwrap()

とすればいい

C++のライブラリをC言語でくるんでRustから使うまで

この記事はAmusementCreatorsAdventCalendar(通称ACAC)の20日目の記事です.

www.adventar.org

にゃん

以前下のような記事を書いたのですが,その時に色々学ぶことがあったので纏めておこうと思います. akitsu-sanae.hatenablog.com

この記事は大まかに分けてC++のコードをCでくるむ部分とそれをRustから使う部分の2つに分かれます.
前半はRustに興味のない人にも役に立つのではと思います.

C++の環境はVisual Studio 2015(v140)
Rustの環境は

  • rustup 0.6.5 (88ef618 2016-11-04)
  • rustc 1.15.0-nightly (2217bd771 2016-11-25) nightly-i686-pc-windows-msvc
  • cargo 0.16.0-nightly (b26d672 2016-11-25)

です.

説明のためライブラリのヘッダファイルはnyan.hpp,ライブラリのバイナリはnyan.libとします.

何か不備などありましたら遠慮なくコメントください.

Visual C++のコードをC言語でくるむ

取り敢えずラップする用のVisual C++プロジェクトを作ります.
ラッパーのcppファイルをwrapper.cppとでもしておきます.
wrapper.cppは基本的に下の様になります.

#include "nyan.hpp"

extern "C" {
    // ここにラップするコードを書いていく
}

フリー関数についてはそのままラップすればよくて特筆すべきことはありません.

例えば下のようなクラスをラップすることを考えます

namespace nyan {

struct Inu {
    explicit Inu() { ... }
    ~Inu() { ... }

    void greet(const char* name) {
        std::cout << "こんにちは!" << name << "さん!wanwan!!" << std::endl;
    }

    ...
};

}

この場合ラップする関数は

  • nyan::Inu::Inu()
  • nyan::Inu::~Inu()
  • nyan::Inu::greet(const char*)

の三つです.
wrapper.cppは下の様になります.

#include "nyan.hpp"

extern "C" {
    typedef struct {
        Inu impl;
    } InuImpl;

    // nyan::Inu::Inu
    InuImpl* nyan_Inu_create() {
        return new InuImpl{};
    }

    // nyan::Inu::~Inu
    void nyan_Inu_delete(InuImpl* inu) {
        delete inu;
    }

    // nyan::Inu::greet
    void nyan_Inu_greet(InuImpl* inu, const char* name) {
        inu->greet(name);
    }
}

まぁつまりC++のクラスであるInuをCのstruct InuImplでラップして,それを経由してコンストラクタやデストラクタ,メンバ関数をラップします.
Inuはほとんどの場合std::shared_ptr<Inu>の形で使われるようならInuImplが持つのはstd::shared_ptr<Inu>でもよいかもしれません.
見てもらえば分かるように,コンストラクタは単純にInuImpl*を返すフリー関数として,デストラクタはInuImpl*を引数に取ってvoidを返すフリー関数としてラップできます.

メンバ関数の扱いなのですが,これはそのクラスのポインタが第一引数になるようなフリー関数としてラップできます.このポインタはC++メンバ関数では暗黙的に渡されているthisポインタのことですね.

こんな感じでバリバリラッパーを書いていきます.

で,当然ですがスタティックリンクライブラリとしてビルドします.
この時,ビルド後イベントにlib /out:lib/nyan-wrapper.lib nyan.lib Release/(プロジェクトの名前).libを付け加えるとwrapper.cppをビルドしたものとnyan.libをくっつけたものをlib/nyan-wrapper.libに吐き出してくれます.
libはLinux環境でのarみたいなものだと思ってくれればよいです.

これでnyan.libをラップしてC言語のインターフェースにしたnyan-wrapper.libが得られました.

C言語インターフェースの静的ライブラリをRustから使う

まずcargo new nyan-rs --binなどとしてRustのプロジェクトをつくります.
取り敢えずsys部分を作っていきます.sysでは静的ライブラリの中にどんな関数があるのかを列挙します.
今回だとsys部分であるnyan-rs/src/sys.rsは下のようになります.

use std::os::raw::c_char;
use std::os::raw::c_int;

pub enum InuImpl {}

#[link(name = "nyan-wrapper")]
extern {
    pub fn nyan_Inu_create() -> *mut InuImpl;
    pub fn nyan_Inu_delete(inu: *mut InuImpl);
    pub fn nyan_Inu_greet(inu: *mut InuImpl, name: *const c_char);
}

今回はstd::os::rawc_charc_intがあったのでそっちを使いましたがlibcを利用した方が良いかもです. InuImplは4行目のようにダミーのenumで宣言してやります.

他にも例えばuser32.libをリンクしたければ

#[link(name = "user32")]
extern {}

などと付け加えます.

これでsys部分は出来ました.
次はsys部分をRustらしくラップしていきます.

src/nyan.rsにこう書きましょう

use std::ffi::CString;

pub struct Inu {
    raw: *mut sys::InuImpl
}

impl Inu {
    pub fn new() -> Self {
        unsafe {
            Inu {
                raw: sys::nyan_Inu_create()
            }
        }
    }

    pub fn greet(&mut self, name: &str) {
        unsafe {
            sys::nyan_Inu_greet(self.raw, CString::new(name).unwrap())
        }
    }
}

impl Drop for Inu {
    fn drop(&mut self) {
        unsafe {
            sys::nyan_Inu_delete(self.raw)
        }
    }
}

これで次からRustのコードでnyan::InuC++nyan::Inuと同じ気持ちで使えるようになります.
やったね!!

参考にしたもの

Foreign Function Interface

Rust と C言語 をコールバックで行き来する(Cブリッジが必要なVer) | d.sunnyone.org