Open14

プログラミング雑メモ

tenkatenka

主に c++ 関係や cmake や vcpkg とかの軽い雑なメモ。
("なんかメモ"から それ関係を分離)

tenkatenka

utf8な linux 環境にて、 gcc で UNICODE文字の char, wchar_t を使う場合、

  1. gcc のオプションに
    -finput-charset=utf-8 -fexec-charset=utf-8 -fwide-exec-charset=utf-32LE
    をつける。(あるいは utf32-BE)
  2. プログラムの最初のほうで setlocale(LC_ALL, ""); する.

wide-exec-charset としてLE,BEの付かない UTF-32 を指定すると、
wcstombs, mbstowcs で BOM 付文字列で変換されたりするので、
必ず LE,BE を付けるが吉.

※ これって iconv あたりの、変換指定ままの挙動なのかも。

tenkatenka

vcpkg な cmake で 生成する VS用の sln で、VSを起動したときに当該プロジェクトが
”スタートアップ プロジェクトに設定"
されるようにするには CMakeLists.txt に

set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})

を追加する

tenkatenka

代入先次第で string か wstring を返す関数もどき

引数でなく、受けとる側の変数次第で string か wstring かを自動で切り変わる関数がほしいことがあり。

    string  s  = hoge();
    wstring ws = hoge();

のように使いたい。
c++の関数でできないけど、

struct hoge {
    operator std::string() const { return "hoge string"; }
    operator std::wstring() const { return L"hoge wstring"; }
};

のように class/struct のキャスト演算子の定義を使うことで関数風の呼び出しが可能。

tenkatenka

char 型の符号無し化

char 型の符号は cpu/コンパイラ依存で、符号無が有利なCPUでも、歴史的な経緯から符号付きのことも多く。オプションで切り替えられるコンパイラは多いけど、わざわざ切り替える人は多くなく。

符号付だと UTF8 とか SJIS とかのマルチバイト文字が負数に化けるので、unsigned char にキャストして扱うけど、template で char, wchar_t, char??_t 複数対応する場合は直接書けない。

c++11 以降の <type_traits> には std::make_unsigned<C> があるので、これが使える。

    bool ascii = (typename std::make_unsigned<C>::type)c < 0x80;  //c++11
    bool ascii = (std::make_unsigned_t<C>)c < 0x80;               //c++14

<type_traits> のない c++11 以前の場合は

template<std::size_t N> struct nbytes_to_uint { };
template<> struct nbytes_to_uint<1> { typedef unsigned char  type; };
template<> struct nbytes_to_uint<2> { typedef unsigned short type; };
template<> struct nbytes_to_uint<4> { typedef unsigned int   type; };
template<class C> struct char_to_uint { typedef typename nbytes_to_uint<sizeof(C)>::type type; };

のような型変換を用意して、(typename char_to_uint<C>::type) でキャスト。
wchar_t が 16bit でも 32bit でも対応できるように sizeof を使用 で、この例では C を限定してないので、文字型・整数型以外の1,2,4バイトの型も巻き込むけど、そのへんは運用任せ。

(ちゃんと判定しようとすると、char?_t のc++バージョンとかコンパイラバージョンとか面倒で、make_unsigned でいい c++03 用意(boost流用)と考えるも面倒)


c++03 でも楽に書けるので uint への変換にしたけど、文字型限定で同サイズ型(charとchar8_t、wchar_t と char16_t or char32_t) の同一視、という性質を使いたい場合、basic_string や basic_string_view に渡すような状況を考えると、char?_t に変換するほうが、よいと思うので その例。

template<std::size_t N> struct nbytes_to_uchar { };
#if __cplusplus >= 202002L || (コンパライラ等の他の条件)
template<> struct nbytes_to_uchar<1> { typedef char8_t  type; };
#else
template<> struct nbytes_to_uchar<1> { typedef unsigned char  type; };
#endif
#if __cplusplus >= 201103L || (コンパライラ等の他の条件)
template<> struct nbytes_to_uchar<2> { typedef char16_t type; };
template<> struct nbytes_to_uchar<4> { typedef char32_t type; };
#else
template<> struct nbytes_to_uchar<2> { typedef unsigned short type; };
template<> struct nbytes_to_uchar<4> { typedef unsigned int   type; };
#endif
template<class C> struct char_to_uchar { typedef typename nbytes_to_uchar<sizeof(C)>::type type; };
tenkatenka

同サイズ型の関数の実体を一つにする

前記の char_to_uchar<C> 変換は、同サイズ型(char と char8_t、wchar_t と char16_tかchar32_t) を同一視するので、例えば、

// char,wchar_t,char?_t がUNICODE前提で、ascii(0x7f以下)範囲の src 1字を dst 1字に置換.
namespace detail {
    template<typename UC>
    void foo(UC* text, std::size_t len, UC src, UC dst) {
        if (src > 0x7f) return; // ascii範囲のみ. 呼び元でUCはunsignedのみにしてるので>のみで可.
        for (std::size_t i = 0; i < len; ++i) {
            if (text[i] == src) // src があれば dst に置換.
                text[i] = dst;
        }
    }
}
template<typename C> inline
void foo(C* text, std::size_t len, C src, C dst) {
    //using UC = typename char_to_uchar<C>::type;
    typedef typename char_to_uchar<C>::type UC;
    detail::foo((UC*)text, len, UC(src), UC(dst));
}

のように、char_to_uchar<C>でキャストする関数をワンクッションかますようにすれば、同サイズ型に関する実体が1つしか生成されないようにできる。

tenkatenka

basic_string_view<C> 引数でも C const* や basic_string<C> を受け付けるようにする

std::string_view は char const* も std::string も受け付けてくれて非常に便利だけど、複数の文字型対応の関数 template を作る場合に

template<typename C>
void bar(std::basic_string_view<C> s) {
    ()
}

のようにすると、templateでのマッチングの都合、basic_string_view<C> 以外にはマッチしてくれない。

これを対処するには、結局

template<class STR> inline
void bar(STR const& s) {
    bar(std::basic_string_view<typename STR::value_type>(s));
}
template<typename C> inline
void bar(C const* s) {
    bar(std::basic_string_view<C>(s));
}

のような、string系用とC const* 用の引数を受け取る関数を用意する必要がある。


同サイズ型をまとめて実体を減らす例の char_to_uchar を使う形にするなら、

namespace detail {
    template<typename C>
    void bar(std::basic_string_view<C> s) {
        ()
    }
}
template<class STR> inline
void bar(STR const& s) {
    detail::bar(std::basic_string_view<
            typename char_to_uchar<typename STR::value_type>::type
          >(s));
}
template<typename C> inline
void bar(C const* s) {
    detail::bar(std::basic_string_view<typename char_to_uchar<C>::type>(s));
}
tenkatenka

watcom で template つらい

16bit exe で遊びたくて watcom c++ を使い、まずは 32bit で string s; cout << s; でつまずく。
operator<<(std::ostream& o, std::string const& s)
が定義されてない... 昔もびっくりした記憶。
c++ライブラリ再構築中にowプロジェクトは頓挫してしまってたのね。

で昔、stlport を使おうとしてたのだっけ?。自分が遊んでた残骸ひっぱりだして試そうとするも、それもいろいろ中途半端で動作も不安定。
ow V1.9 ではビルドできたけど、pre-V2.0 になると c++ライブラリが中途半端に増えてて そのままではうまくヘッダを乗っ取れない。 (pre-V2.0 の stl 関係のファイルを削除して とかすれば)

で、標準ライブラリは回避しつつつかえるとこ使うで、自分のtemplate なライブラリを使うと、こんどは template の不具合に出会うわけで。

class template とのメンバー関数template まわりは、全く使えないわけじゃないけど、こまごま不具合持ち。

まず

template<class T>
struct Foo {
  template<class U>
  void test(char c, U& u) {}
};

はokだけど

template<class T>
struct Foo {
  template<class U>
  void test(char c, U& u);
};
template<class T>
template<class U>  // <- error.
void Foo<T>(char c, U& u) { u = U(c); }

は <-error の箇所でアウト。
class template の メンバー関数 template を class 定義外におく記法に対応していない。でもエラーとしてはわかりやすいし覚えてる。

で今、であったの。

template<class T=void>
struct Foo {
  void test(char c, bool& b, bool a=true) { b = a; }
  template<class U>
  void test(char c, U& u, bool a=true) { u = U(a); }
};
int main() {
    bool sw = false;
    Foo<>().test('C', sw, false);  // ok
    Foo<>().test('C', sw);         // error
}

error の行は、2つの void test 定義が曖昧で確定できないエラーになる。
メンバーtemplateとデフォルト引数が絡むと駄目ぽい。

template<class T=void>
struct Foo {
  void test(char c, bool& b) { test(c,b,true); }
  void test(char c, bool& b, bool a) { b = a; }
  template<class U>
  void test(char c, U& u) { test(c,u,true); }
  template<class U>
  void test(char c, U& u, bool a) { u = U(a); }
};
int main() {
    bool sw = false;
    Foo<void>().test('C', sw, false);  // ok
    Foo<void>().test('C', sw);         // ok
}

デフォルト引数をやめるとコンパイル可能のよう。
なので、デフォルト引数は関数わける対処が必要、と。

※ このへんは dmc のほうが安定してそう。けど dmc は dmc で他の不具合が...

やっぱり今後 Watcom は使わないと誓う

コンパイル時にコンパイラが落ちることままありだし、コンパイル通っても、実行時に意味不明に落ちる。もちろん他では正常に動く。調査し辛いし、時間泥棒でかなり徒労感。毎度、徒労で投げさしてたのを思い出した。忘れた頃に触ってしまう watcom。

なので、さわらないと誓っておくのだった。

dmc のほうが 、少なくとも template まわりはマシな気がするし、量的な問題なだけでイミフは少ないような。

tenkatenka

dmc での class template のメンバー関数templateでのエラー

dmc だと以下がエラー.

#include <stdio.h>
template<typename T>
class Bar {
public:
    template<typename U> void test(T* t, U bgn, U end);        // ng
    //template<typename U> void test(T* dst, U start, U last); // ok
};

template<typename T>
template<typename U>    // watcom error.
void Bar<T>::test(T* dst, U start, U last) {
    while (start != last) *dst++ = *start++;
}

int main() {
    char buf[1000] = {0}, src[] = "hello";
    Bar<char>().test(buf, src, src + sizeof(src) + 1);
    printf("%s\n", buf);
    return 0;
}

test の 'start' が定義されてないと怒られる。
宣言と定義で引数名が違うのが気に食わないみたいで、宣言のほうの bgn,endをstart,lastにすると通る。

保守性的には同じ名前を付けるべきだし、わかっていれば、これは実質ペナルティというほどでもなく。

tenkatenka

class template の一部を使ったときに、他のメンバー関数は実体化する?しない?

c++では使われない inline 関数は実体化しないし、未使用 template も実体化しない。

class template の一部を使った場合は、関連部分は実体化するけど、未使用部分は実体化しないのが理想(仕様のはず?)で。

#include <stdio.h>

template<class T>
struct SmpT {
    T   t_;
    int i_;
    char const* s_;
    SmpT();
    ~SmpT();
    void run();
    void run2();
    void run3() { printf("run3: %x %s\n", i_, s_); }
};


template<class T>
SmpT<T>::SmpT()
    : t_(), i_(1), s_("test")
{
}

template<class T>
SmpT<T>::~SmpT() {
}

template<class T>
void SmpT<T>::run() {
    printf("run: %s\n", s_);
}

template<class T>
void SmpT<T>::run2() {
    printf("run2: %s %d\n", s_, i_);
}

typedef SmpT<int>       SmpInt;
typedef SmpT<double>    SmpDbl;


int smp() {
    SmpInt  si;
    si.run();
    return si.i_;
}

コンストラクタ、デストラクタ、run() が実体化して、
vc,g++,clang++,ow1.9,bcc32(5.5.1)は期待通り run2(), run3() が実体化しないが、
dmc では run3() は実体化しないが run2() は実体化した。

run3 は class 定義内での関数定義なので inline 関数となり実体化を免れるけれど、外に追い出してる run2 は実体化してしまう、と。

あと、inline 関数自体は実体化しないけれど、inline 関数内で参照される template関数が、vc,g++,clang++,ow1.9,bcc32(5.5.1) では実体化しないが、dmc では実体化されてしまうようだ。

#include <stdio.h>

namespace detail {
	template<class T>
	void hoge_impl() { printf("hoge\n"); }
}
inline void hoge() { detail::hoge_impl<void>(); }

リンク時に未使用関数は削除される余地はあるけれど... dmcだと残っているぽい?

ヘッダ・オンリー・ライブラリ作って、ヘッダーinclude のみしたc++ソースをコンパイルして、アセンブラソースを出した時に、実体が出来てると悲しいのでした。

(dmc を考慮して作るのは... 16bit dosを c++ で遊ぼうとするのは間違いかも)

tenkatenka

dmc で cmake する

まず dmc の make は pure なので、gnu make (mingw32-make GnuWin32 make) を使うことにする。

cmake で -G "Unix Makefiles" か "MinGW32 Makefiles" でお試し。

cmake ファイルで悪銭苦闘したが、結局 gccコマンドラインをdmc用に変換するラッパーを噛ますことにした。
※ オプション引数の空白区切りの扱いと、optlink が古の ms link 互換でパス区切りは \ のみで /はオプション開始扱いのため、ラッバーでオプション変換してファイル名の/→\するのが楽だった。

https://github.com/tenk-a/cc_for_dmc

使う場合は、予め、環境変数

set CC=dmc-cc.exe
set CXX=dmc-cc.exe

をしてから cmake すること。

cmake -G "Unix Makefiles"

cmake の引数で指定した -DCMAKE_C_COMPILER や -DCMAKE_CXX_COMPILER が有効になる前に、別途 -G "MinGW32 Makefiles" 側のコンパイラの動作テストが行われるようで、環境変数で指定しないとコンパイラを置き換えられなかった。
※環境変数なしだと cl.exe が gccのオプション形式で使われるという奇妙な状態になる…

簡単なオプションの変換しかしておらず、ライブラリ関係や拡張子等いろいろ違いがあるので、
gcc の互換品になるわけでないので、夢はみないように、と。

ちゃんと使うには dmc 専用の cmake ファイルの類も用意したほうが よさそうだけれど、とりあえず。

※ できたてで、サンプルビルド以外で使ってないので実用なるかは不明

tenkatenka

optlink のソース

optlink はソースが公開されている

http://github.com/digitalmars/optlink

最初はこれのパス名取得のところで / → \ 変換すれば、と思ってソースみたのだけれど、ほぼアセンブラでした。アセンブラでした。
dos時代に爆速が謳われてたのに納得です。
諦めて、そっと閉じ。

tenkatenka

lemon.c のソースを sqlite のサイトのhtmlから取得する

re2c & lemon を試したくて。
しかし vcpkgには re2c はなく、lemon も lemon-parser-genelator の名であるにはあるが lempar.c は付属してなさそうで。(lemon だけだと別ライブラリ)

re2c は公式の git リポジトリ・ページ から取ればいいけれど、lemon は公式では単体配布はされず、sqlite ソース環境の一部(ツール)として入っている。

sqlite は Fossil という sqlite謹製のソース管理ツールでオープンだけれど lemon のために馴染まないツール使って sqlite 全体を取得するのも面倒くさい。

さいわい サイトから lemon.clempar.c は download ボタン押してcソース取得できるが...
できればプロジェクトの初期化バッチ等で自動で取得したいと思ったりもするわけで。

使うのは powershell(ps1) & バッチ。できればパッチ内で完結させたいけれど、記号関係の扱いは面倒で。

dl_sqlite_src_to_c.ps1
param (
    [string]$url,
    [string]$OutFile
)
$htmlContent = Invoke-WebRequest -Uri $url;
$htmlContent -match '(?s)<code class="language-c">(.*?)</code>' |  Out-Null;
$decodedText = [System.Net.WebUtility]::HtmlDecode($matches[1]);
$decodedText | Out-File $OutFile -Encoding ascii;

※ Out-File のエンコードを utf-8 にすると BOM 付。
ソース生成雛形の lempar.c にBOMがあると、生成されたソースの途中にBOMが混ざって困るので ascii で回避。

PowerShell -ExecutionPolicy Bypass -File "dl_sqlite_src_to_c.ps1" -url "https://sqlite.org/src/file/tool/lemon.c"  -OutFile lemon.c

PowerShell -ExecutionPolicy Bypass -File "dl_sqlite_src_to_c.ps1" -url "https://sqlite.org/src/file/tool/lempar.c" -OutFile lempar.c

まあ更新頻度低そうだし Public domain のソースなので、download 済のソースをプロジェクトに追加するで良いのですが、それはそれ。
(きづくの遅かっただけという)

re2c&lemon試す前に疲れたという話。

lemon 非公式リポジトリ

公式リポジトリから取得する必要がなければ github 等で探せば色々ありそう。

目についたメモ

  • lemon-grove
    サンプルソースやc以外へのポートの紹介等あり。
  • lemonCpp
    c++ヘッダ&ソース生成版。
  • lemonex
    lexer 組込拡張版。
tenkatenka

windows で日本語環境かどうかのチェック

bool isJp = GetUserDefaultLCID() == 1041;

※ すぐわすれる...