Files
ortools-clone/ortools/math_opt/cpp/enums.h
Corentin Le Molgat c34026b101 Bump copyright to 2025
note: done using
```sh
git grep -l "2010-2024 Google" | xargs sed -i 's/2010-2024 Google/2010-2025 Google/'
```
2025-01-10 11:33:35 +01:00

344 lines
13 KiB
C++

// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// IWYU pragma: private, include "ortools/math_opt/cpp/math_opt.h"
// IWYU pragma: friend "ortools/math_opt/cpp/.*"
// The MathOpt C++ API defines enums that are used in parameters and results and
// that corresponds to Proto generated enums.
//
// The tools in this header make sure the C++ enums provide the following
// features:
// * enumerating all enum values
// * bidirectional string conversion
// * operator<< stream support
// * bidirectional proto generated enum conversion
//
// Example declaration:
//
// my_file.proto:
// enum MyEnumProto {
// MY_ENUM_UNSPECIFIED = 0;
// MY_ENUM_FIRST_VALUE = 1;
// MY_ENUM_SECOND_VALUE = 2;
// }
//
// my_file.h:
// enum class MyEnum {
// kFirstValue = MY_ENUM_FIRST_VALUE,
// kSecondValue = MY_ENUM_SECOND_VALUE,
// };
//
// MATH_OPT_DEFINE_ENUM(MyEnum, MY_ENUM_UNSPECIFIED);
//
// my_file.cc:
// std::optional<absl::string_view>
// Enum<MyEnum>::ToOptString(MyEnum value) {
// switch (value) {
// case MyEnum::kFirstValue:
// return "first_value";
// case MyEnum::kSecondValue:
// return "second_value";
// }
// return std::nullopt;
// }
//
// absl::Span<const MyEnum> Enum<MyEnum>::AllValues() {
// static constexpr MyEnum kMyEnumValues[] = {MyEnum::kFirstValue,
// MyEnum::kSecondValue};
// return absl::MakeConstSpan(kMyEnumValues);
// }
//
// my_file_test.cc:
// #include "ortools/math_opt/cpp/enums_testing.h"
// ...
// INSTANTIATE_TYPED_TEST_SUITE_P(MyEnum, EnumTest, MyEnum);
//
// Once this is done, the following functions are available:
// * absl::Span<MyEnum> Enum<MyEnum>::AllValues()
// * optional<MyEnum> EnumFromString<MyEnum>(string_view)
// * string_view EnumToString(MyEnum)
// * optional<string_view> EnumToOptString(MyEnum)
// * optional<MyEnum> EnumFromProto(MyEnumProto)
// * MyEnumProto EnumToProto(optional<MyEnum>)
// * MyEnumProto EnumToProto(MyEnum)
// * operator<<(MyEnum)
// * operator<<(std::optional<MyEnum>)
//
// See examples of usage in the Enum struct documentation below.
#ifndef OR_TOOLS_MATH_OPT_CPP_ENUMS_H_
#define OR_TOOLS_MATH_OPT_CPP_ENUMS_H_
#include <optional>
#include <ostream>
#include <type_traits>
#include "absl/log/check.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
namespace operations_research::math_opt {
// This template is specialized for each enum in the C++ API.
//
// It provides a standard way to query properties of those enums and it is used
// by some global functions below to implement conversion from/to string or
// proto enum.
//
// Usage example:
//
// // Iterating on all enum values.
// for (const auto solver_type : Enum<SolverType>::AllValues()) {
// ...
// }
//
// // Parsing a flag as an enum.
// const std::optional<SolverType> solver_type =
// EnumFromString(absl::GetFlag(FLAGS_solver_type));
// if (!solver_type) {
// return util::InvalidArgumentErrorBuilder()
// _ << "failed to parse --solver_type value: "
// << absl::GetFlag(FLAGS_solver_type);
// }
//
// // Conversion to string.
// const SolverType solver_type = ...;
// LOG(INFO) << "solver: " << solver_type;
// absl::StrCat(EnumToString(solver_type), "_test");
// absl::StrCat(EnumToOptString(solver_type).value(), "_test");
//
// // Conversion to Proto.
// const std::optional<SolverType> opt_solver_type = ...;
// const SolverTypeProto solver_type_proto = EnumToProto(opt_solver_type);
//
// // Conversion from Proto.
// const SolverTypeProto solver_type_proto = ...;
// const std::optional<SolverType> opt_solver_type =
// EnumFromProto(solver_type_proto);
//
// Implementation note: don't specialize directly and instead use the
// MATH_OPT_DEFINE_ENUM macro.
template <typename E>
struct Enum {
// Must be true in all implementation. This is used with std::enable_if to
// condition the implementation of some overloads.
static constexpr bool kIsImplemented = false;
// The type of the Proto equivalent to this enum.
//
// (Here we use int as a placeholder so that the code compiles.)
using Proto = int;
// The value Proto enum that represents the unspecified case.
static constexpr Proto kProtoUnspecifiedValue = {};
// Returns a unique string that represent the enum. Returns nullopt if the
// input value is not a valid value of the enum.
//
// The returned string should not include the enum name and should be in
// snake_case (e.g. is the enum is kNoSolutionFound, this should return
// "no_solution_found").
//
// Please prefer using the global functions EnumToString() (or
// EnumToOptString() if support for invalid values is needed) instead to
// benefit from automatic template type deduction.
static std::optional<absl::string_view> ToOptString(E value);
// Returns all possible values of the enum.
static absl::Span<const E> AllValues();
};
using ProtoEnumIsValid = bool (*)(int);
// This template is specialized for each enum in the Proto API. It
// defines the correspondence with the C++ enum.
//
// Implementation note: don't specialize directly and instead use the
// MATH_OPT_DEFINE_ENUM macro.
template <typename P>
struct EnumProto {
// The type of the C++ enum equivalent to the P proto enum.
//
// (Here we use void as a placeholder so that the code compiles.)
using Cpp = void;
// The smallest valid enum value.
static constexpr P kMin = {};
// The largest valid enum value.
static constexpr P kMax = {};
// Proto function returning the true if the input integer matches a valid
// value (some values may be missing in range [kMin, kMax]).
static constexpr ProtoEnumIsValid kIsValid = nullptr;
};
// Returns the Proto enum that matches the input C++ proto, returns
// Enum<E>::kProtoUnspecifiedValue if the input is std::nullopt.
template <typename E>
typename Enum<E>::Proto EnumToProto(std::optional<E> value);
// Returns the Proto enum that matches the input C++ proto.
//
// Implementation note: this overload is necessary for EnumToProto(Xxx::kXxx)
// since C++ won't deduce E in std::optional<E> with the other overload.
template <typename E>
typename Enum<E>::Proto EnumToProto(E value);
// Returns the C++ enum that matches the input Proto enum, returns
// std::nullopt if the input is kProtoUnspecifiedValue.
template <typename P>
std::optional<typename EnumProto<P>::Cpp> EnumFromProto(P proto_value);
// Returns a unique string that represent the enum.
//
// It CHECKs that the input is a valid enum value. For most users this should
// always be the case since MathOpt don't generates invalid data.
//
// Prefer using operator<< when possible though. As a side benefice it does not
// CHECK but instead prints the integer value of the invalid input.
template <typename E>
absl::string_view EnumToString(E value);
// Returns a unique string that represent the enum. Returns nullopt if the input
// value is not a valid value of the enum.
template <typename E>
std::optional<absl::string_view> EnumToOptString(E value);
// Returns the enum value that corresponds to the input string or nullopt if no
// enum matches.
//
// The expected strings are the one returned by EnumToString().
//
// This is O(n) in complexity so use with care.
template <typename E>
std::optional<E> EnumFromString(absl::string_view str);
// Overload of operator<< for enum types that implements Enum<E>.
//
// It calls EnumToOptString(), printing the returned value if not nullopt. When
// nullopt it prints the enum numeric value instead.
// We must use enable_if here to prevent this overload to be selected
// for other types than ones that implement Enum<E>.
template <typename E>
std::enable_if_t<Enum<E>::kIsImplemented, std::ostream&> operator<<(
std::ostream& out, const E value) {
const std::optional<absl::string_view> opt_str = EnumToOptString(value);
if (opt_str.has_value()) {
out << *opt_str;
} else {
out << "<invalid enum (" << static_cast<std::underlying_type_t<E>>(value)
<< ")>";
}
return out;
}
// Overload of operator<< for std::optional<E> when Enum<E> is implemented.
//
// When the value is nullopt, it prints "<unspecified>", else it prints the enum
// value.
template <typename E,
// We must use enable_if here to prevent this overload to be selected
// for other types than ones that implement Enum<E>.
typename = std::enable_if_t<Enum<E>::kIsImplemented>>
std::ostream& operator<<(std::ostream& out, const std::optional<E> opt_value) {
if (opt_value.has_value()) {
out << *opt_value;
} else {
out << "<unspecified>";
}
return out;
}
////////////////////////////////////////////////////////////////////////////////
// Template functions implementations after this point.
////////////////////////////////////////////////////////////////////////////////
template <typename E>
typename Enum<E>::Proto EnumToProto(const std::optional<E> value) {
return value ? static_cast<typename Enum<E>::Proto>(*value)
: Enum<E>::kProtoUnspecifiedValue;
}
template <typename E>
typename Enum<E>::Proto EnumToProto(const E value) {
return EnumToProto(std::make_optional(value));
}
template <typename P>
std::optional<typename EnumProto<P>::Cpp> EnumFromProto(const P proto_value) {
if (proto_value == Enum<typename EnumProto<P>::Cpp>::kProtoUnspecifiedValue) {
return std::nullopt;
}
return static_cast<typename EnumProto<P>::Cpp>(proto_value);
}
template <typename E>
absl::string_view EnumToString(const E value) {
std::optional<absl::string_view> opt_str = Enum<E>::ToOptString(value);
CHECK(opt_str.has_value())
<< "invalid value: " << static_cast<std::underlying_type_t<E>>(value);
return *opt_str;
}
template <typename E>
std::optional<absl::string_view> EnumToOptString(const E value) {
return Enum<E>::ToOptString(value);
}
template <typename E>
std::optional<E> EnumFromString(const absl::string_view str) {
for (const E value : Enum<E>::AllValues()) {
if (EnumToOptString(value) == str) {
return value;
}
}
return std::nullopt;
}
// Macros that defines the templates specializations for Enum and EnumProto.
//
// The CppEnum parameter is the name of the C++ enum class which values are the
// Proto enum values. The C++ enum must contain a value for each value of the
// Proto enum but the UNSPECIFIED one. The proto_unspecified_value is the
// UNSPECIFIED one.
//
// It leaves two functions to be implemented in the .cc file:
//
// absl::string_view Enum<CppEnum>::ToOptString(CppEnum value) {
// absl::Span<const CppEnum> Enum<CppEnum>::AllValues();
//
// See the comment at the top of this file for an example. See the comment on
// Enum struct for the functions that can then be used on enums.
#define MATH_OPT_DEFINE_ENUM(CppEnum, proto_unspecified_value) \
template <> \
struct Enum<CppEnum> { \
static constexpr bool kIsImplemented = true; \
using Proto = CppEnum##Proto; \
static constexpr Proto kProtoUnspecifiedValue = proto_unspecified_value; \
static std::optional<absl::string_view> ToOptString(CppEnum value); \
static absl::Span<const CppEnum> AllValues(); \
}; \
\
template <> \
struct EnumProto<CppEnum##Proto> { \
using Cpp = CppEnum; \
static constexpr CppEnum##Proto kMin = CppEnum##Proto##_MIN; \
static constexpr CppEnum##Proto kMax = CppEnum##Proto##_MAX; \
static constexpr ProtoEnumIsValid kIsValid = CppEnum##Proto##_IsValid; \
} /* missing semicolon to force adding it at the invocation site */
} // namespace operations_research::math_opt
#endif // OR_TOOLS_MATH_OPT_CPP_ENUMS_H_