diff --git a/applications/test/cstring/Test-cstring.cxx b/applications/test/cstring/Test-cstring.cxx
index 002300e00949a58b06f409a1bbf53a4b9f892d58..0adbb54d30d886ea25e38b185487fb84300ca6f6 100644
--- a/applications/test/cstring/Test-cstring.cxx
+++ b/applications/test/cstring/Test-cstring.cxx
@@ -123,7 +123,7 @@ int main(int argc, char *argv[])
         );
 
         Info<< testInput << nl;
-        SubStrings<string> args = stringOps::splitSpace(testInput);
+        auto args = stringOps::splitSpace(testInput);
         Info<< "split into " << args.size() << " args" << nl;
 
         CStringList inC(args);
diff --git a/applications/test/fileHandler-ranks1/Test-fileHandler-ranks1.C b/applications/test/fileHandler-ranks1/Test-fileHandler-ranks1.C
index 253553e7d6a400817e43ac59bded0e0c3f87e62e..2d038b580b3d222b8b974a7f511205fb2e29e8fa 100644
--- a/applications/test/fileHandler-ranks1/Test-fileHandler-ranks1.C
+++ b/applications/test/fileHandler-ranks1/Test-fileHandler-ranks1.C
@@ -47,7 +47,7 @@ using namespace Foam;
 template<class PrimitiveType>
 static List<PrimitiveType> splitStringToList(const std::string& str)
 {
-    const SubStrings<std::string> items = stringOps::splitAny(str, " ,;");
+    const auto items = stringOps::splitAny(str, " ,;");
 
     DynamicList<PrimitiveType> values(items.size());
 
diff --git a/applications/test/string/Test-string.C b/applications/test/string/Test-string.C
index 587eb2e3149174a9b46a7dd4fed827d1d3ce7805..72cd2ab17801bb6cb570d39a45182632f326ca57 100644
--- a/applications/test/string/Test-string.C
+++ b/applications/test/string/Test-string.C
@@ -148,7 +148,7 @@ int main(int argc, char *argv[])
 
             Info<< "input:  " << input  << nl
                 << "expand: " << output << nl
-                << "split: " << stringOps::split(output, "/") << nl << nl;
+                << "split: " << stringOps::split(output, '/') << nl << nl;
         }
     }
 
diff --git a/applications/test/stringSplit/Test-stringSplit.C b/applications/test/stringSplit/Test-stringSplit.C
index 2dc4e6ab98e65e9d11de540e1102887b7196cbb4..bc12f4b1660d9704f60a0533e9ce92f930ea5216 100644
--- a/applications/test/stringSplit/Test-stringSplit.C
+++ b/applications/test/stringSplit/Test-stringSplit.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2017-2021 OpenCFD Ltd.
+    Copyright (C) 2017-2024 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -34,6 +34,7 @@ Description
 #include "argList.H"
 #include "fileName.H"
 #include "stringOps.H"
+#include "Switch.H"
 
 using namespace Foam;
 
@@ -65,6 +66,9 @@ int main(int argc, char *argv[])
 {
     argList::noBanner();
     argList::noParallel();
+    argList::noMandatoryArgs();
+    argList::addArgument("string .. stringN");
+
     argList::addOption
     (
         "any",
@@ -89,6 +93,12 @@ int main(int argc, char *argv[])
         "int",
         "test split on fixed width"
     );
+    argList::addOption
+    (
+        "begin",
+        "int",
+        "begin offset for splits"
+    );
     argList::addBoolOption
     (
         "slash",
@@ -104,18 +114,25 @@ int main(int argc, char *argv[])
         "empty",
         "preserve empty strings in split"
     );
-    argList args(argc, argv, false, true);
+
+    argList args(argc, argv);
 
     if (args.size() <= 1 && args.options().empty())
     {
         args.printUsage();
     }
 
+    const label beginOffset = args.getOrDefault<label>("begin", 0);
+
     const bool keepEmpty = args.found("empty");
 
+    Info<< "begin offset: " << beginOffset << nl;
+    Info<< "keep empty  : " << Switch::name(keepEmpty) << nl;
+
     const label nopts =
         args.count({"any", "slash", "space", "sub", "fixed", "char"});
 
+
     if (args.found("any"))
     {
         const std::string& str = args["any"];
@@ -125,7 +142,7 @@ int main(int argc, char *argv[])
 
         for (label argi=1; argi < args.size(); ++argi)
         {
-            const auto split = stringOps::splitAny(args[argi], str);
+            auto split = stringOps::splitAny(args[argi], str, beginOffset);
             printSubStrings(args[argi], split);
         }
 
@@ -144,7 +161,7 @@ int main(int argc, char *argv[])
 
         for (label argi=1; argi < args.size(); ++argi)
         {
-            const auto split = stringOps::split(args[argi], str);
+            auto split = stringOps::split(args[argi], str, beginOffset);
             printSubStrings(args[argi], split);
         }
 
@@ -161,7 +178,11 @@ int main(int argc, char *argv[])
 
         for (label argi=1; argi < args.size(); ++argi)
         {
-            const auto split = stringOps::splitSpace(args[argi]);
+            auto split = stringOps::splitSpace(args[argi], beginOffset);
+            printSubStrings(args[argi], split);
+
+            Info<< "pop_front(2)" << nl;
+            split.pop_front(2);
             printSubStrings(args[argi], split);
         }
 
@@ -180,7 +201,8 @@ int main(int argc, char *argv[])
 
         for (label argi=1; argi < args.size(); ++argi)
         {
-            const auto split = stringOps::split(args[argi], delim, keepEmpty);
+            auto split =
+                stringOps::split(args[argi], delim, beginOffset, keepEmpty);
             printSubStrings(args[argi], split);
         }
 
@@ -199,7 +221,7 @@ int main(int argc, char *argv[])
 
         for (label argi=1; argi < args.size(); ++argi)
         {
-            const auto split = stringOps::splitFixed(args[argi], width);
+            auto split = stringOps::splitFixed(args[argi], width, beginOffset);
             printSubStrings(args[argi], split);
         }
 
@@ -219,7 +241,8 @@ int main(int argc, char *argv[])
 
         for (label argi=1; argi < args.size(); ++argi)
         {
-            const auto split = stringOps::split(args[argi], delim, keepEmpty);
+            auto split =
+                stringOps::split(args[argi], delim, beginOffset, keepEmpty);
             printSubStrings(args[argi], split);
         }
     }
diff --git a/src/OpenFOAM/global/fileOperations/fileOperation/fileOperationRanks.C b/src/OpenFOAM/global/fileOperations/fileOperation/fileOperationRanks.C
index c500571412a10ba84b406656d3bd67b6cd4fc191..cfedf5313061c62a320b313252227fcdf0799f9b 100644
--- a/src/OpenFOAM/global/fileOperations/fileOperation/fileOperationRanks.C
+++ b/src/OpenFOAM/global/fileOperations/fileOperation/fileOperationRanks.C
@@ -42,7 +42,7 @@ namespace Foam
 template<class PrimitiveType>
 static List<PrimitiveType> splitStringToList(const std::string& str)
 {
-    const SubStrings<std::string> items = stringOps::splitAny(str, " ,;");
+    const auto items = stringOps::splitAny(str, " ,;");
 
     DynamicList<PrimitiveType> values(items.size());
 
diff --git a/src/OpenFOAM/primitives/ranges/scalarRange/scalarRanges.C b/src/OpenFOAM/primitives/ranges/scalarRange/scalarRanges.C
index f94f724742a9f67244f89d2779e8989462e96aac..74c3ce205ed051cc39039c17c94e297979613f2b 100644
--- a/src/OpenFOAM/primitives/ranges/scalarRange/scalarRanges.C
+++ b/src/OpenFOAM/primitives/ranges/scalarRange/scalarRanges.C
@@ -36,7 +36,7 @@ Foam::scalarRanges Foam::scalarRanges::parse
     bool report
 )
 {
-    const SubStrings<std::string> items = stringOps::splitAny(str, " ,;");
+    const auto items = stringOps::splitAny(str, " ,;");
 
     scalarRanges ranges(items.size());
 
diff --git a/src/OpenFOAM/primitives/strings/lists/SubStrings.H b/src/OpenFOAM/primitives/strings/lists/SubStrings.H
index 4892d7976ca5b721ac42983ff45c90e6d06fa0ac..373bebb6d4e8fbe507064f4ce9f2ac2dda167246 100644
--- a/src/OpenFOAM/primitives/strings/lists/SubStrings.H
+++ b/src/OpenFOAM/primitives/strings/lists/SubStrings.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2017-2021 OpenCFD Ltd.
+    Copyright (C) 2017-2024 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -66,12 +66,6 @@ public:
             typename StringType::const_iterator;
 
 
-    // Constructors
-
-        //- Default construct
-        SubStrings() = default;
-
-
     // Member Functions
 
         //- The total string length of all sub-elements.
@@ -109,6 +103,40 @@ public:
             this->push_back(range);
         }
 
+
+        //- Reduce size by 1 or more elements. Can be called on an empty list.
+        void pop_back(size_t n = 1)
+        {
+            if (n >= this->size())
+            {
+                this->clear();
+            }
+            else if (n > 0)
+            {
+                this->resize(this->size() - n);
+            }
+        }
+
+        //- Reduce size by 1 or more elements (from the front).
+        //- Can be called on an empty list.
+        void pop_front(size_t n = 1)
+        {
+            if (n >= this->size())
+            {
+                this->clear();
+            }
+            else if (n > 0)
+            {
+                // Overlapping range, avoid std::copy, std::move
+                for (size_t src = n, dst = 0; src < this->size(); ++src, ++dst)
+                {
+                    (*this)[dst] = (*this)[src];
+                }
+                this->resize(this->size() - n);
+            }
+        }
+
+
     // FUTURE?
     //  #if __cplusplus >= 201703L
     //  std::string_view view(size_t pos) const
diff --git a/src/OpenFOAM/primitives/strings/stringOps/stringOps.H b/src/OpenFOAM/primitives/strings/stringOps/stringOps.H
index 8f8a7a110cf14651d9e0e05b9d08f3e3021bb9dd..1bc9de475807f8f7ad3ca0cd3e9550b5338a7918 100644
--- a/src/OpenFOAM/primitives/strings/stringOps/stringOps.H
+++ b/src/OpenFOAM/primitives/strings/stringOps/stringOps.H
@@ -372,8 +372,13 @@ namespace stringOps
     template<class StringType>
     Foam::SubStrings<StringType> split
     (
+        //! The string to split
         const StringType& str,
+        //! The delimiter for splitting. Ill-defined if NUL character
         const char delim,
+        //! Offset within string to start splitting
+        std::string::size_type pos = 0,
+        //! Retain empty fields
         const bool keepEmpty = false
     );
 
@@ -382,8 +387,13 @@ namespace stringOps
     template<class StringType>
     Foam::SubStrings<StringType> split
     (
+        //! The string to split
         const StringType& str,
+        //! The delimiters for splitting. Ill-defined if empty
         const std::string& delim,
+        //! Offset within string to start splitting
+        std::string::size_type pos = 0,
+        //! Retain empty fields
         const bool keepEmpty = false
     );
 
@@ -393,22 +403,25 @@ namespace stringOps
     template<class StringType>
     Foam::SubStrings<StringType> splitAny
     (
+        //! The string to split
         const StringType& str,
-        const std::string& delim
+        //! The delimiters for splitting. Ill-defined if empty!
+        const std::string& delim,
+        //! Offset within string to start splitting
+        std::string::size_type pos = 0
     );
 
     //- Split string into sub-strings using a fixed field width.
     //  Behaviour is ill-defined if width is zero.
-    //  \param str the string to be split
-    //  \param width the fixed field width for each sub-string
-    //  \param start the optional offset of where to start the splitting.
-    //      Any text prior to start is ignored in the operation.
     template<class StringType>
     Foam::SubStrings<StringType> splitFixed
     (
+        //! The string to split
         const StringType& str,
+        //! Fixed field width for each sub-string
         const std::string::size_type width,
-        const std::string::size_type start = 0
+        //! Offset within string to start splitting
+        std::string::size_type pos = 0
     );
 
     //- Split string into sub-strings at whitespace (TAB, NL, VT, FF, CR, SPC)
@@ -416,23 +429,25 @@ namespace stringOps
     template<class StringType>
     Foam::SubStrings<StringType> splitSpace
     (
-        const StringType& str
+        //! The string to split
+        const StringType& str,
+        //! Offset within string to start splitting
+        std::string::size_type pos = 0
     );
 
     //- Output string with text wrapping.
     //  Always includes a trailing newline, unless the string itself is empty.
-    //
-    //  \param os the output stream
-    //  \param str the text to be output
-    //  \param width the max-width before wrapping
-    //  \param indent indentation for continued lines
-    //  \param escape escape any backslashes on output
     void writeWrapped
     (
+        //! The output stream
         OSstream& os,
+        //! The text to be output
         const std::string& str,
+        //! The max-width before wrapping
         const std::string::size_type width,
+        //! Indentation for continued lines
         const std::string::size_type indent = 0,
+        //! Escape any backslashes on output
         const bool escape = false
     );
 
diff --git a/src/OpenFOAM/primitives/strings/stringOps/stringOpsTemplates.C b/src/OpenFOAM/primitives/strings/stringOps/stringOpsTemplates.C
index 19d06d013667db3fad36c1b4e73d6841de9c4ae3..29b230497fe7b25b27754a47d20a336d62f973ab 100644
--- a/src/OpenFOAM/primitives/strings/stringOps/stringOpsTemplates.C
+++ b/src/OpenFOAM/primitives/strings/stringOps/stringOpsTemplates.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2016-2023 OpenCFD Ltd.
+    Copyright (C) 2016-2024 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -98,34 +98,40 @@ Foam::SubStrings<StringType> Foam::stringOps::split
 (
     const StringType& str,
     const char delim,
+    std::string::size_type pos,
     const bool keepEmpty
 )
 {
-    Foam::SubStrings<StringType> lst;
-    if (str.empty() || !delim)
+    Foam::SubStrings<StringType> list;
+
+    if
+    (
+        !delim
+     || (pos == std::string::npos || pos >= str.size())
+    )
     {
-        return lst;
+        return list;
     }
 
-    lst.reserve(20);
+    list.reserve(20);
 
-    std::string::size_type beg = 0, end = 0;
-    while ((end = str.find(delim, beg)) != std::string::npos)
+    std::string::size_type end;
+    while ((end = str.find(delim, pos)) != std::string::npos)
     {
-        if (keepEmpty || (beg < end))
+        if (keepEmpty || (pos < end))
         {
-            lst.append(str.cbegin() + beg, str.cbegin() + end);
+            list.append(str.cbegin() + pos, str.cbegin() + end);
         }
-        beg = end + 1;
+        pos = end + 1;
     }
 
     // Trailing element
-    if (keepEmpty ? (beg <= str.size()) : (beg < str.size()))
+    if (keepEmpty ? (pos <= str.size()) : (pos < str.size()))
     {
-        lst.append(str.cbegin() + beg, str.cend());
+        list.append(str.cbegin() + pos, str.cend());
     }
 
-    return lst;
+    return list;
 }
 
 
@@ -134,34 +140,40 @@ Foam::SubStrings<StringType> Foam::stringOps::split
 (
     const StringType& str,
     const std::string& delim,
+    std::string::size_type pos,
     const bool keepEmpty
 )
 {
-    Foam::SubStrings<StringType> lst;
-    if (str.empty() || delim.empty())
+    Foam::SubStrings<StringType> list;
+
+    if
+    (
+        delim.empty()
+     || (pos == std::string::npos || pos >= str.size())
+    )
     {
-        return lst;
+        return list;
     }
 
-    lst.reserve(20);
+    list.reserve(20);
 
-    std::string::size_type beg = 0, end = 0;
-    while ((end = str.find(delim, beg)) != std::string::npos)
+    std::string::size_type end;
+    while ((end = str.find(delim, pos)) != std::string::npos)
     {
-        if (keepEmpty || (beg < end))
+        if (keepEmpty || (pos < end))
         {
-            lst.append(str.cbegin() + beg, str.cbegin() + end);
+            list.append(str.cbegin() + pos, str.cbegin() + end);
         }
-        beg = end + delim.size();
+        pos = end + delim.size();
     }
 
     // Trailing element
-    if (keepEmpty ? (beg <= str.size()) : (beg < str.size()))
+    if (keepEmpty ? (pos <= str.size()) : (pos < str.size()))
     {
-        lst.append(str.cbegin() + beg, str.cend());
+        list.append(str.cbegin() + pos, str.cend());
     }
 
-    return lst;
+    return list;
 }
 
 
@@ -169,40 +181,41 @@ template<class StringType>
 Foam::SubStrings<StringType> Foam::stringOps::splitAny
 (
     const StringType& str,
-    const std::string& delim
+    const std::string& delim,
+    std::string::size_type pos
 )
 {
-    Foam::SubStrings<StringType> lst;
-    if (str.empty() || delim.empty())
+    Foam::SubStrings<StringType> list;
+
+    if
+    (
+        delim.empty()
+     || (pos == std::string::npos || pos >= str.size())
+    )
     {
-        return lst;
+        return list;
     }
 
-    lst.reserve(20);
+    list.reserve(20);
 
-    for
-    (
-        std::string::size_type pos = 0;
-        (pos = str.find_first_not_of(delim, pos)) != std::string::npos;
-        /*nil*/
-    )
+    while ((pos = str.find_first_not_of(delim, pos)) != std::string::npos)
     {
         const auto end = str.find_first_of(delim, pos);
 
         if (end == std::string::npos)
         {
             // Trailing element
-            lst.append(str.cbegin() + pos, str.cend());
+            list.append(str.cbegin() + pos, str.cend());
             break;
         }
 
         // Intermediate element
-        lst.append(str.cbegin() + pos, str.cbegin() + end);
+        list.append(str.cbegin() + pos, str.cbegin() + end);
 
         pos = end + 1;
     }
 
-    return lst;
+    return list;
 }
 
 
@@ -211,43 +224,53 @@ Foam::SubStrings<StringType> Foam::stringOps::splitFixed
 (
     const StringType& str,
     const std::string::size_type width,
-    const std::string::size_type start
+    std::string::size_type pos
 )
 {
-    Foam::SubStrings<StringType> lst;
-    if (str.empty() || !width)
+    Foam::SubStrings<StringType> list;
+
+    if
+    (
+        !width
+     || (pos == std::string::npos || pos >= str.size())
+    )
     {
-        return lst;
+        return list;
     }
 
+    list.reserve(1 + ((str.size() - pos) / width));
+
     const auto len = str.size();
-    lst.reserve(1 + (len / width));
 
-    for (std::string::size_type pos = start; pos < len; pos += width)
+    while (pos < len)
     {
         const auto end = (pos + width);
 
         if (end >= len)
         {
             // Trailing element
-            lst.append(str.cbegin() + pos, str.cend());
+            list.append(str.cbegin() + pos, str.cend());
             break;
         }
 
-        lst.append(str.cbegin() + pos, str.cbegin() + end);
+        // Intermediate element
+        list.append(str.cbegin() + pos, str.cbegin() + end);
+
+        pos += width;
     }
 
-    return lst;
+    return list;
 }
 
 
 template<class StringType>
 Foam::SubStrings<StringType> Foam::stringOps::splitSpace
 (
-    const StringType& str
+    const StringType& str,
+    std::string::size_type pos
 )
 {
-    return splitAny(str, "\t\n\v\f\r ");
+    return splitAny(str, "\t\n\v\f\r ", pos);
 }
 
 
diff --git a/src/meshTools/edgeMesh/edgeFormats/obj/OBJedgeFormat.C b/src/meshTools/edgeMesh/edgeFormats/obj/OBJedgeFormat.C
index c349c566cba121d3bb61cb70a7074b8641bbc0eb..0c771c74e5e08ad5d5d73ce6bbfd2662d5f958fb 100644
--- a/src/meshTools/edgeMesh/edgeFormats/obj/OBJedgeFormat.C
+++ b/src/meshTools/edgeMesh/edgeFormats/obj/OBJedgeFormat.C
@@ -120,7 +120,7 @@ bool Foam::fileFormats::OBJedgeFormat::read(const fileName& filename)
             line += this->getLineNoComment(is);
         }
 
-        const SubStrings<string> tokens = stringOps::splitSpace(line);
+        const auto tokens = stringOps::splitSpace(line);
 
         // Require command and some arguments
         if (tokens.size() < 2)
diff --git a/src/surfMesh/surfaceFormats/nas/NASsurfaceFormat.C b/src/surfMesh/surfaceFormats/nas/NASsurfaceFormat.C
index 816433deccb45ba69e30b9fc58a3c7f75404cec1..a836708018c58e5d643cc40f502f06363df0dc15 100644
--- a/src/surfMesh/surfaceFormats/nas/NASsurfaceFormat.C
+++ b/src/surfMesh/surfaceFormats/nas/NASsurfaceFormat.C
@@ -193,7 +193,8 @@ bool Foam::fileFormats::NASsurfaceFormat<Face>::read
         if (line.starts_with("$ANSA_NAME"))
         {
             // Keep empty elements when splitting
-            const auto args = stringOps::split<std::string>(line, ';', true);
+            const auto args =
+                stringOps::split<std::string>(line, ';', 0, true);
 
             if (args.size() > 4 && line.starts_with("$ANSA_NAME_COMMENT"))
             {
diff --git a/src/surfMesh/surfaceFormats/obj/OBJsurfaceFormat.C b/src/surfMesh/surfaceFormats/obj/OBJsurfaceFormat.C
index d471bd66aed9a4799e34f1bdfda6af2f3c1e6f6e..4fd5c08a5f133681f0d5f98ad91aaa00e81d5704 100644
--- a/src/surfMesh/surfaceFormats/obj/OBJsurfaceFormat.C
+++ b/src/surfMesh/surfaceFormats/obj/OBJsurfaceFormat.C
@@ -91,7 +91,7 @@ bool Foam::fileFormats::OBJsurfaceFormat<Face>::read
             line += this->getLineNoComment(is);
         }
 
-        const SubStrings<string> tokens = stringOps::splitSpace(line);
+        const auto tokens = stringOps::splitSpace(line);
 
         // Require command and some arguments
         if (tokens.size() < 2)