private CharSequence generateMessageLength(final List<Token> groups, final List<Token> varData, final String indent)
    {
        final StringBuilder sbEncode = new StringBuilder();
        final StringBuilder sbSkip = new StringBuilder();

        for (int i = 0, size = groups.size(); i < size; i++)
        {
            final Token groupToken = groups.get(i);

            if (groupToken.signal() != Signal.BEGIN_GROUP)
            {
                throw new IllegalStateException("tokens must begin with BEGIN_GROUP: token=" + groupToken);
            }

            final int endSignal = findEndSignal(groups, i, Signal.END_GROUP, groupToken.name());
            final List<Token> thisGroup = groups.subList(i, endSignal + 1);

            final Token numInGroupToken = Generators.findFirst("numInGroup", groups, i);
            final long minCount = numInGroupToken.encoding().applicableMinValue().longValue();
            final long maxCount = numInGroupToken.encoding().applicableMaxValue().longValue();

            final String countName = formatPropertyName(groupToken.name()) +
                (isMessageConstLength(thisGroup) ? "Length" : "ItemLengths.size()");

            final String minCheck = minCount > 0 ? countName + " < " + minCount + "LL || " : "";
            final String maxCheck = countName + " > " + maxCount + "LL";

            new Formatter(sbEncode).format("\n" +
                indent + "    length += %1$s::sbeHeaderSize();\n",
                formatClassName(groupToken.name()));

            if (isMessageConstLength(thisGroup))
            {
                new Formatter(sbEncode).format(
                    indent + "    if (%3$s%4$s)\n" +
                    indent + "    {\n" +
                    indent + "        throw std::runtime_error(\"%5$s outside of allowed range [E110]\");\n" +
                    indent + "    }\n" +
                    indent + "    length += %1$sLength *%2$s::sbeBlockLength();\n",
                    formatPropertyName(groupToken.name()),
                    formatClassName(groupToken.name()),
                    minCheck,
                    maxCheck,
                    countName);
            }
            else
            {
                new Formatter(sbEncode).format(
                    indent + "    if (%3$s%4$s)\n" +
                    indent + "    {\n" +
                    indent + "        throw std::runtime_error(\"%5$s outside of allowed range [E110]\");\n" +
                    indent + "    }\n\n" +
                    indent + "    for (const auto &e: %1$sItemLengths)\n" +
                    indent + "    {\n" +
                    indent + "        #if __cplusplus >= 201703L\n" +
                    indent + "        length += std::apply(%2$s::computeLength, e);\n" +
                    indent + "        #else\n" +
                    indent + "        length += %2$s::computeLength(%6$s);\n" +
                    indent + "        #endif\n" +
                    indent + "    }\n",
                    formatPropertyName(groupToken.name()),
                    formatClassName(groupToken.name()),
                    minCheck,
                    maxCheck,
                    countName,
                    generateMessageLengthCallPre17Helper(thisGroup));
            }

            new Formatter(sbSkip).format(
                indent + ("    auto &%1$sGroup { %1$s() };\n") +
                indent + ("    while (%1$sGroup.hasNext())\n") +
                indent + ("    {\n") +
                indent + ("        %1$sGroup.next().skip();\n") +
                indent + ("    }\n"),
                formatPropertyName(groupToken.name()));

            i = endSignal;
        }

        for (int i = 0, size = varData.size(); i < size;)
        {
            final Token varDataToken = varData.get(i);

            if (varDataToken.signal() != Signal.BEGIN_VAR_DATA)
            {
                throw new IllegalStateException("tokens must begin with BEGIN_VAR_DATA: token=" + varDataToken);
            }

            final Token lengthToken = Generators.findFirst("length", varData, i);

            new Formatter(sbEncode).format("\n" +
                indent + "    length += %1$sHeaderLength();\n" +
                indent + "    if (%1$sLength > %2$dLL)\n" +
                indent + "    {\n" +
                indent + "        throw std::runtime_error(\"%1$sLength too long for length type [E109]\");\n" +
                indent + "    }\n" +
                indent + "    length += %1$sLength;\n",
                formatPropertyName(varDataToken.name()),
                lengthToken.encoding().applicableMaxValue().longValue());

            new Formatter(sbSkip).format(
                indent + "    skip%1$s();\n",
                toUpperFirstChar(varDataToken.name()));

            i += varDataToken.componentTokenCount();
        }

        final StringBuilder sb = new StringBuilder();

        new Formatter(sb).format("\n" +
            indent + "void skip()\n" +
            indent + "{\n" +
            sbSkip +
            indent + "}\n\n" +

            indent + "SBE_NODISCARD static SBE_CONSTEXPR bool isConstLength() SBE_NOEXCEPT\n" +
            indent + "{\n" +
            indent + "    return " + (groups.isEmpty() && varData.isEmpty()) + ";\n" +
            indent + "}\n\n" +

            indent + "SBE_NODISCARD static std::size_t computeLength(%1$s)\n" +
            indent + "{\n" +
            "#if defined(__GNUG__) && !defined(__clang__)\n" +
            "#pragma GCC diagnostic push\n" +
            "#pragma GCC diagnostic ignored \"-Wtype-limits\"\n" +
            "#endif\n" +
            indent + "    std::size_t length = sbeBlockLength();\n" +
            sbEncode + "\n" +
            indent + "    return length;\n" +
            "#if defined(__GNUG__) && !defined(__clang__)\n" +
            "#pragma GCC diagnostic pop\n" +
            "#endif\n" +
            indent + "}\n",
            generateMessageLengthArgs(groups, varData, indent + INDENT, true)[0]);

        return sb;
    }
\n");
        sb.append("     * The states in which a encoder/decoder/codec can live.\n");
        sb.append("     *\n");
        sb.append("     * <p>The state machine diagram below, encoded in the dot language, describes\n");
        sb.append("     * the valid state transitions according to the order in which fields may be\n");
        sb.append("     * accessed safely. Tools such as PlantUML and Graphviz can render it.\n");
        sb.append("     *\n");
        sb.append("     * <pre>{@code\n");
        fieldPrecedenceModel.generateGraph(sb, "     *   ");
        sb.append("     * }</pre>\n");
        sb.append("
sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppGenerator.java