Extracting all-caps variables from string expressions I'm extracting variables from user entered mathematical expressions, so that I can enter them into the expression evaluator exprtk. For now, the condition is given that variable names will be composed of consecutive capital letters. Any mathematical constants or function names will be lowercase. Some hypothetical inputs and expected outputs:

Input: "(A + B) - 2"
Expected Output: { A, B }

Input: "pow(AC,E) * ( E * F )"
Expected Output: { AC, E, F }

The code I have right now seems to work, but I would love a critique. I add a space to the end of the string to make my algorithm catch cases at the end of the string, which seems a bit hacky(?).

template&lt;class T&gt;
std::vector&lt;std::string&gt; MapEvaluator&lt;T&gt;::extractVariables(std::string expression) {

    expression = expression + " ";
    std::vector&lt;std::string&gt; variables = {};
    std::string substring;
    size_t consecutiveCharsCaps = 0;
    bool previousCharCaps = false;

    for (size_t i = 0; i &lt; expression.length(); i++)
    {
        if (isCapital(expression[i]))
        {
            consecutiveCharsCaps++;
            previousCharCaps = true;
        }
        else {
            if(previousCharCaps) {
                substring = expression.substr(i - consecutiveCharsCaps, consecutiveCharsCaps);
                variables.push_back(substring);
                consecutiveCharsCaps = 0;
                previousCharCaps = false;
            }
        }
    }

    unique(variables);
    return variables;
}

template &lt;class T&gt;
void MapEvaluator&lt;T&gt;::unique(std::vector&lt;std::string&gt; &amp;vec)
{
    auto end = vec.end();
    for (auto it = vec.begin(); it != end; ++it) {
        end = std::remove(it + 1, end, *it);
    }
    vec.erase(end, vec.end());
}

template&lt;class T&gt;
bool MapEvaluator&lt;T&gt;::isCapital(char c) {
    return (c &gt;='A' &amp;&amp; c &lt;= 'Z');
}
Some observations:


It seems that std::adjacent_find essentially already does what you want, which is extracting all continuous sub-strings consisting of uppercase letters.
Removing duplicates from a vector might be fine, but you can also avoid this completely by inserting the found elements into a std::set. I suspect that the number of unique variables is always small, so this is a cleaner approach.
There is no reason for isCapital to be a member function. Instead, it should be a free function. Remember that interfaces should be complete but minimal. But in fact, there's no reason for the function in the first place: the standard already has std::isupper that we should rather use.


So with these in mind, we can re-write your function to e.g.,:

std::vector&lt;std::string&gt; get_variables(const std::string&amp; str)
{
    std::set&lt;std::string&gt; vars;

    for (auto first = str.cbegin(); first != str.cend(); )
    {
        auto var_end = std::adjacent_find(first, str.cend(),
            [](char a, char b) 
        { 
            return std::isupper(static_cast&lt;unsighed char&gt;(a)) !=
                std::isupper(static_cast&lt;unsigned char&gt;(b)); 
        });

        if (var_end != str.cend())
        {
            ++var_end;
        }

        if (std::isupper(static_cast&lt;unsigned char&gt;(*first)))
        {
            vars.insert(std::string(first, var_end));
        }

        first = var_end;
    }

    return std::vector&lt;std::string&gt;(vars.cbegin(), vars.cend());
}


Notice that we've cast char to unsigned char before the call to std::isupper (thanks to Deduplicator for explaining this to me). The reason is that std::isupper takes an argument of type int which must be in the range of unsigned char (or special value EOF, -1). Unfortunately, a plain char has either the range of signed char or unsigned char, so we perform a cast to confer to this common convention of character classification functions.