Gaming Life

一日24時間、ゲームは10時間

【C++20】constメンバ関数の実装を要求するconcept

C++20から導入されるコンセプトは、requires節を使用して、型に対してあるメンバ関数の実装を要求することが出来る。 そのメンバ関数に対して、const関数であることを要求できないかと考え、実装してみた。

#include <type_traits>

template <typename T>
concept Drawable = requires(T x) {
    // 型がdraw() const:関数を持つことを要求する
    std::declval<std::add_const_t<T>>().draw();
};

template <Drawable T>
void f(T& x) {
    x.draw();
}

struct A {
    void draw() const {}
};
struct B {
    void draw() {}
};
struct C {
    void func() {}
};

int main() {
    A a;
    B b;
    C c;
    f(a);
    f(b); // draw()関数が非constなのでコンパイルエラー
    f(c); // draw()関数をもたないのでコンパイルエラー
}

wandbox.org

※追記

他の書き方もあるみたい。

// conceptの引数型をconst型にする
template <typename T>
concept Drawable = requires(const T& x) {
    { x.draw() };
};

// 非constメンバ関数の実装も同時に制約条件に加えたい場合
// 1.
template <typename T>
concept ActorClass = requires(const T& x, T& y) {
    { x.draw() };
    { y.update() };
};

// 2.
template <typename T>
concept ActorClass = requires(const T& x) {
    { x.draw() };
} and
requires(T& x) {
   { x.update() };
};

// 3.
template <typename T>
concept ActorClass = requires(const T& x) {
    { x.draw() };
    { const_cast<T&>(x).update() };
};

個人的には、2の書き方が記述量こそ多いが一番直感的かなと思う。が、どれも特に挙動に違いはないと思われるので、好きなのを使えばいい。

Visual Studioのコードスニペットのshortcut名にはハイフンは使えない

Visual Studioコードスニペットを自作していたら、表題の件にハマったのでメモがてら共有。

qiita.com

上記の記事で紹介されている Visual Studio Snippet Generatorで、スニペットを作ったのだが、Shortcutにハイフン(-)を入れると、コードスニペットが正しく機能しなくなった。

tools.unitycoder.com

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>commentblock-test</Title>
      <Shortcut>commentblock-test</Shortcut>
      <Description>create basic style comment block</Description>
      <Author>ai</Author>
    </Header>
    <Snippet>
      <Declarations>
        <Literal Editable="false"></Literal>
      </Declarations>
      <Code Language="cpp"><![CDATA[// ---------------------------------------------------------------- //
//  $end$
// ---------------------------------------------------------------- //]]></Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

このXMLVisual Studioにインポートして commentblock-test を入力してTabキーを押すと、正しく動かない。

f:id:ai_gaminglife:20200813201649j:plain f:id:ai_gaminglife:20200813201651j:plain

色々試した結果、Shortcutからハイフンを取り除くと、正しく動作するようになった。

↓改良後のXML

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>commentblockTest</Title>
      <Shortcut>commentblockTest</Shortcut>
      <Description>create basic style comment block</Description>
      <Author>ai</Author>
    </Header>
    <Snippet>
      <Declarations>
        <Literal Editable="false"></Literal>
      </Declarations>
      <Code Language="cpp"><![CDATA[// ---------------------------------------------------------------- //
//  $end$
// ---------------------------------------------------------------- //]]></Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

f:id:ai_gaminglife:20200813201707j:plain f:id:ai_gaminglife:20200813201712j:plain

ちなみに

Visual Studio Snippet Generatorは、デフォルトでC#用のスニペットファイルを出力する。C++などでそのスニペットを使用したいときは、 <Code Language> をcppなどに変更する必要がある。

参考

docs.microsoft.com

WPF勉強中(1)最初に参考にした資料とか疑問点とか

前回の記事以来、時間を見つけてはWPFの学習を進めている。しかし、ただ黙々と一人学習していてもモチベが続かないので、定期的にブログで進捗や参考にしている資料、疑問点を文章化していこうと思う。

参考にしている資料

» WPF 学習用ドキュメント作りました

最初の学習に利用した資料。「WPF 入門」「WPF 実践」「WPF 逆引き集」の内、「WPF 実践」しか読んでないが、非常に良かった。私はあるフレームワークを最初に学ぶ時、参考コードの設計をやたら気にしてしまうタイプなのだが、この資料のサンプルコードはMVVMのスタイルを守っているので、かなり肌にあっていた。

  • WPF 4.5入門

www.slideshare.net

日本語でWPFについて調べると、必ずヒットする「かずきのBlog@hatena」の著者が執筆した、WPFの入門資料。全部は読んでないが、ざっと流し見した感じ、WPFの機能について順を追って丁寧に説明されており良さげ。何より現役マイクロソフト社員が作成した資料ということもあり、信頼ができる。

特に参考になったのは「6.2 データバインディングを前提としたプログラミングモデル」の章。MVVMライブラリであるPrismの簡単な使い方が学べた。

  • XAML Controls Gallery

www.microsoft.com

マイクロソフト謹製、XAMLのサンプルをまとめたアプリ。このコントロールでどんな表現ができるのだろう、と確認したい時によく使う。ただ問題点が一つありまして(後述)……

github.com

マイクロソフト謹製のWPFのサンプル集。大量のサンプルコードが公開されている。MVVMデザインは(見た限り)無視して、初学者に理解しやすい形でコーディングされてるっぽい。

作ったもの

f:id:ai_gaminglife:20200721011956g:plain 実用性は皆無の習作。一応MVVMを意識してコーディングした。

疑問点

どうもXAML Controls Galleryは、UWP(WPFの後継的扱いのライブラリ)で作られているっぽい。一応下記の手順で、UWPのコントロールWPFで扱うことはできるらしいのだが、うまくいかなかった。その内リベンジしたい。

docs.microsoft.com

  • Prism(もしくはLivet)の使い方

「WPF4.5入門」で、BindableBaseとDelegateCommandの使い方は大体把握したが、それ以外はまだからっきし。WPFでそこそこの規模のアプリケーションを作るなら、PrismやLivetといったMVVMフレームワークを使うのが普通らしいので、使えるようにしたい。どこかにサンプル無いかなあ。

  • データの永続化

SQ Liteとかでデータを永続化できるらしいけど……外に公開するようなツールを作る、という段階まで来たらこれも意識しなきゃ駄目っぽい。まだそこまで出来そうにないので優先度は低め。

まとめ

というわけで、現時点での進捗をまとめてみた。思い描くようなツールを実装できるまでには、まだまだ時間がかかりそう。

DirectX12の魔導書を読んだ

『DirectX12の魔導書』を大体読み終わったので感想。

自分はDirectXOpenGLなどのグラフィックスAPIをほぼ触ったことがなかったので(昔DX11で三角ポリゴン出したくらい?)、勉強として買った。

本の構成は以下の通り。

  • Chapter1 前提となる知識とDirectX12の概略
  • Chapter2 グラフィックスパイプラインと様々なシェーダー
  • Chapter3 初期化から画面クリアまで
  • Chapter4 ポリゴンの表示
  • Chapter5 ポリゴンにテクスチャを貼り付ける
  • Chapter6 行列による座標変換
  • Chapter7 PMDの読み込みとモデルの表示
  • Chapter8 マテリアル
  • Chapter9 リファクタリング
  • Chapter10 スキニングとアニメーション
  • Chapter11 インバースキネマティック(IK)
  • Chapter12 マルチパスレンダリング
  • Chapter13 影行列とシャドウマップ
  • Chapter14 マルチレンダーターゲットとその応用
  • Chapter15 スクリーンスペースアンビエントオクルージョン(SSAO)
  • Chapter16 imguiの利用
  • Chapter17 Effecseerライブラリの利用
  • Chapter18 DirectXTKの利用(文字列表示)

自分はChapter13以降、実装がしんどそうだったので軽く流し読みした程度。その代わり、Chapter12まではかなり時間をかけて実際にコーディングして実装した。(ただしChapter11のIKは難しくて断念した)

以下その頃の作業記録ツイート。

本書は高校数学とC++の最低限の知識がある読者を想定して描かれている。ただ、自分が読んだ限りそこまでの知識でこの本を完走するのは厳しく、追加でグラフィックスパイプライン(頂点シェーダとかピクセルシェーダとか……)など、現代のコンピュータグラフィックスに求められる基礎知識を持っておいたほうが良いと感じた。(副読書としてCGエンジニア検定の参考書あたりを持っておくのがよさそう)

2020年7月現在、DirectX12に関する日本語の参考資料は公式のリファレンスぐらいしかない(追記: すらりんラボさんの本があった)。そのため本書の発売前は、3Dモデルの描画まで実装したら、後は影を描画して終わりくらいの分量かなーと思っていた。

ところが実際には、DirectX12の初期化から始まり、ポリゴンを描画し、3Dモデルの描画にアニメーション、果てはポストプロセスの実装とかなり奥深くまで解説している。ここまでの分量の実装を一冊で解説する本は、DirectX11の解説書も含めて心当たりがない(OpenGLならあるかも)。この分量をこなすのには時間はかかったが、ポストプロセスを使ってアウトラインもどきのシェーダーが実装できた時は感動した。

誤植がやたら多かったのはマイナスポイント。また、Github経由で動作確認が取れているサンプルコードが公開されているのだが、それもある章を境にほぼ別モノに書き換わっていて、対応を取るのに苦労した。

しかしながら、誤植などの対応を取る作業がむしろDirectX12の理解を深める一助となったところがある。誤植がほぼ無い書籍だったら、サンプルコードを写経するばかりで、ほぼ理解できずに終わっていたと思う。なのでヨシ(?)。

総じて、DirectX12の入門書として充分人に勧められるクオリティだった。どうしてもDirectX12を習得しなければならない人や、DirectX12そのものの難解さや書籍中の誤植も笑って許せる、マゾ気質の人は是非買って挑戦してみることをおすすめします。

……これ推薦文になってるのか?

GUIフレームワーク探しの旅の果てにWPFにチャレンジしている

GUIフレームワークを探して三千里

数年前から使い勝手の良いGUIツール作成用のフレームワークを探している。

今自分は、ゲームを作るならUE4かOpenSiv3D(それとUnity)を使い、CLIツールや少し込み入ったスクリプトの実装にはPythonを使っている。たまにProcessingやシェルスクリプトを使うこともある。プログラムを真面目に書くようになってもうすぐ4年、こんな具合に「これを作るならこれを使う」といった流れが確立しつつあるのだが、GUIツールの実装に使える、自分の肌にあったフレームワーク・ライブラリには出会えていない。

最初に試したのはQt。クロスプラットフォームGUIツールで最もよく使われているフレームワークの一つだと思う。映像・ゲーム業界的にも広く使われているフレームワークで、就職活動中、中途のツールエンジニアの採用要項に「Qtの使用経験○年以上」と書かれているのを頻繁に見かけた。

www.qt.io

挑戦の結果、Qtを極めるのは断念した。理由としては独自のQMakeというビルドシステムが理解できなかったこと、専用IDEであるQt Designerにどうしても馴染めなかったこと、そして何より必要なストレージ容量が多すぎた(おおよそ20GBから40GB)ことが挙げられる。特に最後の必要な容量の多さは致命的で、ただでさえ莫大な容量が必要になるUE4を使っているのに、それに加えてQtを入れておく余裕は自分のPCにはなかった。

次に試したのがOpenSiv3D。本来はゲーム開発に使うのが主なフレームワークだが、モダンなC++で書ける上、開発者が日本人でサポートも充実してることからこれでGUIツールが作れないかなーと考えた。

これも結果は断念。最低限のUIパーツは揃っているが、まだまだGUIアプリケーション開発に投入するには機能不足だった。何よりデザイナーを使ってウィジェットを配置出来ないのが不便だった。(なおOpenSiv3Dはオープンソースで開発されているので自分で実装する、という手もあったが、そんな技術力ななかった)

他にPyQt(QtのPythonバインディング)も試したが、動的型付け言語でGUIを作るというのがどうも気持ち悪くなって諦めている。

そんなこんなで色々と迷走した挙げ句、今はWPF(Windows Presentation Foundation)に挑戦している。WPFとは、Microsoftが開発したGUI開発用のフレームワークで、主にC#VB、そしてXAMLというUI配置を簡単に表現できるマークアップ言語で実装できる。WPFの歴史は意外と古く、2006年頃から使えたらしい。

WPFというと自分は「Windowwsでしか動かない」「Windowsフォームに取って代わる程の人気が出なかった(UWPと並ぶ)残念な子」「よく知らないけど.NET一族のひとつらしい」ぐらいの認識を持っていた。特にWindowsでしか動かないというのが、せっかく開発したツールもMac使いが多い(と勝手に思っている)デザイナーに使ってもらえず終いになってしまうのでは、という考えにさせてしまい、これまでチャレンジしてこなかった。

だが2019年に、クロスプラットフォームフレームワークである.NET CoreがWPFをサポートすることが発表され、WPFでWin / Linux / macOSに対応したGUIツールが開発できるようになっていた

(追記)と思ったら、.NET Coreに対応しただけでWPFWindowsでしか動かないらしい。びえん。

codezine.jp

これはやるしかないだろう、とWPFに挑戦することにした。

(ちなみに.NET Fremeworkと.NET Coreは今後統合され、.NET 5として新生。新たにUIクロスプラットフォームフレームワークが公開されるらしい。UWPと同じ運命を辿らなければ良いなあ)

触ってみての所感

まだWPFを触り始めて数日程度しか立っていないが、とにかくC#が難しい。多少なりUnityの経験があるのでなんとかなるだろうと思っていたが、C++Pythonには無い構文、当然のように使うことが求められる大量のインターフェース、参照型と値型の違いなどなど予想以上に苦しめられている。C++より難しいんじゃねえのこの言語。

ただ、WPFそのものは想像以上に使い勝手が良い。日本語ドキュメントが充実していないのは玉に瑕だが、直感的に使えて、かつVisual Studioとの相性が抜群に良いので、今後もWPFの学習を続けたいと思えた。C++書くときもコレくらいVisual Studioが仕事してくれたらなあ……

参考にしている資料

チュートリアルとして、こちらのサイトの資料を使っている。プログラミング自体の経験が浅い人には難しすぎるが、ある程度の経験がある人なら非常に参考になる資料だと思う。

http://kisuke0303.sakura.ne.jp/blog/?p=340

また、WPFが推奨している開発スタイル(デザインパターン?)であるMVVMについては、下記のページで紹介されているリンクを主に見て勉強している。まだ全部は見切れていないが……

https://qiita.com/usada-kumi/items/046c9a43b15b9e376198

結び

以上、WPFに挑戦しているよという話でした。ブログに記事として上げた以上は、これまでの挑戦のように途中で断念することなく、何かしらツールを完成させて、ここで公開できるといいなぁ。

Visual Studio 2019でプロジェクトを複製して別名をつける方法

DirectXの入門本を進めている際、Visual Studio 2019において、いま作っているプロジェクトをコピーして、それに別名をつけた上でソリューションに追加したくなったのでそのメモ。

ようはこういう事↓。

f:id:ai_gaminglife:20200502152146p:plain

手順

  1. エクスプローラーを開き、コピーしたいプロジェクトファイル(.vcxprojなど)が入ったフォルダをまるごと同じ階層にコピー。
  2. コピーしたフォルダに別名をつける
  3. コピーしたフォルダを開いて、その中にある、.vcxprojファイル、.vcxproj.filtersファイル、.vcxproj.userファイル全ての名前を、先程コピーしたフォルダにつけた名前に変える。
  4. Visual Studioを開き、コピー元のプロジェクトを開く。
  5. ソリューションエクスプローラー上で、ソリューションアイコン上で右クリック→追加→既存のプロジェクトを選ぶ。
  6. 先程コピーしたフォルダの中にある.vcxprojファイルを選択する。
  7. 完了

C++のRange-based for文(範囲for文)で逆順に走査するイテレータを自作した

モチベーション

Pythonのfor文は、C++でいうところのRange-based for文、C#で言うところのforeachと同等の動作をする。つまり、

    for (int i = 0; i < 10; i++) {}

のようなforが存在しない。

Pythonのfor文において、iterable objectをenumerate メソッドに渡すと、添字付きRange-based forができる。

    l = [100, 200, 300]
    for i, num in enumerate(l):
        print(i, num)
    # 0 100
    # 1 200
    # 2 300

これが非常に便利なのでC++でも同じ事ができないかなーと調べてみると、あった。

reedbeta.com

(リンク先に載っている実際のソースコードはここには載せない)

というわけでC++のRange-based for文でもPythonenumerate 同等の、インデックス付き走査が可能ということが分かった……

と、ここで終わってはクソ記事もいいとこなので、この実装を参考に逆順に走査するイテレータを自作することにした。

仕様

まず、リンク先の実装で問題なところを挙げてみる。

  1. operator++ の返り値がvoidになっている
  2. std::beginstd::end の形でイテレータを取得しているので、ユーザが独自に定義したiterable objectに対応できない(わざわざ std::beginstd::end をオーバーラップしなければならない)
  3. beginとendの型が違う場合に対応していない (C++17でbeginとendの型が違っても動作するようになった。詳しくは↓の記事で)

cpprefjp.github.io

今回の実装では 面倒なので 3. の問題については無視することにした。

また逆順に走査するためには、対象のiterable objectは rbeginrend 、つまり逆イテレータを持っていなければならない。従って SFINAE を使って rbegin rend を持たないオブジェクトを渡した時はコンパイルを通さないような実装とした。

実装

(2020/04/12/18:30 コメントでの指摘通りに修正)

    template <typename Range,
              typename reversed_iterator = decltype(std::declval<Range>().rbegin()),
              typename = decltype(std::declval<Range>().rend())>
    constexpr auto Reversed(Range&& range) {
        struct iterator {
            reversed_iterator iter;
            bool operator!= (const iterator& end) const { return iter != end.iter; }
            auto& /* void */ operator++() { ++iter; return iter; }
            auto& operator*() const { return *iter; }
        };
        struct iterator_wrapper {
            Range iterable;
            auto begin() {
                return iterator{ iterable.rbegin() };
            }
            auto end() { 
                return iterator{ iterable.rend() };
            }
        };
        return iterator_wrapper{ std::forward<Range>(range) };
    }

wandbox.org

operator++の戻り値について

operator++の行に謎のコメントが残っている。

    auto& /* void */ operator++() { ++iter; return iter; }

実は今の実装のままだと、MSVCではコンパイルが通らない。

調べてみると、どうやら現在のgccvectorの実装では vector::begin() をデクリメントしても、エラー終了しないらしい(当然Undefined Behaviorなので本来はやるべきでない)。一方MSVCの場合、 vector::begin() をデクリメントした時点で、 can't decrement vector iterator before begin というエラーメッセージと共にエラー終了する。

そのため、この関数をMSVCで使いたいときは、悪い実装ではあるがoperator++の実装を以下の通りに変える必要がある。

    /* auto& */ void operator++() { ++iter; /* return iter; */ }

参考サイト

Python-Like enumerate() In C++17

C++ 範囲ベースforに自分で作ったclassを対応させる - ゲーム作りは楽しい