Dear Readers!

Today I encountered a simple, yet interesting problem regarding STL algorithms. In most of the cases using them is a no-brainer, but as it turned out, function overload resolution can be a bit problematic while employing them. Let’s see a couple of examples:

The snippet below shows a usual use case:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
std::string capitalizeElem(const std::string& elem)
{
    if (!elem.empty()) {
        std::string tempElem = elem;
        tempElem[0] = std::toupper(tempElem[0]);
        return tempElem;
    }
    return elem;
}

template<typename Cont>
void printContainer(const Cont& container) {
    for (auto elem : container) {
        std::cout << elem << std::endl;
    }
}

int main()
{
    std::vector<std::string> fruitVec{"apple", "plum", "pear", "banana", "orange", "grape"};
    std::vector<std::string> capitalFruitVec;

    std::transform(std::cbegin(fruitVec),
                   std::cend(fruitVec),
                   std::back_inserter(capitalFruitVec),
                   capitalizeElem
    );

    printContainer(capitalFruitVec);
}

There is nothing fancy about it, the capitalizeElem makes the first letter of the fruit names an uppercase letter and std::transform collects the output into a new vector. But what if there is an overload to capitalizeElem because we want to support containers having QString elements?

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
std::string capitalizeElem(const std::string& fruit)
{
    if (!fruit.empty()) {
        std::string tempFruit = fruit;
        tempFruit[0] = std::toupper(tempFruit[0]);
        return tempFruit;
    }
    return fruit;
}

std::string capitalizeElem(const QString& fruit) {
    return capitalizeElem(fruit.toStdString());
}

In this case overload resolution won’t work while trying to call capitalizeElem in std::transform, as the compiler has no way to determine what type the container elements will have. The compiler error message basically tells you the same:

Shell

error: no matching function for call to ‘transform(std::vector<std::__cxx11::basic_string<char> >::const_iterator, std::vector<std::__cxx11::basic_string<char> >::const_iterator, std::back_insert_iterator<std::vector<std::__cxx11::basic_string<char> > >, <unresolved overloaded function type>)

Fortunately, lambdas can be used to resolve such issues, as they basically insert a much needed type information right before capitalizeElem is called, and as such overload resolution is now able to work:

C++

1
2
3
4
5
    std::transform(std::cbegin(fruitVec),
                   std::cend(fruitVec),
                   std::back_inserter(capitalFruitVec),
                   [](const std::string& fruit){return capitalizeElem(fruit);}
    );

Obviously, this is a naive approach, as we basically hardwire the type the elements in the container need to have. This is not an ideal solution of course, but we can call modern C++ to the rescue:

C++

1
2
3
4
5
    std::transform(std::cbegin(fruitVec),
                   std::cend(fruitVec),
                   std::back_inserter(capitalFruitVec),
                   [](const auto& fruit){return capitalizeElem(fruit);}
    );

By simply using auto instead of std::string in the lambda, we opened up the door for the compiler to determine what type the container elements have, and with this information in hand the compiler can now call the right function overload. Simply said: by using compile time type deduction we managed to turn function overload resolution back on. Here’s the final code:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
std::string capitalizeElem(const std::string& elem)
{
    if (!elem.empty()) {
        std::string tempElem = elem;
        tempElem[0] = std::toupper(tempElem[0]);
        return tempElem;
    }
    return elem;
}

std::string capitalizeElem(const QString& fruit) {
    return capitalizeElem(fruit.toStdString());
}

template<typename Cont>
void printContainer(const Cont& container) {
    for (auto elem : container) {
        std::cout << elem << std::endl;
    }
}

int main()
{
    std::vector<std::string> fruitVec{"apple", "plum", "pear", "banana", "orange", "grape"};
    QList<QString> berryList{"blackberry", "raspberry", "boysenberry", "strawberry"};

    std::vector<std::string> capitalFruitVec;
    QList<std::string> capitalBerryList;

    std::transform(std::cbegin(fruitVec),
                   std:: cend(fruitVec),
                   std::back_inserter(capitalFruitVec),
                   [](const auto& fruit){return capitalizeElem(fruit);}
    );

    std::transform(std::cbegin(berryList),
                   std::cend(berryList),
                   std::back_inserter(capitalBerryList),
                   [](const auto& fruit){return capitalizeElem(fruit);}
    );

    printContainer(capitalFruitVec);
    printContainer(capitalBerryList);
}

So, the lesson of the day is:

Use lambdas to enable compile time type deduction to help function overload resolution.