1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-07-17 05:55:23 +02:00
jami-client-qt/src/libclient/containerview.h
Sébastien Blin 84150e8977 misc: bump copyright to 2024
Change-Id: I8d5f968fbedbc884c91416246049a0ef4cd652eb
2024-01-03 09:54:49 -05:00

280 lines
9.8 KiB
C++

/*
* Copyright (C) 2020-2024 Savoir-faire Linux Inc.
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "typedefs.h"
#include <algorithm>
#include <functional>
#include <iostream>
#include <iterator>
#include <optional>
#include <type_traits>
template<class T>
using OptRef = typename std::optional<std::reference_wrapper<T>>;
// Some SFINAE helpers to clarify what happened when someone tries
// to make a ContainerView<float> or something wacky like that.
namespace detail {
template<typename... Ts>
struct has_defs
{};
template<typename T, typename _ = void>
struct is_container : std::false_type
{};
template<typename T>
struct is_container<T,
std::conditional_t<false,
has_defs<decltype(std::declval<T>().front()),
decltype(std::declval<T>().begin()),
decltype(std::declval<T>().end())>,
void>> : public std::true_type
{};
} // namespace detail
// Extra compile-time check to clarify why compilation has failed.
// Otherwise, the error message will be "'x' uses undefined struct ContainerView<decltype(x), void>"
template<class BaseType, class Enable = void>
struct ContainerView
{
static_assert(detail::is_container<BaseType>::value == true,
"Template parameter is probably not a container!");
};
template<class BaseType>
struct ContainerView<BaseType, std::enable_if_t<detail::is_container<BaseType>::value>>
{
private:
// Form a type by rebinding the underlying container type to a reference
// wrapped value type.
template<class ContainerType, class NewType>
struct rebind;
template<class ValueType, class... Args, template<class...> class ContainerType, class NewType>
struct rebind<ContainerType<ValueType, Args...>, NewType>
{
using type = ContainerType<NewType, typename rebind<Args, NewType>::type...>;
};
using value_type = std::remove_reference_t<decltype(std::declval<BaseType>().front())>;
using const_reference = const value_type&;
using view_type = typename rebind<BaseType, std::reference_wrapper<value_type>>::type;
public:
using FilterCallback = std::function<bool(const_reference)>;
using SortCallback = std::function<bool(const_reference, const_reference)>;
using OnEntryCallback = std::function<void(const_reference)>;
ContainerView() = default;
ContainerView(const BaseType& container)
{
data_ = std::make_optional(std::ref(container));
auto& dataSource = std::remove_const_t<BaseType&>(data());
view_.assign(dataSource.begin(), dataSource.end());
};
ContainerView(const ContainerView& other)
: data_(std::nullopt)
, view_(other().begin(), other().end())
, dirty_(other.dirty_)
, sortCallback_(other.sortCallback_)
, filterCallback_(other.filterCallback_) {};
ContainerView& operator=(const ContainerView& other) = default;
ContainerView& operator=(ContainerView&& other) = default;
ContainerView(ContainerView&& other) noexcept = default;
// Allow concatenation of views.
ContainerView& operator+=(const ContainerView& rhs)
{
view_.insert(view_.cend(), rhs.get().cbegin(), rhs.get().cend());
return *this;
}
friend ContainerView operator+(ContainerView lhs, const ContainerView& rhs)
{
lhs += rhs;
return lhs;
}
// Reset the underlying container and initialize the view.
ContainerView& reset(const BaseType& container)
{
data_ = std::make_optional(std::ref(container));
auto& dataSource = std::remove_const_t<BaseType&>(data());
view_.assign(dataSource.begin(), dataSource.end());
invalidate();
return *this;
}
// Alternately, reset the view to another view and disregard underlying data.
ContainerView& reset(const ContainerView& other)
{
data_ = std::nullopt;
auto& dataSource = std::remove_const_t<ContainerView&>(other);
view_.assign(dataSource.get().begin(), dataSource.get().end());
invalidate();
return *this;
}
// Sort the reference wrapped elements of the view container with the
// given predicate or the stored one.
ContainerView& sort(SortCallback&& pred = {})
{
if (!dirty_) {
std::cout << "view not dirty, no-op sort" << std::endl;
return *this;
}
if (auto&& sortCallback = pred ? pred : sortCallback_)
std::sort(view_.begin(), view_.end(), sortCallback);
else
std::cout << "no sort function specified or bound" << std::endl;
return *this;
}
// Filter the reference wrapped elements of the view container with the
// given predicate or the stored one.
// Only done if the view has been invalidated(e.g. the underlying container
// has been updated)
ContainerView& filter(FilterCallback&& pred = {})
{
if (!dirty_) {
std::cout << "view not dirty, no-op filter" << std::endl;
return *this;
}
if (auto&& filterCallback = pred ? pred : filterCallback_) {
if (data_.has_value()) {
auto& dataSource = std::remove_const_t<BaseType&>(data());
applyFilter(dataSource, filterCallback);
} else {
auto viewSource = view_;
applyFilter(viewSource, filterCallback);
}
} else
std::cout << "no filter function specified or bound" << std::endl;
return *this;
}
// Iterate over the the reference wrapped elements of the view container
// and execute a callback on each element.
ContainerView& for_each(OnEntryCallback&& pred)
{
for (const auto& e : view_)
pred(e);
return *this;
}
// Store a non-static member function as a SortCallback.
// The member function must match that of a binary predicate.
template<typename T, typename... Args>
void bindSortCallback(T* inst, bool (T::*func)(Args...))
{
bindCallback(sortCallback_, inst, func);
}
// Overload for function objects.
template<typename Func = SortCallback>
void bindSortCallback(Func&& func)
{
sortCallback_ = func;
}
// Store a non-static member function as a FilterCallback.
// The member function must match that of a unary predicate.
template<typename T, typename... Args>
void bindFilterCallback(T* inst, bool (T::*func)(Args...))
{
bindCallback(filterCallback_, inst, func);
}
// Overload for function objects.
template<typename Func = FilterCallback>
void bindFilterCallback(Func&& func)
{
filterCallback_ = func;
}
// Basic container operations should be avoided.
size_t size() const { return view_.size(); }
const_reference at(size_t pos) const { return view_.at(pos); }
void clear() { view_.clear(); }
// TODO: re-filtering ?? should maybe observe underlying data in order to
// not track this manually.
bool isDirty() const noexcept { return dirty_; };
ContainerView& invalidate() noexcept
{
dirty_ = true;
return *this;
};
ContainerView& validate() noexcept
{
dirty_ = false;
return *this;
};
// Returns whether or not this view has a concrete data source
// or is just a view of a view.
constexpr bool hasUnderlyingData() const noexcept { return data_.has_value(); }
// Access the view.
constexpr const view_type& operator()() const noexcept { return view_; }
constexpr const view_type& get() const noexcept { return view_; }
private:
// A reference to the optional underlying container data source.
// If not used, the view can be constructed from another proxy
// container view, and refiltered and sorted.
OptRef<const BaseType> data_;
// The 'view' is a container of reference wrapped values suitable
// for container operations like sorting and filtering.
view_type view_;
// TODO: remove this invalidation flag if possible.
bool dirty_ {true};
// Stores the sorting/filtering predicates that can be re-applied
// instead of passing a lambda as a parameter to sort().
FilterCallback filterCallback_ {};
// Same as above but for sorting.
SortCallback sortCallback_ {};
// A generic non-static member function storing function.
template<typename C, typename T, typename... Args>
void bindCallback(C& callback, T* inst, bool (T::*func)(Args...))
{
// Using a lambda instead of std::bind works better for functions with an
// unknown number of parameters. e.g. callback = std::bind(func, inst, _1, ???);
callback = [=](Args... args) -> bool {
return (inst->*func)(args...);
};
}
// Hard unbox and unwrap underlying data container.
const BaseType& data() {
return data_.value().get();
}
// Actually filter the view.
template<typename T, typename Func = FilterCallback>
ContainerView& applyFilter(T& source, Func&& pred)
{
view_.clear();
std::copy_if(source.begin(), source.end(), std::back_inserter(view_), pred);
return *this;
}
};