// Copyright 2010-2021 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. // 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 // Enum::ToOptString(MyEnum value) { // switch (value) { // case MyEnum::kFirstValue: // return "first_value"; // case MyEnum::kSecondValue: // return "second_value"; // } // return std::nullopt; // } // // absl::Span Enum::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 Enum::AllValues() // * optional EnumFromString(string_view) // * string_view EnumToString(MyEnum) // * optional EnumToOptString(MyEnum) // * optional EnumFromProto(MyEnumProto) // * MyEnumProto EnumToProto(optional) // * MyEnumProto EnumToProto(MyEnum) // * operator<<(MyEnum) // * operator<<(std::optional) // // 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 #include #include "absl/strings/string_view.h" #include "absl/types/span.h" #include "ortools/base/logging.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::AllValues()) { // ... // } // // // Parsing a flag as an enum. // const std::optional 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 opt_solver_type = ...; // const SolverTypeProto solver_type_proto = EnumToProto(opt_solver_type); // // // Conversion from Proto. // const SolverTypeProto solver_type_proto = ...; // const std::optional opt_solver_type = // EnumFromProto(solver_type_proto); // // Implementation note: don't specialize directly and instead use the // MATH_OPT_DEFINE_ENUM macro. template 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 ToOptString(E value); // Returns all possible values of the enum. static absl::Span 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 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::kProtoUnspecifiedValue if the input is std::nullopt. template typename Enum::Proto EnumToProto(const std::optional 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 with the other overload. template typename Enum::Proto EnumToProto(const E value); // Returns the C++ enum that matches the input Proto enum, returns // std::nullopt if the input is kProtoUnspecifiedValue. template std::optional::Cpp> EnumFromProto(const 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 absl::string_view EnumToString(const 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 std::optional EnumToOptString(const 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 std::optional EnumFromString(const absl::string_view str); // Overload of operator<< for enum types that implements Enum. // // It calls EnumToOptString(), printing the returned value if not nullopt. When // nullopt it prints the enum numeric value instead. template . typename = std::enable_if_t::kIsImplemented>> std::ostream& operator<<(std::ostream& out, const E value) { const std::optional opt_str = EnumToOptString(value); if (opt_str.has_value()) { out << *opt_str; } else { out << ">(value) << ")>"; } return out; } // Overload of operator<< for std::optional when Enum is implemented. // // When the value is nullopt, it prints "", else it prints the enum // value. template . typename = std::enable_if_t::kIsImplemented>> std::ostream& operator<<(std::ostream& out, const std::optional opt_value) { if (opt_value.has_value()) { out << *opt_value; } else { out << ""; } return out; } //////////////////////////////////////////////////////////////////////////////// // Template functions implementations after this point. //////////////////////////////////////////////////////////////////////////////// template typename Enum::Proto EnumToProto(const std::optional value) { return value ? static_cast::Proto>(*value) : Enum::kProtoUnspecifiedValue; } template typename Enum::Proto EnumToProto(const E value) { return EnumToProto(std::make_optional(value)); } template std::optional::Cpp> EnumFromProto(const P proto_value) { if (proto_value == Enum::Cpp>::kProtoUnspecifiedValue) { return std::nullopt; } return static_cast::Cpp>(proto_value); } template absl::string_view EnumToString(const E value) { std::optional opt_str = Enum::ToOptString(value); CHECK(opt_str.has_value()) << "invalid value: " << static_cast>(value); return *opt_str; } template std::optional EnumToOptString(const E value) { return Enum::ToOptString(value); } template std::optional EnumFromString(const absl::string_view str) { for (const E value : Enum::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::ToOptString(CppEnum value) { // absl::Span Enum::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 { \ static constexpr bool kIsImplemented = true; \ using Proto = CppEnum##Proto; \ static constexpr Proto kProtoUnspecifiedValue = proto_unspecified_value; \ static std::optional ToOptString(CppEnum value); \ static absl::Span AllValues(); \ }; \ \ template <> \ struct EnumProto { \ 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_