Vix.cpp v2.8.5 is here Read the blog
Skip to content

Expected Results ​

The conversion module returns expected-style results instead of throwing exceptions or silently producing fallback values.

This matters because conversion usually happens at the boundary of an application. A value may come from a configuration file, an environment variable, a command-line argument, or a request parameter. At that point, failure is normal and should be handled directly. The result type makes that workflow explicit: the conversion either contains a typed value, or it contains a ConversionError.

Use the public Vix header when working with conversion results:

cpp
#include <vix/conversion.hpp>

For examples that print output:

cpp
#include <vix/print.hpp>

Result shape ​

Most conversion functions return this form:

cpp
vix::conversion::expected<T, vix::conversion::ConversionError>

For example, to_int32 returns a result that either contains an int or a ConversionError.

cpp
#include <vix/conversion.hpp>
#include <vix/print.hpp>

int main()
{
  auto result = vix::conversion::to_int32("42");

  if (!result)
  {
    vix::print(
        "conversion failed:",
        vix::conversion::to_string(result.error().code));

    return 1;
  }

  int value = result.value();

  vix::print("value:", value);

  return 0;
}

The important rule is simple: check the result before reading the value. When the result is false, read the error. When the result is true, read the converted value.

Check success ​

The result can be checked with if (result).

cpp
#include <vix/conversion.hpp>
#include <vix/print.hpp>

int main()
{
  auto value = vix::conversion::to_float64("3.14");

  if (value)
  {
    vix::print("parsed:", value.value());
  }

  return 0;
}

This keeps conversion code compact without hiding failure. The condition is not checking whether the parsed value is truthy; it is checking whether the conversion succeeded.

Use has_value ​

The same check can be written with has_value() when that reads better in the surrounding code.

cpp
#include <vix/conversion.hpp>
#include <vix/print.hpp>

int main()
{
  auto enabled = vix::conversion::to_bool("yes");

  if (enabled.has_value())
  {
    vix::print("enabled:", enabled.value());
  }

  return 0;
}

Both forms are valid. Use the one that makes the code easier to read.

Read the error ​

When conversion fails, the result contains a ConversionError.

cpp
#include <vix/conversion.hpp>
#include <vix/print.hpp>

int main()
{
  auto value = vix::conversion::to_int32("12a");

  if (!value)
  {
    const auto &error = value.error();

    vix::print("error:", vix::conversion::to_string(error.code));
    vix::print("input:", error.input);
    vix::print("position:", error.position);

    return 1;
  }

  return 0;
}

The error gives technical information about the failed conversion. Higher layers can map that into a user-facing message, a log entry, or a domain-specific validation error.

Avoid reading value before checking ​

value() should only be used after checking that the result contains a value.

cpp
#include <vix/conversion.hpp>
#include <vix/print.hpp>

int main()
{
  auto port = vix::conversion::to_int32("8080");

  if (!port)
  {
    vix::print("invalid port");
    return 1;
  }

  vix::print("port:", port.value());

  return 0;
}

This pattern is clear and safe. It makes the failure path visible before the converted value is used.

Return conversion results from functions ​

A function can return the expected result directly when conversion is part of its own responsibility.

cpp
#include <vix/conversion.hpp>
#include <vix/print.hpp>

vix::conversion::expected<int, vix::conversion::ConversionError>
parse_port(std::string_view input)
{
  return vix::conversion::to_int32(input);
}

int main()
{
  auto port = parse_port("8080");

  if (!port)
  {
    vix::print(
        "invalid port:",
        vix::conversion::to_string(port.error().code));

    return 1;
  }

  vix::print("port:", port.value());

  return 0;
}

This is useful when a lower-level function only knows about conversion. The caller can decide whether the error should become a log message, an HTTP error, a CLI message, or a configuration failure.

Map conversion errors at the boundary ​

The conversion module reports technical errors. Application code should add context when presenting the failure.

cpp
#include <vix/conversion.hpp>
#include <vix/print.hpp>

int main()
{
  auto workers = vix::conversion::to_int32("many");

  if (!workers)
  {
    vix::print(
        "WORKERS must be a base-10 integer:",
        vix::conversion::to_string(workers.error().code));

    return 1;
  }

  vix::print("workers:", workers.value());

  return 0;
}

The conversion error says what failed technically. The surrounding code says what the input meant in the application.

Expected implementation ​

The module uses std::expected when the standard library supports it. When std::expected is not available, Vix provides a small fallback implementation with the API needed by the conversion module.

The public workflow stays the same in both cases:

cpp
auto result = vix::conversion::to_int32("42");

if (result)
{
  vix::print(result.value());
}
else
{
  vix::print(vix::conversion::to_string(result.error().code));
}

This allows the conversion API to work in C++20 projects while still using the standard library implementation in environments where C++23 std::expected is available.

make_unexpected ​

make_unexpected is used internally and in advanced code to construct an error result.

cpp
#include <vix/conversion.hpp>
#include <vix/print.hpp>

vix::conversion::expected<int, vix::conversion::ConversionError>
always_fail(std::string_view input)
{
  return vix::conversion::make_unexpected(
      vix::conversion::ConversionError{
          vix::conversion::ConversionErrorCode::InvalidCharacter,
          input});
}

int main()
{
  auto result = always_fail("abc");

  if (!result)
  {
    vix::print(
        "error:",
        vix::conversion::to_string(result.error().code));
  }

  return 0;
}

Most application code will not need to call make_unexpected directly. It is mainly useful when writing helper functions that follow the same result style as the conversion module.

API overview ​

APIPurpose
expected<T, E>Represents either a value of type T or an error of type E.
unexpected<E>Wraps an error value for constructing failed results.
make_unexpected(error)Creates an unexpected error result.
operator bool()Checks whether the result contains a value.
has_value()Checks whether the result contains a value.
value()Returns the contained value. Use only after success.
error()Returns the contained error. Use only after failure.

Next step ​

Continue with errors to understand the structure and meaning of ConversionError.

Released under the MIT License.