6

Before marking this as duplicate, I have been here, here, and here, a duplicate of the first.

I'm aware of boost::multi_index, and use an environment where I lack it, and that a std::unordered_set is not bound to store elements in a deterministic insertion order.

I find the concept of using two containers, say an additional std::vector as uncouth.

What I would love is a solution involving a comparator that I can use in a std::set's template parameters (clarification, this could be a trivial functor struct, containing a bool operator()() overload, a regular function, or a lambda). Is it possible?

Addenda

  1. Initialization must occur through a std:: container's begin iterator/end iterator constructor, such as in this snippet.

    std::string str; cin >> str;
    std::set<char>(str.begin(), str.end());
    
  2. Also, another interesting use-case would be to create a dumb hash wrapping functor that allows insertion order to be pushed in to a std::unordered_set's template parameter.

Vivraan
  • 163
  • 1
  • 9
  • 1
    What's your question? (Make your question *self-contained*, and you can tell whether something is a question by the presence of a question mark. Everything else is a rant, or a tweet.) – Kerrek SB Oct 01 '17 at 10:46
  • 1
    *Is it possible?* I do admit that I ended up sounding like that, so I wanted to clarify. Any help is appreciated. :) – Vivraan Oct 01 '17 at 10:47
  • Context -- I still have no idea what you want to do. – Kerrek SB Oct 01 '17 at 10:47
  • @KerrekSB The heading, please. – Vivraan Oct 01 '17 at 10:49
  • Stop using containers in a way they were not designed to work. If you want a set sorted in insertion order then why not make your *own* structure? – Some programmer dude Oct 01 '17 at 10:49
  • 1
    Because it is cumbersome and probably a lambda would work here. I'm lazy af, that's all :P Also AFAIK a `std::set` allows exactly that, so why not use a container how I like it if I can configure its behavior? – Vivraan Oct 01 '17 at 10:50
  • If (insertion-) order matters don't use unordered containers.Use one of the ordered ones instead. `std::vector` is a pretty decent default. – nwp Oct 01 '17 at 10:51
  • 1
    No - headings are for indexing a post on the questions list. Your question body needs to be self-contained separately. Say *in your question* what you mean to ask, don't expect people to scavenge around to piece together *your* question. – Kerrek SB Oct 01 '17 at 10:51
  • @nwp exactly! Still, need duplicate removal and insertion order. No `std::vectors`, sorry. – Vivraan Oct 01 '17 at 10:53
  • @mushi: An *unsorted* vector would work, at the expense of search time. – Kerrek SB Oct 01 '17 at 10:54
  • @KerrekSB yeah, that's one way I'd do it, but duplication removal. – Vivraan Oct 01 '17 at 10:56
  • 1
    @mushi: no no, you still scan for duplicates on insertion, but the scan has to be a linear search (since the range isn't sorted). No duplicates. – Kerrek SB Oct 01 '17 at 10:56
  • @KerrekSB maybe. Well it works, suffice to say. Somehow doesn't strike as elegant. Still, +1 to you. – Vivraan Oct 01 '17 at 10:59
  • Or just reimplement multiindex. Not really hard, you just need two separate data structures that you keep in sync. – Kerrek SB Oct 01 '17 at 11:08

4 Answers4

3

You cannot directly have a lambda expression as the set's template parameter, because a lambda expression is a value, and the set's template parameter is a type. The obvious correction of the question, whether a construction using a lambda and decltype can work, leads to the interesting problem that a lambda expression denotes a unique type (a "closure type"), so you can never make two separate lambda expressions of the same closure type.*

However, in a more abstract sense what you want can be achieved in a local context using template argument deduction, for example:

template <typename F>
int f(int* first, int* last, F comp)
{
    std::set<int, F> s(comp);
    while (first != last) s.insert(*first++);
    ...
}

Now you can call f with a lambda expression as the argument, thus effectively "using a lambda as the set's comparator". Or, for a simpler example, you could just have a named variable for the lambda (putting all the template deduction into a single auto:

auto comp = [](...) { ... };
std::set<int, decltype(comp)> s(comp);

*) There is a proposal to allow lambdas in unevaluated contexts to address this point, but its outlook is uncertain. It has interesting side effects like making closure types affect name mangling.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 1
    It's obvious I'll need a `decltype()` to get to the lambda's type in the template parameter list. Just a one-shot lambda will do. The answer isn't valid for this question. Still, the effort is commendable, so I'd upvote it. – Vivraan Oct 01 '17 at 11:07
  • 1
    It' can't be "one-shot" if you would need the lambda at least twice, though! Closure types aren't default-constructible. (There's another proposal to address that, which actually has good chances for C++20.) – Kerrek SB Oct 01 '17 at 11:10
  • I assume I won't need it twice. I could say this lambda exists within `main()`, so it may not *need* to be a lambda. Thanks for highlighting this point, I've updated my question. – Vivraan Oct 01 '17 at 11:11
  • It isn't the use case I'm looking for sir ._. – Vivraan Oct 01 '17 at 11:12
  • 2
    (Yes, you could say `auto f = []...; set s(f);`; that's a simpler variant on the "deduction" I used in my example.) – Kerrek SB Oct 01 '17 at 11:13
  • EXACTLY. THANK YOU. Jk thank goodness we're on the same page `:) – Vivraan Oct 01 '17 at 11:16
  • Right :-) But that's not a lambda in the set -- now that's a named variable of closure type. The lambda expression now appears separately, unrelated to the set. The deeper point here is that you *cannot* have such kinds of sets in public interfaces, for example. – Kerrek SB Oct 01 '17 at 11:19
  • Yeah, that's something I have to consider. – Vivraan Oct 01 '17 at 11:22
1

An adt that preserves the order of insertion is an std::vector. You can just as easily wrap it like this to get an std::set-like behavior:

#include <iostream>
#include <vector>
#include <utility>
#include <algorithm>

using namespace std;

template < typename T >
class VectorSet : public vector<T> {
public:
    using iterator = typename vector<T>::iterator;
    using value_type = typename vector<T>::value_type;

    pair<iterator, bool> insert (const value_type& val) {
        auto it = ::find(this->begin(), this->end(), val);
        if (it == this->end())
            it = ::vector<T>::insert(this->end(), val);

        return pair<iterator, bool>(it, true);
    }
};

int main()
{
    VectorSet<int> my;
    my.insert(1);
    my.insert(4);
    my.insert(3);
    my.insert(4);

    for (auto & v : my) {
        cout << v << endl;
    }

    return 0;
}
Daniel Trugman
  • 8,186
  • 20
  • 41
1

You cannot, unless you use additional indexes. Two approaches:

1. using an explicit index

Live On Coliru

#include <set>
#include <vector>
#include <functional>
#include <algorithm>

using namespace std;

#include <iostream>

string read_word() {
    string str;
    cin >> str;
    return str;
}

int main() {
    using Ref = std::reference_wrapper<char const>;

    auto const str = read_word();
    std::cout << "Word: "   << str << "\n";

    auto v = [&]() -> vector<Ref> {
        set<Ref> u(str.begin(), str.end());
        return {u.begin(), u.end()};
    }();

    std::cout << "Unique: " << string(v.begin(), v.end()) << "\n";

    auto pos = [str](char ch) { return str.find(ch); };
    std::sort(v.begin(), v.end(), [pos](auto& a, auto& b) { return pos(a) < pos(b); });

    std::cout << "Insertion: " << string(v.begin(), v.end()) << "\n";
}

Prints e.g.

Word: pineapple
Unique: aeilnp
Insertion: pineal

2. using Boost Multi-Index

Same deal

Live On Coliru

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index/ordered_index.hpp>

namespace bmi = boost::multi_index; 

using Index = bmi::multi_index_container<char, 
      bmi::indexed_by<
          bmi::sequenced<>,
          bmi::ordered_unique<bmi::tag<struct unique>, bmi::identity<char> >
      > > ;

#include <iostream>

std::string read_word() {
    std::string str;
    std::cin >> str;
    return str;
}

int main() {

    auto const str = read_word();
    std::cout << "Word: "   << str << "\n";

    Index idx(str.begin(), str.end());

    std::cout << "Insertion: " << std::string(idx.begin(), idx.end()) << "\n";

    auto& u = idx.get<unique>();
    std::cout << "Unique: " << std::string(u.begin(), u.end()) << "\n";
}

Prints

Word: pineapple
Insertion: pineal
Unique: aeilnp
sehe
  • 374,641
  • 47
  • 450
  • 633
  • I'd say the boost multi-index approach is basically what you wanted. Note that you can achieve this particular mix using an intrusive set over another container too. – sehe Oct 01 '17 at 18:48
0

I thought a weird solution (though not one involving any sets) could be to use a std::map of the element type and std::time_point as the key type. That will ensure insertion order if not anything at all.

Vivraan
  • 163
  • 1
  • 9
  • Except then it's just a very very inefficient vector. How will you still look up by the original key – sehe Dec 23 '18 at 20:38
  • A red-black implemented map wouldn't become a one dimensional vector, and AFAIR when I had made the OP I thought I wouldn't need random access, so simple iteration was probably what I was aiming for, in which case I wouldn't need to look up using the `std::time_point` key. – Vivraan Dec 24 '18 at 06:37
  • 2
    Indeed. It will not become a one dimensional vector, and that's exactly what makes it wasteful, because you didn't need the fancy features. A map is orders of magnitudes more complicated/ expensive and doesn't give you anything in terms of traversal that a vector wouldn't – sehe Dec 25 '18 at 01:22
  • Except requiring an explicit call to `std::sort`. Otherwise, I like the `std::vector` approach. It's probably more efficient. – Vivraan Dec 26 '18 at 07:18
  • huh. No need to sort a vector just to preserve insertion order – sehe Dec 26 '18 at 11:49
  • have you ever considered https://www.boost.org/doc/libs/1_69_0/doc/html/boost/container/flat_map.html and similar? (It didn't fit the question but it seems you're really looking for something else) – sehe Dec 26 '18 at 11:52
  • 1
    That was pretty enlightening. Of course, when I had made the post I hadn't considered it. Boost covers all the use-cases all too well. :p – Vivraan Dec 27 '18 at 03:43