Line data Source code
1 1 : // Distributed under the MIT License. 2 : // See LICENSE.txt for details. 3 : 4 : /// \file 5 : /// Defines class template Factory. 6 : 7 : #pragma once 8 : 9 : #include <algorithm> 10 : #include <iomanip> 11 : #include <memory> 12 : #include <sstream> 13 : #include <string> 14 : #include <utility> 15 : #include <yaml-cpp/yaml.h> 16 : 17 : #include "Options/Options.hpp" 18 : #include "Options/ParseError.hpp" 19 : #include "Options/Protocols/FactoryCreation.hpp" 20 : #include "Utilities/ErrorHandling/Assert.hpp" 21 : #include "Utilities/PrettyType.hpp" 22 : #include "Utilities/ProtocolHelpers.hpp" 23 : #include "Utilities/StdHelpers.hpp" 24 : #include "Utilities/TMPL.hpp" 25 : #include "Utilities/TypeTraits/CreateGetStaticMemberVariableOrDefault.hpp" 26 : 27 : namespace Options { 28 : namespace Factory_detail { 29 : struct print_derived { 30 : // Not a stream because brigand requires the functor to be copyable. 31 : std::string value; 32 : template <typename T> 33 : void operator()(tmpl::type_<T> /*meta*/) { 34 : // These are zero-based 35 : const size_t name_col = 2; 36 : const size_t help_col = 22; 37 : const size_t end_col = 80; 38 : 39 : std::ostringstream ss; 40 : ss << std::left << std::setw(name_col) << "" 41 : << std::setw(help_col - name_col - 1) << pretty_type::name<T>(); 42 : if (ss.str().size() >= help_col) { 43 : ss << "\n" << std::setw(help_col - 1) << ""; 44 : } 45 : 46 : std::string help_snippet(T::help); 47 : if (help_snippet.size() > end_col - help_col) { 48 : help_snippet.resize(end_col - help_col - 3); 49 : help_snippet += "..."; 50 : } 51 : std::replace(help_snippet.begin(), help_snippet.end(), '\n', ' '); 52 : ss << " " << help_snippet << "\n"; 53 : 54 : value += ss.str(); 55 : } 56 : }; 57 : 58 : template <typename CreatableClasses> 59 : std::string help_derived() { 60 : return "Known Ids:\n" + 61 : tmpl::for_each<CreatableClasses>( 62 : Factory_detail::print_derived{}) 63 : .value; 64 : } 65 : 66 : // This is for handling legacy code that still uses creatable_classes. 67 : // It should be inlined once everything is converted. 68 : template <typename BaseClass, typename Metavariables, typename = std::void_t<>> 69 : struct get_creatable_classes { 70 : using factory_creation = typename Metavariables::factory_creation; 71 : // This assertion is normally done in tests, but the executable 72 : // metavariables don't have tests, so we do it here. 73 : static_assert(tt::assert_conforms_to_v<factory_creation, 74 : Options::protocols::FactoryCreation>); 75 : using type = tmpl::at<typename factory_creation::factory_classes, BaseClass>; 76 : }; 77 : 78 : template <typename BaseClass, typename Metavariables> 79 : struct get_creatable_classes< 80 : BaseClass, Metavariables, 81 : std::void_t<typename BaseClass::creatable_classes>> { 82 : using type = typename BaseClass::creatable_classes; 83 : }; 84 : 85 : CREATE_GET_STATIC_MEMBER_VARIABLE_OR_DEFAULT(factory_creatable) 86 : 87 : template <typename T> 88 : struct is_factory_creatable 89 : : std::bool_constant<get_factory_creatable_or_default_v<T, true>> {}; 90 : 91 : template <typename BaseClass, typename Metavariables> 92 : std::unique_ptr<BaseClass> create(const Option& options) { 93 : using all_creatable_classes = 94 : typename get_creatable_classes<BaseClass, Metavariables>::type; 95 : static_assert(not std::is_same_v<all_creatable_classes, tmpl::no_such_type_>, 96 : "List of creatable derived types for this class is missing " 97 : "from Metavariables::factory_classes."); 98 : using creatable_classes = 99 : tmpl::filter<all_creatable_classes, is_factory_creatable<tmpl::_1>>; 100 : 101 : const auto& node = options.node(); 102 : Option derived_opts(options.context()); 103 : derived_opts.append_context("While operating factory for " + 104 : pretty_type::name<BaseClass>()); 105 : std::string id; 106 : if (node.IsScalar()) { 107 : id = node.as<std::string>(); 108 : } else if (node.IsMap()) { 109 : if (node.size() != 1) { 110 : PARSE_ERROR(derived_opts.context(), 111 : "Expected a single class to create, got " 112 : << node.size() << ":\n" << node); 113 : } 114 : id = node.begin()->first.as<std::string>(); 115 : derived_opts.set_node(node.begin()->second); 116 : } else if (node.IsNull()) { 117 : PARSE_ERROR(derived_opts.context(), 118 : "Expected a class to create:\n" 119 : << help_derived<creatable_classes>()); 120 : } else { 121 : PARSE_ERROR(derived_opts.context(), 122 : "Expected a class or a class with options, got:\n" 123 : << node); 124 : } 125 : 126 : std::unique_ptr<BaseClass> result; 127 : tmpl::for_each<creatable_classes>( 128 : [&id, &derived_opts, &result](auto derived_v) { 129 : using Derived = tmpl::type_from<decltype(derived_v)>; 130 : if (pretty_type::name<Derived>() == id) { 131 : ASSERT(result == nullptr, "Duplicate factory id: " << id); 132 : result = std::make_unique<Derived>( 133 : derived_opts.parse_as<Derived, Metavariables>()); 134 : } 135 : }); 136 : if (result != nullptr) { 137 : return result; 138 : } 139 : PARSE_ERROR(derived_opts.context(), 140 : "Unknown Id '" << id << "'\n" 141 : << help_derived<creatable_classes>()); 142 : } 143 : } // namespace Factory_detail 144 : 145 : template <typename T> 146 : struct create_from_yaml<std::unique_ptr<T>> { 147 : template <typename Metavariables> 148 : static std::unique_ptr<T> create(const Option& options) { 149 : return Factory_detail::create<T, Metavariables>(options); 150 : } 151 : }; 152 : } // namespace Options