💁

スマートにエラーを示して「期待」に応えよう

に公開

最近見ているコードは、殆どの関数が戻り値のエラーコードと出力引数の両方を返すようになっているせいでかなり使いづらいです。以下のような単純な関数でも呼ぶ時は出力引数のせいで一行で呼べないです。

int get_name (std::string& name)
{
    if (!check_something())
    {
        return -1;
    }
    name = m_name;
    return 0;
}

std::string name;
const int ret = get_name (name);
if (ret < 0)
    std::cout << "Error code: " << ret << std::endl;
else
    std::cout << "Name: " << name << std::endl;

できれば、出力引数を使わずに、成功の場合の値とエラーの場合のエラーコードを戻り値で直接返すようにしたいです。

C++17 以降は、std::variantを使えば、以下のようにどちらも戻り値で返せます。

std::variant<std::string, int> get_name()
{
    if (!check_something())
    {
         return -1;
    }

    return m_name;
}

const auto name = get_name();
if (std::holds_alternative<int> (name))
    std::cout << "Error code: " << std::get<int> (name) << std::endl;
else
    std::cout << "Name: " << std::get<std::string> (name) << std::endl;

でもこれもあんまり使いやすくはないし、std::variantが一般的な機能っていうのもあって、一目でどちらがエラーの場合の戻り値なのか、どちらが通常の戻り値なのかも微妙に分かりづらいです。

でも安心してください!C++23 からはこういうケースに特化したstd::expectedという機能が追加されます。

std::expected<std::string, int> get_name()
{
    if (!check_something())
    {
         return std::unexpected (-1);
    }

    return m_name;
}

const auto name = get_name();
if (!name)
    std::cout << "Error code: " << name.error() << std::endl;
else
    std::cout << "Name: " << name.value() << std::endl;

関数の中からは、エラーの方を返す時はstd::unexpectedを使って、通常の戻り値はそのまま返せばいいです。
呼び出し側は、成功かエラーのチェックがstd::optionalに似ていて、凄く使いやすいです。error()でエラーの場合の戻り値、value()で通常の戻り値を取得できます。
これでようやく出力引数を使わなくてもいいようになりますね。後は C++23 が使えるようになるまで何年かかるのやら…


|cpp記事一覧へのリンク|

Discussion