Как создать std :: set или Boost flat_set из элементов данных объектов? - PullRequest
2 голосов
/ 28 сентября 2019
#include <iostream>
#include <vector>
#include <set>

class Myclass
{
    int member_a;
    int member_b;
public:
    Myclass() {};
    Myclass(int a_init, int b_init) : member_a(a_init), member_b(b_init) {};

    operator int() const {      return member_a;    }
    int get_a() const {     return member_a;    }
};

int main()
{
    auto myvector = std::vector<Myclass>({ {1, 0}, {2, 0}, {2, 0}, {3, 0} });
    auto myset = std::set<int>(myvector.begin(), myvector.end());
    for (auto element : myset) {
        std::cout << "element: " << element << "\n";
    }
}

Как видите, я создаю std::set, который содержит только конкретный элемент данных каждого объекта в std::vector.Я достигаю этого с помощью operator int().

Однако мне не нравится это решение, потому что оно не очень читабельно и создает потенциальные ловушки, и я также могу захотеть создать набор только из member_b s.

Есть ли способпостроить множество, используя get_a() вместо operator int(), без использования цикла?Я также хотел бы избежать создания временного вектора, который содержит только member_a.

Эта же проблема особенно актуальна для построения Boost::flat_set, который, насколько я понимаю, будетсортировать без необходимости, если элементы добавляются один за другим в цикле.

1 Ответ

1 голос
/ 28 сентября 2019

Вы можете использовать std::transform до , вставляя нужные элементы до myset вместо использования operator int(). ( Смотреть онлайн )

#include <algorithm> // std::transform
#include <iterator>  // std::inserter

std::transform(myvector.cbegin(), myvector.cend()
    , std::inserter(myset, myset.begin())
    , [](const auto& cls) { return cls.get_a(); }
);

Достаточно общего?Хорошо, чтобы сделать его более универсальным, вы можете поместить его в функцию, в которой передается вектор из Myclass, myset для заполнения и указатель на функцию-член который нужно было назвать. ( Смотреть онлайн )

#include <algorithm>  // std::transform
#include <iterator>   // std::inserter
#include <functional> // std::invoke
#include <utility>    // std::forward

using MemFunPtrType = int(Myclass::*)() const; // convenience type

void fillSet(const std::vector<Myclass>& myvector, std::set<int>& myset, MemFunPtrType func)
{
    std::transform(myvector.cbegin(), myvector.cend()
        , std::inserter(myset, myset.begin())
        , [func](const Myclass& cls) { 
               return (cls.*func)(); 
               // or in C++17 simply invoke the func with each instace of the MyClass
               // return std::invoke(func, cls);
        }
    );
}

Или полностью универсальным, используя шаблоны , можно: ( Смотреть вживуюонлайн )

template<typename Class, typename RetType, typename... Args>
void fillSet(const std::vector<Class>& myvector
    , std::set<RetType>& myset
    , RetType(Class::*func)(Args&&...)const
    , Args&&... args)
{
    std::transform(myvector.cbegin(), myvector.cend()
        , std::inserter(myset, myset.begin())
        , [&](const Myclass& cls) { return std::invoke(func, cls, std::forward<Args>(args)...);  }
    );
}

Теперь вы заполните myset как.

fillSet(myvector, myset, &Myclass::get_a); // to fill with member a
fillSet(myvector, myset, &Myclass::get_b); // to fill with member b

Вот полный рабочий пример:

#include <iostream>
#include <vector>
#include <set>
#include <algorithm>  // std::transform
#include <iterator>   // std::inserter
#include <functional> // std::invoke
#include <utility>    // std::forward

class Myclass
{
    int member_a;
    int member_b;
public:
    Myclass(int a_init, int b_init) : member_a{ a_init }, member_b{ b_init } {};
    int get_a() const noexcept { return member_a;   }
    int get_b() const noexcept { return member_b;   }
};

template<typename Class, typename RetType, typename... Args>
void fillSet(const std::vector<Class>& myvector
    , std::set<RetType>& myset
    , RetType(Class::*func)(Args&&...)const
    , Args&&... args)
{
    std::transform(myvector.cbegin(), myvector.cend()
        , std::inserter(myset, myset.begin())
        , [&](const Myclass& cls) { return std::invoke(func, cls, std::forward<Args>(args)...);  }
    );
}

int main()
{
    auto myvector = std::vector<Myclass>({ {1, 0}, {2, 0}, {2, 0}, {3, 0} });
    std::set<int> myset;

    std::cout << "Filling with member a\n";
    fillSet(myvector, myset, &Myclass::get_a);
    for (auto element : myset)  std::cout << "element: " << element << "\n";

    std::cout << "Filling with member b\n"; 
    myset.clear();
    fillSet(myvector, myset, &Myclass::get_b);
    for (auto element : myset) std::cout << "element: " << element << "\n";

}

Вывод :

Filling with member a
element: 1
element: 2
element: 3
Filling with member b
element: 0
...