35. 無効値を表現できる型¶
無効値を表現できる型 Optional
の基本的な使い方を学びます。
35.1 Optional¶
Optional<Type>
はstd::optional<Type>
に相当する型ですType
型のすべての表現に加え、有効な値を持たないことを表す「無効値」を持つことができる型です- イメージとしては、サイズが 0 か 1 のどちらかである
Array<Type>
です- 有効な値を持つとき、配列のサイズは 1 で、その値にアクセスできます
- 無効値のときはサイズが 0 で、値にはアクセスできません
Optional<Type>
型の値は、初期値を与えられなかった場合、無効値として初期化されますOptional
の値をPrint
で出力すると、有効値の場合は(Optional)
とその値が出力され、無効値の場合はnone
が出力されます
# include <Siv3D.hpp>
void Main()
{
// 有効値で初期化する
Optional<Point> pos1 = Point{ 100, 200 };
// 無効値で初期化する
Optional<Point> pos2;
Print << pos1;
Print << pos2;
while (System::Update())
{
}
}
35.2 有効値を持つかを調べる¶
Optional
型の値opt
が有効値を持つかを調べるには、次の方法がありますopt.has_value()
がtrue
を返す場合、有効値を持つif (opt)
またはif (not opt)
で調べる
# include <Siv3D.hpp>
void Main()
{
// 有効値で初期化する
Optional<Point> pos1 = Point{ 100, 200 };
// 無効値で初期化する
Optional<Point> pos2;
Print << pos1.has_value();
Print << pos2.has_value();
if (pos1)
{
Print << U"pos1 has a value";
}
if (not pos2)
{
Print << U"pos2 does not have a value";
}
while (System::Update())
{
}
}
35.3 有効値へのアクセス¶
Optional
型の値opt
が有効値を持つ場合、*opt
でその値にアクセスできますopt->x
やopt->y
のように、->
演算子を使ってメンバにアクセスすることもできます- 有効値を持たないときに値にアクセスしてはいけません
# include <Siv3D.hpp>
void Main()
{
// 有効値で初期化する
Optional<Point> pos = Point{ 100, 200 };
if (pos)
{
Print << *pos;
pos->x += 20;
pos->y += 30;
Print << *pos;
}
while (System::Update())
{
}
}
35.4 無効値にする¶
none
はOptional
型の無効値を表す定数ですOptional
型の値opt
に無効値を代入するには、opt = none
としますopt.reset()
でも同じです
# include <Siv3D.hpp>
void Main()
{
Optional<Point> pos = Point{ 100, 200 };
Print << pos;
pos = none;
Print << pos;
pos = Point{ 300, 400 };
Print << pos;
pos.reset();
Print << pos;
while (System::Update())
{
}
}
35.5 有効値または代わりの値の取得¶
.value_or(defaultValue)
は、有効値を持つ場合、有効値の値を返し、無効値である場合はdefaultValue
を返します
# include <Siv3D.hpp>
void Main()
{
Optional<Point> pos1 = Point{ 100, 200 };
Optional<Point> pos2;
// pos1 は有効値を持つため Point{ 100, 200 } を返す
Print << pos1.value_or(Point{ 0, 0 });
// pos2 は有効値を持たないため Point{ 0, 0 } を返す
Print << pos2.value_or(Point{ 0, 0 });
while (System::Update())
{
}
}
35.6 if との組み合わせ¶
- 次のように
if
と組み合わせることで、Optional
型の値が有効値を持つ場合の処理を簡潔に書くことができます
# include <Siv3D.hpp>
Optional<int32> GetResult1()
{
return 123;
}
Optional<int32> GetResult2()
{
return none;
}
void Main()
{
if (const auto result = GetResult1())
{
Print << *result;
}
if (const auto result = GetResult2())
{
Print << *result;
}
while (System::Update())
{
}
}
35.7 活用例(1)¶
- マウスのドラッグで矢印を作るサンプルです
- マウスの左ボタンが押された位置を
Optional<Point>
で表現し、有効値を持つ場合はその位置から現在のマウスカーソルの位置まで矢印を描きます - マウスの左ボタンが離されたら、無効値にします
# include <Siv3D.hpp>
void Main()
{
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
// マウスの左ボタンが押された位置
Optional<Point> start;
while (System::Update())
{
ClearPrint();
Print << start;
if (MouseL.down()) // マウスの左ボタンが押されたら
{
// マウスカーソルの位置を有効値として代入する
start = Cursor::Pos();
}
else if (MouseL.up()) // マウスの左ボタンが離されたら
{
// 無効値にする
start.reset();
}
// 有効値を持っていれば
if (start)
{
// スタート地点を中心に円を描く
start->asCircle(10).draw(ColorF{ 0.2 });
// 現在のマウスカーソルの位置まで矢印を描く
Line{ *start, Cursor::Pos() }.drawArrow(6, SizeF{ 20, 20 }, ColorF{ 0.2 });
}
}
}
35.8 活用例(2)¶
- マウスのドラッグでアイテムを移動するサンプルです
- ドラッグ中のアイテムの種類を
Optional<int32>
で表現し、有効値を持つ場合はそのアイテムをマウスカーソルの位置に描きます - ドラッグが終了したら、無効値にします
# include <Siv3D.hpp>
void Main()
{
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
const Texture item1Texture{ U"🍌"_emoji };
const Texture item2Texture{ U"🍎"_emoji };
const Circle item1Circle{ 100, 200, 60 };
const Circle item2Circle{ 100, 400, 60 };
const Rect boxRect{ 500, 200, 200, 200 };
Optional<int32> grabbedItem;
while (System::Update())
{
ClearPrint();
Print << grabbedItem;
if (grabbedItem || item1Circle.mouseOver() || item2Circle.mouseOver())
{
Cursor::RequestStyle(CursorStyle::Hand);
}
if (item1Circle.leftClicked())
{
grabbedItem = 1;
}
else if (item2Circle.leftClicked())
{
grabbedItem = 2;
}
else if (MouseL.up())
{
grabbedItem.reset();
}
item1Texture.drawAt(item1Circle.center);
item2Texture.drawAt(item2Circle.center);
boxRect.draw();
// アイテムをつかんでいる
if (grabbedItem)
{
// ボックスの上にカーソルがある場合
if (boxRect.mouseOver())
{
// ボックスの枠を赤く描画する
boxRect.drawFrame(0, 20, ColorF{ 1.0, 0.5, 0.5 });
}
// アイテムを描画する
if (grabbedItem == 1)
{
item1Texture.drawAt(Cursor::Pos());
}
else if (grabbedItem == 2)
{
item2Texture.drawAt(Cursor::Pos());
}
}
}
}