Always prefer set<T, less<>> to set<T> since C++14?
Asked Answered
I

2

30
#include <set>
#include <string>
#include <string_view>

using namespace std;

int main()
{
    string_view key = "hello";

    set<string> coll1;
    coll1.find(key); // error

    set<string, less<>> coll2;
    coll2.find(key); // ok since C++14
}

Then, should it be a rule:

Always prefer set<T, less<>> to set<T> since C++14?

Internecine answered 12/12, 2016 at 4:12 Comment(5)
Are you using the transparent comparitor functionality? If not, then why bother?Manometer
@ildjarn: What is "transparent comparitor functionality"?Pith
@NicolBolas : Misspelling aside, it's the difference between less<> and less<T>.Manometer
@ildjarn: Oh, that's why Google failed me. Nevermind then.Pith
string_view is not in C++14Ethelynethene
O
19

It's trivial to find a counterexample:

#include <set>
#include <string>

using namespace std;

struct converts_to_string {
    operator string() const { return ""; }
};

int main()
{
    converts_to_string key;

    set<string> coll1;
    coll1.find(key); // OK

    set<string, less<>> coll2;
    coll2.find(key); // error
}
Oxen answered 12/12, 2016 at 7:3 Comment(2)
... what's the reason why it doesn't work? Is it because the Standard does not require (forbid?) operator< on basic_string to be a non-member function, and therefore deduction fails? Would it work if operator< for basic_string was defined as a friend (non-template) function?Ichthyology
@Ichthyology Yep, deduction failure because it's a non-member function template (and unlike basic_string_view doesn't have a "sufficient additional overload" rule). And yes, a friend non-template function would make this work (at the cost of one temporary string per comparison, just like your stupid_string).Oxen
I
10

There can be a performance downside when using associative_container<T, less<>>: Consider a type like

#include <iostream>
#include <set>
#include <string>

struct stupid_string
{
    stupid_string(char const* s)
      : s(s)
    { std::cout << "copy\n"; }

    stupid_string(char const* s, int) // silent
      : s(s)
    {}

    friend bool operator<(stupid_string const& lhs, stupid_string const& rhs);

private:
    std::string s;
};

bool operator<(stupid_string const& lhs, stupid_string const& rhs) {
    return lhs.s < rhs.s;
}

int main() {
    std::set<stupid_string, std::less<>> s;
    s.emplace("hello", 0);
    s.emplace("world", 0);
    s.emplace("foobar", 0);
    std::cout << "find\n";
    (void)s.find("test");
}

Here, the application of operator< in the algorithm performed by s.find will convert the character literal to a stupid_string implicitly. This happens for each comparison performed! Live demo

I know of one case where something similar happened in production code, with a non-conforming C++03 StdLib implementation.


This is by the way the main reason why heterogeneous lookup via less<> was made opt-in; see N3657:

Stephan T. Lavavej suggested that the two problems of preserving existing behaviour and allowing heterogeneous lookups could both be solved by making the containers detect when the comparison object accepts heterogeneous arguments and only conditionally overloading the current lookup functions with template versions.

Ichthyology answered 12/12, 2016 at 11:4 Comment(2)
This is basically the reason heterogeneous lookup was made opt-in.Oxen
@Oxen Yes, but I couldn't find a quote :(Ichthyology

© 2022 - 2024 — McMap. All rights reserved.