«

""


[C++] std::variantを使ってシミュレータでオートプレイさせる

この記事は C++ Advent Calendar 2023 24日目の記事です。

ゲームドメインでのお話です
少し前にC++でAIシミュレータ作ってオートプレイさせてみたので、どんだけの人に刺さる内容かは判らないですが
自分の振り返り的に紹介します

前半が、シミュレータについての話で、
後半が、オートプレイの時に std::variant が良い感じだった話
です

記載量が多くなってしまいました
前半はC++に直接関係のない話なので、読み物として見て頂ければと思います!

シミュレータとは

シミュレータと聞いてピンとくる方は少ないと思います
実際、自分自身もあまり意味が判っていませんでした

きっかけは、ゲームで機械学習を取り入れるには、シミュレータが必要と聞いたことが始まりです

自分なりにまとめてみると、シミュレータには観点が3つあることに気が付きました

■ AI学習観点

何らかの学習系AIを導入しようと思ったら、必ずシミュレータが必要になります
そして学習するだけでなく、シミュレータでのオートプレイが出来ると以下のように色々と夢も広がります

■ テスト観点

こんな感じで
シミュレータがあると、テスト関係の人たちも喜ぶわけです

■ リプレイ観点

つまりシミュレータを作ることが出来たら、

に有効なネタとなりそうです

シミュレータを作るのはちょっと工夫が必要

実際にシミュレータを作るには
周りからの理解も必要であり、性能面でもシビアな要求が求められます

例えば、学習系のAIモデルを作るためのシミュレータであれば、億の回数くらいのバトルはしゃっと実行して欲しいところです
1バトルシミュレーションするのに1分かかります~では話にならないのです
それだと2日経っても3000バトルすらこなせないことになります

そもそも、「●●を入力したら必ず▲▲の結果になること!」という 決定論的シミュレータ を作るのは 最初から設計を考えておく必要があり、後付けでは難しいです

また、キー情報を入力値としてリプレイさせるのは昔からよくある手法だと思いますが
後々のこと(学習系で利用)を考えると、入力値は「キー」ではなく、「状態」が好ましいです

簡単にターン制バトルで言うとこんな感じ

<入力>
「勇者」が「鉄の盾」「鉄の剣」を装備して
「はぐれメタル」に「剣で攻撃」をした  
(攻撃力10)

<それを受けた出力>
「はぐれメタル」が(内部防御力9で)
「ダメージ-1」を受けた  
(そして逃げるフラグが立った)  
※(カッコ)の中は内部パラメータなので、ゲーム画面では見えない情報

こんな感じで「バトルを直接的に変化させる内部状態を入出力の対象にする」のが良いです
「状態」であれば学習系でも利用できるし、何より人が見ても読めます(キー入力よりは…)

→実際に目検で状態を読むのは困難ではありますが…

他にも大量のログを管理する方法なども含めて「シミュレータそのもの」を作るコツはたくさんあるのですが
今回の話題からはちょっと外れるのでこのへんにしときます

作ったシミュレータを活躍させる方法

ヤッター!シミュレータが出来たぞー
同じログを入力したら、同じ結果になったぞー!

と喜んでいるのもつかの間…

苦労して作ったのですが、もちろんそれだけではとある部品が出来ただけなので、何にも良いことはありません

何かの目的があってシミュレータを作ったのでしょうから、それに向かって突き進むべきなのですが
最初に成果が出やすくて、チームに役に立ったと思ってもらえるのは
上に挙げた3つ(Ai学習/テスト/リプレイ)の中でも特に、テスト観点での活用 が良いように思います

テスト観点であれば、本体プロダクトとは別で話を進めやすく、またシミュレータ作成側のエンジニアのみで作業の完結も出来るので、おすすめです

そこでやっと今回の本題に来ました

オートプレイ です!

強化学習にしろ機械学習にしろ、オートプレイは必須!
それがテストにも転用できる!

となれば、時間のかかる学習系を粛々と進めつつ
直近はテスト目的でシミュレータを活躍させるのが良いと思います

→もちろんプランナーが乗り気でリプレイ機能を求めているなら、そっちを作ってください

みんなが学習系に乗り気で、結果をすぐに出したいと思っていても
残念ながらなかなか早期に学習系の結果は出ないのです…
R&D要素の大きく、学習させても期待値に満たない可能性もあります

なのでAI学習目的でシミュレータを作り始めたとしても、テスト活躍案は持っておいた方が良いです

しかもテストでの実績が目に見えて出てくると
リプレイ観点の設計もぐっと提案しやすくなり、録画機能やチート対策などにも話題が広がると思います

→このリプレイ観点の話題は、GDCセッションでもよく出てます

(本当は学習系で成果がポンと出せるのが一番良いのですがね…)

ただし、テスト観点のみで走り進めてしまうと、リプレイ出来ない設計になってしまうこともあります
「テストのためにシミュレータでオートプレイをする」のと
「学習のためにシミュレータでオートプレイをする」のと
「リプレイのためにシミュレータでオートプレイをする」のとだと
結果が似てるようで設計思想が異なるので、良い感じの和集合設計を意識しながら作るのもエンジニアの腕の見せ所ではあります

オートプレイの実装1(とりあえず動作した版)

今回はC++のアドカレ記事で、やっとC++の話が出てきます
長い前置きでしたが、お待たせしました

<前提>

状況はこんな感じで、実際に実装してみます

<実装例>

main() で動作するように、1ターンだけオートプレイとして動作するバージョンを例に書いてみました
Conpiler Explorerに全く同じものをアップしています

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// ---------------------
// main.cpp

#include <functional>
#include <iostream>
#include <vector>
#include <memory>

enum class CharactorId
{
    Player,       // プレイヤー
    FriendAlice,  // 味方
    FriendBob,
    EnemyEve,     // 以下は敵
    /* ... */
};

// 技の種類
enum class OrderType
{
    Attack,    // 攻撃
    Buff,      // バフ
    Summon,    // 召喚
    /* ... */
};

// 実際にそのターンでどんな技を出すのかの情報(★0)
class AutoPlayOrder
{
public:
    AutoPlayOrder(CharactorId id, OrderType type)
    {
        charactorId_ = id;
        CurrentOrderType = type;

        // enumでのタイプ分け(★1)
        switch (CurrentOrderType)
        {
        case OrderType::Attack:
            hitPoint_ = 10;     // default
            ExecuteFunction = [this]() {ExecuteAttack(); };
            break;
        case OrderType::Buff:
            buffPoint_ = 5;     // default
            ExecuteFunction = [this]() {ExecuteBuff(); };
            break;
        case OrderType::Summon:
        default:
            summonFriendId_ = CharactorId::FriendBob;   // default
            ExecuteFunction = [this]() {ExecuteSummon(); };
            break;
        }
    }

    OrderType CurrentOrderType;
    std::function<void()> ExecuteFunction;
    void ExecuteAttack() const { std::cout << "Attack:" << hitPoint_ << "\n"; }
    void ExecuteBuff() const { std::cout << "Buff:" << buffPoint_ << "\n"; }
    void ExecuteSummon() const { std::cout << "Summon:" << (int)summonFriendId_ << "\n"; }

private:
    CharactorId charactorId_;

    // 技によって必要なメンバ変数が異なる(★2)
    int hitPoint_;                 // orderType == Attack なら、攻撃力
    int buffPoint_;                // orderType == Buff なら、バフ量
    CharactorId summonFriendId_;   // orderType == Summon なら、一緒に召喚する味方ID
    /* ... */

};

// オートプレイの取りまとめクラス
class AutoPlay
{
public:
    std::vector<std::shared_ptr<AutoPlayOrder>> OrderList;
};

int main()
{
    std::cout << "===AutoPlay===\n";

    auto autoPlay = std::make_unique<AutoPlay>();

    // 今回は、とあるターンでの技が3つあったとする
    // 何の技が出せるのかは、今出している技を見たりするので結構複雑な処理になる(★3)
    autoPlay->OrderList.emplace_back(std::make_shared<AutoPlayOrder>(CharactorId::Player, OrderType::Attack));
    autoPlay->OrderList.emplace_back(std::make_shared<AutoPlayOrder>(CharactorId::FriendAlice, OrderType::Summon));
    autoPlay->OrderList.emplace_back(std::make_shared<AutoPlayOrder>(CharactorId::FriendBob, OrderType::Buff));

    // execute
    for (auto const& order : autoPlay->OrderList)
    {
        // オートプレイ実行時はポリモーフィックに出来てる(★4)
        order->ExecuteFunction();
    }
}

出力結果

===AutoPlay===
Attack:10
Summon:2
Buff:5

オートプレイの実装1の考察

これは OrderList に出せる技をバンバン登録して、それをしていくコードです

■新規に登録する用の型が必要(★0)

OrderList というリストに登録するための管理用の型 AutoPlayOrder を新たに作っています

■技にによっては使わないメンバを持っている(★2)

無駄があり、あまり良いコードとは言えないです
そしてこれは、全部の敵や味方が必要な情報を網羅する必要のある神クラスになってしまってます

1
2
3
4
// 技によって必要なメンバ変数が異なる(★2)
int hitPoint_;                 // orderType == Attack なら、攻撃力
int buffPoint_;                // orderType == Buff なら、バフ量
CharactorId summonFriendId_;   // orderType == Summon なら、一緒に召喚する味方ID

ちょっと何とかしたい…

そんな時には、Orderのベースクラスを作って、継承で攻撃クラスを作れば
プレイヤーだけが hitPoint_ のメンバを持てるわけなので
継承するか
数が少ないのであれば、我慢して必要情報を全持ちするかで、とりあえずは動作出来ます

どうしよう…これだけのために継承するのは、依存関係や制約が強すぎる気がします
(今回の場合は継承でリファクタしても悪くないとは思いますが…)

■結局enum判定をがっつりやっている(★1)

技を実行している箇所について↓↓↓

1
order->ExecuteFunction();

ここあたりは、std::function を使ってポリモーフィックで良い感じに見えますが
結局のところ、「何の技を出せる?」という判断をするときに、今時点で何がリストに上がっているか?を調べたくなるので(★3)
(サンプルコードには書いてませんけど)実際はリスト内の OrderTypeenum をめちゃめちゃ判定に利用しています
つまり実行時のみポリモーフィックにしたところで、結局 enum で場合分けすることが多いので、あまりうまみが無くてシュン…って感じ

この状態でもオートプレイの動作に問題は無いのですが、リファクタしていこうと思います

オートプレイの実装2(std::variant版)

継承はあんまり使いたくないという発想のもとで、
とりあえず、コンストラクタ生成時の引数制約で、必要な情報を入手するようにします
今回はやってませんが、共通クラスを作ってコンポジションで強制しても良いと思います

また、enum 判定を減らしたいので、std::varriant を用いて OrderList に既に登録されている技を型判定無しで取得する手段を提供してみます

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// ---------------------
// main.cpp

#include <functional>
#include <iostream>
#include <vector>
#include <memory>
#include <variant>

// ... 既存のenum定義はそのまま記載 ...

// ---------------------------------------------------
// 実際にそのターンでどんな技を出すのかの情報(★0')
// 攻撃
class OrderAttack
{
public:
    OrderAttack(CharactorId id, int hitPoint) :
        CurrentOrderType(OrderType::Attack),
        charactorId_(id),
        hitPoint_(hitPoint) {}

    OrderType CurrentOrderType;
    CharactorId GetCharactor() { return charactorId_; }
    void ExecuteOrder() { std::cout << "Attack:" << hitPoint_ << "\n"; }
    void UpdatePlayerAttack(int newHitPoint){hitPoint_ = newHitPoint;}
private:
    CharactorId charactorId_;
    int hitPoint_;                 // 攻撃力
};
// バフ
class OrderBuff
{
public:
    OrderBuff(CharactorId id, int buff) :
        CurrentOrderType(OrderType::Buff),
        charactorId_(id),
        buffPoint_(buff) {}

    OrderType CurrentOrderType;
    CharactorId GetCharactor() { return charactorId_; }
    void ExecuteOrder() { std::cout << "Buff:" << buffPoint_ << "\n"; }
private:
    CharactorId charactorId_;
    int buffPoint_;                 // バフ量
};
// 召喚
class OrderSummon
{
public:
    OrderSummon(CharactorId id, CharactorId summonFriend) :
        CurrentOrderType(OrderType::Summon),
        charactorId_(id),
        summonFriendId_(summonFriend) {}

    OrderType CurrentOrderType;
    CharactorId GetCharactor() { return charactorId_; }
    void ExecuteOrder() { std::cout << "Summon:" << (int)summonFriendId_ << "\n"; }
private:
    CharactorId charactorId_;
    CharactorId summonFriendId_;            // 一緒に召喚する味方ID
};

// ---------------------------------------------------
// キャラクターIDを取得するための構造体
struct GetCharactorId
{
    void operator()(const std::shared_ptr<OrderAttack>& order) const { order->GetCharactor(); }
    void operator()(const std::shared_ptr<OrderBuff>& order) const { order->GetCharactor(); }
    void operator()(const std::shared_ptr<OrderSummon>& order) const { order->GetCharactor(); }
};

// 技の実行をまとめている構造体
struct ExecuteOrder
{
    void operator()(const std::shared_ptr<OrderAttack>& order) const { order->ExecuteOrder(); }
    void operator()(const std::shared_ptr<OrderBuff>& order) const { order->ExecuteOrder(); }
    void operator()(const std::shared_ptr<OrderSummon>& order) const { order->ExecuteOrder(); }
};

// ---------------------------------------------------
// オートプレイの取りまとめクラス
class AutoPlay
{
public:
    std::vector<std::variant<std::shared_ptr<OrderAttack>, std::shared_ptr<OrderBuff>, std::shared_ptr<OrderSummon>>> OrderList;

    // このターンで実行したい、全部のオートプレイの技を実行する
    void ExecuteAllOrder()
    {
        for (auto const& order : OrderList)
        {
            std::visit(ExecuteOrder{}, order);
        }
    }

    // 特定の何かの値を更新したい
    // 例)プレイヤーの攻撃力をあとから更新
    void UpdatePlayerAttackPoint(int newHitPoint)
    {
        for (auto& order : OrderList)
        {
            // OrderAttackオブジェクトを探す
            auto attackOrder = std::get_if<std::shared_ptr<OrderAttack>>(&order);
            if (attackOrder && (*attackOrder)->GetCharactor() == CharactorId::Player)
            {
                (*attackOrder)->UpdatePlayerAttack(newHitPoint);  // (★3')
                break;
            }
        }
    }
};

int main()
{
    std::cout << "===AutoPlay===\n";

    auto autoPlay = std::make_unique<AutoPlay>();

    // 何の技が出せるのかは、今出している技を見たりして登録していく
    autoPlay->OrderList.emplace_back(std::make_shared<OrderAttack>(CharactorId::Player, 10));
    autoPlay->OrderList.emplace_back(std::make_shared<OrderSummon>(CharactorId::FriendAlice, CharactorId::FriendBob));

    // 例えば、ここの時点で何かバフがかかったとして、既にPlayerに登録されている値をこの時点で更新したい、という時
    autoPlay->UpdatePlayerAttackPoint(20);

    autoPlay->OrderList.emplace_back(std::make_shared<OrderBuff>(CharactorId::FriendBob, 5));

    // 実行したい技が整ったので、実行!
    autoPlay->ExecuteAllOrder();
}
===AutoPlay===
Attack:20
Summon:2
Buff:5

先ほどは OrderType のenumを使って、技の処理を振り分けしていたのですが

1
struct GetCharactorId{}

これをうまく使うことによって、場合分けしなくて済むようになりました

さっきは、std::function のおかげで実行時のみは、かろうじてポリモーフィックに動作していましたが、std::variant のおかげで、他の処理も技の型( OrderType で持っていたもの)に依存せずに記載することができました(★3’)

また、struct GetCharactorIdstruct ExecuteOrder を定義する必要があるものの、class OrderAttack はインタフェースや継承を持っていないので、なんだか自由を手に入れた気分になります

std::variant を使ってみると、こんな書き方が出来るのか~という自分なりの新たな発見でした

終わりに

今回、シミュレータを作った際に、色々と勉強になることがあったので、ひっくるめて記載してみました

シミュレータという機能がニッチながらも、ゲーム開発においては重要なポイントになりそうなこと、
また今回記載した std::variant を使って書いた処理は Visitor Pattern というらしいですが
継承、インタフェース、コンポジションなどなど、やりながらあれ?これは?という箇所がたくさんあったので、そういったコードを試行錯誤できたことなどが良い経験だったなーと思って書きました

自分としては継承を使った記載方法でも、今回は良さそうだなと思ったし、
最初に記載した、 AutoPlayOrder という手続き的な神クラスの記載方法も、技の数が少ないなど状況によってはそこまで悪くないかもなと思いました

シミュレータを突き詰めていくと、技や手札やゲーム環境などのパラメータ追加の容易さと、実行時にいかに高速に動作することが求められてくるので
そうなっていくとまた設計思想も変わってきそうだなとも思いましたが、今回はこのへんまでで終わろうと思います!

参考

std::variant に気が付けたのは、CppCon2022の Klaus Iglberger さんの
「Breaking Dependencies - The Visitor Design Pattern in Cpp」
というセッションを見て、おおおおおーと思ったからです

C++を言語にしたデザインパターンの書籍も出されています

この著書の中で、ベースクラスそんなに気軽に更新できないし、純仮想関数を強いた日には同僚とのバーベキューパーティに呼ばれなくなるかもしれないよ!という注釈が何度も出てきて、個人的にウケました


[C++] C++の新しいパターンマッチ「inspect」

この記事は meetup app osaka@7 online への参加記事です

先日 2023/2/20 ごろにISO WG21 C++ 委員会メンバーのAntony PolukhinさんがMedium(Yandexサイト)で 「C++23が完成したよ~」と記事を書かれていました

それでちょっと新しいC++の機能には何があるのかなと調べていたところ
知らない文法が増えそうな予感の「Pattern Matching」inspect を見つけたので
それについてメモしてます

これはどこのC++に入るかは決まってないので、将来的には採用されないかもしれないです


<試した環境>


もともとパターンマッチするといえば、こんな感じで switch case を使ってると思います

1
2
3
4
5
6
7
8
9
int x(42);
switch (x) {
    case 0: std::cout << "zero"; break;
    case 42: std::cout << "forty-two"; break;
    default: std::cout << "something else";
}

// output
// "forty-two"

これが、inspect を用いるとこんな感じに書けます

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// number
int x(42);
inspect(x) {
    0 => { std::cout <<  "zero"; }
    42=> { std::cout <<  "forty-two"; }
    _=> { std::cout <<  "something else"; }
};

// output
// "forty-two"

文字列マッチもこんな感じに

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// string
std::string s = "foo";
inspect (s) {
    "foo" => { std::cout << "got foo";}
    "bar" => { std::cout << "got bar";}
    __ => { std::cout << "don't care";}
};

// output
// "foo"

タプル的な値のマッチも比較的少ない行数で書ける

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// tuple
auto box = std::pair(0, 42);
auto&& [i, j] = box;
inspect (box) {
    [0, 0]=> { std::cout << "all zero";}
    [1, 0]=> { std::cout << "i=1, j=0";}
    [0, 1]=> { std::cout << "i=0, j=1";}
    [__, __]=> { std::cout << i << ',' << j;}
};

// output
// 0,42

ポリモーフィックに型判定も出来るらしい
というのも、現時点ではまだ実装されていないみたいで動作しなかったです

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// polymorphic type
struct Fruits { virtual ~Fruits() = default; };
struct Orange : Fruits { int ore; };
struct Apple : Fruits { int ap; };
auto o = new Orange();
inspect (o)
{
//    2023/3/3 時はコンパイルエラー
    <Orange> => { std::cout <<  "Orange"; }
    <Apple>  => { std::cout <<  "Apple"; }
};

これが出来れば、あとはテンプレートのオペレーター演算子も比較的少ない行数で実装できるという…


まだまだ未来の機能ですが、差分学習しておかないと完全に置いて行かれますね…
C++の言語機能は複雑すぎます

とはいえ業務コードでも、競プロなどでも
少ない行数で書けるのはとても良いと思うので(=バグの入る余地が減る)
どんどん進化していってもらいたいですね!

<参考>


[C++] VC++でHackerRankの環境構築

この記事は C++ Advent Calendar 2022 7日目の記事です。

(内容まとめ)
競プロ勢にとったら当たり前のローカル環境設定についての記事です
自分の言葉でまとめなおしたメモになります
みんな!AtCoder もいいけど HackerRank も面白いよ!


今年の3月ごろ、同僚(海外の方)から
「HackerRank 結構いいよ、わたしは週末によくやってるよ~」
と教えてもらいまして
たまにポチポチ解いています

ざっくり言うと、お題に対するコードを書いて、要求通りの出力が出来れば Pass! でポイントが溜まっていってバッチがもらえたりします
わたしは主にC++の Problem Solving を解いています
(数学系の問題も多いです)

C++ language のジャンルで解くことも出来ますが、こっちは文法問題なので、あまり面白くない

HackerRankは他のオンラインコーディングプラットフォームとはちょっと異なっていて
競技で問題を解いてランクを上げるというよりは、スキルの習得に焦点を当てているような気がします
解いて欲しい問題用の関数がミニマムに提供されてて、最初に記載するであろう入力系の処理が予め記載されているので、解くのに集中出来ますね

で、それまで真面目に Easy 問題を解いていたのですが、簡単すぎるし量も多い…つまらん…
となって、教えてくれた同僚に
「HackerRank、何の問題を解いてる?」
と聞いたところ、
「えぇー Easy やってるの?時間の無駄だよ~わたしは Hard しかやらないよ」
と突っ込まれました><。

さてそこで、Hard 問題をやり始めましたが、あら。。。難しい。。。
こんなけ難しかったらデバッグしたくなるんだけど、HackerRankのサイトに組み込まれたコードエディタ上から問題を解くので、プリントデバッグしか方法がない
変数の中身や配列の中身をもっとアグレッシブに確認したい…

前置きが長くなりましたが、HackerRankをやるときに、Visual Studio上でコードを書いて確認する方法を記載しましたmm

<今回の環境>


VSにコードコピペしてHackerRankの問題を解きたい

コピペ出来ないとしんどいです!

しかし HackerRank も他の競プロと同じく、#include <bits/stdc++.h> があります
そのまんまじゃリンク通りません

なので、ライブラリを認知する箇所に stdc++.h を置いておけば良いのです

他にも色んな方が公開されてますけど、検索するとこの方の↑↑↑リンクがよく見つかりました

これを、VC++がデフォで認知する場所に置きます

一番簡単な場所に調べ方は、

1
#include <iostream>

とコードに書いて、F12iostream の場所までジャンプ
→ソースのタブを右クリックすると、そこのディレクトリの場所までジャンプ出来る

ちなみにわたしの場合は、ここにありました
C:\Program Files (x86)
 \Microsoft Visual Studio
  \2019
   \Enterprise
    \VC
     \Tools
      \MSVC
       \14.29.30133
        \include

エラーやワーニングが出る場合

めんどいですが、毎回エラー除去のための define を書くのが無難な対応だと思います
(業務のコードじゃないし)

例えば、HackerRank でよく出てくるこんなコード

1
ofstream fout(getenv("OUTPUT_PATH"));

エラーになります

error C4996: ‘getenv’: This function or variable may be unsafe. Consider using _dupenv_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.

あんた getenv は非推奨だけど使いたいなら、_CRT_SECURE_NO_WARNINGS 定義しなさいよ!ってやつです
ええ、使いたいんです、HackerRank にコードコピペしたいんで!
なのでわたしはガッツリと先ほどの stdc++.h の上の方に定義を追記しています

自分はVSに慣れてるので、慣れた環境でコード書けるのが快適ですね

(余談) VSCodeでやろうとしたが…

最初は、Windoows の VSCode で環境構築をしようとしてたのですが、なかなか手ごわいです…

mac はさくっとものの数分で C++デバッグ環境が構築できたのに
Windows だと、半日かけてもうまく動きませんでした><。
(VC++ がうまく動かなくて、gcc はなんとか動作したって感じです)

色々調べていた時に、VSCode on Windows 情報でいちばん面白かったのが、この中国の投稿サイトでした

わたしは中国語はよくわからないので、Googleのページ翻訳でよくここの Zhihu サイトを見るのですが
この記事は見てて笑っちゃいました。。。
情報量も多いし、皮肉アリ、ぶっちゃけトークありで、読み物として面白かったです

ここを見て、あ、VSCode on Windows でVC++使うのちょっと大変かも…と思いました
中国は同じように感じがあるので、そこの悩みも詳細にかかれてて、親近感沸きました

あんまり日本でこういう記事見ないな~なんでだろ、ぶっちゃけすぎるからかな?

C++だけじゃなくてUnityの情報も多いです、知識投稿サイトってやつかなー?とにかく面白い


(まとめ)

コード書くなら開発環境は整えようね!

C++ で mac なら VSCode だけど、Win なら VS 使おうぜ!

HackerRank ほんとお勧め、地道に解くのが楽しい


(参考)


[C++] 2次元Vectorのメモ

HackerRankやってるときによく2次元配列が出てくるので、ちゃちゃっとやりたいときのメモです

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 2次元配列の宣言,,,出来れば v じゃなくて用途名にしたいところ…
std::vector<vector<int>> v(2, vector<int>(3));
// こんな配列が出来てる↓↓↓
v.at(0).at(0);
v.at(0).at(1);
v.at(0).at(2);
v.at(1).at(0);
v.at(1).at(1);
v.at(1).at(2);

// 呼び先でも内容を更新したいときは参照が楽…
void func(vector<int> &t)
void func(vector<vector<int>> &v)

// べた書きループはかなり楽
for(unsigned int i = 0; i < v.size(); ++i){
    for(unsigned int j = 0; j < v.at(i).size(); ++j){
        // 何か処理
    }
}
// べた書きループ、
// サイズチェックが気になる or 他でも利用するなるなら…
unsigned int row = v.size();
for(unsigned int i = 0; i < row; ++i){
    auto &box = v.at(i);
    unsigned int col = box.size();
    for(unsigned int j = 0; j < col; ++j){
        auto &ck_data = v.at(i).at(j);
        // 何か処理
    }
}

業務や後に残るコードではなく、とにかくちゃちゃっとやりたい、という要望って
自分はこれまで出会ったことが無かったので
この手のコーディングは、自分的にはかなり新しいというか新鮮な感じがします


[C++] Windowsの空のアプリケーションを作る手順メモ

いつもどうやったっけな?と思って同じ過ちを繰り返してるのでメモ

環境:
Visual Studio 2022

作りたいソリューション

いつもわたし、空のプロジェクトを作りたいからといって
Empty Project を選んでしまうのです…
すると、コンソールベースの exe を作るソリューションになってしまいます
ちゃうねん、これちゃうねん!


Windowアプリの空のソリューションを作りたい場合はこれ

  1. Create a new project
  2. Windows Desktop Wizard を選択
  3. Configure your new project で名前を入れて次に進む
  4. Windows Desktop ProjectDesktop Application (.exe) を選択
  5. 更に Empty project にチェックを入れる
  6. これで OK を押せば、よし!

これこれ、、、これやねん。。。


[C++] ラムダのパラメータリスト()が省略できるようになった

この記事は meetup app osaka@6 の参加記事です。

C++23 でタイトルの通り、パラメータリストのカッコが省略できるようになりました

gccなら12以上、Clangは14以上で動作するようです

1
2
3
4
[=]
// ()    // mutable 書いてても省略可能に!
mutable
{}

(もう少し追記するとおもいますが、今はこれで…)


[C++] HackerRank始めました

友達に、HackerRankやってみるといいよと言われたので、始めてみました

自分のプロフィールはこれ。。。
まだC++の問題を2問解いただけだけど
https://www.hackerrank.com/haruka_sao

履歴のために、自分の解いたコードをGitHubにアップしたりして…

https://github.com/h-sao/HackerRank/

ランクによってバッチが付きます

バッチは Language Proficiency でポイントを稼ぐとスターが付いていく方式です

最初に触ってみたのは c++ Language Proficiency Badges だったのですが
どう考えても面白くないというか、C++もう業務で何年も使えてるので
言語のほうは余暇で楽しむとして、アルゴリズム問題のほうを頑張っていこかなと思います

Problem Solving Badges は

こんな感じ…
先は長そうだけど、Gold目指して勉強になりそうだからぼちぼちやってみよかな~

他の競技プロブラムとは違って、自分のペースで取り組めそうなモードなので、わたしには合ってる予感

普段業務で使ってる/使ってたことでも、
自分なりに勉強してバージョンアップしていかないといけないなーと思ってたので、ちょうど良いタイミングで始められたような気がする

(自分が学生だったら、こういうのにめっちゃのめりこんでたんだろうなーと思うと、今の学生さんは環境が良くてうらやましいです)

時代遅れな人にならないように意識していかないとね。。。


[C++] Advent Calendarへの参加履歴 2010-2021

自分メモです

過去に参加した C++ Advent Calendar の自分の記事のリンク

C++ Advent Calendar jp 2010

Boost Advent Calendar 2011

C++ Advent Calendar jp 2012

C++ Advent Calendar 2013

C++ Advent Calendar 2021


意外と参加してました。。。

こうやって記録に残すのは、なんだか気分が良いですね


[C++] Modulesのコンパイル(MSVC ver)とBMIについて

この記事は C++ Advent Calendar 2021 1日目(初日!)の記事です。

少し前からC++に Modules がやってきました
C++20 対応のメジャーどころのコンパイラ(MSVC/gcc/Clang など)で使うことができます

個人的にはビックウェーブが来たーーーと思ってまして、つねづねポチポチと Modules について調べていました

それを少しまとめたいと思います

モジュールの説明

昔ながらのプリコンパイルヘッダの概念を、今風にした感じでしょうか

ヘッダファイルをインクルードしていたものを Modules に置き換えることが出来ます

ヘッダファイルだと、

インクルードの順番に気を付けたり
コンパイル時間が長くなったり
インクルードガード書いたりと
ゆーてローテク文字列だったのですが、

それがバイナリとして公式に提供されました

Modulesを記載するファイル

各コンパイラによって(推奨)拡張子が異なります

(参考)

以下、、MSVCで話を進めます
(コンパイラによって異なる所が多いので)

モジュールファイルについて

MSVC の場合、モジュールにしたいファイルは .ixx という拡張子を付けます
(Clangだと .cppm、GCCだと .cxx とかになります)

サンプルはこんな感じ

hello.ixx

1
2
3
4
5
6
// hello.ixx:
export module MyHello;

export int f() {
    return 42;
 }

利用する側はこんな感じで↓↓↓ import すれば使えます
(従来は #include "hello.h" とか書いていた場所です!)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// main.cpp 
#include <iostream>
#include <cstdlib>

import MyHello;  // モジュール使うよの宣言

int main() {
  auto a = f();

  std::cout << f();
}

サンプルだけ見ると、なんてことのないコードですね
メイン関数は #include してた箇所を inport に変更しただけです

モジュールをバイナリ化する

モジュールを記載する .ixx ファイルは、.ifc にプリコンパイルでバイナリ化されます

ややこしいのが、コンパイラによってこのあたりの呼び名が異なってて、こんな感じ

MSVC Clang
モジュールファイル名  → .ixx .cppm
モジュールファイルをプリコンパイルしたもの→ .ifc .pcm

ちなみに「モジュールファイルをプリコンパイルしたもの」を BMI と呼び、
Binary Module Interface の略になります

この BMI ファイルを各 cpp ファイルなどが import することになります

(参考)

VC++でのコンパイル方法

単純のため、まずはコマンドラインでやってみます

利用したVC++バージョンはこちら

コマンドは2つ

1
2
> cl /c /std:c++20 /EHsc hello.ixx
> cl /std:c++20 /EHsc /reference MyHello=MyHello.ifc main.cpp hello.obj

1つ目のコマンドでモジュールをプリコンパイルし、
2つ目のコマンドで obj をリンク、そして main.exe の出力をします

<1. モジュールのプリコンパイル>

C:\my\dev\sample_module01>cl /c /std:c++20 hello.ixx
Microsoft (R) C/C++ Optimizing Compiler Version 19.30.30705 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

hello.ixx

これで、hello.objMyHello.ifc が出来ました

(余談)cl.exe はモジュールのコンパイル時に同時に2つ(.obj.ifc)のファイルを出力してくれて楽です

Clang は別々のコマンドで出力するので、cl.exe とは少し作り方が異なっています
.pcm ( VC++で言うところの .ifc) を出力した後、それを元に .o ( VC++の .obj) を出力します
このあたりはまだ次回に投稿したいです

<2. 実行ファイル生成>

C:\my\dev\sample_module01>cl /std:c++20 /EHsc /reference MyHello=MyHello.ifc main.cpp hello.obj
Microsoft (R) C/C++ Optimizing Compiler Version 19.30.30705 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

main.cpp
Microsoft (R) Incremental Linker Version 14.30.30705.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:main.exe
main.obj
hello.obj

main.exe が出来ました

実行してみます

C:\my\dev\sample_module01>main.exe
42

問題なし(^^♪

(備考)コンパイルオプションの注意点

VC++のコマンドラインオプションが

これらの間で一致していないとき、ワーニングが出ました(C5050

main.cpp(7): warning C5050: Possible incompatible environment while importing module ‘MyHello’: _CPPUNWIND is defined in current command line and not in module command line

そのため今回、hello.ixx 内では <iostream> は利用していないのですが、/EHsc オプションを付けています

(参考)

BMIってなによ?

プリコンパイルされた Binary Module Interface って何なのでしょうか?
この中身は一体?

さんざん調べましたが、大した情報が載ってないですね。。。

これ、BMIというのは Modules を扱うための概念のようです

これはコンパイルの過程のものなので、各コンパイラベンダーが作る箇所であり、明確に公開情報として詳細が提示されているわけではなさそうです
現時点では概念がちらほらと記載されているだけのようでした

実際に、プリコンパイルで作られた .ifc.pcm の中身は

がひとまとめにされてるようです

という記述を見つけましたが、出展元が判らなくなったので、思い出したら追記します。。。

(2021/12/4追記)
CppCon2019の資料が参考になりました!
Practical Cpp Modules - CppCon 2019
https://github.com/CppCon/CppCon2019/blob/master/Presentations/practical_cpp_modules/practical_cpp_modulesboris_kolpackovcppcon_2019.pdf

もちろん、コンパイラによって、さらにバージョンによっても形式は様々なんでしょう

モジュールの配布ってどうやるの?

モジュールのバイナリを配布することはできないです
共有する場合はあくまでソースコードと共に配布になります

また、作られた BMI、つまり .ifc.pcm ファイルは不変的なバイナリではないみたいです

ライブラリのように配布はできないのですね。。。

(余談)Modules の提案を推し進めている Gabriel Dos Reisさん は、Common Module Interface Format を作りたいみたいですが…
具体的に今の段階では、配布目的のものは出ていないようです

(参考)

所感

まだ2021年12月の時点では、Modules 機能が完全に動作するコンパイラもないみたいですし
そもそも各コンパイラによって記載方法や推奨が異なるので、なんとも道半ばな印象はあります

Modules を利用するための手順やお作法も多く、また情報も限られているので、取り組みにくいですね

マイクロソフトの VC++チームブログ は、コンパイラベンダーとして結構 Moduleds の具体的な情報を出しているように感じました
(Clangももうちょっと頑張って。。。あとGCC。。。おまえはやる気あるのか。。。)

Modules に関して取り組まれている人たちの歴史はとても長く
かつ現在のヘッダファイルがベストだとは思えないので
個人的に一押ししたい機能だと改めて思いました

所感その2

C++ Advent Calendar!

ずいぶん昔に一度だけ参加したことがあったのですが、それからなーんにもしてませんでした

今回、意を決して投稿できて良かったです

かなりよく調べたつもりですけど、間違いなどあったら気軽に @hr_sao まで教えていただけると嬉しいです

C++楽しい~♪


[C++]Compiler Explorerの出力結果の表示方法

Compiler Explorer というオンラインコンパイルサービスがあります

書いたC++コードのアセンブリ出力結果をリナルタイムに表示してくれるサービスです

すごーーー👀
初めてこのサービスを見た時には感激しました〜

さておき、
わたし、これで実行結果を見る方法を知らなかったんですね…ハズカシー(*ノдノ)

「Output」を押すと出力結果ウィンドウは出てきますが
このリターン値だけじゃなくて、 std::cout の結果も欲しいんですけど…👀

ちょっと調べてみたらすぐに判明しました><。

Outputの設定項目に Execute the code のチェックボックスがあるので、それを選択すれば表示されます

出力はこんな感じですね〜

便利です〜