ALib C++ Framework
by
Library Version: 2511 R0
Documentation generated by doxygen
Loading...
Searching...
No Matches
commandline.inl
Go to the documentation of this file.
1//==================================================================================================
2/// \file
3/// This header-file is part of module \alib_cli of the \aliblong.
4///
5/// \emoji :copyright: 2013-2025 A-Worx GmbH, Germany.
6/// Published under #"mainpage_license".
7//==================================================================================================
8ALIB_EXPORT namespace alib { namespace cli {
9
10class CLIUtil;
11
12//==================================================================================================
13/// This class provides a foundation for software executables that processes command-line
14/// parameters.
15///
16/// \see
17/// "Utility" methods which could have been implemented as an interface of this
18/// class have instead been located as static methods in friend class #"CLIUtil" which
19/// receive a pointer to an instance of this type.
20///
21/// ## Friends ##
22/// class #"CLIUtil"
23///
24///\I{#############################################################################################}
25/// @throws Exception #"alib::cli::Exceptions;2" \I{CLANGDUMMY}
26//==================================================================================================
28 //################################################################################################
29 // Type definitions
30 //################################################################################################
31 #if !DOXYGEN
32 // This friend provides utility methods for using this class.
33 friend class CommandDecl;
34 friend struct Command;
35 friend struct Option;
36 friend struct Parameter;
37 friend class CLIUtil;
38 #endif
39
40 //################################################################################################
41 // Type definitions
42 //################################################################################################
43 public:
44
45 //################################################################################################
46 // protected fields
47 //################################################################################################
48 protected:
49 /// Monotonic allocator used for all resourced static definitions as well as the data
50 /// used during parsing.
52
53 /// The element recycler shared between lists of strings.
55
56 /// The element recycler shared between fields #"Command::ParametersMandatory;*" and
57 /// #"Command::ParametersOptional;*".
59
60 //################################################################################################
61 // Fields
62 //################################################################################################
63 public:
64 /// Application information text.
65 /// Used as a sort of "header" output by class #"CLIUtil" .
67
68 //########################################### Arguments ##########################################
69 /// A vector of args. If the type of CLI argument strings provided with the constructor does
70 /// not match the #"ALIB_CHARACTERS_WIDE;default ALib string width", the strings get
71 /// converted.<br>
72 /// Values that are 'consumed' by options that get defined, are \b not removed from this
73 /// list. Instead, they are removed from index vector #"ArgsLeft".
75
76 /// A vector of args. If constructor variant accepting \b wchar strings is used,
77 /// those unicode strings get converted to 1-byte strings using the current locale.<br>
78 /// Values that are 'consumed' by options that get defined, are removed.
80
81 //################################ Declarations (from custom enums) ##############################
82 /// Commands defined.
84
85 /// Possible Options.
87
88 /// Possible Parameters.
90
91 /// Possible Errors.
93
94 //####################################### Parsed CLI objects #####################################
95
96 /// The options parsed in the order of their appearance.
98
99 /// List of arguments that start with a hyphen and are ignored by this class due to the
100 /// fact that they were not recognized.
101 ///
102 /// \see Method #"ReadOptions" for details on this.
104
105 /// A list of commands actually parsed. Filled with method #"ReadNextCommands".
107
108 /// The next command in #"CommandsParsed" to be processed. Used with method #"NextCommand".
110
111 /// The maximum length of token names:
112 /// - 0: Commands
113 /// - 1: Options
114 ///
115 /// Note: Used for formatted help/dump output. (0= command, 1= option, 2= param)
116 integer MaxNameLength[3] ={ 0, 0, 0 };
117
118 /// The resource pool used to fetch resources from.
119 /// Several resources are loaded from this in addition to what is loaded as enum
120 /// meta-information of the cli declaration objects.
121 ///
122 /// It is recommended to have the main application implement a custom module, as
123 /// #"alib_mod_bs_custommods;described here".
125
126 /// The resource category to fetch CLI resources within field #"Resources".
128
129 /// Specifies if a "dry run" should be performed.
130 /// For more information, see #"CLIUtil::GetDryOpt;*".
132
133 //################################################################################################
134 // Constructor/destructor
135 //################################################################################################
136 public:
137 /// Constructor.
153
154 /// Virtual empty destructor.
155 virtual ~CommandLine() {}
156
157 //################################################################################################
158 // Definition interface
159 //################################################################################################
160 public:
161 /// Returns the allocator used for all command parsing, resourced enum record creation
162 /// and so on. This allocator might be used for allocations that align with (or are shorter
163 /// as) the lifecycle of the instance of this class.
164 ///
165 /// @return The internal allocator
166 MonoAllocator& GetAllocator() noexcept { return allocator; }
167
168 /// Helper function that uses fields #".Resources" and #".ResourceCategory" to fetch a
169 /// resourced string.<br>
170 ///
171 /// With debug-builds, this method asserts that a resource was found. If this is not
172 /// wanted, use #".TryResource".
173 ///
174 /// @param name The resource name.
175 /// @return The resource string, respectively a \e nulled string on failure.
176 const String& GetResource( const NString& name )
177 { return Resources->Get( ResourceCategory, name ALIB_DBG(, true) ); }
178
179 /// Helper function that uses fields #"Resources" and #".ResourceCategory" to fetch a
180 /// resourced string.<br>
181 ///
182 /// \note
183 /// Usually, it is recommended to use #GetResource, which asserts with debug-builds
184 /// if a resource was not found.
185 ///
186 /// @param name The resource name.
187 /// @return The resource string, respectively a \e nulled string on failure.
188 const String& TryResource( const NString& name )
189 { return Resources->Get( ResourceCategory, name ALIB_DBG(, false) ); }
190
191
192 /// Simple helper method that invokes #Init(resources::ResourcePool*, NCString )
193 /// providing the resource pool and categery of the given \p{resModule}.
194 ///
195 /// @param resModule The module used to load resource strings.
196 void Init( camp::Camp* resModule )
197 { Init( &resModule->GetResourcePool(), resModule->ResourceCategory ); }
198
199 /// Initializes this class. This function has to be invoked after construction and
200 /// after \alib #"alib_mod_bs;was bootstrapped".
201 ///
202 /// This method accesses global \alib variables #"ARG_C", #"ARG_VN", and
203 /// #"ARG_VW", and thus those have to be set by the user's <c>main()</c>-function
204 /// properly.
205 ///
206 /// A resource pool has to be provided along with a corresponding resource category
207 /// to use. While it is not necessary to do, it is recommended to create a custom
208 /// \alib module, which holds such resource pool. For this case, overloaded
209 /// helper method #Init(camp::Camp*) is provided which calls this method by forwarding
210 /// the pool and category name from that module.
211 ///
212 /// @param resourcePool The resource pool used to load resource strings.
213 /// @param resCategory The resource category used to load resource strings.
215 virtual
216 void Init( resources::ResourcePool* resourcePool, NCString resCategory );
217
218 /// Defines parameters given with enumeration \p{TEnum}.
219 /// #"alib_enums_records;ALib Enum Records" of type #"ERParameterDecl" need to
220 /// be associated to the given enumeration type \p{TEnum}.
221 ///
222 /// @tparam TEnum The enum type.
223 template<typename TEnum>
224 requires ( EnumRecords<TEnum>::template AreOfType<ERParameterDecl>() )
226 for( auto recordIt= EnumRecords<TEnum>::begin()
227 ; recordIt!= EnumRecords<TEnum>::end ()
228 ; ++ recordIt )
229 {
230 ParameterDecls.emplace_back( allocator().New<ParameterDecl>(recordIt.Enum() ) );
231
232 if ( MaxNameLength[2] < recordIt->EnumElementName.Length() )
233 MaxNameLength[2]= recordIt->EnumElementName.Length();
234 } }
235
236 /// Removes a recognizable parameter previously defined with #DefineParameters.
237 /// @see Chapter #"alib_cli_detail_undef" of the Programmer's Manual.
238 /// @param parameter The enumeration element that identifies the parameter to undefine.
239 void UndefineParameter(Enum parameter) {
240 for ( auto it= ParameterDecls.begin(); it!= ParameterDecls.end() ; ++it )
241 if ( (*it)->Element() == parameter ) {
242 ParameterDecls.erase( it );
243 return;
244 }
245 ALIB_ERROR("CLI", "Parameter '{}' not defined!", parameter)
246 }
247
248
249 /// Defines commands given with enumeration \p{TEnum}.
250 /// #"alib_enums_records;ALib Enum Records" of type #"ERCommandDecl" need to
251 /// be associated to the given enumeration type \p{TEnum}.
252 /// @tparam TEnum The enum type.
253 template<typename TEnum>
254 requires( EnumRecords<TEnum>::template AreOfType<ERCommandDecl>() )
256 for( auto recordIt= EnumRecords<TEnum>::begin()
257 ; recordIt!= EnumRecords<TEnum>::end ()
258 ; ++ recordIt )
259 {
260 CommandDecls.emplace_back( allocator().New<CommandDecl>(recordIt.Enum(), *this ) );
261
262 auto& name= CommandDecls.back()->Identifier();
263 if ( MaxNameLength[0] < name.Length() )
264 MaxNameLength[0]= name.Length();
265 } }
266
267 /// Removes a recognizable command previously defined with #DefineCommands.
268 /// @see Chapter #"alib_cli_detail_undef" of the Programmer's Manual.
269 /// @param command The enumeration element that identifies the command to undefine.
270 void UndefineCommand(Enum command) {
271 for ( auto it= CommandDecls.begin(); it!= CommandDecls.end() ; ++it )
272 if ( (*it)->Element() == command ) {
273 CommandDecls.erase( it );
274 return;
275 }
276 ALIB_ERROR("CLI", "Command '{}' not defined!", command)
277 }
278
279
280 /// Defines options given with enumeration \p{TEnum}.
281 /// #"alib_enums_records;ALib Enum Records" of type #"EROptionDecl" need to
282 /// be associated to the given enumeration type \p{TEnum}.
283 /// @tparam TEnum The enum type.
284 template<typename TEnum>
285 requires ( EnumRecords<TEnum>::template AreOfType<EROptionDecl>() )
287 for( auto recordIt= EnumRecords<TEnum>::begin()
288 ; recordIt!= EnumRecords<TEnum>::end ()
289 ; ++ recordIt )
290 {
291 OptionDecls.emplace_back( allocator().New<OptionDecl>(recordIt.Enum() ) );
292
293 if ( MaxNameLength[1] < recordIt->EnumElementName.Length() )
294 MaxNameLength[1]= recordIt->EnumElementName.Length();
295 } }
296
297 /// Removes a recognizable option previously defined with #DefineOptions.
298 /// @see Chapter #"alib_cli_detail_undef" of the Programmer's Manual.
299 /// @param option The enumeration element that identifies the option to undefine.
300 void UndefineOption(Enum option) {
301 for ( auto it= OptionDecls.begin(); it!= OptionDecls.end() ; ++it )
302 if ( (*it)->Element() == option ) {
303 OptionDecls.erase( it );
304 return;
305 }
306 ALIB_ERROR("CLI", "Option '{}' not defined!", option)
307 }
308
309 /// Defines exit-codes given with enumeration \p{TEnum}.
310 /// #"alib_enums_records;ALib Enum Records" of type #"ERExitCodeDecl" need to
311 /// be associated to the given enumeration type \p{TEnum}.
312 ///
313 /// @tparam TEnum The enum type.
314 template<typename TEnum>
315 requires ( EnumRecords<TEnum>::template AreOfType<ERExitCodeDecl>() )
317 for( auto recordIt= EnumRecords<TEnum>::begin()
318 ; recordIt!= EnumRecords<TEnum>::end ()
319 ; ++ recordIt )
320 ExitCodeDecls.EmplaceUnique( recordIt.Enum(),
321 allocator().New<ExitCodeDecl>(recordIt.Enum()) );
322 }
323
324 /// Removes a recognizable exit-code previously defined with #DefineExitCodes.
325 /// @see Chapter #"alib_cli_detail_undef" of the Programmer's Manual.
326 /// @param exitCode The enumeration element that identifies the exitCode to undefine.
327 void UndefineExitCode(Enum exitCode) {
328 ALIB_DBG( auto cnt= )
329 ExitCodeDecls.EraseUnique(exitCode);
330 ALIB_ASSERT_ERROR(cnt==1, "CLI", "ExitCode '{}' not defined!", exitCode)
331 }
332
333
334 //################################################################################################
335 // Parsing interface
336 //################################################################################################
337 public:
338 /// Finalizes Initialization and has to be called after all invocations of:
339 /// - DefineCommands,
340 /// - DefineOptions,
341 /// - DefineParameters and
342 /// - DefineExitCodes,
343 ///
344 /// have been performed. All options recognized get collected in list #Options
345 /// The arguments of the options get removed from #ArgsLeft.
346 ///
347 /// In case of options that have own parameter arguments, such arguments may not be fully
348 /// removed. This depends on whether it is possible with the simple flags and values
349 /// provided in #"OptionDecl" to enable class #"cli::Option" to fully detect
350 /// them. Therefore, after this method is invoked, for options with more complex syntax,
351 /// custom code may be needed to pull the "remainders" of option arguments from #ArgsLeft.
352 /// For this, the inherited field #"^Option::Position;*" is quite useful, as well as the method
353 /// #RemoveArg.
354 ///
355 /// It has to be ensured that before the next step, which is the invocation of
356 /// #ReadNextCommands, all option-related CLI arguments are cleaned away!
357 ///
358 /// For this reason, this method removes all arguments that start with a
359 /// hyphen \c '-' from the #ArgsLeft, even if they got \e not recognized. Those CLI arguments
360 /// get collected in #OptionArgsIgnored.
361 /// Finding unrecognized options is not considered an error, because other libraries used
362 /// with the software might read CLI options autonomously.
363 ///
364 /// \note
365 /// A good sample for this is class #"CLIVariablesPlugin" used with \alib
366 /// configuration variables. After this method has been invoked, vector
367 /// #OptionArgsIgnored may/should be passed to the plug-in \b %CLIVariablesPlugin of
368 /// (all) #"Configuration" object(s) used with the application. For this,
369 /// an invocation, similar to this may be used with all \alibmods that use an own
370 /// configuration object:
371 ///
372 /// XYZModule.GetConfig()->GetPluginTypeSafe<alib::variables::CLIVariablesPlugin>()->SetArgs( &OptionArgsIgnored );
373 ///
374 /// In the case that other libraries have more complex option syntax, e.g., options
375 /// consisting of multiple arguments or such that do not even start with a hyphen character,
376 /// then, this method should be called <b>only after a custom code removes such 3rd party
377 /// options in a reliable way!</b>
378 ///
379 /// If all this was not done, the "remainder" of custom options might confuse parsing
380 /// of commands and its parameters and most probably would lead to an
381 /// "unknown" command error when the remainders of non-removed 3rd party option arguments
382 /// are consumed later.
383 ///
384 /// As a consequence of this approach, a subsequent call to this method has no effect.
386 virtual
387 void ReadOptions();
388
389 /// Searches and returns the last occurrence of the specified option.
390 ///
391 /// This method is to be used with options that overwrite previous values in case
392 /// that it was given multiple times as a CLI argument. Instead, options that may occur
393 /// multiple times without overriding a previous occurrence, are to be processed
394 /// "manually" by examining field #Options.
395 ///
396 /// @param element The option's declaration enum element.
397 /// @return A pointer to the parsed option, respectively \c nullptr if not given.
399 Option* GetOption( Enum element );
400
401 /// Parses as many commands as possible and stores them in #CommandsParsed. Prior
402 /// to invoking this method, all CLI declarations have to be made. Furthermore, usually
403 /// method #ReadOptions has to be invoked before this method.
404 ///
405 /// The name of the method indicates that one or more, but maybe not all commands are read.
406 /// The reason for this behavior lies in the fact that commands may be defined that
407 /// do their own, specifically coded parsing. As a matter of the fact that the flags and
408 /// options of structs #"CommandDecl" and #"ParameterDecl" are kept rather
409 /// simple to match the most usual cases, the parsing of arguments of a command often
410 /// has to be left to be done by custom code. Mostly just when processing (executing) a
411 /// command. In contrast to the need of parsing (and processing) all CLI options,
412 /// given before processing commands, this is not a problem. The usual inner part of a
413 /// command processing loop then looks like this:
414 /// - Check if #CommandsParsed is empty
415 /// - Invoke this method (\b %ReadNextCommands )
416 /// - If still no command is found, break loop
417 /// - Remove and process first command in #CommandsParsed.
418 ///
419 /// A similar parsing approach is supported with method #NextCommand. The only difference
420 /// is that the parsed commands stay in #CommandsParsed and instead field #NextCommandIt
421 /// indicates the position of the next command to read. This way, commands may refer
422 /// to previous ones, if this is needed.
423 ///
424 /// As a final note it should be mentioned, that implementing a "dry run" option on the
425 /// level of command parsing, for the reasons explained above, might need some specific
426 /// custom code to be able to parse all commands (without processing them). If such
427 /// functionality is wanted, it is best to separate custom command parsing from
428 /// command execution (the opposite was recommended above). Only the last command in the list
429 /// has to be 'manually' processed, as the previous ones obviously got parsed well.
430 /// With this approach, all commands can be parsed without executing them. Static utility
431 /// method #"CLIUtil::DumpParseResults;*" is a predefined way to then write information
432 /// about all options and commands parsed.<br>
433 /// A lower level "dry run", that receives information about the concrete actions that the
434 /// processing of commands would perform, is of course a different, application specific
435 /// task.
437 virtual
438 void ReadNextCommands();
439
440 /// Returns the next item in vector #NextCommand. If needed, #ReadNextCommands is
441 /// invoked.
442 /// @return The next command parsed from CLI argument list. \c nullptr, if no more commands
443 /// are found.
445 virtual
447
448 /// Retrieves the number of arguments.
449 ///
450 /// @return The number of arguments given.
451 virtual
452 int ArgCount() { return int(ArgStrings.size()); }
453
454 /// Retrieves the argument at the given position.<br>
455 /// In debug-builds, this method asserts the index is in the available range.
456 /// @param idx The requested argument's index.
457 /// @return The element \p{idx} of vector #ArgStrings.
458 virtual
460 ALIB_ASSERT_ERROR( idx >= 0 && size_t(idx) < ArgStrings.size(),
461 "CLI", "Argument index out of bounds" )
462 return ArgStrings[size_t(idx)];
463 }
464
465 /// Retrieves the next argument from the list without removing it.
466 ///
467 /// \see Method #PopArg, #RemoveArg and #ReadNextCommands.
468 ///
469 /// @return The first argument of (respectively remaining in) the list.
470 /// If no argument is available, a \e nulled string is returned.
471 virtual
473 {
474 return ArgsLeft.size() > 0 ? ArgStrings[size_t(ArgsLeft[0])]
475 : String();
476 }
477
478 /// Retrieves the next argument and removes it from list #ArgsLeft.
479 ///
480 /// \see Method #PeekArg, #RemoveArg and #ReadNextCommands.
481 ///
482 /// @return The first argument of vector #ArgsLeft.
483 /// If no argument is available, a \e nulled string is returned.
485 virtual
486 String PopArg();
487
488 /// Removes the argument at position \p{argNo}.
489 /// If the argument is not in #ArgsLeft, an \alib_assertion is raised.
490 ///
491 /// \see Method #PeekArg, #PopArg and #ReadNextCommands.
492 ///
493 /// @param argNo The argument number to remove.
495 void RemoveArg( integer argNo );
496}; // class CommandLine
497
498
499
500//##################################################################################################
501// Definitions of methods of related objects that can be only mede after CommandLine is defined
502//##################################################################################################
503inline
505: Parsed( cmdLine )
506, Args ( cmdLine->stringListRecycler ) {}
507
509: Parsed( cmdLine )
510, Args ( cmdLine->stringListRecycler ) {}
511
512template<typename TEnum>
513CommandDecl::CommandDecl( TEnum element, CommandLine& cmdLine )
514: declElement (element)
515, resourceInfo(element)
516, CmdLine (cmdLine )
517, Parameters (cmdLine.allocator) {
518 // make a copy of the resourced record
520
522}
523
525: Parsed ( cmdLine )
526, ParametersMandatory( cmdLine->paramListRecycler )
527, ParametersOptional ( cmdLine->paramListRecycler ) {}
528
529
530} // namespace alib[::basecamp]
531
532/// Type alias in namespace \b alib.
534
535
536} // namespace [alib]
#define ALIB_DLL
Definition alib.inl:573
#define A_CHAR(STR)
Definition alib.inl:1325
#define ALIB_ERROR(domain,...)
Definition alib.inl:1140
#define ALIB_EXPORT
Definition alib.inl:562
#define ALIB_DBG(...)
Definition alib.inl:931
#define ALIB_ASSERT_ERROR(cond, domain,...)
Definition alib.inl:1144
ERCommandDecl record
A copy (!) of the enum record.
ListMA< ParameterDecl * > Parameters
Command parameters.
CommandLine & CmdLine
The command-line instance we belong to.
CommandDecl(TEnum element, CommandLine &cmdLine)
ResourceInfo resourceInfo
The resource information of the enumeration type given with construction.
Enum declElement
The enumeration element given with construction.
virtual String PeekArg()
void RemoveArg(integer argNo)
const String & TryResource(const NString &name)
virtual void ReadOptions()
virtual void ReadNextCommands()
StdVectorMA< integer > ArgsLeft
Option * GetOption(Enum element)
void UndefineCommand(Enum command)
ListMA< String, Recycling::Shared > OptionArgsIgnored
MonoAllocator allocator
StdVectorMA< String > ArgStrings
void Init(camp::Camp *resModule)
ListMA< CommandDecl * > CommandDecls
Commands defined.
ListMA< Parameter *, Recycling::Shared >::SharedRecyclerType paramListRecycler
MonoAllocator & GetAllocator() noexcept
ListMA< Command * > CommandsParsed
A list of commands actually parsed. Filled with method #"ReadNextCommands".
const String & GetResource(const NString &name)
void UndefineExitCode(Enum exitCode)
CommandLine()
Constructor.
ListMA< String, Recycling::Shared >::SharedRecyclerType stringListRecycler
The element recycler shared between lists of strings.
ListMA< Option * > Options
The options parsed in the order of their appearance.
ListMA< ParameterDecl * > ParameterDecls
Possible Parameters.
NCString ResourceCategory
The resource category to fetch CLI resources within field #"Resources".
virtual ~CommandLine()
Virtual empty destructor.
ListMA< Command * >::iterator NextCommandIt
The next command in #"CommandsParsed" to be processed. Used with method #"NextCommand".
void UndefineOption(Enum option)
ListMA< OptionDecl * > OptionDecls
Possible Options.
void UndefineParameter(Enum parameter)
HashMap< MonoAllocator, Enum, ExitCodeDecl * > ExitCodeDecls
Possible Errors.
resources::ResourcePool * Resources
virtual String PopArg()
virtual Command * NextCommand()
virtual String GetArg(integer idx)
typename detail::RecyclingSelector< TRecycling > ::template HookType< MonoAllocator, detail::ListElement< T > > SharedRecyclerType
Definition list.inl:103
DryRunModes
Dry run modes.
Definition clicamp.inl:86
const RecordsTraits< TEnum >::Type & GetRecord(TEnum element)
Definition records.inl:185
monomem::TMonoAllocator< lang::HeapAllocator > MonoAllocator
strings::TString< nchar > NString
Type alias in namespace alib.
Definition string.inl:2181
strings::TCString< nchar > NCString
Type alias in namespace alib.
Definition cstring.inl:408
containers::List< T, MonoAllocator, TRecycling > ListMA
Type alias in namespace alib.
Definition list.inl:697
cli::CommandLine CommandLine
Type alias in namespace alib.
lang::integer integer
Type alias in namespace alib.
Definition integers.inl:149
enumrecords::EnumRecords< TEnum > EnumRecords
Type alias in namespace alib.
Definition records.inl:482
containers::HashMap< TAllocator, TKey, TMapped, THash, TEqual, THashCaching, TRecycling > HashMap
Type alias in namespace alib.
strings::TString< character > String
Type alias in namespace alib.
Definition string.inl:2172
std::vector< T, StdMA< T > > StdVectorMA
Type alias in namespace alib.
boxing::Enum Enum
Type alias in namespace alib.
Definition enum.inl:211
A command of a ALib CLI command-line.
Command(CommandLine *cmdLine)
ListMA< Parameter *, Recycling::Shared > ParametersMandatory
Mandatory parameters parsed.
ListMA< Parameter *, Recycling::Shared > ParametersOptional
Optional parameters parsed.
ListMA< String, Recycling::Shared > Args
Arguments belonging to this option.
Option(CommandLine *cmdLine)
A declaration for a #"cli::Parameter".
Parameter(CommandLine *cmdLine)
ListMA< String, Recycling::Shared > Args
Arguments belonging to us.
Parsed(CommandLine *cmdLine)
Definition arguments.inl:39
static ForwardIterator begin()
Definition records.inl:378
static constexpr ForwardIterator end()
Definition records.inl:386