Namaran

Code daily. Without assist.

2026-08-23

shape_switch.cpp

#include <print>
#include <variant>
#include <string>

struct Circle { double radius; };
struct Square { double side; };
struct Triangle { double base; double height; };

using Shape = std::variant<Circle, Square, Triangle>;

std::string describe(const Shape& shape) {
    switch (shape.index()) {
        case 0: return std::format("circle r={}", std::get<Circle>(shape).radius);
        case 1: return std::format("square s={}", std::get<Square>(shape).side);
    }
    return "unknown";
}

int main() {
    Shape shapes[] = { Circle{2.0}, Square{3.0}, Triangle{4.0, 5.0} };
    for (const auto& shape : shapes) {
        std::println("{}", describe(shape));
    }
}

Triangleを表示するはずの3行目が"unknown"になってしまいます。何が問題で、どう直せばよいでしょうか?

Answer

ShapeTriangleを追加したときに、describeswitchcase 2を足し忘れています。switchは単なる整数値であるshape.index()で分岐しているだけなので、Shapeの定義を変えてもswitch側は何も強制されず、コンパイラは警告すら出しません。これがC++のswitchとパターンマッチングの違いです。std::visitoverloadedを使えば、渡した呼び出し可能オブジェクトがShapeの全ての型を網羅しているかコンパイル時に検査されるので、Triangleを追加した時点で対応するラムダを書くまでコンパイルが通らなくなります:

template <class... Fs>
struct overloaded : Fs... { using Fs::operator()...; };
template <class... Fs>
overloaded(Fs...) -> overloaded<Fs...>;

std::string describe(const Shape& shape) {
    return std::visit(overloaded{
        [](const Circle& c) { return std::format("circle r={}", c.radius); },
        [](const Square& s) { return std::format("square s={}", s.side); },
        [](const Triangle& t) { return std::format("triangle b={} h={}", t.base, t.height); },
    }, shape);
}