Group

dataRepository::Group serves as a base class for most objects in GEOS. In GEOS, the Group may be thought of as an analogy to the file folder in a hierachical filesystem-like structure. As such, a Group is used as a container class that holds a collection of other Groups, or sub-Groups, a pointer to the parent of the Group, and a collection of Wrappers. The Group also defines a general capability to create and traverse/access the objects in the hierarchy. The Wrappers contained in a Group may be of arbitrary type, but in the case of an LvArray object, a Group size and capacity may be translated down to array, thereby keeping a collection of wrapped Array objects that will resize in unison with the Group. Each group has a string “name” that defines its key in the parent Group. This key string must be unique in the scope of the parent Group.

Implementation Details

Some noteworthy implementation details inside the declaration of dataRepository::Group are:

/// The default key type for entries in the hierarchy.
using keyType = string;

/// The default index type for entries the hierarchy.
using indexType = localIndex;
  • In the GEOS repository, the keyType is specified to be a string for all collection objects, while the indexType is specified to be a localIndex. The types are set in the common/DataTypes.hpp file, but are typically a string and a std::ptrdiff_t respectively.

  /// The template specialization of MappedVector to use for the collection of sub-Group objects.
  using subGroupMap = MappedVector< Group, Group *, keyType, indexType >;

  /// The template specialization of MappedVector to use for the collection wrappers objects.
  using wrapperMap = MappedVector< WrapperBase, WrapperBase *, keyType, indexType >;
  • The subGroupMap and wrapperMap aliases represent the type of container that the collection of sub-Group s and Wrapper s are stored in for each Group. These container types are template specializations of the MappedVector class, which store a pointer to a type, and provides functionality for a key or index based lookup. More details may be found in the documentation for MappedVector.

  /// The parent Group that contains "this" Group in its "sub-Group" collection.
  Group * m_parent = nullptr;

  /// Specification that this group will have the same m_size as m_parent.
  integer m_sizedFromParent;

  /// The container for the collection of all wrappers continued in "this" Group.
  wrapperMap m_wrappers;

  /// The container for the collection of all sub-groups contained in "this" Group.
  subGroupMap m_subGroups;

  /// The size/length of this Group...and all Wrapper<> that are are specified to have the same size as their
  /// owning group.
  indexType m_size;

  /// The capacity for wrappers in this group...and all Wrapper<> that are specified to have the same size as their
  /// owning group.
  indexType m_capacity;

  /// The name/key of this Group in its parent collection of sub-Groups.
  string m_name;

  /// Verbosity flag for group logs
  integer m_logLevel;
  • The m_parent member is a pointer to the Group that contains the current Group as part of its collection of sub-Group s.

    Warning

    The existence of the non-const m_parent gives the current Group access to alter the parent Group. Special care should be taken to avoid using this access whenever possible. Remember…with great power comes great responsibility.

  • The m_wrappers member is the collection of Wrappers contained in the current Group.

  • The m_subGroups member is the collection of Group s contained in the current Group.

  • The m_size and m_capacity members are used to set the size and capacity of any objects contained in the m_wrappers collection that have been specified to be set by their owning Group. This is typically only useful for Array types and is implemented within the WrapperBase object.

  • The m_name member is the key of this Group in the collection of m_parent->m_subGroups. This key is unique in the scope of m_parent, so some is required when constructing the hierarchy.

Interface Functions

The public interface for dataRepository::Group provides functionality for constructing a hierarchy, and traversing that hierarchy, as well as accessing the contents of objects stored in the Wrapper containers stored within a Group.

Adding New Groups

To add new sub-Group s there are several registerGroup functions that add a new Group under the calling Group scope. A listing of these functions is provided:

  /**
   * @name Sub-group registration interface
   */
  ///@{

  /**
   * @brief Register a new Group as a sub-group of current Group.
   *
   * @tparam T The type of the Group to add/register. This should be a type that derives from Group.
   * @param[in] name      The name of the group to use as a string key.
   * @param[in] newObject A unique_ptr to the object that is being registered.
   * @return              A pointer to the newly registered Group.
   *
   * Registers a Group or class derived from Group as a subgroup of this Group and takes ownership.
   */
  template< typename T = Group >
  T & registerGroup( string const & name, std::unique_ptr< T > newObject )
  {
    newObject->m_parent = this;
    return dynamicCast< T & >( *m_subGroups.insert( name, newObject.release(), true ) );
  }

  /**
   * @brief @copybrief registerGroup(string const &,std::unique_ptr<T>)
   *
   * @tparam T The type of the Group to add/register. This should be a type that derives from Group.
   * @param[in] name          The name of the group to use as a string key.
   * @param[in] newObject     A unique_ptr to the object that is being registered.
   * @return                  A pointer to the newly registered Group.
   *
   * Registers a Group or class derived from Group as a subgroup of this Group but does not take ownership.
   */
  template< typename T = Group >
  T & registerGroup( string const & name, T * newObject )
  { return dynamicCast< T & >( *m_subGroups.insert( name, newObject, false ) ); }


  /**
   * @brief @copybrief registerGroup(string const &,std::unique_ptr<T>)
   *
   * @tparam T The type of the Group to add/register. This should be a type that derives from Group.
   * @param[in] name The name of the group to use as a string key.
   * @return         A pointer to the newly registered Group.
   *
   * Creates and registers a Group or class derived from Group as a subgroup of this Group.
   */
  template< typename T = Group >
  T & registerGroup( string const & name )
  { return registerGroup< T >( name, std::make_unique< T >( name, this ) ); }

  /**
   * @brief @copybrief registerGroup(string const &,std::unique_ptr<T>)
   *
   * @tparam T The type of the Group to add/register. This should be a type that derives from Group.
   * @param keyIndex A KeyIndexT object that will be used to specify the name of
   *   the new group. The index of the KeyIndex will also be set.
   * @return A pointer to the newly registered Group, or @c nullptr if no group was registered.
   *
   * Creates and registers a Group or class derived from Group as a subgroup of this Group.
   */
  template< typename T = Group >
  T & registerGroup( subGroupMap::KeyIndex const & keyIndex )
  {
    T & rval = registerGroup< T >( keyIndex.key(), std::make_unique< T >( keyIndex.key(), this ) );
    keyIndex.setIndex( m_subGroups.getIndex( keyIndex.key() ) );
    return rval;
  }

  /**
   * @brief @copybrief registerGroup(string const &,std::unique_ptr<T>)
   *
   * @tparam T The type of the Group to add/register. This should be a type that derives from Group.
   * @tparam TBASE The type whose type catalog will be used to look up the new sub-group type
   * @param[in] name        The name of the group to use as a string key.
   * @param[in] catalogName The catalog name of the new type.
   * @return                A pointer to the newly registered Group.
   *
   * Creates and registers a Group or class derived from Group as a subgroup of this Group.
   */
  template< typename T = Group, typename TBASE = Group >
  T & registerGroup( string const & name, string const & catalogName )
  {
    std::unique_ptr< TBASE > newGroup = TBASE::CatalogInterface::Factory( catalogName, name, this );
    return registerGroup< T >( name, std::move( newGroup ) );
  }

  /**
   * @brief Removes a child group from this group.
   * @param name the name of the child group to remove from this group.
   */
  void deregisterGroup( string const & name );

  /**
   * @brief Creates a new sub-Group using the ObjectCatalog functionality.
   * @param[in] childKey The name of the new object type's key in the
   *                     ObjectCatalog.
   * @param[in] childName The name of the new object in the collection of
   *                      sub-Groups.
   * @return A pointer to the new Group created by this function.
   */
  virtual Group * createChild( string const & childKey, string const & childName );

  ///@}

These functions all take in a name for the new Group, which will be used as the key when trying to access the Group in the future. Some variants create a new Group, while some variants take in an existing Group . The template argument is to specify the actaul type of the Group as it it is most likely a type that derives from Group that is we would like to create in the repository. Please see the doxygen documentation for a detailed description of each option.

Getting Groups

The collection of functions to retrieve a Group and their descriptions are taken from source and shown here:

  /**
   * @name Sub-group retrieval methods.
   *
   * This collection of functions are used to get a sub-Group from the current group. Various methods
   * for performing the lookup are provided (localIndex, string, KeyIndex), and each have their
   * advantages and costs. The lowest cost lookup is the "localIndex" lookup. The KeyIndex lookup
   * will add a cost for checking to make sure the index stored in KeyIndex is valid (a string
   * compare, and a hash if it is incorrect). The string lookup is the full cost hash lookup every
   * time that it is called.
   *
   * The template parameter specifies the "type" that the caller expects to lookup, and thus attempts
   * to cast the pointer that is stored in m_subGroups to a pointer of the desired type. If this
   * cast fails, then a @p nullptr is returned. If no template parameter is specified then a default
   * type of Group is assumed.
   */
  ///@{

  /**
   * @brief Return a pointer to a sub-group of the current Group.
   * @tparam T The type of subgroup.
   * @tparam KEY The type of the lookup.
   * @param key The key used to perform the lookup.
   * @return A pointer to @p T that refers to the sub-group, if the Group does not exist or it
   *   has an incompatible type a @c nullptr is returned.
   */
  template< typename T = Group, typename KEY = void >
  T * getGroupPointer( KEY const & key )
  { return dynamicCast< T * >( m_subGroups[ key ] ); }

  /**
   * @copydoc getGroupPointer(KEY const &)
   */
  template< typename T = Group, typename KEY = void >
  T const * getGroupPointer( KEY const & key ) const
  { return dynamicCast< T const * >( m_subGroups[ key ] ); }

  /**
   * @brief Return a reference to a sub-group of the current Group.
   * @tparam T The type of subgroup.
   * @tparam KEY The type of the lookup.
   * @param key The key used to perform the lookup.
   * @return A reference to @p T that refers to the sub-group.
   * @throw std::domain_error If the Group does not exist is thrown.
   */
  template< typename T = Group, typename KEY = void >
  T & getGroup( KEY const & key )
  {
    Group * const child = m_subGroups[ key ];
    GEOS_THROW_IF( child == nullptr,
                   "Group " << getDataContext() << " has no child named " << key << std::endl
                            << dumpSubGroupsNames(),
                   std::domain_error );

    return dynamicCast< T & >( *child );
  }

  /**
   * @copydoc getGroup( KEY const & )
   */
  template< typename T = Group, typename KEY = void >
  T const & getGroup( KEY const & key ) const
  {
    Group const * const child = m_subGroups[ key ];
    GEOS_THROW_IF( child == nullptr,
                   "Group " << getDataContext() << " has no child named " << key << std::endl
                            << dumpSubGroupsNames(),
                   std::domain_error );

    return dynamicCast< T const & >( *child );
  }

  /**
   * @brief Retrieve a group from the hierarchy using a path.
   * @tparam T type of subgroup
   * @param[in] path a unix-style string (absolute, relative paths valid)
   *                 to lookup the Group to return. Absolute paths search
   *                 from the tree root, while relative - from current group.
   * @return A reference to @p T that refers to the sub-group.
   * @throw std::domain_error If the Group doesn't exist.
   */
  template< typename T = Group >
  T & getGroupByPath( string const & path )
  { return dynamicCast< T & >( const_cast< Group & >( getBaseGroupByPath( path ) ) ); }

  /**
   * @copydoc getGroupByPath(string const &)
   */
  template< typename T = Group >
  T const & getGroupByPath( string const & path ) const
  { return dynamicCast< T const & >( getBaseGroupByPath( path ) ); }

Register Wrappers

  /**
   * @name Wrapper registration interface
   */
  ///@{

  /**
   * @brief Create and register a Wrapper around a new object.
   * @tparam T The type of the object allocated.
   * @tparam TBASE The type of the object that the Wrapper holds.
   * @param[in] name the name of the wrapper to use as a string key
   * @param[out] rkey a pointer to a index type that will be filled with the new
   *   Wrapper index in this Group
   * @return A reference to the newly registered/created Wrapper
   */
  template< typename T, typename TBASE=T >
  Wrapper< TBASE > & registerWrapper( string const & name,
                                      wrapperMap::KeyIndex::index_type * const rkey = nullptr );

  /**
   * @copybrief registerWrapper(string const &,wrapperMap::KeyIndex::index_type * const)
   * @tparam T the type of the wrapped object
   * @tparam TBASE the base type to cast the returned wrapper to
   * @param[in] viewKey The KeyIndex that contains the name of the new Wrapper.
   * @return A reference to the newly registered/created Wrapper
   */
  template< typename T, typename TBASE=T >
  Wrapper< TBASE > & registerWrapper( Group::wrapperMap::KeyIndex const & viewKey );

  /**
   * @brief Register a Wrapper around a given object and take ownership.
   * @tparam T the type of the wrapped object
   * @param[in] name the name of the wrapper to use as a string key
   * @param[in] newObject an owning pointer to the object that is being registered
   * @return A reference to the newly registered/created Wrapper
   * @note Not intended to register a @p WrapperBase instance. Use dedicated member function instead.
   */
  template< typename T >
  Wrapper< T > & registerWrapper( string const & name, std::unique_ptr< T > newObject );

  /**
   * @brief Register a Wrapper around an existing object, does not take ownership of the object.
   * @tparam T the type of the wrapped object
   * @param[in] name the name of the wrapper to use as a string key
   * @param[in] newObject a pointer to the object that is being registered
   * @return A reference to the newly registered/created Wrapper
   * @note Not intended to register a @p WrapperBase instance. Use dedicated member function instead.
   */
  template< typename T >
  Wrapper< T > & registerWrapper( string const & name,
                                  T * newObject );

  /**
   * @brief Register and take ownership of an existing Wrapper.
   * @param wrapper A pointer to the an existing wrapper.
   * @return An un-typed pointer to the newly registered/created wrapper
   */
  WrapperBase & registerWrapper( std::unique_ptr< WrapperBase > wrapper );

  /**
   * @brief Removes a Wrapper from this group.
   * @param name the name of the Wrapper to remove from this group.
   */
  void deregisterWrapper( string const & name );

  ///@}

Getting Wrappers/Wrapped Objects

  /**
   * @name Untyped wrapper retrieval methods
   *
   * These functions query the collection of Wrapper objects for the given
   * index/name/KeyIndex and returns a WrapperBase pointer to the object if
   * it exists. If it is not found, nullptr is returned.
   */
  ///@{

  /**
   * @brief Return a reference to a WrapperBase stored in this group.
   * @tparam KEY The lookup type.
   * @param key The value used to lookup the wrapper.
   * @return A reference to the WrapperBase that resulted from the lookup.
   * @throw std::domain_error if the wrapper doesn't exist.
   */
  template< typename KEY >
  WrapperBase const & getWrapperBase( KEY const & key ) const
  {
    WrapperBase const * const wrapper = m_wrappers[ key ];
    GEOS_THROW_IF( wrapper == nullptr,
                   "Group " << getDataContext() << " has no wrapper named " << key << std::endl
                            << dumpWrappersNames(),
                   std::domain_error );

    return *wrapper;
  }

  /**
   * @copydoc getWrapperBase(KEY const &) const
   */
  template< typename KEY >
  WrapperBase & getWrapperBase( KEY const & key )
  {
    WrapperBase * const wrapper = m_wrappers[ key ];
    GEOS_THROW_IF( wrapper == nullptr,
                   "Group " << getDataContext() << " has no wrapper named " << key << std::endl
                            << dumpWrappersNames(),
                   std::domain_error );

    return *wrapper;
  }

  /**
   * @brief
   * @param name
   * @return
   */
  indexType getWrapperIndex( string const & name ) const
  { return m_wrappers.getIndex( name ); }

  /**
   * @brief Get access to the internal wrapper storage.
   * @return a reference to wrapper map
   */
  wrapperMap const & wrappers() const
  { return m_wrappers; }

  /**
   * @copydoc wrappers() const
   */
  wrapperMap & wrappers()
  { return m_wrappers; }

  /**
   * @brief Return the number of wrappers.
   * @return The number of wrappers.
   */
  indexType numWrappers() const
  { return m_wrappers.size(); }

  /**
   * @return An array containing all wrappers keys
   */
  std::vector< string > getWrappersNames() const;

  ///@}

  /**
   * @name Typed wrapper retrieval methods
   *
   * These functions query the collection of Wrapper objects for the given
   * index/key and returns a Wrapper<T> pointer to the object if
   * it exists. The template parameter @p T is used to perform a cast
   * on the WrapperBase pointer that is returned by the lookup, into
   * a Wrapper<T> pointer. If the wrapper is not found, or the
   * WrapperBase pointer cannot be cast to a Wrapper<T> pointer, then nullptr
   * is returned.
   */
  ///@{

  /**
   * @brief Check if a wrapper exists
   * @tparam LOOKUP_TYPE the type of key used to perform the lookup.
   * @param[in] lookup a lookup value used to search the collection of wrappers
   * @return @p true if wrapper exists (regardless of type), @p false otherwise
   */
  template< typename LOOKUP_TYPE >
  bool hasWrapper( LOOKUP_TYPE const & lookup ) const
  { return m_wrappers[ lookup ] != nullptr; }

  /**
   * @brief Retrieve a Wrapper stored in this group.
   * @tparam T the object type contained in the Wrapper
   * @tparam LOOKUP_TYPE the type of key used to perform the lookup
   * @param[in] index    a lookup value used to search the collection of wrappers
   * @return A reference to the Wrapper<T> that resulted from the lookup.
   * @throw std::domain_error if the Wrapper doesn't exist.
   */
  template< typename T, typename LOOKUP_TYPE >
  Wrapper< T > const & getWrapper( LOOKUP_TYPE const & index ) const
  {
    WrapperBase const & wrapper = getWrapperBase( index );
    return dynamicCast< Wrapper< T > const & >( wrapper );
  }

  /**
   * @copydoc getWrapper(LOOKUP_TYPE const &) const
   */
  template< typename T, typename LOOKUP_TYPE >
  Wrapper< T > & getWrapper( LOOKUP_TYPE const & index )
  {
    WrapperBase & wrapper = getWrapperBase( index );
    return dynamicCast< Wrapper< T > & >( wrapper );
  }

  /**
   * @brief Retrieve a Wrapper stored in this group.
   * @tparam T the object type contained in the Wrapper
   * @tparam LOOKUP_TYPE the type of key used to perform the lookup
   * @param[in] index a lookup value used to search the collection of wrappers
   * @return A pointer to the Wrapper<T> that resulted from the lookup, if the Wrapper
   *   doesn't exist or has a different type a @c nullptr is returned.
   */
  template< typename T, typename LOOKUP_TYPE >
  Wrapper< T > const * getWrapperPointer( LOOKUP_TYPE const & index ) const
  { return dynamicCast< Wrapper< T > const * >( m_wrappers[ index ] ); }

  /**
   * @copydoc getWrapperPointer(LOOKUP_TYPE const &) const
   */
  template< typename T, typename LOOKUP_TYPE >
  Wrapper< T > * getWrapperPointer( LOOKUP_TYPE const & index )
  { return dynamicCast< Wrapper< T > * >( m_wrappers[ index ] ); }

  ///@}

  /**
   * @name Wrapper data access methods.
   *
   * These functions can be used to get referece/pointer access to the data
   * stored by wrappers in this group. They are essentially just shortcuts for
   * @p Group::getWrapper() and @p Wrapper<T>::getReference().
   * An additional template parameter can be provided to cast the return pointer
   * or reference to a base class pointer or reference (e.g. Array to ArrayView).
   */
  ///@{

  /**
   * @brief Look up a wrapper and get reference to wrapped object.
   * @tparam T return value type
   * @tparam WRAPPEDTYPE wrapped value type (by default, same as return)
   * @tparam LOOKUP_TYPE type of value used for wrapper lookup
   * @param lookup       value for wrapper lookup
   * @return reference to @p T
   * @throw A std::domain_error if the Wrapper does not exist.
   */
  template< typename T, typename LOOKUP_TYPE >
  GEOS_DECLTYPE_AUTO_RETURN
  getReference( LOOKUP_TYPE const & lookup ) const
  { return getWrapper< T >( lookup ).reference(); }

  /**
   * @copydoc getReference(LOOKUP_TYPE const &) const
   */
  template< typename T, typename LOOKUP_TYPE >
  T & getReference( LOOKUP_TYPE const & lookup )
  { return getWrapper< T >( lookup ).reference(); }

Looping Interface

  /**
   * @name Functor-based subgroup iteration
   *
   * These functions loop over sub-groups and executes a functor that uses the sub-group as an
   * argument. The functor is only executed if the group can be cast to a certain type specified
   * by the @p ROUPTYPE/S pack. The variadic list consisting of @p GROUPTYPE/S will be used recursively
   * to check if the group is able to be cast to the one of these types. The first type in the
   * @p GROUPTYPE/S list will be used to execute the functor, and the next sub-group will be processed.
   */
  ///@{

  /**
   * @brief Apply the given functor to subgroups that can be casted to one of specified types.
   * @tparam GROUPTYPE  the first type that will be used in the attempted casting of group.
   * @tparam GROUPTYPES a variadic list of types that will be used in the attempted casting of group.
   * @tparam LAMBDA     the type of functor to call
   * @param[in] lambda  the functor to call on subgroups
   */
  template< typename GROUPTYPE = Group, typename ... GROUPTYPES, typename LAMBDA >
  void forSubGroups( LAMBDA && lambda )
  {
    for( auto & subGroupIter : m_subGroups )
    {
      applyLambdaToContainer< GROUPTYPE, GROUPTYPES... >( *subGroupIter.second, [&]( auto & castedSubGroup )
      {
        lambda( castedSubGroup );
      } );
    }
  }

  /**
   * @copydoc forSubGroups(LAMBDA &&)
   */
  template< typename GROUPTYPE = Group, typename ... GROUPTYPES, typename LAMBDA >
  void forSubGroups( LAMBDA && lambda ) const
  {
    for( auto const & subGroupIter : m_subGroups )
    {
      applyLambdaToContainer< GROUPTYPE, GROUPTYPES... >( *subGroupIter.second, [&]( auto const & castedSubGroup )
      {
        lambda( castedSubGroup );
      } );
    }
  }


  /**
   * @brief Apply the given functor to subgroups that can be casted to one of specified types.
   * @tparam GROUPTYPE  the first type that will be used in the attempted casting of group.
   * @tparam GROUPTYPES a variadic list of types that will be used in the attempted casting of group.
   * @tparam LAMBDA     the type of functor to call
   * @param[in] lambda  the functor to call on subgroups
   */
  template< typename GROUPTYPE = Group, typename ... GROUPTYPES, typename LAMBDA >
  void forSubGroupsIndex( LAMBDA && lambda )
  {
    localIndex counter = 0;
    for( auto & subGroupIter : m_subGroups )
    {
      applyLambdaToContainer< GROUPTYPE, GROUPTYPES... >( *subGroupIter.second,
                                                          [&]( auto & castedSubGroup )
      {
        lambda( counter, castedSubGroup );
      } );
      ++counter;
    }
  }

  /**
   * @copydoc forSubGroupsIndex(LAMBDA &&)
   */
  template< typename GROUPTYPE = Group, typename ... GROUPTYPES, typename LAMBDA >
  void forSubGroupsIndex( LAMBDA && lambda ) const
  {
    localIndex counter = 0;
    for( auto const & subGroupIter : m_subGroups )
    {
      applyLambdaToContainer< GROUPTYPE, GROUPTYPES... >( *subGroupIter.second,
                                                          [&]( auto const & castedSubGroup )
      {
        lambda( counter, castedSubGroup );
      } );
      ++counter;
    }
  }

  /**
   * @copybrief forSubGroups(LAMBDA &&)
   * @tparam GROUPTYPE        the first type that will be used in the attempted casting of group.
   * @tparam GROUPTYPES       a variadic list of types that will be used in the attempted casting of group.
   * @tparam LOOKUP_CONTAINER type of container of subgroup lookup keys (names or indices), must support range-based for
   * loop
   * @tparam LAMBDA           type of functor callable with an index in lookup container and a reference to casted
   * subgroup
   * @param[in] subGroupKeys  container with subgroup lookup keys (e.g. names or indices) to apply the functor to
   * @param[in] lambda        the functor to call
   */
  template< typename GROUPTYPE = Group, typename ... GROUPTYPES, typename LOOKUP_CONTAINER, typename LAMBDA >
  void forSubGroups( LOOKUP_CONTAINER const & subGroupKeys, LAMBDA && lambda )
  {
    localIndex counter = 0;
    for( auto const & subgroup : subGroupKeys )
    {
      applyLambdaToContainer< GROUPTYPE, GROUPTYPES... >( getGroup( subgroup ), [&]( auto & castedSubGroup )
      {
        lambda( counter, castedSubGroup );
      } );
      ++counter;
    }
  }

  /**
   * @copybrief forSubGroups(LAMBDA &&)
   * @tparam GROUPTYPE        the first type that will be used in the attempted casting of group.
   * @tparam GROUPTYPES       a variadic list of types that will be used in the attempted casting of group.
   * @tparam LOOKUP_CONTAINER type of container of subgroup lookup keys (names or indices), must support range-based for
   * loop
   * @tparam LAMBDA           type of functor callable with an index in lookup container and a reference to casted
   * subgroup
   * @param[in] subGroupKeys  container with subgroup lookup keys (e.g. names or indices) to apply the functor to
   * @param[in] lambda        the functor to call
   */
  template< typename GROUPTYPE = Group, typename ... GROUPTYPES, typename LOOKUP_CONTAINER, typename LAMBDA >
  void forSubGroups( LOOKUP_CONTAINER const & subGroupKeys, LAMBDA && lambda ) const
  {
    localIndex counter = 0;
    for( auto const & subgroup : subGroupKeys )
    {
      applyLambdaToContainer< GROUPTYPE, GROUPTYPES... >( getGroup( subgroup ), [&]( auto const & castedSubGroup )
      {
        lambda( counter, castedSubGroup );
      } );
      ++counter;
    }
  }
  ///@}

  /**
   * @name Functor-based wrapper iteration
   *
   * These functions loop over the wrappers contained in this group, and executes a functor that
   * uses the Wrapper as an argument. The functor is only executed if the Wrapper can be casted to
   * a certain type specified by the @p TYPE/S pack. The variadic list consisting of
   * @p TYPE/S will be used recursively to check if the Wrapper is able to be casted to the
   * one of these types. The first type in the @p WRAPPERTYPE/S list will be used to execute the
   * functor, and the next Wrapper will be processed.
   */
  ///@{

  /**
   * @brief Apply the given functor to wrappers.
   * @tparam LAMBDA the type of functor to call
   * @param[in] lambda  the functor to call
   */
  template< typename LAMBDA >
  void forWrappers( LAMBDA && lambda )
  {
    for( auto & wrapperIter : m_wrappers )
    {
      lambda( *wrapperIter.second );
    }
  }

  /**
   * @copydoc forWrappers(LAMBDA &&)
   */
  template< typename LAMBDA >
  void forWrappers( LAMBDA && lambda ) const
  {
    for( auto const & wrapperIter : m_wrappers )
    {
      lambda( *wrapperIter.second );
    }
  }

  /**
   * @brief Apply the given functor to wrappers that can be cast to one of specified types.
   * @tparam TYPE   the first type that will be used in the attempted casting of Wrapper
   * @tparam TYPES  a variadic list of types that will be used in the attempted casting of Wrapper
   * @tparam LAMBDA the type of functor to call
   * @param[in] lambda  the functor to call
   */
  template< typename TYPE, typename ... TYPES, typename LAMBDA >
  void forWrappers( LAMBDA && lambda )
  {
    for( auto & wrapperIter : m_wrappers )
    {
      applyLambdaToContainer< Wrapper< TYPE >, Wrapper< TYPES >... >( *wrapperIter.second,
                                                                      std::forward< LAMBDA >( lambda ));
    }
  }

  /**
   * @brief Apply the given functor to wrappers that can be cast to one of specified types.
   * @tparam TYPE   the first type that will be used in the attempted casting of Wrapper
   * @tparam TYPES  a variadic list of types that will be used in the attempted casting of Wrapper
   * @tparam LAMBDA the type of functor to call
   * @param[in] lambda  the functor to call
   */
  template< typename TYPE, typename ... TYPES, typename LAMBDA >
  void forWrappers( LAMBDA && lambda ) const
  {
    for( auto const & wrapperIter : m_wrappers )
    {
      applyLambdaToContainer< Wrapper< TYPE >, Wrapper< TYPES >... >( *wrapperIter.second,
                                                                      std::forward< LAMBDA >( lambda ));
    }
  }

  ///@}