[C++]ディレクトリ内の同サイズ・同ハッシュ値のファイルをハードリンクに置き換えるサンプルコード

タイトルまんまです。C++17の手習いで作りました。
動作確認はWindowsでしかしてません。

こんなことをしてます。

- ディレクトリ内のファイルを列挙する。
- コンテナから条件を満たす要素のみをフィルターする。
- コンテナの要素をグループ化して`map`にする。
- mapからキーの一覧を`set`で取得する。
- バイナリーモードでファイル全体を読み込む。


SHA-2のようなハッシュ関数は標準ライブラリにはないので、
https://github.com/stbrumme/hash-library
を使用しました。

```cpp
#include <fstream>
#include <iostream>
#include <string>
#include <memory>
#include <filesystem>
#include <algorithm>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <locale>
#if __has_include(<execution>)
#include <execution>
#endif

#include "hash-library/sha256.h"

template<class OutContainer, class InContainer, class Predicate>
constexpr auto filtered(const InContainer& c, Predicate f) -> OutContainer {
	using std::begin;
	using std::end;
	OutContainer r;
	std::copy_if(begin(c), end(c), std::back_inserter(r), f);
	return r;
}


template<class InContainer, class UnaryFunc>
constexpr decltype(auto) group_by(const InContainer& c, UnaryFunc f) {
	std::unordered_multimap<decltype(f(c.front())), typename InContainer::value_type> mm{ 0 };
	for (auto&& i : c) {
		mm.insert(std::make_pair(f(i), i));
	}
	return mm;
}

template<class Map>
constexpr auto get_keys(const Map& map) {
	std::unordered_set<typename Map::key_type> keys;
	for (auto&& [k, v] : map) {
		keys.insert(k);
	}
	return keys;
}

namespace fs = std::filesystem;

auto get_hash_from_file(const fs::directory_entry& entry) -> std::string {
	// ハッシュ関数は標準ライブラリにはないので、別途ライブラリを使用する。
	SHA256 sha256;
	constexpr auto bufSize = 8 * 1024 * 1024u;
	const auto buf = std::make_unique<char[]>(bufSize);
	// バイナリーモードでファイル全体を読み込む
	std::basic_ifstream<char> ifs{ entry.path(), std::ios::in | std::ios::binary };
	//ifs.imbue(std::locale::classic());
	ifs.imbue(std::locale(""));
	while (ifs.good()) { // TODO エラーケース未対応
		ifs.read(buf.get(), bufSize);
		auto r = ifs.gcount();
		sha256.add(buf.get(), static_cast<size_t>(r));
	}

	return sha256.getHash();
}


int main(int argc, char* argv[]) {
	setlocale(LC_ALL, "");

	using std::begin;
	using std::end;

	const fs::path target{ argc > 1 ? argv[1] : "." };

	// 条件を満たす要素のみをフィルターしてvectorに格納する
	const auto files = filtered<std::vector<fs::directory_entry>>(
		fs::directory_iterator{ target, fs::directory_options::skip_permission_denied },
		[](auto& entry) { return entry.is_regular_file(); });

	// ファイルサイズでグループ化したmultimap(unordered_multimap)を取得する。
	const auto g = group_by(files, [](auto& entry) { return entry.file_size(); });

	// mapからキーの一覧を取得する
	const auto keys = get_keys(g);

	std::for_each(
#ifdef __cpp_lib_execution
		std::execution::par, // 気休め程度に並列処理する
#endif
		begin(keys), end(keys),
		[&g](const auto& key) {
			std::cout << key << ":" << g.count(key) << std::endl;

			if (g.count(key) < 2)
				return;

			// 同じキーを持つ、複数の値を取得する
			auto&& [rangeBegin, rangeEnd] = g.equal_range(key);
			const auto& firstEntry = rangeBegin->second;
			++rangeBegin;
			const auto firstEntryHash = get_hash_from_file(firstEntry);
			std::cout << " " << firstEntry.path() << ":" << firstEntryHash << std::endl;
			std::for_each(
				rangeBegin, rangeEnd,
				[&firstEntry, firstEntryHash](const auto& entry) {
					const auto h = get_hash_from_file(entry.second);
					std::cout << " " << entry.second.path() << ":" << h << std::endl;
					if (h.length() > 0 && firstEntryHash == h && fs::remove(entry.second.path())) {
						std::error_code ec;
						fs::create_hard_link(firstEntry.path(), entry.second.path(), ec);
						if (ec) {
							std::cerr << "failed to create_hard_link(" << ec.message() << " ):" << entry.second.path();
						}
					}
				});
		});
}

```