Skip to content

73 Advanced The Hundred

The Overload That Swallowed Everything

you wrote a std::string overload; a std::string walks straight past it

#include <iostream>
#include <string>

void handle(const std::string& s) { std::cout << "const ref: " << s << '\n'; }

template <class T> void handle(T&& s) { std::cout << "template:  " << s << '\n'; }

int main() {
    std::string name = "Ada";
    const std::string title = "Countess";

    handle(name);   // a plain std::string — surely the string overload
    handle(title);
    handle("Grace");
}

Run it. Which overload gets each of the three calls?

Answer
template:  Ada
const ref: Countess
template:  Grace

Only the const one reaches the overload you wrote for strings.

Why

T&& on a deduced template parameter is not an rvalue reference — it is a forwarding reference: for the non-const lvalue name, T deduces to std::string&, and reference collapsing (std::string& &&std::string&) leaves a parameter binding it exactly. Both candidates bind without a conversion, but when two reference bindings differ only in const, the less cv-qualified one wins — so the template takes it before "prefer the non-template" applies. The literal loses too: T deduces to const char (&)[6], an exact match against a user-defined conversion. title escapes only because T deduces to const std::string&, making the parameters identical — and in that tie the non-template wins.

Neither -Wall nor -Wextra says a word about any of it. As a constructor, the same template out-ranks the copy constructor for any non-const object.

The fix

Constrain it (<type_traits>), so it stops competing for the types you already handle:

template <class T, class = std::enable_if_t<!std::is_convertible_v<T, std::string>>>
void handle(T&& s) { std::cout << "template:  " << s << '\n'; }

All three calls now print const ref:, while handle(42) still reaches the template; in C++20 the guard reads requires (!std::convertible_to<T, std::string>), from <concepts>. Simplest of all: never overload on a forwarding reference — give that template its own name.

Takeaway: a forwarding reference is the greediest overload in the language — it binds anything exactly, and between two exact reference bindings the less const one wins.

Try it: g++ -std=c++17 main.cpp -o demo && ./demo

Open in Compiler Explorer ↗ Quiz this entry