Skip to content

Instantly share code, notes, and snippets.

@kubo39
Last active September 4, 2020 02:31
Show Gist options
  • Save kubo39/a040696cb1e5498c89aad6d6d71e5d48 to your computer and use it in GitHub Desktop.
Save kubo39/a040696cb1e5498c89aad6d6d71e5d48 to your computer and use it in GitHub Desktop.
随時更新予定

CEDEC2020の内容とD言語の対応

1. std::clampを使おう

  • std.algorithm.comparision.clamp を使う
import std.algorithm.comparison : clamp;

void compress(ref int quality)
{
    quality = clamp(quality, 0, 100);
}

void main()
{
    int quality = 110;
    compress(quality);
    assert(quality == 100);
}

2. 標準ライブラリの数学定数を使おう

  • std.math に数学定数が定義されている
import std.math;
import std.stdio;

void main()
{
    writeln(2.0 * PI);  // real型
    writeln(2.0 * cast(double) PI); // double型
}

3. std::stringに追加された便利関数を使おう

  • std.string に便利関数が定義されている
import std.string : endsWith, startsWith;

void main()
{
    string path = "test.png";
    assert(path.endsWith("png"));
    assert(!path.endsWith("jpg"));

    string url = "https://dlang.org";
    assert(url.startsWith("https://"));
}

4. 連想コンテナ内のキーの有無の確認をcontainsで明快にしよう

  • 組み込み連想配列に対する操作inでキーの有無チェックが可能
import std.stdio;

void main()
{
    int[string] table = ["one": 1, "two": 2];
    auto p = "one" in table;
    if (p !is null)
        writeln("oneというキーを持つ要素が存在");
}

5. using enumで短く

  • より汎用的な with文 を使う
enum ImageFormat { BMP, PNG, JPEG, DDS };

// 返り値の型手抜き
int decode(ImageFormat format)
{
    switch (format) with (ImageFormat)
    {
    case BMP: return 0;
    case PNG: return 1;
    case JPEG: return 2;
    case DDS: return 3;
    default: return -1;
    }
}

static assert(decode(ImageFormat.PNG) == 1);

6. コンテナからの要素の削除

  • std.algorithm.mutation.remove を使う
import std.algorithm.mutation : remove;
import std.array : array;
import std.stdio : writeln;

void main()
{
    int[] v = [8, 9, 10, 11, 12];
    int[] arr = v.remove!(a => a < 10).array;
    arr.writeln();
}

7. 変数未使用の警告を抑制

  • できない

8. bit操作のための便利な関数

  • core.bitop が使える
import core.bitop;
import std.stdio;

void main()
{
    immutable ushort x = 0b0001_0110_1110_1111;
    writeln(rol(x, 2));
    writeln(ror(x, 2));
    writeln(popcnt(x));
}

9. 配列にもコンテナにも使えるサイズ・イテレータ関数

  • プロパティlengthが使える
  • range(イテレータみたいなもの)に対しては std.algorithmstd.range の関数が使える
import std.algorithm.mutation : fill;
import std.stdio : writeln;

void f(T)(T c)
{
    size_t n = c.length;
    c.fill(0);
}

void main()
{
    int[] v = [1, 2, 3, 4];
    int[4] a = [10, 20, 30, 40];
    v.f;
    a[].f; // スライスで渡す
    v.writeln;  // [0, 0, 0, 0]
    a.writeln;  // [0, 0, 0, 0]
}

10. ソースコード上の位置情報を手軽に扱う

  • __traits(getLocation, ...) を使う
import std.stdio : writefln;

void main()
{
    enum s = __traits(getLocation, main);
    writefln("ファイル名: %s", s[0]);
    writefln("行番号: %d", s[1]);
    writefln("列番号: %d", s[2]);
}

11. オブジェクトのバイト列の表現

  • ビット操作のみを許容するような型はない
    • std.bitmanipを使う

12. 範囲を一つの引数で渡すアルゴリズムを使おう

  • そもそもrangeは範囲をひとつの引数で渡すので特別なことは必要ない
import std.algorithm;

void main()
{
    int[] v = [1, 2, 3, 4, 5];
    auto b = v.count!"a % 2 == 0";
    auto max = v.maxElement;
    v.sort; v.fill(5);
}

13. 標準のファイルとディレクトリ操作を使おう

  • std.file を使う
import std.algorithm;
import std.file;
import std.stdio;

void main()
{
    if ("test.txt".exists)
        copy("test.txt", "test2.txt");
    mkdirRecurse("aaa/bbb/ccc");
    "test.txt".getSize;
    dirEntries("./", SpanMode.depth)
        .map!"a.name"
        .each!writeln;
}

14. 初期化付き分岐条件

  • 特に制限なく使える
void main()
{
    import std.stdio : writefln;
    string[] ss = ["aa", "bb", "cc"];
    foreach (i, s; ss)
        writefln("%d: %s", i, s);
}

15. 比較演算子の実装を楽に

  • 三方演算子相当はない

  • 比較演算子は opEquals opCmp を定義する

struct Date
{
    int year, month, day;

    // auto refを使えばl-valuesにもr-valuesにも適用される
    bool opEquals(auto ref const Date other) const
    {
        return year == other.year
            && month == other.month
            && day == other.day;
    }

    int opCmp(ref const Date other) const
    {
        if (year < other.year) return -1;
        else if (year > other.year) return 1;
        else if (month < other.month) return -1;
        else if (month > other.month) return 1;
        else if (day < other.day) return -1;
        else if (day > other.day) return 1;
        else return 0;
    }
}

16. 構造体のメンバ変数を名前でわかりやすく初期化しよう

  • ベストプラクティスとしてdisableを用いてdefault constructionを禁止するidiomがある
  • 以下のように明示的に書く
struct D3D12_VIEWPORT
{
    float topLeftX;
    float topLeftY;
    float width;
    float height;
    float minDepth;
    float maxDepth;

    @disable this();
    @disable this(this);

    this(float topLeftX,
         float topLeftY,
         float width,
         float height,
         float minDepth,
         float maxDepth)
    {
        this.topLeftX = topLeftX;
        this.topLeftY = topLeftY;
        this.width    = width;
        this.height   = height;
        this.minDepth = minDepth;
        this.maxDepth = maxDepth;
    }
}

void main()
{
    auto viewport = D3D12_VIEWPORT(
        0.0f,
        0.0f,
        1280.0f,
        720.0f,
        0.0f,
        1.0f);
}

17. リテラル演算子を定義して定数を簡潔に

  • ない

18. 柔軟になったラムダ式のキャプチャ

  • そもそもキャプチャを選択はできない

19. 構造化束縛

  • これはできない

  • 以下のような書き方は可能

import std.meta : AliasSeq;
import std.typecons : Tuple, tuple;
import std.stdio : writefln;

Tuple!(double, double) getTwo()
{
    return tuple(35.2, 136.9);
}

void main()
{
    double x, y;
    AliasSeq!(x, y) = getTwo();
    writefln!"%f,%f"(x, y);
}

20. [[nodiscard]]

  • これはできない

21. クラステンプレートのテンプレート引数推論

  • これはできない

22. 無効値を表現したいときに std::optional を使おう

  • std.typecons.Nullable を使う
import std.stdio;
import std.typecons;

void main()
{
    Nullable!int oi;
    Nullable!int oi2 = 200;

    if (!oi.isNull)
        oi.get.writeln; // ここは実行されない
    if (!oi2.isNull)
        oi2.get.writeln;

    oi = 100;
    oi2.nullify;

    if (!oi.isNull)
        oi.get.writeln;
    if (!oi2.isNull)
        oi2.get.writeln; // ここは実行されない
}

23. 型安全な共用体を std::variant で実現

  • std.variant.Algebraic を使う
import std.stdio : writeln;
import std.variant : Algebraic;

void main()
{
    Algebraic!(string, double, bool) v1 = 3.14, v2 = "Hello";

    if (v1.type == typeid(double))
        writeln(v1.get!1);

    if (v2.convertsTo!string)
        writeln(v2.get!string);

    v2 = true;
    writeln(v2.type);
}
  • visit関数もある
import std.stdio : writeln;
import std.variant : Algebraic, visit;

void main()
{
    Algebraic!(string, double, bool) v1 = 3.14, v2 = "Hello";
    auto result = v2.visit!((string s) => s,
                            (double d) {
                                import std.conv : to;
                                return d.to!string;
                            },
                            (bool b) => b ? "true" : "false");
    result.writeln;
}

24. テンプレートの型に応じた処理には constexpr if を使おう

  • static ifis式 の組み合わせでわりとなんでもできる
import std.stdio;

auto getValue(T)(T t)
{
    static if (is(T U : U*))
        return *t;
    else
        return t;
}

void main()
{
    int a = 100;
    int* p = &a;
    a.getValue.writeln;
    p.getValue.writeln;
}
  • 特殊化テンプレートでも可能
import std.stdio;

auto getValue(T : int)(T t)
{
    return t;
}

auto getValue(T)(T t)
{
    return *t;
}

void main()
{
    int a = 100;
    int* p = &a;
    a.getValue.writeln;
    p.getValue.writeln;
}

25. テンプレートパラメータにコンセプトで制約を加えよう

  • template constraints を使う
import std.math;
import std.stdio;

T mod(T)(T x, T y) if (__traits(isIntegral, T))
{
    return x % y;
}

T mod(T)(T x, T y) if (__traits(isFloating, T))
{
    return fmod(x, y);
}

void main()
{
    mod(12, 5).writeln;
    mod(1.75, 1.5).writeln;
}
  • 制約の定義も可能
enum isAddable(T) = __traits(compiles, T.init + T.init);

auto add3(T)(T a, T b, T c) if (isAddable!T)
{
    return a + b + c;
}

static assert(add3(1, 2, 3) == 6);

26. CPOを優先して使おう

  • D言語ではCPOは関係がないので省略

27. 文字列を std::string_view で受け渡ししよう

  • stringは暗黙に参照型で受け渡されるのでとくに注意する必要はない

28. ポインタのバイト境界をコンパイラに伝えよう

29. 配列要素の効率的なシフト操作を知ろう

30. 非順序連想コンテナをもっと効率よく使おう

31. ステートレスなメンバ変数のメモリ消費をゼロにしよう

32. 分岐の起こりやすさをコンパイラに伝える方法を知ろう

  • おそらくポータブルな方法はない
    • GDCやLDCには拡張がある

33. 数値->文字列の変換にはstd::to_charsを使おう

  • より汎用的な std.conv.to を使う
import std.conv : to;
import std.stdio : writeln;

void main()
{
    double x = 3.141593;
    string s = x.to!string;
    s.writeln;
}

34. 文字列->数値の変換にはstd::from_charsを使おう

  • より汎用的な std.conv.parse を使う
import std.conv : parse;
import std.stdio;

void main()
{
    string s1 = "3.14159265358979323";
    const d = parse!double(s1);
    writefln!"%f"(d);
}

35. 洗練された文字列フォーマットを使おう

  • std.format.format を使う
import std.format : format;
import std.stdio : writeln;

void main()
{
    string a = format!"C++ %d"(20);
    a.writeln;
}

36. コンパイル時に実行できる標準ライブラリ関数

  • enumで受けれるかどうかで判断できる
import std.algorithm : sort;

enum sorted = [15, 30, 10, 5].sort;
static assert(sorted[0] == 5);

37. コンパイル時と実行時で処理をわける方法

  • __ctfe で分岐
import std.random;

double sqrt(double x)
{
    if (__ctfe)
    {
        return 1.414; // 手抜き
    }
    else
    {
        static import std.math;
        return std.math.sqrt(x);
    }
}


void main()
{
    enum a = sqrt(2.0);
    double d = sqrt(uniform(0.0, 1.0));
}

38. 新しいアロケータを知ろう

39. モジュール

  • そもそも全部モジュールでできている

40. 中断と再開をサポートする「コルーチン」

  • core.thread.fiber とか std.concurrency.Generator を使う
import std.concurrency;
import std.stdio : writeln;

void main()
{
    auto r = new Generator!int({
            foreach (i; 0..10)
                yield(i);
        });
    foreach (i; r)
        writeln(i);
}

41. 契約プログラミング

  • inで事前条件を、outで事後条件を指定
double sqrtChecked(double x)
in(x >= 0)
out(r; r >= 0)
do
{
    static import std.math;
    return std.math.sqrt(x);
}

void main()
{
    auto r = sqrtChecked(-1.0);
}

42. 柔軟な条件分岐「パターンマッチング」

  • できない
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment