std::set::find dla własnej klasy/struktury

0

Witam,

Mam strukturę, do której zapisuję dane pojedynczego pliku z folderu ze skanami. Wszystko trzymam w std::set<ScannedFile> files. W trakcie programu zmieniam ich nazwy i na koniec zapisuję. W jaki sposób przeładować w moim przypadku funkcję find tak żeby wyszukiwanie odbywało się zarówno dla old_name jak i new_name ?

class ScannedFile 
{
public:
	std::wstring old_name{ L"" };
	mutable std::wstring new_name{ L"" };

	ScannedFile() {}

	ScannedFile(const std::wstring &old_n, const std::wstring &new_n, const std::string &ext) :
		old_name(old_n), new_name(new_n), extension(ext) 
	{
		counter++;
		if (new_n.empty())
			this->new_name = L"pusta_nazwa" + std::to_wstring(counter);
	}
	
	void SetExtension(const std::string &s);
	std::wstring GetExtension() const;

	bool operator<(const ScannedFile &f) const 
	{
		return (this->old_name < f.old_name) && (this->new_name < f.new_name);
	}

private:
	static int counter;
	std::string extension;
};

Dodam, że takie przeładowanie operatora " < " działa (blokuje dodanie) gdy chcę wstawić do files (istniejący już tam) plik skanu funkcją insert, dlaczego więc nie działa dla find lub count ?

2

Podejrzewam, że problem jest w operatorze <. Operator < musi spełniać kilka warunków (strict weak ordering):

  • ~ (x < x): element nie może być mniejszy od samego siebie
  • x < y => ~ (y < x): jeśli x jest mniejszy od y to y nie może być mniejszy od x
  • x < y i y < z => x < z: jeśli x jest mniejszy od y i y jest mniejsze od z to x jest mniejsze od z

U Ciebie jest problem z drugim warunkiem, choć nie wprost - można znaleźć dwa takie elementy (różne), że x < y zwróci false i y < x zwróci również false. Operator == (równoważności) używany w find i count jest zdefiniowany następująco: x == y <==> !(x < y) && !(y < x). To tłumaczy anomalie. Więcej szczegółów tutaj: https://en.cppreference.com/w/cpp/named_req/Compare

0

Dzięki za odpowiedzi. std::tie pomogło.

ps: jakiś czas temu czytałem już właśnie ten zalinkowany wpis @kq, ale o nim kompletnie zapomniałem... Ech :)

0

Zbyt szybko krzyknąłem hurra.
@rrowniak ma rację. W moim przypadku istnieje taka możliwość, że x < y zwróci false i y < x
np.

//ad hoc: using namespace std; oraz konstruktor bez trzeciego argumentu

	auto files = set<ScannedFile> {ScannedFile(L"001", L""), ScannedFile(L"002", L"")};
	
	if(!files.insert(ScannedFile(L"001", L"123")).second)
		cout << "insertion failed" << endl;
	else
		cout << "insertion succeeded" << endl;

wynik to dodanie tego pliku do kontenera, a powinno zwrócić false, ponieważ "001" już w nim jest. Dopiero wywołanie np.

files.insert(ScannedFile(L"", L"123")).second

powinno zwrócić sukces.

Pewnie muszę napisać jakiś własny typ porównujący, tylko pytanie jakich warunków użyć, żeby niezależnie od siebie oba pola były traktowane jako unikatowe ?

EDIT:

Chyba udało mi się znaleźć rozwiązanie, ale jeszcze je testuję :)
Wklejam poniżej, może komuś się przyda

class SFilesComp
{
	public:
	bool operator()(const ScannedFile &lhs, const ScannedFile &rhs)
	{
		bool ret, ret2;

		ret = lhs.old_name.compare(rhs.old_name) >= 0 ? false : true;
		ret2 = lhs.new_name.compare(rhs.new_name) >= 0 ? false : true;
		
		return ret && ret2;
	}
};
0

Ale... std::tie rozwiązuje również tę sytuację. Porządek leksykograficzny polega na tym, że pierw testowana jest pierwsza wartość. Jeśli jest ekwiwalentna, to dopiero porównywana jest druga, a jak i ta jest ekwiwalentna, to potem trzecia itd...

0

Nie wiem właśnie dlaczego tak nie jest.
https://ideone.com/236kaL

Wkładanie do seta "001" nie powinno przejść

Ten EDIT z poprzedniego posta też nie działa prawidłowo. Np. dla przypadku "223" >= "123" zablokuje dodanie nowego pliku.

Rozwiązaniem jest chyba poniższe przeładowanie operatora <

	bool operator<(const ScannedFile &f) const 
	{
		if (this->old_name == f.old_name || this->new_name == f.new_name)
			return false;

		return this->old_name < f.old_name;

	}

1 użytkowników online, w tym zalogowanych: 0, gości: 1