diff options
Diffstat (limited to 'include/o3tl')
31 files changed, 4332 insertions, 0 deletions
diff --git a/include/o3tl/any.hxx b/include/o3tl/any.hxx new file mode 100644 index 0000000000..0acccff643 --- /dev/null +++ b/include/o3tl/any.hxx @@ -0,0 +1,325 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_O3TL_ANY_HXX +#define INCLUDED_O3TL_ANY_HXX + +#include <sal/config.h> + +#include <cassert> +#include <type_traits> +#include <utility> + +#include <optional> + +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/XInterface.hpp> +#include <cppu/unotype.hxx> +#include <rtl/ustring.hxx> +#include <sal/types.h> + +// Some functionality related to css::uno::Any that would ideally be part of +// <com/sun/star/uno/Any.hxx>, but (for now) cannot be for some reason. + +namespace o3tl { + +namespace detail { + +struct Void {}; + +template<typename T> struct Optional { using type = T const *; }; +template<> struct Optional<void> { using type = std::optional<Void const>; }; +template<> struct Optional<bool> { using type = std::optional<bool const>; }; +template<> struct Optional<sal_Int8> { + using type = std::optional<sal_Int8 const>; +}; +template<> struct Optional<sal_Int16> { + using type = std::optional<sal_Int16 const>; +}; +template<> struct Optional<sal_uInt16> { + using type = std::optional<sal_uInt16 const>; +}; +template<> struct Optional<sal_Int32> { + using type = std::optional<sal_Int32 const>; +}; +template<> struct Optional<sal_uInt32> { + using type = std::optional<sal_uInt32 const>; +}; +template<> struct Optional<sal_Int64> { + using type = std::optional<sal_Int64 const>; +}; +template<> struct Optional<sal_uInt64> { + using type = std::optional<sal_uInt64 const>; +}; +template<> struct Optional<float> { + using type = std::optional<float const>; +}; +template<> struct Optional<double> { + using type = std::optional<double const>; +}; +template<typename T> struct Optional<css::uno::Reference<T>> { + using type = std::optional<css::uno::Reference<T> const>; +}; +template<> struct Optional<css::uno::Reference<css::uno::XInterface>> { + using type = css::uno::Reference<css::uno::XInterface> const *; +}; + +template<typename> struct IsDerivedReference: std::false_type {}; +template<typename T> struct IsDerivedReference<css::uno::Reference<T>>: + std::true_type +{}; +template<> struct IsDerivedReference<css::uno::Reference<css::uno::XInterface>>: + std::false_type +{}; + +template<typename> struct IsUnoSequenceType: std::false_type {}; +template<typename T> struct IsUnoSequenceType<cppu::UnoSequenceType<T>>: + std::true_type +{}; + +template<typename T> inline std::optional<T const> tryGetConverted( + css::uno::Any const & any) +{ + T v; + return (any >>= v) + ? std::optional<T const>(std::move(v)) : std::optional<T const>(); +} + +} + +/** Try to access the value of a specific type stored in an Any. + + In trying to obtain a value, the same set of conversions as supported by + ">>=" are considered. + + The returned object is a proxy. Proxies can be either positive or negative. + Each proxy can be contextually converted to bool, yielding true iff the + proxy is positive. For a positive proxy P representing a value of requested + type T, for any T other than void, the expression *P yields that value of + type T. (Technically, the proxy is either a plain pointer or a + std::optional, depending on whether a plain pointer into the given Any can + be returned for the specified type.) + + @attention A proxy returned from this function must not outlive the + corresponding Any passed into this function (as it may constitute a pointer + into the Any's internals). That is the reason why this function is + restricted to lvalue arguments (i.e., to non-temporary Any objects), to + avoid misuses like + @code + css::uno::Any f(); + + if (auto p = o3tl::tryAccess<css::beans::NamedValue>(f())) { + return p->Name; + } + @endcode + + @note Ideally this would be a public member function of css::uno::Any (at + least conditional on LIBO_INTERNAL_ONLY, as it requires C++11). However, as + std::optional (which would be needed to implement the proxies) is only + available since C++14, we need to use std::optional for now. But To not + make every entity that includes <com/sun/star/uno/Any.hxx> depend on + boost_headers, keep this here for now. + + @tparam T the C++ representation of a UNO type that can be contained in a + UNO ANY (i.e., any UNO type other than ANY itself). The legacy C++ + representations sal_Bool, cppu::UnoVoidType, cppu::UnoUnsignedShortType, + cppu::UnoCharType, and cppu::UnoSequenceType are not supported. Must be a + complete type or void. + + @param any an Any value. + + @return a positive proxy for the value of the specified type obtained from + the given Any, or a negative proxy if no such value can be obtained. +*/ +template<typename T> inline +typename std::enable_if< + !(detail::IsDerivedReference<T>::value + || detail::IsUnoSequenceType<T>::value + || std::is_base_of<css::uno::XInterface, T>::value), + typename detail::Optional<T>::type>::type +tryAccess(css::uno::Any const & any) { + // CHAR, STRING, TYPE, sequence types, enum types, struct types, exception + // types, and com.sun.star.uno.XInterface interface type: + return cppu::UnoType<T>::get().isAssignableFrom(any.getValueType()) + ? static_cast<T const *>(any.getValue()) : nullptr; +} + +template<> inline detail::Optional<void>::type tryAccess<void>( + css::uno::Any const & any) +{ + return any.hasValue() + ? std::optional<detail::Void const>() + : std::optional<detail::Void const>(detail::Void()); +} + +template<> inline detail::Optional<bool>::type tryAccess<bool>( + css::uno::Any const & any) +{ + return detail::tryGetConverted<bool>(any); +} + +template<> inline detail::Optional<sal_Int8>::type tryAccess<sal_Int8>( + css::uno::Any const & any) +{ + return detail::tryGetConverted<sal_Int8>(any); +} + +template<> inline detail::Optional<sal_Int16>::type tryAccess<sal_Int16>( + css::uno::Any const & any) +{ + return detail::tryGetConverted<sal_Int16>(any); +} + +template<> inline detail::Optional<sal_uInt16>::type tryAccess<sal_uInt16>( + css::uno::Any const & any) +{ + return detail::tryGetConverted<sal_uInt16>(any); +} + +template<> inline detail::Optional<sal_Int32>::type tryAccess<sal_Int32>( + css::uno::Any const & any) +{ + return detail::tryGetConverted<sal_Int32>(any); +} + +template<> inline detail::Optional<sal_uInt32>::type tryAccess<sal_uInt32>( + css::uno::Any const & any) +{ + return detail::tryGetConverted<sal_uInt32>(any); +} + +template<> inline detail::Optional<sal_Int64>::type tryAccess<sal_Int64>( + css::uno::Any const & any) +{ + return detail::tryGetConverted<sal_Int64>(any); +} + +template<> inline detail::Optional<sal_uInt64>::type tryAccess<sal_uInt64>( + css::uno::Any const & any) +{ + return detail::tryGetConverted<sal_uInt64>(any); +} + +template<> inline detail::Optional<float>::type tryAccess<float>( + css::uno::Any const & any) +{ + return detail::tryGetConverted<float>(any); +} + +template<> inline detail::Optional<double>::type tryAccess<double>( + css::uno::Any const & any) +{ + return detail::tryGetConverted<double>(any); +} + +template<> detail::Optional<css::uno::Any>::type tryAccess<css::uno::Any>( + css::uno::Any const &) = delete; + +template<> detail::Optional<sal_Bool>::type tryAccess<sal_Bool>( + css::uno::Any const &) = delete; + +/* + +// Already prevented by std::is_base_of<css::uno::XInterface, T> requiring T to +// be complete: + +template<> detail::Optional<cppu::UnoVoidType>::type +tryAccess<cppu::UnoVoidType>(css::uno::Any const &) = delete; + +template<> detail::Optional<cppu::UnoUnsignedShortType>::type +tryAccess<cppu::UnoUnsignedShortType>(css::uno::Any const &) = delete; + +template<> detail::Optional<cppu::UnoCharType>::type +tryAccess<cppu::UnoCharType>(css::uno::Any const &) = delete; + +*/ + +template<typename T> inline +typename std::enable_if< + detail::IsDerivedReference<T>::value, + typename detail::Optional<T>::type>::type +tryAccess(css::uno::Any const & any) { + return detail::tryGetConverted<T>(any); +} + +template<typename T> typename detail::Optional<T>::type tryAccess( + css::uno::Any const volatile &&) = delete; + +/** Access the value of a specific type stored in an Any, throwing an exception + on failure. + + @attention A proxy returned from this function must not outlive the + corresponding Any passed into this function (as it may constitute a pointer + into the Any's internals). However, unlike with tryAccess, the benefit of + allowing this function to operate on temporaries appears to outweigh its + dangers. + + @note Ideally this would be a public member function of css::uno::Any. See + tryAccess for details. + + @tparam T the C++ representation of a UNO type that can be contained in a + UNO ANY. See tryAccess for details. + + @param any an Any value. + + @return a positive proxy for the value of the specified type obtained from + the given Any. See tryAccess for details. + + @throws css::uno::RuntimeException when a value of the requested type + cannot be obtained. +*/ +template<typename T> inline typename detail::Optional<T>::type doAccess( + css::uno::Any const & any) +{ + auto opt = tryAccess<T>(any); + if (!opt) { + throw css::uno::RuntimeException( + OUString( + cppu_Any_extraction_failure_msg( + &any, cppu::UnoType<T>::get().getTypeLibType()), + SAL_NO_ACQUIRE)); + } + return opt; +} + +/** Access the value of a specific type stored in an Any, knowing the Any + contains a value of a matching type. + + @attention A proxy returned from this function must not outlive the + corresponding Any passed into this function (as it may constitute a pointer + into the Any's internals). However, unlike with tryAccess, the benefit of + allowing this function to operate on temporaries appears to outweigh its + dangers. + + @note Ideally this would be a public member function of css::uno::Any. See + tryAccess for details. + + @tparam T the C++ representation of a UNO type that can be contained in a + UNO ANY. See tryAccess for details. + + @param any an Any value. + + @return a positive proxy for the value of the specified type obtained from + the given Any. See tryAccess for details. +*/ +template<typename T> inline typename detail::Optional<T>::type forceAccess( + css::uno::Any const & any) +{ + auto opt = tryAccess<T>(any); + assert(opt); + return opt; +} + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/o3tl/char16_t2wchar_t.hxx b/include/o3tl/char16_t2wchar_t.hxx new file mode 100644 index 0000000000..c2640f6271 --- /dev/null +++ b/include/o3tl/char16_t2wchar_t.hxx @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +namespace o3tl +{ +#if defined _WIN32 +// Helpers for safe conversion between wchar_t and char16_t in MSVC + +static_assert(sizeof(char16_t) == sizeof(wchar_t), + "These helper functions are only applicable to implementations with 16-bit wchar_t"); + +// While other implementations define wchar_t as 32-bit integral value, and mostly use +// char-based UTF-8 string APIs, in MSVC wchar_t is (non-conformant) 16-bit, and Unicode +// support is implemented by Unicode-aware WinAPI functions taking UTF-16 LE strings, +// and also stdlib functions taking those strings. +// +// In LibreOffice, internal string representation is also UTF-16 with system endianness +// (sal_Unicode that is typedef for char16_t); so it is an important implementation concept +// to treat internal strings as directly usable by WinAPI/stdlib functions and vice versa. +// Also, it's important to use safe conversion between unrelated underlying C++ types +// used for MSVC and LibreOffice string storage without plain reinterpret_cast that brings +// risks of masking errors like casting char buffers to wchar_t/char16_t. +// +// Use these helpers for wchar_t (WSTR, WCHAR, OLESTR etc) to char16_t (sal_Unicode) string +// conversions instead of reinterpret-cast in Windows-specific code. +inline wchar_t* toW(char16_t* p) { return reinterpret_cast<wchar_t*>(p); } +inline wchar_t const* toW(char16_t const* p) { return reinterpret_cast<wchar_t const*>(p); } +inline char16_t* toU(wchar_t* p) { return reinterpret_cast<char16_t*>(p); } +inline char16_t const* toU(wchar_t const* p) { return reinterpret_cast<char16_t const*>(p); } + +inline std::u16string_view toU(std::wstring_view v) { return { toU(v.data()), v.size() }; } +inline std::wstring_view toW(std::u16string_view v) { return { toW(v.data()), v.size() }; } +#endif +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/o3tl/concepts.hxx b/include/o3tl/concepts.hxx new file mode 100644 index 0000000000..261d063ebc --- /dev/null +++ b/include/o3tl/concepts.hxx @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <sal/config.h> + +#include <concepts> + +// LLVM 12 libc++ only provides a bare-bones <concepts> that lacks most of its C++20 content, so +// replicate here fore now what we need: + +#if defined __cpp_lib_concepts + +namespace o3tl +{ +using std::integral; +using std::signed_integral; +using std::unsigned_integral; +} + +#else + +#include <type_traits> + +namespace o3tl +{ +// Taken from the C++20 spec: + +template <typename T> concept integral = std::is_integral_v<T>; + +template <typename T> concept signed_integral = integral<T>&& std::is_signed_v<T>; + +template <typename T> concept unsigned_integral = integral<T> && !signed_integral<T>; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/o3tl/cow_wrapper.hxx b/include/o3tl/cow_wrapper.hxx new file mode 100644 index 0000000000..cc823f60ec --- /dev/null +++ b/include/o3tl/cow_wrapper.hxx @@ -0,0 +1,387 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_O3TL_COW_WRAPPER_HXX +#define INCLUDED_O3TL_COW_WRAPPER_HXX + +#include <osl/interlck.h> + +#include <optional> +#include <cstddef> + +namespace o3tl +{ + /** Thread-unsafe refcounting + + This is the default locking policy for cow_wrapper. No + locking/guarding against concurrent access is performed + whatsoever. + */ + struct UnsafeRefCountingPolicy + { + typedef std::size_t ref_count_t; + static void incrementCount( ref_count_t& rCount ) { ++rCount; } + static bool decrementCount( ref_count_t& rCount ) { return --rCount != 0; } + }; + + /** Thread-safe refcounting + + Use this to have the cow_wrapper refcounting mechanisms employ + the thread-safe oslInterlockedCount . + */ + struct ThreadSafeRefCountingPolicy + { + typedef oslInterlockedCount ref_count_t; + static void incrementCount( ref_count_t& rCount ) { osl_atomic_increment(&rCount); } + static bool decrementCount( ref_count_t& rCount ) + { + return osl_atomic_decrement(&rCount) != 0; + } + }; + + /** Copy-on-write wrapper. + + This template provides copy-on-write semantics for the wrapped + type: when copying, the operation is performed shallow, + i.e. different cow_wrapper objects share the same underlying + instance. Only when accessing the underlying object via + non-const methods, a unique copy is provided. + + The type parameter <code>T</code> must satisfy the following + requirements: it must be default-constructible, copyable (it + need not be assignable), and be of non-reference type. Note + that, despite the fact that this template provides access to + the wrapped type via pointer-like methods + (<code>operator->()</code> and <code>operator*()</code>), it does + <em>not</em> work like e.g. the std smart pointer wrappers + (shared_ptr, unique_ptr, etc.). Internally, the cow_wrapper + holds a by-value instance of the wrapped object. This is to + avoid one additional heap allocation, and providing access via + <code>operator->()</code>/<code>operator*()</code> is because + <code>operator.()</code> cannot be overridden. + + Regarding thread safety: this wrapper is <em>not</em> + thread-safe per se, because cow_wrapper has no way of + synchronizing the potentially many different cow_wrapper + instances, that reference a single shared value_type + instance. That said, when passing + <code>ThreadSafeRefCountingPolicy</code> as the + <code>MTPolicy</code> parameter, accessing a thread-safe + pointee through multiple cow_wrapper instances might be + thread-safe, if the individual pointee methods are + thread-safe, <em>including</em> pointee's copy + constructor. Any wrapped object that needs external + synchronisation (e.g. via an external mutex, which arbitrates + access to object methods, and can be held across multiple + object method calls) cannot easily be dealt with in a + thread-safe way, because, as noted, objects are shared behind + the client's back. + + @attention if one wants to use the pimpl idiom together with + cow_wrapper (i.e. put an opaque type into the cow_wrapper), + then <em>all<em> methods in the surrounding class needs to be + non-inline (<em>including</em> destructor, copy constructor + and assignment operator). + + @example + <pre> +class cow_wrapper_client_impl; + +class cow_wrapper_client +{ +public: + cow_wrapper_client(); + cow_wrapper_client( const cow_wrapper_client& ); + cow_wrapper_client( cow_wrapper_client&& ); + ~cow_wrapper_client(); + + cow_wrapper_client& operator=( const cow_wrapper_client& ); + cow_wrapper_client& operator=( cow_wrapper_client&& ); + + void modify( int nVal ); + int queryUnmodified() const; + +private: + o3tl::cow_wrapper< cow_wrapper_client_impl > maImpl; +}; + </pre> + and the implementation file would look like this: + <pre> +class cow_wrapper_client_impl +{ +public: + void setValue( int nVal ) { mnValue = nVal; } + int getValue() const { return mnValue; } + +private: + int mnValue; +} + +cow_wrapper_client::cow_wrapper_client() : + maImpl() +{ +} +cow_wrapper_client::cow_wrapper_client( const cow_wrapper_client& rSrc ) : + maImpl( rSrc.maImpl ) +{ +} +cow_wrapper_client::cow_wrapper_client( cow_wrapper_client& rSrc ) : + maImpl( std::move( rSrc.maImpl ) ) +{ +} +cow_wrapper_client::~cow_wrapper_client() +{ +} +cow_wrapper_client& cow_wrapper_client::operator=( const cow_wrapper_client& rSrc ) +{ + maImpl = rSrc.maImpl; + return *this; +} +cow_wrapper_client& cow_wrapper_client::operator=( cow_wrapper_client&& rSrc ) +{ + maImpl = std::move( rSrc.maImpl ); + return *this; +} +void cow_wrapper_client::modify( int nVal ) +{ + maImpl->setValue( nVal ); +} +int cow_wrapper_client::queryUnmodified() const +{ + return maImpl->getValue(); +} + </pre> + */ + template<typename T, class MTPolicy=UnsafeRefCountingPolicy> class cow_wrapper + { + /** shared value object - gets cloned before cow_wrapper hands + out a non-const reference to it + */ + struct impl_t + { + impl_t(const impl_t&) = delete; + impl_t& operator=(const impl_t&) = delete; + + impl_t() : + m_value(), + m_ref_count(1) + { + } + + explicit impl_t( const T& v ) : + m_value(v), + m_ref_count(1) + { + } + + explicit impl_t( T&& v ) : + m_value(std::move(v)), + m_ref_count(1) + { + } + + T m_value; + typename MTPolicy::ref_count_t m_ref_count; + }; + + void release() + { + if( m_pimpl && !MTPolicy::decrementCount(m_pimpl->m_ref_count) ) + { + delete m_pimpl; + m_pimpl = nullptr; + } + } + + public: + typedef T value_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef MTPolicy mt_policy; + + /** Default-construct wrapped type instance + */ + cow_wrapper() : + m_pimpl( new impl_t() ) + { + } + + /** Copy-construct wrapped type instance from given object + */ + explicit cow_wrapper( const value_type& r ) : + m_pimpl( new impl_t(r) ) + { + } + + /** Move-construct wrapped type instance from given object + */ + explicit cow_wrapper( value_type&& r ) : + m_pimpl( new impl_t(std::move(r)) ) + { + } + + /** Shallow-copy given cow_wrapper + */ + explicit cow_wrapper( const cow_wrapper& rSrc ) : // nothrow + m_pimpl( rSrc.m_pimpl ) + { + MTPolicy::incrementCount( m_pimpl->m_ref_count ); + } + + /** Move-construct and steal rSrc shared resource + */ + explicit cow_wrapper( cow_wrapper&& rSrc ) noexcept : + m_pimpl( rSrc.m_pimpl ) + { + rSrc.m_pimpl = nullptr; + } + + // Only intended to be used by std::optional specialisations + explicit cow_wrapper( std::nullopt_t ) noexcept : + m_pimpl( nullptr ) + { + } + + // Only intended to be used by std::optional specialisations + explicit cow_wrapper( const cow_wrapper& rSrc, std::nullopt_t ) : // nothrow + m_pimpl( rSrc.m_pimpl ) + { + if (m_pimpl) + MTPolicy::incrementCount( m_pimpl->m_ref_count ); + } + + ~cow_wrapper() // nothrow, if ~T does not throw + { + release(); + } + + /// now sharing rSrc cow_wrapper instance with us + cow_wrapper& operator=( const cow_wrapper& rSrc ) // nothrow + { + // this already guards against self-assignment + MTPolicy::incrementCount( rSrc.m_pimpl->m_ref_count ); + + release(); + m_pimpl = rSrc.m_pimpl; + + return *this; + } + + /// stealing rSrc's resource + cow_wrapper& operator=(cow_wrapper&& rSrc) noexcept + { + // self-movement guts ourself, see also 17.6.4.9 + release(); + m_pimpl = rSrc.m_pimpl; + + rSrc.m_pimpl = nullptr; + + return *this; + } + + /// unshare with any other cow_wrapper instance + value_type& make_unique() + { + if( m_pimpl->m_ref_count > 1 ) + { + impl_t* pimpl = new impl_t(m_pimpl->m_value); + release(); + m_pimpl = pimpl; + } + + return m_pimpl->m_value; + } + + /// true, if not shared with any other cow_wrapper instance + bool is_unique() const // nothrow + { + return !m_pimpl || m_pimpl->m_ref_count == 1; + } + + /// return number of shared instances (1 for unique object) + typename MTPolicy::ref_count_t use_count() const // nothrow + { + return m_pimpl ? m_pimpl->m_ref_count : 0; + } + + void swap(cow_wrapper& r) // never throws + { + std::swap(m_pimpl, r.m_pimpl); + } + + pointer operator->() { return &make_unique(); } + value_type& operator*() { return make_unique(); } + const_pointer operator->() const { return &m_pimpl->m_value; } + const value_type& operator*() const { return m_pimpl->m_value; } + + pointer get() { return &make_unique(); } + const_pointer get() const { return &m_pimpl->m_value; } + + /// true, if both cow_wrapper internally share the same object + bool same_object( const cow_wrapper& rOther ) const + { + return rOther.m_pimpl == m_pimpl; + } + + // Only intended to be used by std::optional specialisations + bool empty() const { return m_pimpl == nullptr; } + // Only intended to be used by std::optional specialisations + void set_empty() + { + if (m_pimpl) + { + release(); + m_pimpl = nullptr; + } + } + + private: + impl_t* m_pimpl; + }; + + + template<class T, class P> inline bool operator==( const cow_wrapper<T,P>& a, + const cow_wrapper<T,P>& b ) + { + return a.same_object(b) || *a == *b; + } + + template<class T, class P> inline bool operator!=( const cow_wrapper<T,P>& a, + const cow_wrapper<T,P>& b ) + { + return !a.same_object(b) && *a != *b; + } + + template<class A, class B, class P> inline bool operator<( const cow_wrapper<A,P>& a, + const cow_wrapper<B,P>& b ) + { + return *a < *b; + } + + template<class T, class P> inline void swap( cow_wrapper<T,P>& a, + cow_wrapper<T,P>& b ) + { + a.swap(b); + } + +} + +#endif /* INCLUDED_O3TL_COW_WRAPPER_HXX */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/o3tl/cppunittraitshelper.hxx b/include/o3tl/cppunittraitshelper.hxx new file mode 100644 index 0000000000..2a6fb88362 --- /dev/null +++ b/include/o3tl/cppunittraitshelper.hxx @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_O3TL_CPPUNITTRAITSHELPER_HXX +#define INCLUDED_O3TL_CPPUNITTRAITSHELPER_HXX + +#include <sal/config.h> + +#include <cstdint> +#include <string> + +#include <cppunit/TestAssert.h> + +// ostream << char16_t is deleted since C++20 (but just keep outputting numeric values): +template <> inline std::string CppUnit::assertion_traits<char16_t>::toString(char16_t const& x) +{ + return assertion_traits<std::uint_least16_t>::toString(std::uint_least16_t(x)); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/o3tl/deleter.hxx b/include/o3tl/deleter.hxx new file mode 100644 index 0000000000..76d8d5a357 --- /dev/null +++ b/include/o3tl/deleter.hxx @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_O3TL_DELETER_HXX +#define INCLUDED_O3TL_DELETER_HXX + +#include <sal/config.h> + +#include <cstdlib> + +#if defined(__COVERITY__) +#define suppress_fun_call_w_exception(expr) \ + do \ + { \ + try \ + { \ + expr; \ + } \ + catch (...) \ + { \ + std::terminate(); \ + } \ + } while (false) +#else +#define suppress_fun_call_w_exception(expr) \ + do \ + { \ + expr; \ + } while (false) +#endif + +namespace o3tl +{ +/** To markup std::unique_ptr that coverity warns might throw exceptions + which won't throw in practice, or where std::terminate is + an acceptable response if they do +*/ +template <typename T> struct default_delete +{ + void operator()(T* p) noexcept { suppress_fun_call_w_exception(delete p); } +}; + +struct free_delete +{ + void operator()(void* p) { std::free(p); } +}; + +template <typename uniqueptr> void reset_preserve_ptr_during(uniqueptr& ptr) +{ + // HACK: for the case where the dtor of the obj held by ptr will trigger + // functions which expect ptr to still be set during the dtor. + // e.g. SdrObject::GetBroadcaster() is called during the destructor + // in SdrEdgeObj::Notify(). So delete first, then clear the pointer + delete ptr.get(); + // coverity[leaked_storage] - not a leak, deleted on line above + (void)ptr.release(); +} +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/o3tl/enumarray.hxx b/include/o3tl/enumarray.hxx new file mode 100644 index 0000000000..4fd5cab315 --- /dev/null +++ b/include/o3tl/enumarray.hxx @@ -0,0 +1,157 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_O3TL_ENUMARRAY_HXX +#define INCLUDED_O3TL_ENUMARRAY_HXX + +#include <iterator> +#include <type_traits> +#include <utility> +#include <array> +#include <cassert> + +namespace o3tl { + +template<typename EA> +class enumarray_iterator; +template<typename EA> +class enumarray_const_iterator; + +/// +/// This is a container convenience class for arrays indexed by enum values. +/// +/// This assumes that the 'enum class' definition +/// - starts at zero +/// - has no holes in its sequence of values +/// - defines a value called LAST which refers to the greatest constant. +/// +/// \param E the 'enum class' type. +/// \param V the value type to be stored in the array +template<typename E, typename V> +class enumarray final +{ +public: + typedef enumarray<E, V> self_type; + typedef enumarray_iterator<self_type> iterator; + typedef enumarray_const_iterator<self_type> const_iterator; + + typedef V value_type; + typedef E key_type; + typedef size_t size_type; + + static const size_type max_index = static_cast<size_type>(E::LAST); + + // If this ctor only had the args parameter pack, it would erroneously get picked as a better + // choice than the (implicit) copy ctor (taking a const lvalue reference) when a copy is made + // from a non-const lvalue enumarray; the easiest way to avoid that is the additional arg + // parameter; and to keep things simple that parameter is always passed by const lvalue + // reference for now even if there could be cases where passing it by rvalue reference might be + // beneficial or even necessary if V is a move-only type: + template<typename... T> constexpr enumarray(V const & arg, T && ...args): + detail_values{arg, std::forward<T>(args)...} + { + static_assert(sizeof... (T) == max_index); + } + + // coverity[uninit_ctor] - by design: + enumarray() {} + + const V& operator[](E index) const + { + assert(index>=static_cast<E>(0) && index<=E::LAST); + return detail_values[static_cast<size_type>(index)]; + } + + V& operator[](E index) + { + assert(index>=static_cast<E>(0) && index<=E::LAST); + return detail_values[static_cast<size_type>(index)]; + } + + void fill(V val) + { for (size_type i=0; i<=max_index; ++i) detail_values[i] = val; } + + static size_type size() { return max_index + 1; } + iterator begin() { return iterator(*this, 0); } + iterator end() { return iterator(*this, size()); } + const_iterator begin() const { return const_iterator(*this, 0); } + const_iterator end() const { return const_iterator(*this, size()); } + + V* data() { return detail_values.data(); } + +private: + std::array<V, max_index + 1> detail_values; +}; + + +template<typename EA> +class enumarray_iterator { + EA* m_buf; + size_t m_pos; +public: + typedef enumarray_iterator<EA> self_type; + typedef typename EA::value_type value_type; + typedef typename EA::key_type key_type; + typedef std::bidirectional_iterator_tag iterator_category; //should be random access, but that would require define subtraction operators on the enums + typedef + typename std::make_signed< + typename std::underlying_type<typename EA::key_type>::type>::type + difference_type; + typedef typename EA::value_type* pointer; + typedef typename EA::value_type& reference; + + enumarray_iterator(EA& b, size_t start_pos) + : m_buf(&b), m_pos(start_pos) {} + value_type& operator*() const { return (*m_buf)[static_cast<key_type>(m_pos)]; } + value_type* operator->() const { return &(operator*()); } + self_type& operator++() { ++m_pos; return *this; } + bool operator!=(self_type const & other) const { return m_buf != other.m_buf || m_pos != other.m_pos; } + bool operator==(self_type const & other) const { return m_buf == other.m_buf && m_pos == other.m_pos; } +}; + +template<typename EA> +class enumarray_const_iterator { + EA const * m_buf; + size_t m_pos; +public: + typedef enumarray_const_iterator<EA> self_type; + typedef typename EA::value_type const value_type; + typedef typename EA::key_type key_type; + typedef std::bidirectional_iterator_tag iterator_category; //should be random access, but that would require define subtraction operators on the enums + typedef + typename std::make_signed< + typename std::underlying_type<typename EA::key_type>::type>::type + difference_type; + typedef typename EA::value_type const * pointer; + typedef typename EA::value_type const & reference; + + enumarray_const_iterator(EA const & b, size_t start_pos) + : m_buf(&b), m_pos(start_pos) {} + value_type& operator*() const { return (*m_buf)[static_cast<key_type>(m_pos)]; } + value_type* operator->() const { return &(operator*()); } + self_type& operator++() { ++m_pos; return *this; } + bool operator!=(self_type const & other) const { return m_buf != other.m_buf || m_pos != other.m_pos; } + bool operator==(self_type const & other) const { return m_buf == other.m_buf && m_pos == other.m_pos; } +}; + +}; // namespace o3tl + +#endif /* INCLUDED_O3TL_ENUMARRAY_HXX */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/o3tl/enumrange.hxx b/include/o3tl/enumrange.hxx new file mode 100644 index 0000000000..60f069c9f5 --- /dev/null +++ b/include/o3tl/enumrange.hxx @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_O3TL_ENUMRANGE_HXX +#define INCLUDED_O3TL_ENUMRANGE_HXX + +namespace o3tl { + +/// +/// This is a container convenience class iterating over scoped enumerations. +/// +/// This assumes that the 'enum class' definition +/// - starts at zero +/// - has no holes in its sequence of values +/// - defines a value called LAST which refers to the greatest constant. +/// +/// Use like this: +/// enum class COLOR { RED, GREEN, BLUE, LAST=BLUE }; +/// for( auto e : o3tl::enumrange<Color>() ) +/// ....; +/// +/// \param T the 'enum class' type. +template< typename T> +class enumrange +{ +public: + class Iterator + { + public: + Iterator( int value ) : + m_value( value ) + { + } + + T operator*( void ) const + { + return static_cast<T>(m_value); + } + + void operator++( void ) + { + ++m_value; + } + + bool operator!=( Iterator rhs ) const + { + return m_value != rhs.m_value; + } + + private: + int m_value; + }; +}; + +template< typename T > +typename enumrange<T>::Iterator begin( enumrange<T> ) +{ + return typename enumrange<T>::Iterator( int(0) ); +} + +template< typename T > +typename enumrange<T>::Iterator end( enumrange<T> ) +{ + return typename enumrange<T>::Iterator( static_cast<int>(T::LAST) + 1 ); +} + +}; // namespace o3tl + +#endif /* INCLUDED_O3TL_ENUMRANGE_HXX */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/o3tl/float_int_conversion.hxx b/include/o3tl/float_int_conversion.hxx new file mode 100644 index 0000000000..403bed6221 --- /dev/null +++ b/include/o3tl/float_int_conversion.hxx @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_O3TL_FLOAT_INT_CONVERSION_HXX +#define INCLUDED_O3TL_FLOAT_INT_CONVERSION_HXX + +#include <sal/config.h> + +#include <cmath> +#include <limits> +#include <type_traits> + +namespace o3tl +{ +// Return true iff `value` of floating-point type `F` converts to a value of integral type `I` no +// smaller than `min`: +template <typename F, typename I> +constexpr std::enable_if_t<std::is_floating_point_v<F> && std::is_integral_v<I>, bool> +convertsToAtLeast(F value, I min) +{ + // If `F(min)`, `F(min) - F(1)` are too large in magnitude for `F`'s precision, then they either + // fall into the same bucket, in which case we should return false if `value` represents that + // bucket, or they are on the boundary of two adjacent buckets, in which case we should return + // true if `value`represents the higher bucket containing `F(min)`: + return value > F(min) - F(1); +} + +// Return true iff `value` of floating-point type `F` converts to a value of integral type `I` no +// larger than `max`: +template <typename F, typename I> +constexpr std::enable_if_t<std::is_floating_point_v<F> && std::is_integral_v<I>, bool> +convertsToAtMost(F value, I max) +{ + // If `F(max)`, `F(max) + F(1)` are too large in magnitude for `F`'s precision, then they either + // fall into the same bucket, in which case we should return false if `value` represents that + // bucket, or they are on the boundary of two adjacent buckets, in which case we should return + // true if `value`represents the lower bucket containing `F(max)`: + return value < F(max) + F(1); +} + +// Casts a floating-point to an integer, avoiding overflow. Used like: +// sal_Int64 n = o3tl::saturating_cast<sal_Int64>(f); +template <typename I, typename F> +constexpr std::enable_if_t<std::is_floating_point_v<F> && std::is_integral_v<I>, I> +saturating_cast(F f) +{ + if constexpr (std::is_signed_v<I>) + if (!convertsToAtLeast(f, std::numeric_limits<I>::min())) + return std::numeric_limits<I>::min(); + if (!convertsToAtMost(f, std::numeric_limits<I>::max())) + return std::numeric_limits<I>::max(); + return f; +} + +// Return `value` of floating-point type `F` rounded to the nearest integer away from zero (which +// can be useful in calls to convertsToAtLeast/Most(roundAway(x), n), to reject x that are +// smaller/larger than n because they have a fractional part): +template <typename F> std::enable_if_t<std::is_floating_point_v<F>, F> roundAway(F value) +{ + return value >= 0 ? std::ceil(value) : std::floor(value); +} +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/o3tl/functional.hxx b/include/o3tl/functional.hxx new file mode 100644 index 0000000000..9ca86924f8 --- /dev/null +++ b/include/o3tl/functional.hxx @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * + * Copyright (c) 1994 + * Hewlett-Packard Company + * + * Copyright (c) 1996-1998 + * Silicon Graphics Computer Systems, Inc. + * + * Copyright (c) 1997 + * Moscow Center for SPARC Technology + * + * Copyright (c) 1999 + * Boris Fomitchev + * + * This material is provided "as is", with absolutely no warranty expressed + * or implied. Any use is at your own risk. + * + * Permission to use or copy this software for any purpose is hereby granted + * without fee, provided the above notices are retained on all copies. + * Permission to modify the code and to distribute modified code is granted, + * provided the above notices are retained, and a notice that the code was + * modified is included with the above copyright notice. + * + */ + +/* + * Lifted and paraphrased from STLport - with additions from Fridrich + * Strba and Thorsten Behrens + */ + +#ifndef INCLUDED_O3TL_FUNCTIONAL_HXX +#define INCLUDED_O3TL_FUNCTIONAL_HXX + +namespace o3tl +{ +/// Select first value of a pair +template<typename P> +struct select1st +{ + typedef P argument_type; + typedef typename P::first_type result_type; + const result_type& operator()( const argument_type& cp ) const { + return cp.first; + } +}; + +/// Select second value of a pair +template<typename P> +struct select2nd +{ + typedef P argument_type; + typedef typename P::second_type result_type; + const result_type& operator()( const argument_type& cp ) const { + return cp.second; + } +}; + +} // namespace o3tl + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/o3tl/hash_combine.hxx b/include/o3tl/hash_combine.hxx new file mode 100644 index 0000000000..48e1426222 --- /dev/null +++ b/include/o3tl/hash_combine.hxx @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <cstddef> +#include <functional> +#include <type_traits> + +namespace o3tl +{ +template <typename T, typename N> +inline std::enable_if_t<(sizeof(N) == 4)> hash_combine(N& nSeed, T const* pValue, size_t nCount) +{ + static_assert(sizeof(nSeed) == 4); + for (size_t i = 0; i < nCount; ++i) + { + nSeed ^= std::hash<T>{}(*pValue) + 0x9E3779B9u + (nSeed << 6) + (nSeed >> 2); + ++pValue; + } +} + +template <typename T, typename N> +inline std::enable_if_t<(sizeof(N) == 4)> hash_combine(N& nSeed, T const& nValue) +{ + static_assert(sizeof(nSeed) == 4); + nSeed ^= std::hash<T>{}(nValue) + 0x9E3779B9u + (nSeed << 6) + (nSeed >> 2); +} + +template <typename T, typename N> +inline std::enable_if_t<(sizeof(N) == 8)> hash_combine(N& nSeed, T const* pValue, size_t nCount) +{ + static_assert(sizeof(nSeed) == 8); + for (size_t i = 0; i < nCount; ++i) + { + nSeed ^= std::hash<T>{}(*pValue) + 0x9E3779B97F4A7C15llu + (nSeed << 12) + (nSeed >> 4); + ++pValue; + } +} + +template <typename T, typename N> +inline std::enable_if_t<(sizeof(N) == 8)> hash_combine(N& nSeed, T const& nValue) +{ + static_assert(sizeof(nSeed) == 8); + nSeed ^= std::hash<T>{}(nValue) + 0x9E3779B97F4A7C15llu + (nSeed << 12) + (nSeed >> 4); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/o3tl/intcmp.hxx b/include/o3tl/intcmp.hxx new file mode 100644 index 0000000000..dbc10d9052 --- /dev/null +++ b/include/o3tl/intcmp.hxx @@ -0,0 +1,132 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <sal/config.h> + +#include <type_traits> +#include <utility> + +#include <o3tl/safeint.hxx> + +namespace o3tl +{ +// An approximation of the C++20 integer comparison functions +// (<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0586r2.html> "Safe integral +// comparisons"), still missing from LLVM 12 libc++: +#if defined __cpp_lib_integer_comparison_functions + +using std::cmp_equal; +using std::cmp_not_equal; +using std::cmp_less; +using std::cmp_greater; +using std::cmp_less_equal; +using std::cmp_greater_equal; + +#else + +template <typename T1, typename T2> constexpr bool cmp_equal(T1 value1, T2 value2) noexcept +{ + // coverity[same_on_both_sides: FALSE] + if constexpr (std::is_signed_v<T1> == std::is_signed_v<T2>) + { + return value1 == value2; + } + else if constexpr (std::is_signed_v<T1>) + { + return value1 >= 0 && o3tl::make_unsigned(value1) == value2; + } + else + { + return value2 >= 0 && value1 == o3tl::make_unsigned(value2); + } +} + +template <typename T1, typename T2> constexpr bool cmp_not_equal(T1 value1, T2 value2) noexcept +{ + return !cmp_equal(value1, value2); +} + +template <typename T1, typename T2> constexpr bool cmp_less(T1 value1, T2 value2) noexcept +{ + if constexpr (std::is_signed_v<T1> == std::is_signed_v<T2>) + { + return value1 < value2; + } + else if constexpr (std::is_signed_v<T1>) + { + return value1 < 0 || o3tl::make_unsigned(value1) < value2; + } + else + { + return value2 >= 0 && value1 < o3tl::make_unsigned(value2); + } +} + +template <typename T1, typename T2> constexpr bool cmp_greater(T1 value1, T2 value2) noexcept +{ + return cmp_less(value2, value1); +} + +template <typename T1, typename T2> constexpr bool cmp_less_equal(T1 value1, T2 value2) noexcept +{ + return !cmp_greater(value1, value2); +} + +template <typename T1, typename T2> constexpr bool cmp_greater_equal(T1 value1, T2 value2) noexcept +{ + return !cmp_less(value1, value2); +} + +#endif + +// A convenient operator syntax around the standard integer comparison functions: +template <typename T> struct IntCmp +{ + explicit constexpr IntCmp(T theValue) + : value(theValue) + { + } + + T value; +}; + +template <typename T1, typename T2> constexpr bool operator==(IntCmp<T1> value1, IntCmp<T2> value2) +{ + return o3tl::cmp_equal(value1.value, value2.value); +} + +template <typename T1, typename T2> constexpr bool operator!=(IntCmp<T1> value1, IntCmp<T2> value2) +{ + return o3tl::cmp_not_equal(value1.value, value2.value); +} + +template <typename T1, typename T2> constexpr bool operator<(IntCmp<T1> value1, IntCmp<T2> value2) +{ + return o3tl::cmp_less(value1.value, value2.value); +} + +template <typename T1, typename T2> constexpr bool operator>(IntCmp<T1> value1, IntCmp<T2> value2) +{ + return o3tl::cmp_greater(value1.value, value2.value); +} + +template <typename T1, typename T2> constexpr bool operator<=(IntCmp<T1> value1, IntCmp<T2> value2) +{ + return o3tl::cmp_less_equal(value1.value, value2.value); +} + +template <typename T1, typename T2> constexpr bool operator>=(IntCmp<T1> value1, IntCmp<T2> value2) +{ + return o3tl::cmp_greater_equal(value1.value, value2.value); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/o3tl/lazy_update.hxx b/include/o3tl/lazy_update.hxx new file mode 100644 index 0000000000..6f9325dfd1 --- /dev/null +++ b/include/o3tl/lazy_update.hxx @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_O3TL_LAZY_UPDATE_HXX +#define INCLUDED_O3TL_LAZY_UPDATE_HXX + +#include <utility> + +namespace o3tl +{ + /** Update output object lazily + + This template collects data in input type, and updates the + output type with the given update functor, but only if the + output is requested. Useful if updating is expensive, or input + changes frequently, but output is only comparatively seldom + used. + + @example + <pre> +LazyUpdate<InType,OutType,decltype(F)> myValue(F); +*myValue = newInput; +myValue->updateInput( this, that, those ); + +output( *myValue ); + </pre> + or + <pre> +output( myValue.getOutValue() ); + </pre> + if the compiler does not recognize the const context. + */ + template<typename In, typename Out, typename Func> class LazyUpdate { + public: + LazyUpdate(Func func): func_(std::move(func)), input_(), dirty_(true) {} + + In const & getInValue() const { return input_; } + + Out const & getOutValue() const { return update(); } + + In & operator *() { + dirty_ = true; + return input_; + } + + In * operator ->() { + dirty_ = true; + return &input_; + } + + Out const & operator *() const { return update(); } + + Out const * operator ->() const { return &update(); } + + private: + Out const & update() const { + if (dirty_) { + output_ = func_(input_); + dirty_ = false; + } + return output_; + } + + Func const func_; + In input_; + mutable Out output_; + mutable bool dirty_; + }; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/o3tl/lru_map.hxx b/include/o3tl/lru_map.hxx new file mode 100644 index 0000000000..7e046fd1db --- /dev/null +++ b/include/o3tl/lru_map.hxx @@ -0,0 +1,297 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +#ifndef INCLUDED_O3TL_LRU_MAP_HXX +#define INCLUDED_O3TL_LRU_MAP_HXX + +#include <cassert> +#include <list> +#include <unordered_map> +#include <cstddef> + +namespace o3tl +{ +namespace detail +{ +// Helper base class to keep total cost for lru_map with custom item size. +// Custom size is specified by the ValueSize functor, the default of each +// item counting as 1 is specified using the void type. +template <class ValueSize> class lru_map_base +{ +public: + // Returns total of ValueSize for all items. + size_t total_size() const { return mCurrentSize; } + +protected: + size_t mCurrentSize = 0; // sum of ValueSize for all items +}; + +// By default cost of each item is 1, so it doesn't need to be tracked. +template <> class lru_map_base<void> +{ +}; +} // namespace + +/** LRU map + * + * Similar to unordered_map (it actually uses it) with additionally functionality + * which removes the entries that have been "least recently used" when the size + * hits the specified capacity. + * + * It only implements the minimal methods needed and the implementation is NOT + * thread safe. + * + * The implementation is as simple as possible but it still uses O(1) complexity + * for most of the operations with a combination unordered map and linked list. + * + * It is optionally possible to specify a function for ValueSize template + * argument (that can be called as 'size_t func(Value)') that will return + * a size (cost) for an item instead of the default size of 1 for each item. + * The size of an item must not change for an item (if needed, re-insert + * the item). A newly inserted item is guaranteed to be in the container, + * even if its size exceeds the maximum size. + * + **/ +template <typename Key, typename Value, class KeyHash = std::hash<Key>, + class KeyEqual = std::equal_to<Key>, class ValueSize = void> +class lru_map final : public detail::lru_map_base<ValueSize> +{ +public: + typedef typename std::pair<Key, Value> key_value_pair_t; + +private: + typedef std::list<key_value_pair_t> list_t; + typedef typename list_t::iterator list_iterator_t; + typedef typename list_t::const_iterator list_const_iterator_t; + + typedef std::unordered_map<Key, list_iterator_t, KeyHash, KeyEqual> map_t; + typedef typename map_t::iterator map_iterator_t; + typedef typename map_t::const_iterator map_const_iterator_t; + + list_t mLruList; + map_t mLruMap; + size_t mMaxSize; + + void addSize(const Value& value) + { + // by default total size is equal to number of items + if constexpr (!std::is_void_v<ValueSize>) + this->mCurrentSize += ValueSize()(value); + } + + void removeSize(const Value& value) + { + // by default total size is equal to number of items + if constexpr (!std::is_void_v<ValueSize>) + { + size_t itemSize = ValueSize()(value); + assert(itemSize <= this->mCurrentSize); + this->mCurrentSize -= itemSize; + } + } + + void removeOldestItem() + { + removeSize(mLruList.back().second); + // remove from map + mLruMap.erase(mLruList.back().first); + // remove from list + mLruList.pop_back(); + } + + void checkLRUItemInsert() + { + if constexpr (std::is_void_v<ValueSize>) + { // One added, so it's enough to remove one, if needed. + if (mLruMap.size() > mMaxSize) + removeOldestItem(); + } + else + { + // This must leave at least one item (it's called from insert). + while (this->mCurrentSize > mMaxSize && mLruList.size() > 1) + removeOldestItem(); + } + } + + void checkLRUItemUpdate() + { + // Item update does not change total size by default. + if constexpr (!std::is_void_v<ValueSize>) + { + // This must leave at least one item (it's called from insert). + while (this->mCurrentSize > mMaxSize && mLruList.size() > 1) + removeOldestItem(); + } + } + + void checkLRUMaxSize() + { + if constexpr (std::is_void_v<ValueSize>) + { + while (mLruMap.size() > mMaxSize) + removeOldestItem(); + } + else + { + while (this->mCurrentSize > mMaxSize) + removeOldestItem(); + } + } + + void clearSize() + { + if constexpr (!std::is_void_v<ValueSize>) + { +#ifdef DBG_UTIL + for (const key_value_pair_t& item : mLruList) + removeSize(item.second); + assert(this->mCurrentSize == 0); +#else + this->mCurrentSize = 0; +#endif + } + } + +public: + typedef list_iterator_t iterator; + typedef list_const_iterator_t const_iterator; + + lru_map(size_t nMaxSize) + : mMaxSize(nMaxSize) + { + assert(mMaxSize > 0); + } + ~lru_map() + { + clearSize(); + // Some code .e.g. SalBitmap likes to remove itself from a cache during it's destructor, which means we + // get calls into lru_map while we are in destruction, so use the swap-and-clear idiom to avoid those problems. + mLruMap.clear(); + list_t().swap(mLruList); + } + + void setMaxSize(size_t nMaxSize) + { + mMaxSize = nMaxSize; + assert(mMaxSize > 0); + checkLRUMaxSize(); + } + + void insert(key_value_pair_t& rPair) + { + map_iterator_t i = mLruMap.find(rPair.first); + + if (i == mLruMap.end()) // doesn't exist -> add to queue and map + { + addSize(rPair.second); + // add to front of the list + mLruList.push_front(rPair); + // add the list position (iterator) to the map + auto it = mLruList.begin(); + mLruMap[it->first] = it; + checkLRUItemInsert(); + } + else // already exists -> replace value + { + // update total cost + removeSize(i->second->second); + addSize(rPair.second); + // replace value + i->second->second = rPair.second; + // bring to front of the lru list + mLruList.splice(mLruList.begin(), mLruList, i->second); + checkLRUItemUpdate(); + } + } + + void insert(key_value_pair_t&& rPair) + { + map_iterator_t i = mLruMap.find(rPair.first); + + if (i == mLruMap.end()) // doesn't exist -> add to list and map + { + addSize(rPair.second); + // add to front of the list + mLruList.push_front(std::move(rPair)); + // add the list position (iterator) to the map + auto it = mLruList.begin(); + mLruMap[it->first] = it; + checkLRUItemInsert(); + } + else // already exists -> replace value + { + removeSize(i->second->second); + addSize(rPair.second); + // replace value + i->second->second = std::move(rPair.second); + // push to back of the lru list + mLruList.splice(mLruList.begin(), mLruList, i->second); + checkLRUItemUpdate(); + } + } + + list_const_iterator_t find(const Key& key) + { + const map_iterator_t i = mLruMap.find(key); + if (i == mLruMap.cend()) // can't find entry for the key + { + // return empty iterator + return mLruList.cend(); + } + else + { + // push to back of the lru list + mLruList.splice(mLruList.begin(), mLruList, i->second); + return i->second; + } + } + + // reverse-iterates the list removing all items matching the predicate + template <class UnaryPredicate> void remove_if(UnaryPredicate pred) + { + auto it = mLruList.rbegin(); + while (it != mLruList.rend()) + { + if (pred(*it)) + { + removeSize(it->second); + mLruMap.erase(it->first); + it = decltype(it){ mLruList.erase(std::next(it).base()) }; + } + else + ++it; + } + } + + list_const_iterator_t begin() const { return mLruList.cbegin(); } + + list_const_iterator_t end() const { return mLruList.cend(); } + + size_t size() const + { + assert(mLruMap.size() == mLruList.size()); + return mLruMap.size(); + } + + // size_t total_size() const; - only if custom ValueSize + + void clear() + { + clearSize(); + mLruMap.clear(); + mLruList.clear(); + } +}; +} + +#endif /* INCLUDED_O3TL_LRU_MAP_HXX */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/o3tl/make_shared.hxx b/include/o3tl/make_shared.hxx new file mode 100644 index 0000000000..5d4d98e42b --- /dev/null +++ b/include/o3tl/make_shared.hxx @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_O3TL_MAKE_SHARED_HXX +#define INCLUDED_O3TL_MAKE_SHARED_HXX + +#include <o3tl/deleter.hxx> +#include <memory> +#include <type_traits> + +namespace o3tl +{ +/** Allocate an array stored in a shared_ptr, calling operator delete[]. + Note that this is only allowed for arithmetic types because shared_ptr + implicitly converts to sub-types. + */ +template <typename T> std::shared_ptr<T> make_shared_array(size_t const size) +{ + static_assert(std::is_arithmetic<T>::value, "only arrays of arithmetic types allowed"); + return std::shared_ptr<T>(new T[size], std::default_delete<T[]>()); +} + +/** To markup std::shared_ptr that coverity warns might throw exceptions + which won't throw in practice, or where std::terminate is + an acceptable response if they do +*/ +template <class T, class... Args> std::shared_ptr<T> make_shared(Args&&... args) +{ +#if defined(__COVERITY__) + return std::shared_ptr<T>(new T(std::forward<Args>(args)...), o3tl::default_delete<T>()); +#else + return std::make_shared<T>(std::forward<Args>(args)...); +#endif +} + +} // namespace o3tl + +#endif // INCLUDED_O3TL_MAKE_SHARED_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/o3tl/numeric.hxx b/include/o3tl/numeric.hxx new file mode 100644 index 0000000000..9980319a64 --- /dev/null +++ b/include/o3tl/numeric.hxx @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_O3TL_NUMERIC_HXX +#define INCLUDED_O3TL_NUMERIC_HXX + +#include <stdexcept> + +namespace o3tl +{ + struct divide_by_zero final : public std::runtime_error + { + explicit divide_by_zero() + : std::runtime_error("divide by zero") + { + } + }; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/o3tl/runtimetooustring.hxx b/include/o3tl/runtimetooustring.hxx new file mode 100644 index 0000000000..6d6f27ac18 --- /dev/null +++ b/include/o3tl/runtimetooustring.hxx @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_O3TL_RUNTIMETOOUSTRING_HXX +#define INCLUDED_O3TL_RUNTIMETOOUSTRING_HXX + +#include <sal/config.h> + +#include <cassert> +#include <cstring> + +#include <rtl/textcvt.h> +#include <rtl/textenc.h> +#include <rtl/ustring.h> +#include <rtl/ustring.hxx> + +namespace o3tl +{ +/** Convert an NTBS from the C++ runtime to an OUString. + + This is used to convert an NTBS as provided by std::exception::what or + std::type_info::name into an OUString in a "lossless" way. The conversion + is done using RTL_TEXTENCODING_ISO_8859_1, so each char in the input maps + to one Unicode character in the output. +*/ +inline OUString runtimeToOUString(char const* runtimeString) +{ + OUString s; + bool ok = rtl_convertStringToUString( + &s.pData, runtimeString, std::strlen(runtimeString), RTL_TEXTENCODING_ISO_8859_1, + (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR)); + assert(ok); + (void)ok; + return s; +} +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/o3tl/safeCoInitUninit.hxx b/include/o3tl/safeCoInitUninit.hxx new file mode 100644 index 0000000000..0ceb4a9746 --- /dev/null +++ b/include/o3tl/safeCoInitUninit.hxx @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#if defined _WIN32 +#include <prewin.h> + +// for CoInitializeEx / CoUninitialize +#include <combaseapi.h> + +#include <postwin.h> + +// for std::abort +#include <cstdlib> + +namespace o3tl +{ +// Helpers for safe calls to CoInitializeEx and CoUninitialize in MSVC +// Indeed if a thread has been already initialized with a concurrency model +// (in LO case COINIT_APARTMENTTHREADED or COINIT_MULTITHREADED) +// CoInitializeEx can't succeed without calling first CoUninitialize +// also, CoUninitialize must be called the number of times CoInitializeEx has been called +inline HRESULT safeCoInitializeEx(DWORD dwCoInit, int& nbReinit) +{ + HRESULT hr; + while ((hr = CoInitializeEx(nullptr, dwCoInit)) == RPC_E_CHANGED_MODE) + { + // so we're in RPC_E_CHANGED_MODE case + // the pb was it was already initialized with a different concurrency model + // close this init + CoUninitialize(); + // and increment counter for dtr part + ++nbReinit; + + // and keep on the loop if there were multi initializations + } + if (FAILED(hr)) + std::abort(); + return hr; +} + +inline void safeCoUninitializeReinit(DWORD dwCoInit, int nbReinit) +{ + CoUninitialize(); + // Put back all the inits, if there were, before the use of the caller to safeCoInitializeEx + for (int i = 0; i < nbReinit; ++i) + CoInitializeEx(nullptr, dwCoInit); +} +} +#endif +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/o3tl/safeint.hxx b/include/o3tl/safeint.hxx new file mode 100644 index 0000000000..a32c6beea1 --- /dev/null +++ b/include/o3tl/safeint.hxx @@ -0,0 +1,236 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <sal/config.h> + +#include <algorithm> +#include <cassert> +#include <limits> +#include <type_traits> + +#if defined(_MSC_VER) +#include <safeint.h> +#else +#ifndef __has_builtin +# define __has_builtin(x) 0 +#endif +#endif + +namespace o3tl +{ + +template <typename T> inline constexpr T saturating_add(T a, T b) +{ + if (b >= 0) { + if (a <= std::numeric_limits<T>::max() - b) { + return a + b; + } else { + return std::numeric_limits<T>::max(); + } + } else { + if (a >= std::numeric_limits<T>::min() - b) { + return a + b; + } else { + return std::numeric_limits<T>::min(); + } + } +} + +template <typename T> inline constexpr T saturating_sub(T a, T b) +{ + if (b >= 0) { + if (a >= std::numeric_limits<T>::min() + b) { + return a - b; + } else { + return std::numeric_limits<T>::min(); + } + } else { + if (a <= std::numeric_limits<T>::max() + b) { + return a - b; + } else { + return std::numeric_limits<T>::max(); + } + } +} + +template<typename T> inline +typename std::enable_if<std::is_signed<T>::value, T>::type saturating_toggle_sign( + T a) +{ + if (a == std::numeric_limits<T>::min()) + return std::numeric_limits<T>::max(); + return a * -1; +} + +#if defined(_MSC_VER) + +template<typename T> inline bool checked_multiply(T a, T b, T& result) +{ + return !msl::utilities::SafeMultiply(a, b, result); +} + +template<typename T> inline bool checked_add(T a, T b, T& result) +{ + return !msl::utilities::SafeAdd(a, b, result); +} + +template<typename T> inline bool checked_sub(T a, T b, T& result) +{ + return !msl::utilities::SafeSubtract(a, b, result); +} + +#elif (defined __GNUC__ && !defined __clang__) || (__has_builtin(__builtin_mul_overflow) && !(defined ANDROID && defined __clang__) && !(defined(__clang__) && (defined(__arm__) || defined(__i386__)))) +// 32-bit clang fails with undefined reference to `__mulodi4' + +template<typename T> inline bool checked_multiply(T a, T b, T& result) +{ + return __builtin_mul_overflow(a, b, &result); +} + +template<typename T> inline bool checked_add(T a, T b, T& result) +{ + return __builtin_add_overflow(a, b, &result); +} + +template<typename T> inline bool checked_sub(T a, T b, T& result) +{ + return __builtin_sub_overflow(a, b, &result); +} + +#else + +//https://www.securecoding.cert.org/confluence/display/c/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow +template<typename T> inline typename std::enable_if<std::is_signed<T>::value, bool>::type checked_multiply(T a, T b, T& result) +{ + if (a > 0) { /* a is positive */ + if (b > 0) { /* a and b are positive */ + if (a > (std::numeric_limits<T>::max() / b)) { + return true; /* Handle error */ + } + } else { /* a positive, b nonpositive */ + if (b < (std::numeric_limits<T>::min() / a)) { + return true; /* Handle error */ + } + } /* a positive, b nonpositive */ + } else { /* a is nonpositive */ + if (b > 0) { /* a is nonpositive, b is positive */ + if (a < (std::numeric_limits<T>::min() / b)) { + return true; /* Handle error */ + } + } else { /* a and b are nonpositive */ + if ( (a != 0) && (b < (std::numeric_limits<T>::max() / a))) { + return true; /* Handle error */ + } + } /* End if a and b are nonpositive */ + } /* End if a is nonpositive */ + + result = a * b; + + return false; +} + +//https://www.securecoding.cert.org/confluence/display/c/INT30-C.+Ensure+that+unsigned+integer+operations+do+not+wrap +template<typename T> inline typename std::enable_if<std::is_unsigned<T>::value, bool>::type checked_multiply(T a, T b, T& result) +{ + if (b && a > std::numeric_limits<T>::max() / b) { + return true;/* Handle error */ + } + + result = a * b; + + return false; +} + +//https://www.securecoding.cert.org/confluence/display/c/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow +template<typename T> inline typename std::enable_if<std::is_signed<T>::value, bool>::type checked_add(T a, T b, T& result) +{ + if (((b > 0) && (a > (std::numeric_limits<T>::max() - b))) || + ((b < 0) && (a < (std::numeric_limits<T>::min() - b)))) { + return true; + } + + result = a + b; + + return false; +} + +//https://www.securecoding.cert.org/confluence/display/c/INT30-C.+Ensure+that+unsigned+integer+operations+do+not+wrap +template<typename T> inline typename std::enable_if<std::is_unsigned<T>::value, bool>::type checked_add(T a, T b, T& result) +{ + if (std::numeric_limits<T>::max() - a < b) { + return true;/* Handle error */ + } + + result = a + b; + + return false; +} + +//https://www.securecoding.cert.org/confluence/display/c/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow +template<typename T> inline typename std::enable_if<std::is_signed<T>::value, bool>::type checked_sub(T a, T b, T& result) +{ + if ((b > 0 && a < std::numeric_limits<T>::min() + b) || + (b < 0 && a > std::numeric_limits<T>::max() + b)) { + return true; + } + + result = a - b; + + return false; +} + +//https://www.securecoding.cert.org/confluence/display/c/INT30-C.+Ensure+that+unsigned+integer+operations+do+not+wrap +template<typename T> inline typename std::enable_if<std::is_unsigned<T>::value, bool>::type checked_sub(T a, T b, T& result) +{ + if (a < b) { + return true; + } + + result = a - b; + + return false; +} + +#endif + +template<typename T> constexpr std::enable_if_t<std::is_signed_v<T>, std::make_unsigned_t<T>> +make_unsigned(T value) +{ + assert(value >= 0); + return value; +} + +template<typename T1, typename T2> constexpr std::enable_if_t<std::is_unsigned_v<T1>, T1> +clamp_to_unsigned(T2 value) { + if constexpr (std::is_unsigned_v<T2>) { + // coverity[result_independent_of_operands] - suppress warning for template + return value <= std::numeric_limits<T1>::max() ? value : std::numeric_limits<T1>::max(); + } else { + static_assert(std::is_signed_v<T2>); + return value < 0 ? 0 : clamp_to_unsigned<T1>(make_unsigned(value)); + } +} + +// An implicit conversion from T2 to T1, useful in places where an explicit conversion from T2 to +// T1 is needed (e.g., in list initialization, if the implicit conversion would be narrowing) but +// tools like -fsanitize=implicit-conversion should still be able to detect truncation: +template<typename T1, typename T2> constexpr T1 narrowing(T2 value) { return value; } + +// std::min wrapped to inform coverity that the result is now deemed sanitized +// coverity[ -taint_source ] +template<typename T> [[nodiscard]] inline T sanitizing_min(T a, T b) +{ + return std::min(a, b); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/o3tl/sorted_vector.hxx b/include/o3tl/sorted_vector.hxx new file mode 100644 index 0000000000..0f31bc5176 --- /dev/null +++ b/include/o3tl/sorted_vector.hxx @@ -0,0 +1,424 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_O3TL_SORTED_VECTOR_HXX +#define INCLUDED_O3TL_SORTED_VECTOR_HXX + +#include <vector> +#include <algorithm> +#include <cassert> +#include <functional> +#include <iterator> +#include <memory> +#include <type_traits> + +namespace o3tl +{ + +// forward declared because it's default template arg for sorted_vector +template<class Value, class Compare> +struct find_unique; + +/** Represents a sorted vector of values. + + @tpl Value class of item to be stored in container + @tpl Compare comparison method + @tpl Find look up index of a Value in the array +*/ +template< + typename Value, + typename Compare = std::less<Value>, + template<typename, typename> class Find = find_unique, + bool = std::is_copy_constructible<Value>::value > +class sorted_vector +{ +private: + typedef Find<Value, Compare> Find_t; + typedef typename std::vector<Value> vector_t; + typedef typename std::vector<Value>::iterator iterator; +public: + typedef typename std::vector<Value>::const_iterator const_iterator; + typedef typename std::vector<Value>::const_reverse_iterator const_reverse_iterator; + typedef typename std::vector<Value>::difference_type difference_type; + typedef typename std::vector<Value>::size_type size_type; + typedef Value value_type; + + constexpr sorted_vector( std::initializer_list<Value> init ) + : m_vector(init) + { + std::sort(m_vector.begin(), m_vector.end(), Compare()); + } + sorted_vector() = default; + sorted_vector(sorted_vector const&) = default; + sorted_vector(sorted_vector&&) = default; + + sorted_vector& operator=(sorted_vector const&) = default; + sorted_vector& operator=(sorted_vector&&) = default; + + // MODIFIERS + + std::pair<const_iterator,bool> insert( Value&& x ) + { + std::pair<const_iterator, bool> const ret(Find_t()(m_vector.begin(), m_vector.end(), x)); + if (!ret.second) + { + const_iterator const it = m_vector.insert(m_vector.begin() + (ret.first - m_vector.begin()), std::move(x)); + return std::make_pair(it, true); + } + return std::make_pair(ret.first, false); + } + + std::pair<const_iterator,bool> insert( const Value& x ) + { + std::pair<const_iterator, bool> const ret(Find_t()(m_vector.begin(), m_vector.end(), x)); + if (!ret.second) + { + const_iterator const it = m_vector.insert(m_vector.begin() + (ret.first - m_vector.begin()), x); + return std::make_pair(it, true); + } + return std::make_pair(ret.first, false); + } + + size_type erase( const Value& x ) + { + std::pair<const_iterator, bool> const ret(Find_t()(m_vector.begin(), m_vector.end(), x)); + if (ret.second) + { + m_vector.erase(m_vector.begin() + (ret.first - m_vector.begin())); + return 1; + } + return 0; + } + + void erase_at(size_t index) + { + m_vector.erase(m_vector.begin() + index); + } + + // like C++ 2011: erase with const_iterator (doesn't change sort order) + const_iterator erase(const_iterator const& position) + { // C++98 has vector::erase(iterator), so call that + return m_vector.erase(m_vector.begin() + (position - m_vector.begin())); + } + + void erase(const_iterator const& first, const_iterator const& last) + { + m_vector.erase(m_vector.begin() + (first - m_vector.begin()), + m_vector.begin() + (last - m_vector.begin())); + } + + /** + * make erase return the removed element, otherwise there is no useful way of extracting a std::unique_ptr + * from this. + */ + Value erase_extract( size_t index ) + { + Value val = std::move(m_vector[index]); + m_vector.erase(m_vector.begin() + index); + return val; + } + + void clear() + { + m_vector.clear(); + } + + void swap(sorted_vector & other) + { + m_vector.swap(other.m_vector); + } + + void reserve(size_type amount) + { + m_vector.reserve(amount); + } + + // ACCESSORS + + size_type size() const + { + return m_vector.size(); + } + + bool empty() const + { + return m_vector.empty(); + } + + // Only return a const iterator, so that the vector cannot be directly updated. + const_iterator begin() const + { + return m_vector.begin(); + } + + // Only return a const iterator, so that the vector cannot be directly updated. + const_iterator end() const + { + return m_vector.end(); + } + + // Only return a const iterator, so that the vector cannot be directly updated. + const_reverse_iterator rbegin() const + { + return m_vector.rbegin(); + } + + // Only return a const iterator, so that the vector cannot be directly updated. + const_reverse_iterator rend() const + { + return m_vector.rend(); + } + + const Value& front() const + { + return m_vector.front(); + } + + const Value& back() const + { + return m_vector.back(); + } + + const Value& operator[]( size_t index ) const + { + return m_vector.operator[]( index ); + } + + // OPERATIONS + + const_iterator lower_bound( const Value& x ) const + { + return std::lower_bound( m_vector.begin(), m_vector.end(), x, Compare() ); + } + + const_iterator upper_bound( const Value& x ) const + { + return std::upper_bound( m_vector.begin(), m_vector.end(), x, Compare() ); + } + + /* Searches the container for an element with a value of x + * and returns an iterator to it if found, otherwise it returns an + * iterator to sorted_vector::end (the element past the end of the container). + * + * Only return a const iterator, so that the vector cannot be directly updated. + */ + const_iterator find( const Value& x ) const + { + std::pair<const_iterator, bool> const ret(Find_t()(m_vector.begin(), m_vector.end(), x)); + return (ret.second) ? ret.first : m_vector.end(); + } + + size_type count(const Value& v) const + { + return find(v) != end() ? 1 : 0; + } + + bool operator==(const sorted_vector & other) const + { + return m_vector == other.m_vector; + } + + bool operator!=(const sorted_vector & other) const + { + return m_vector != other.m_vector; + } + + void insert(sorted_vector<Value,Compare,Find> const& rOther) + { + // optimization for the rather common case that we are overwriting this with the contents + // of another sorted vector + if ( empty() ) + m_vector.insert(m_vector.begin(), rOther.m_vector.begin(), rOther.m_vector.end()); + else + insert_internal( rOther.m_vector ); + } + + void insert_sorted_unique_vector(const std::vector<Value>& rOther) + { + assert( std::is_sorted(rOther.begin(), rOther.end(), Compare())); + assert( std::unique(rOther.begin(), rOther.end(), compare_equal) == rOther.end()); + if ( empty() ) + m_vector.insert(m_vector.begin(), rOther.m_vector.begin(), rOther.m_vector.end()); + else + insert_internal( rOther ); + } + + void insert_sorted_unique_vector(std::vector<Value>&& rOther) + { + assert( std::is_sorted(rOther.begin(), rOther.end(), Compare())); + assert( std::unique(rOther.begin(), rOther.end(), compare_equal) == rOther.end()); + if ( empty() ) + m_vector.swap( rOther ); + else + insert_internal( rOther ); + } + + /* Clear() elements in the vector, and free them one by one. */ + void DeleteAndDestroyAll() + { + for (const_iterator it = m_vector.begin(); it != m_vector.end(); ++it) + { + delete *it; + } + + clear(); + } + + // fdo#58793: some existing code in Writer (SwpHintsArray) + // routinely modifies the members of the vector in a way that + // violates the sort order, and then re-sorts the array. + // This is a kludge to enable that code to work. + // If you are calling this function, you are Doing It Wrong! + void Resort() + { + std::stable_sort(m_vector.begin(), m_vector.end(), Compare()); + } + +private: + static bool compare_equal( const Value& v1, const Value& v2 ) + { // Synthetize == check from < check for std::unique asserts above. + return !Compare()( v1, v2 ) && !Compare()( v2, v1 ); + } + + void insert_internal( const std::vector<Value>& rOther ) + { + // Do a union in one pass rather than repeated insert() that could repeatedly + // move large amounts of data. + vector_t tmp; + tmp.reserve( m_vector.size() + rOther.size()); + std::set_union( m_vector.begin(), m_vector.end(), + rOther.begin(), rOther.end(), + std::back_inserter( tmp ), Compare()); + m_vector.swap( tmp ); + } + + vector_t m_vector; +}; + +/* Specialise the template for cases like Value = std::unique_ptr<T>, where + MSVC2017 needs some help +*/ +template< + typename Value, + typename Compare, + template<typename, typename> class Find > +class sorted_vector<Value,Compare,Find,false> : public sorted_vector<Value, Compare, Find, true> +{ +public: + using sorted_vector<Value, Compare, Find, true>::sorted_vector; + typedef sorted_vector<Value, Compare, Find, true> super_sorted_vector; + + sorted_vector(sorted_vector const&) = delete; + sorted_vector& operator=(sorted_vector const&) = delete; + + sorted_vector() = default; + sorted_vector(sorted_vector&&) = default; + sorted_vector& operator=(sorted_vector&&) = default; + + /** + * implement find for sorted_vectors containing std::unique_ptr + */ + typename super_sorted_vector::const_iterator find( typename Value::element_type const * x ) const + { + Value tmp(const_cast<typename Value::element_type*>(x)); + auto ret = super_sorted_vector::find(tmp); + // coverity[ resource_leak : FALSE] - this is only a pretend unique_ptr, to avoid allocating a temporary + tmp.release(); + return ret; + } + /** + * implement upper_bound for sorted_vectors containing std::unique_ptr + */ + typename super_sorted_vector::const_iterator upper_bound( typename Value::element_type const * x ) const + { + Value tmp(const_cast<typename Value::element_type*>(x)); + auto ret = super_sorted_vector::upper_bound(tmp); + // coverity[ resource_leak : FALSE] - this is only a pretend unique_ptr, to avoid allocating a temporary + tmp.release(); + return ret; + } + /** + * implement lower_bound for sorted_vectors containing std::unique_ptr + */ + typename super_sorted_vector::const_iterator lower_bound( typename Value::element_type const * x ) const + { + Value tmp(const_cast<typename Value::element_type*>(x)); + auto ret = super_sorted_vector::lower_bound(tmp); + // coverity[ resource_leak : FALSE] - this is only a pretend unique_ptr, to avoid allocating a temporary + tmp.release(); + return ret; + } +}; + + +/** Implements an ordering function over a pointer, where the comparison uses the < operator on the pointed-to types. + Very useful for the cases where we put pointers to objects inside a sorted_vector. +*/ +template <class T> struct less_ptr_to +{ + bool operator() ( T* const& lhs, T* const& rhs ) const + { + return (*lhs) < (*rhs); + } +}; + +template <class T> struct less_uniqueptr_to +{ + bool operator() ( std::unique_ptr<T> const& lhs, std::unique_ptr<T> const& rhs ) const + { + return (*lhs) < (*rhs); + } +}; + +/** the elements are totally ordered by Compare, + for no 2 elements !Compare(a,b) && !Compare(b,a) is true + */ +template<class Value, class Compare> +struct find_unique +{ + typedef typename sorted_vector<Value, Compare, + o3tl::find_unique> ::const_iterator const_iterator; + std::pair<const_iterator, bool> operator()( + const_iterator first, const_iterator last, + Value const& v) + { + const_iterator const it = std::lower_bound(first, last, v, Compare()); + return std::make_pair(it, (it != last && !Compare()(v, *it))); + } +}; + +/** the elements are partially ordered by Compare, + 2 elements are allowed if they are not the same element (pointer equal) + */ +template<class Value, class Compare> +struct find_partialorder_ptrequals +{ + typedef typename sorted_vector<Value, Compare, + o3tl::find_partialorder_ptrequals>::const_iterator const_iterator; + std::pair<const_iterator, bool> operator()( + const_iterator first, const_iterator last, + Value const& v) + { + std::pair<const_iterator, const_iterator> const its = + std::equal_range(first, last, v, Compare()); + for (const_iterator it = its.first; it != its.second; ++it) + { + if (v == *it) + { + return std::make_pair(it, true); + } + } + return std::make_pair(its.first, false); + } +}; + +} // namespace o3tl +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/o3tl/sprintf.hxx b/include/o3tl/sprintf.hxx new file mode 100644 index 0000000000..373fff744c --- /dev/null +++ b/include/o3tl/sprintf.hxx @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <sal/config.h> + +#include <cassert> +#include <cstddef> +#include <cstdio> +#include <utility> + +#include <o3tl/safeint.hxx> + +namespace o3tl +{ +// A drop-in replacement for safe uses of std::sprintf where it is statically known that the +// provided buffer is large enough. Compared to a plain use of std::sprintf, using o3tl::sprintf +// for one makes it explicit that the call is considered safe and for another avoids deprecation +// warnings on platforms like the macOS 13 SDK that mark std::sprintf as deprecated. Many simple +// uses of std::sprintf across the code base can be replaced with alternative code using e.g. +// OString::number. This is for the remaining formatting-rich cases for which there is no easy +// replacement yet in our C++17 baseline. Ultimately, it should be removed again once alternatives +// for those remaining cases, like C++20 std::format, are available. +template <std::size_t N, typename... T> +int sprintf(char (&s)[N], char const* format, T&&... arguments) +{ + auto const n = std::snprintf(s, N, format, std::forward<T>(arguments)...); + assert(n < 0 || o3tl::make_unsigned(n) < N); + (void)n; + return n; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/o3tl/string_view.hxx b/include/o3tl/string_view.hxx new file mode 100644 index 0000000000..1e5db5eb02 --- /dev/null +++ b/include/o3tl/string_view.hxx @@ -0,0 +1,558 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <sal/config.h> + +#include <cassert> +#include <cstddef> +#include <string> +#include <string_view> + +#include <o3tl/intcmp.hxx> +#include <rtl/character.hxx> +#include <rtl/ustring.h> +#include <rtl/math.h> +#include <sal/types.h> + +namespace o3tl +{ +// Like OUString::equalsAscii/OUString::equalsAsciiL, but for std::u16string_view: +inline bool equalsAscii(std::u16string_view s1, std::string_view s2) +{ + return s1.size() == s2.size() + && rtl_ustr_ascii_shortenedCompare_WithLength(s1.data(), s1.size(), s2.data(), s2.size()) + == 0; +} + +// Like OUString::compareToAscii, but for std::u16string_view and std::string_view: +inline int compareToAscii(std::u16string_view s1, std::string_view s2) +{ + return rtl_ustr_asciil_reverseCompare_WithLength(s1.data(), s1.size(), s2.data(), s2.size()); +}; + +// Like OUString::equalsIgnoreAsciiCase, but for two std::u16string_view: +inline bool equalsIgnoreAsciiCase(std::u16string_view s1, std::u16string_view s2) +{ + if (s1.size() != s2.size()) + return false; + if (s1.data() == s2.data()) + return true; + return rtl_ustr_compareIgnoreAsciiCase_WithLength(s1.data(), s1.size(), s2.data(), s2.size()) + == 0; +}; + +inline bool equalsIgnoreAsciiCase(std::u16string_view s1, std::string_view s2) +{ + return s1.size() == s2.size() + && (rtl_ustr_ascii_shortenedCompareIgnoreAsciiCase_WithLength(s1.data(), s1.size(), + s2.data(), s2.size()) + == 0); +} + +inline bool equalsIgnoreAsciiCase(std::string_view s1, std::string_view s2) +{ + if (s1.size() != s2.size()) + return false; + if (s1.data() == s2.data()) + return true; + return rtl_str_compareIgnoreAsciiCase_WithLength(s1.data(), s1.size(), s2.data(), s2.size()) + == 0; +}; + +// Like OUString::compareToIgnoreAsciiCase, but for two std::u16string_view: +inline int compareToIgnoreAsciiCase(std::u16string_view s1, std::u16string_view s2) +{ + return rtl_ustr_compareIgnoreAsciiCase_WithLength(s1.data(), s1.size(), s2.data(), s2.size()); +}; + +// Like OUString::matchIgnoreAsciiCase, but for two std::u16string_view: +inline bool matchIgnoreAsciiCase(std::u16string_view s1, std::u16string_view s2, + sal_Int32 fromIndex = 0) +{ + return rtl_ustr_shortenedCompareIgnoreAsciiCase_WithLength( + s1.data() + fromIndex, s1.size() - fromIndex, s2.data(), s2.size(), s2.size()) + == 0; +} + +// Like OUString::matchIgnoreAsciiCase, but for std::u16string_view and std::string_view: +inline bool matchIgnoreAsciiCase(std::u16string_view s1, std::string_view s2, + sal_Int32 fromIndex = 0) +{ + return rtl_ustr_ascii_shortenedCompareIgnoreAsciiCase_WithLength( + s1.data() + fromIndex, s1.size() - fromIndex, s2.data(), s2.size()) + == 0; +} + +// Like OUString::endsWithIgnoreAsciiCase, but for std::u16string_view +inline bool endsWithIgnoreAsciiCase(std::u16string_view s1, std::u16string_view s2, + std::u16string_view* rest = nullptr) +{ + auto const b = s2.size() <= s1.size() && matchIgnoreAsciiCase(s1, s2, s1.size() - s2.size()); + if (b && rest != nullptr) + { + *rest = s1.substr(0, s1.size() - s2.size()); + } + return b; +} + +inline bool endsWithIgnoreAsciiCase(std::u16string_view s1, std::string_view s2, + std::u16string_view* rest = nullptr) +{ + auto const b = s2.size() <= s1.size() + && rtl_ustr_ascii_compareIgnoreAsciiCase_WithLengths( + s1.data() + s1.size() - s2.size(), s2.size(), s2.data(), s2.size()) + == 0; + if (b && rest != nullptr) + { + *rest = s1.substr(0, s1.size() - s2.size()); + } + return b; +} + +// Similar to O[U]String::getToken, returning the first token of a std::[u16]string_view starting +// at a given position. +// +// Attention: There are two sets of o3tl::getToken overloads here. This first set has an interface +// based on std::size_t length parameters, and its semantics don't match those of +// O[U]String::getToken exactly (buf if needed, it can be extended to return the n'th token instead +// of just the first, and/or support an initial position of npos, to make the semantics match). +template <typename charT, typename traits = std::char_traits<charT>> +inline std::basic_string_view<charT, traits> getToken(std::basic_string_view<charT, traits> sv, + charT delimiter, std::size_t& position) +{ + assert(position <= sv.size()); + auto const n = sv.find(delimiter, position); + std::basic_string_view<charT, traits> t; + if (n == std::string_view::npos) + { + t = sv.substr(position); + position = std::string_view::npos; + } + else + { + t = sv.substr(position, n - position); + position = n + 1; + } + return t; +} +// The following two overloads prevent overload resolution mistakes that would occur with their +// template counterpart, when sv is of a type that is implicitly convertible to basic_string_view +// (like OString or OUString), in which case overload resolution would erroneously choose the +// three-argument overloads (taking sv, nToken, cTok) from the second set of +// o3tl::getToken overloads below: +inline std::string_view getToken(std::string_view sv, char delimiter, std::size_t& position) +{ + return getToken<char>(sv, delimiter, position); +} +inline std::u16string_view getToken(std::u16string_view sv, char16_t delimiter, + std::size_t& position) +{ + return getToken<char16_t>(sv, delimiter, position); +} + +// Similar to O[U]String::getToken. +// +// Attention: There are two sets of o3tl::getToken overloads here. This second set has an +// interface based on sal_Int32 length parameters, and is meant to be a drop-in replacement for +// O[U]String::getToken. +template <typename charT, typename traits = std::char_traits<charT>> +inline std::basic_string_view<charT, traits> getToken(std::basic_string_view<charT, traits> pStr, + sal_Int32 nToken, charT cTok, + sal_Int32& rnIndex) +{ + assert(o3tl::IntCmp(rnIndex) <= o3tl::IntCmp(pStr.size())); + + // Return an empty string and set rnIndex to -1 if either nToken or rnIndex is + // negative: + if (rnIndex >= 0 && nToken >= 0) + { + const charT* pOrgCharStr = pStr.data(); + const charT* pCharStr = pOrgCharStr + rnIndex; + sal_Int32 nLen = pStr.size() - rnIndex; + sal_Int32 nTokCount = 0; + const charT* pCharStrStart = pCharStr; + while (nLen > 0) + { + if (*pCharStr == cTok) + { + nTokCount++; + + if (nTokCount > nToken) + break; + if (nTokCount == nToken) + pCharStrStart = pCharStr + 1; + } + + pCharStr++; + nLen--; + } + if (nTokCount >= nToken) + { + if (nLen > 0) + rnIndex = pCharStr - pOrgCharStr + 1; + else + rnIndex = -1; + return std::basic_string_view<charT, traits>(pCharStrStart, pCharStr - pCharStrStart); + } + } + + rnIndex = -1; + return std::basic_string_view<charT, traits>(); +} +// The following two overloads prevent deduction failures that would occur with their template +// counterpart, when sv is of a type that is implicitly convertible to basic_string_view (like +// OString or OUString): +inline std::string_view getToken(std::string_view sv, sal_Int32 nToken, char cTok, + sal_Int32& rnIndex) +{ + return getToken<char>(sv, nToken, cTok, rnIndex); +} +inline std::u16string_view getToken(std::u16string_view sv, sal_Int32 nToken, char16_t cTok, + sal_Int32& rnIndex) +{ + return getToken<char16_t>(sv, nToken, cTok, rnIndex); +} +inline std::string_view getToken(std::string_view sv, sal_Int32 nToken, char cTok) +{ + sal_Int32 nIndex = 0; + return getToken<char>(sv, nToken, cTok, nIndex); +} +inline std::u16string_view getToken(std::u16string_view sv, sal_Int32 nToken, char16_t cTok) +{ + sal_Int32 nIndex = 0; + return getToken<char16_t>(sv, nToken, cTok, nIndex); +} + +// Implementations of C++20 std::basic_string_view::starts_with and +// std::basic_string_view::ends_with, until we can use those directly on all platforms: +template <typename charT, typename traits = std::char_traits<charT>> +constexpr bool starts_with(std::basic_string_view<charT, traits> sv, + std::basic_string_view<charT, traits> x) noexcept +{ +#if defined __cpp_lib_starts_ends_with + return sv.starts_with(x); +#else + return sv.substr(0, x.size()) == x; +#endif +} +template <typename charT, typename traits = std::char_traits<charT>> +constexpr bool starts_with(std::basic_string_view<charT, traits> sv, charT x) noexcept +{ +#if defined __cpp_lib_starts_ends_with + return sv.starts_with(x); +#else + return !sv.empty() && traits::eq(sv.front(), x); +#endif +} +template <typename charT, typename traits = std::char_traits<charT>> +constexpr bool starts_with(std::basic_string_view<charT, traits> sv, charT const* x) +{ +#if defined __cpp_lib_starts_ends_with + return sv.starts_with(x); +#else + return starts_with(sv, std::basic_string_view<charT, traits>(x)); +#endif +} +template <typename charT, typename traits = std::char_traits<charT>> +constexpr bool ends_with(std::basic_string_view<charT, traits> sv, + std::basic_string_view<charT, traits> x) noexcept +{ +#if defined __cpp_lib_starts_ends_with + return sv.ends_with(x); +#else + return sv.size() >= x.size() + && sv.compare(sv.size() - x.size(), std::basic_string_view<charT, traits>::npos, x) == 0; +#endif +} +template <typename charT, typename traits = std::char_traits<charT>> +constexpr bool ends_with(std::basic_string_view<charT, traits> sv, charT x) noexcept +{ +#if defined __cpp_lib_starts_ends_with + return sv.ends_with(x); +#else + return !sv.empty() && traits::eq(sv.back(), x); +#endif +} +template <typename charT, typename traits = std::char_traits<charT>> +constexpr bool ends_with(std::basic_string_view<charT, traits> sv, charT const* x) +{ +#if defined __cpp_lib_starts_ends_with + return sv.ends_with(x); +#else + return ends_with(sv, std::basic_string_view<charT, traits>(x)); +#endif +} +// The following overloads prevent deduction failures that would occur with their template +// counterparts, when x is of a type that is implicitly convertible to basic_string_view (like +// OString or OUString, and we only bother to provide overloads for the char and char16_t cases, not +// also for char32_t and wchar_t, nor for C++20 char8_t): +constexpr bool starts_with(std::string_view sv, std::string_view x) noexcept +{ + return starts_with<char>(sv, x); +} +constexpr bool starts_with(std::u16string_view sv, std::u16string_view x) noexcept +{ + return starts_with<char16_t>(sv, x); +} +constexpr bool ends_with(std::string_view sv, std::string_view x) noexcept +{ + return ends_with<char>(sv, x); +} +constexpr bool ends_with(std::u16string_view sv, std::u16string_view x) noexcept +{ + return ends_with<char16_t>(sv, x); +} + +// Variants of C++20 std::basic_string_view::starts_with and +// std::basic_string_view::ends_with that have a rest out parameter, similar to our OString and +// OUString startsWith and endsWith member functions: +template <typename charT, typename traits = std::char_traits<charT>> +constexpr bool starts_with(std::basic_string_view<charT, traits> sv, + std::basic_string_view<charT, traits> x, + std::basic_string_view<charT, traits>* rest) noexcept +{ + assert(rest != nullptr); + auto const found = starts_with(sv, x); + if (found) + { + *rest = sv.substr(x.length()); + } + return found; +} +template <typename charT, typename traits = std::char_traits<charT>> +constexpr bool starts_with(std::basic_string_view<charT, traits> sv, charT x, + std::basic_string_view<charT, traits>* rest) noexcept +{ + assert(rest != nullptr); + auto const found = starts_with(sv, x); + if (found) + { + *rest = sv.substr(1); + } + return found; +} +template <typename charT, typename traits = std::char_traits<charT>> +constexpr bool starts_with(std::basic_string_view<charT, traits> sv, charT const* x, + std::basic_string_view<charT, traits>* rest) +{ + assert(rest != nullptr); + auto const found = starts_with(sv, x); + if (found) + { + *rest = sv.substr(traits::length(x)); + } + return found; +} +template <typename charT, typename traits = std::char_traits<charT>> +constexpr bool ends_with(std::basic_string_view<charT, traits> sv, + std::basic_string_view<charT, traits> x, + std::basic_string_view<charT, traits>* rest) noexcept +{ + assert(rest != nullptr); + auto const found = ends_with(sv, x); + if (found) + { + *rest = sv.substr(0, sv.length() - x.length()); + } + return found; +} +template <typename charT, typename traits = std::char_traits<charT>> +constexpr bool ends_with(std::basic_string_view<charT, traits> sv, charT x, + std::basic_string_view<charT, traits>* rest) noexcept +{ + assert(rest != nullptr); + auto const found = ends_with(sv, x); + if (found) + { + *rest = sv.substr(0, sv.length() - 1); + } + return found; +} +template <typename charT, typename traits = std::char_traits<charT>> +constexpr bool ends_with(std::basic_string_view<charT, traits> sv, charT const* x, + std::basic_string_view<charT, traits>* rest) +{ + assert(rest != nullptr); + auto const found = ends_with(sv, x); + if (found) + { + *rest = sv.substr(0, sv.length() - traits::length(x)); + } + return found; +} +// The following overloads prevent deduction failures that would occur with their template +// counterparts, when x is of a type that is implicitly convertible to basic_string_view (like +// OString or OUString, and we only bother to provide overloads for the char and char16_t cases, not +// also for char32_t and wchar_t, nor for C++20 char8_t): +constexpr bool starts_with(std::string_view sv, std::string_view x, std::string_view* rest) noexcept +{ + return starts_with<char>(sv, x, rest); +} +constexpr bool starts_with(std::u16string_view sv, std::u16string_view x, + std::u16string_view* rest) noexcept +{ + return starts_with<char16_t>(sv, x, rest); +} +constexpr bool ends_with(std::string_view sv, std::string_view x, std::string_view* rest) noexcept +{ + return ends_with<char>(sv, x, rest); +} +constexpr bool ends_with(std::u16string_view sv, std::u16string_view x, + std::u16string_view* rest) noexcept +{ + return ends_with<char16_t>(sv, x, rest); +} + +namespace internal +{ +inline bool implIsWhitespace(sal_Unicode c) +{ + /* Space or Control character? */ + if ((c <= 32) && c) + return true; + + /* Only in the General Punctuation area Space or Control characters are included? */ + if ((c < 0x2000) || (c > 0x2029)) + return false; + + if ((c <= 0x200B) || /* U+2000 - U+200B All Spaces */ + (c >= 0x2028)) /* U+2028 LINE SEPARATOR, U+2029 PARAGRAPH SEPARATOR */ + return true; + + return false; +} +} // namespace internal + +// Like OUString::trim, but for std::[u16]string_view: +template <typename charT, typename traits = std::char_traits<charT>> +std::basic_string_view<charT, traits> trim(std::basic_string_view<charT, traits> str) +{ + auto pFirst = str.data(); + auto pLast = pFirst + str.size(); + + while ((pFirst < pLast) && internal::implIsWhitespace(*pFirst)) + ++pFirst; + + if (pFirst == pLast) + return {}; + + do + --pLast; + while (internal::implIsWhitespace(*pLast)); + + return std::basic_string_view<charT, traits>(pFirst, pLast - pFirst + 1); +} + +// "deduction guides" + +inline auto trim(std::string_view str) { return trim<>(str); } +inline auto trim(std::u16string_view str) { return trim<>(str); } + +// Like OString::toInt32, but for std::string_view: +inline sal_Int32 toInt32(std::u16string_view str, sal_Int16 radix = 10) +{ + sal_Int64 n = rtl_ustr_toInt64_WithLength(str.data(), radix, str.size()); + if (n < SAL_MIN_INT32 || n > SAL_MAX_INT32) + n = 0; + return n; +} +inline sal_Int32 toInt32(std::string_view str, sal_Int16 radix = 10) +{ + sal_Int64 n = rtl_str_toInt64_WithLength(str.data(), radix, str.size()); + if (n < SAL_MIN_INT32 || n > SAL_MAX_INT32) + n = 0; + return n; +} + +// Like OString::toUInt32, but for std::string_view: +inline sal_uInt32 toUInt32(std::u16string_view str, sal_Int16 radix = 10) +{ + sal_Int64 n = rtl_ustr_toInt64_WithLength(str.data(), radix, str.size()); + if (n < 0 || n > SAL_MAX_UINT32) + n = 0; + return n; +} +inline sal_uInt32 toUInt32(std::string_view str, sal_Int16 radix = 10) +{ + sal_Int64 n = rtl_str_toInt64_WithLength(str.data(), radix, str.size()); + if (n < 0 || n > SAL_MAX_UINT32) + n = 0; + return n; +} + +// Like OString::toInt64, but for std::string_view: +inline sal_Int64 toInt64(std::u16string_view str, sal_Int16 radix = 10) +{ + return rtl_ustr_toInt64_WithLength(str.data(), radix, str.size()); +} +inline sal_Int64 toInt64(std::string_view str, sal_Int16 radix = 10) +{ + return rtl_str_toInt64_WithLength(str.data(), radix, str.size()); +} + +// Like OString::toDouble, but for std::string_view: +inline double toDouble(std::u16string_view str) +{ + return rtl_math_uStringToDouble(str.data(), str.data() + str.size(), '.', 0, nullptr, nullptr); +} +inline double toDouble(std::string_view str) +{ + return rtl_math_stringToDouble(str.data(), str.data() + str.size(), '.', 0, nullptr, nullptr); +} + +// Like OUString::iterateCodePoints, but for std::string_view: +inline sal_uInt32 iterateCodePoints(std::u16string_view string, sal_Int32* indexUtf16, + sal_Int32 incrementCodePoints = 1) +{ + std::size_t n; + char16_t cu; + sal_uInt32 cp; + assert(indexUtf16 != nullptr); + n = *indexUtf16; + assert(n <= string.length()); + while (incrementCodePoints < 0) + { + assert(n > 0); + cu = string[--n]; + if (rtl::isLowSurrogate(cu) && n != 0 && rtl::isHighSurrogate(string[n - 1])) + { + --n; + } + ++incrementCodePoints; + } + assert(n < string.length()); + cu = string[n]; + if (rtl::isHighSurrogate(cu) && string.length() - n >= 2 && rtl::isLowSurrogate(string[n + 1])) + { + cp = rtl::combineSurrogates(cu, string[n + 1]); + } + else + { + cp = cu; + } + while (incrementCodePoints > 0) + { + assert(n < string.length()); + cu = string[n++]; + if (rtl::isHighSurrogate(cu) && n != string.length() && rtl::isLowSurrogate(string[n])) + { + ++n; + } + --incrementCodePoints; + } + assert(n <= string.length()); + *indexUtf16 = n; + return cp; +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/o3tl/strong_int.hxx b/include/o3tl/strong_int.hxx new file mode 100644 index 0000000000..8210795589 --- /dev/null +++ b/include/o3tl/strong_int.hxx @@ -0,0 +1,156 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_O3TL_STRONG_INT_HXX +#define INCLUDED_O3TL_STRONG_INT_HXX + +#include <sal/config.h> +#include <limits> +#include <cassert> +#include <type_traits> + +namespace o3tl +{ + +#if !defined __COVERITY__ + +namespace detail { + +template<typename T1, typename T2> constexpr +typename std::enable_if< + std::is_signed<T1>::value && std::is_signed<T2>::value, bool>::type +isInRange(T2 value) { + return value >= std::numeric_limits<T1>::min() + && value <= std::numeric_limits<T1>::max(); +} + +template<typename T1, typename T2> constexpr +typename std::enable_if< + std::is_signed<T1>::value && std::is_unsigned<T2>::value, bool>::type +isInRange(T2 value) { + return value + <= static_cast<typename std::make_unsigned<T1>::type>( + std::numeric_limits<T1>::max()); +} + +template<typename T1, typename T2> constexpr +typename std::enable_if< + std::is_unsigned<T1>::value && std::is_signed<T2>::value, bool>::type +isInRange(T2 value) { + return value >= 0 + && (static_cast<typename std::make_unsigned<T2>::type>(value) + <= std::numeric_limits<T1>::max()); +} + +template<typename T1, typename T2> constexpr +typename std::enable_if< + std::is_unsigned<T1>::value && std::is_unsigned<T2>::value, bool>::type +isInRange(T2 value) { + return value <= std::numeric_limits<T1>::max(); +} + +} + +#endif + +/// +/// Wrap up an integer type so that we prevent accidental conversion to other integer types. +/// +/// e.g. +/// typedef o3tl::strong_int<unsigned, struct MyIntTag> MyInt; +/// +/// \param UNDERLYING_TYPE the underlying scalar type +/// \param PHANTOM_TYPE a type tag, used to distinguish this instantiation of the template +/// from other instantiations with the same UNDERLYING_TYPE. +/// +template <typename UNDERLYING_TYPE, typename PHANTOM_TYPE> +struct strong_int +{ +public: + template<typename T> explicit constexpr strong_int( + T value, + typename std::enable_if<std::is_integral<T>::value, int>::type = 0): + m_value(value) + { +#if !defined __COVERITY__ + // catch attempts to pass in out-of-range values early + assert(detail::isInRange<UNDERLYING_TYPE>(value) + && "out of range"); +#endif + } + strong_int() : m_value(0) {} + + explicit constexpr operator UNDERLYING_TYPE() const { return m_value; } + explicit operator bool() const { return m_value != 0; } + UNDERLYING_TYPE get() const { return m_value; } + + bool operator<(strong_int const & other) const { return m_value < other.m_value; } + bool operator<=(strong_int const & other) const { return m_value <= other.m_value; } + bool operator>(strong_int const & other) const { return m_value > other.m_value; } + bool operator>=(strong_int const & other) const { return m_value >= other.m_value; } + bool operator==(strong_int const & other) const { return m_value == other.m_value; } + bool operator!=(strong_int const & other) const { return m_value != other.m_value; } + strong_int& operator++() { ++m_value; return *this; } + strong_int operator++(int) { UNDERLYING_TYPE nOldValue = m_value; ++m_value; return strong_int(nOldValue); } + strong_int& operator--() { --m_value; return *this; } + strong_int operator--(int) { UNDERLYING_TYPE nOldValue = m_value; --m_value; return strong_int(nOldValue); } + strong_int& operator+=(strong_int const & other) { m_value += other.m_value; return *this; } + strong_int& operator-=(strong_int const & other) { m_value -= other.m_value; return *this; } + strong_int& operator%=(strong_int const & other) { m_value %= other.m_value; return *this; } + strong_int& operator*=(strong_int const & other) { m_value *= other.m_value; return *this; } + strong_int& operator/=(strong_int const & other) { m_value /= other.m_value; return *this; } + [[nodiscard]] + strong_int operator%(strong_int const & other) const { return strong_int(m_value % other.m_value); } + [[nodiscard]] + strong_int operator-() const { return strong_int(-m_value); } + [[nodiscard]] + strong_int operator*(strong_int const & other) const { return strong_int(m_value * other.m_value); } + [[nodiscard]] + strong_int operator/(strong_int const & other) const { return strong_int(m_value / other.m_value); } + + bool anyOf(strong_int v) const { + return *this == v; + } + + template<typename... Args> + bool anyOf(strong_int first, Args... args) const { + return *this == first || anyOf(args...); + } + +private: + UNDERLYING_TYPE m_value; +}; + +template <typename UT, typename PT> +strong_int<UT,PT> operator+(strong_int<UT,PT> const & lhs, strong_int<UT,PT> const & rhs) +{ + return strong_int<UT,PT>(lhs.get() + rhs.get()); +} + +template <typename UT, typename PT> +strong_int<UT,PT> operator-(strong_int<UT,PT> const & lhs, strong_int<UT,PT> const & rhs) +{ + return strong_int<UT,PT>(lhs.get() - rhs.get()); +} + +}; // namespace o3tl + +#endif /* INCLUDED_O3TL_STRONG_INT_HXX */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/o3tl/temporary.hxx b/include/o3tl/temporary.hxx new file mode 100644 index 0000000000..50d006e26d --- /dev/null +++ b/include/o3tl/temporary.hxx @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_O3TL_TEMPORARY_HXX +#define INCLUDED_O3TL_TEMPORARY_HXX + +#include <sal/config.h> + +namespace o3tl +{ +// Cast an rvalue to an lvalue. Can be useful if a function parameter is a pointer/reference to T, +// and some call site doesn't need the value beyond the call itself (e.g., in a call like +// std::modf(x, &o3tl::temporary(double())) to obtain the fractional part of x, ignoring the +// integral part). +template <typename T> constexpr T& temporary(T&& x) { return static_cast<T&>(x); } +template <typename T> constexpr T& temporary(T&) = delete; +} + +#endif /* INCLUDED_O3TL_TEMPORARY_HXX */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/o3tl/typed_flags_set.hxx b/include/o3tl/typed_flags_set.hxx new file mode 100644 index 0000000000..413ee9579d --- /dev/null +++ b/include/o3tl/typed_flags_set.hxx @@ -0,0 +1,325 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_O3TL_TYPED_FLAGS_SET_HXX +#define INCLUDED_O3TL_TYPED_FLAGS_SET_HXX + +#include <sal/config.h> + +#include <cassert> +#include <type_traits> + +#include <o3tl/underlyingenumvalue.hxx> + +namespace o3tl { + +namespace detail { + +template<typename T> constexpr +typename std::enable_if<std::is_signed<T>::value, bool>::type isNonNegative( + T value) +{ + return value >= 0; +} + +template<typename T> constexpr +typename std::enable_if<std::is_unsigned<T>::value, bool>::type isNonNegative(T) +{ + return true; +} + +} + +template<typename T> struct typed_flags {}; + +/// Mark a (scoped) enumeration as a set of bit flags, with accompanying +/// operations. +/// +/// template<> +/// struct o3tl::typed_flags<TheE>: o3tl::is_typed_flags<TheE, TheM> {}; +/// +/// All relevant values must be non-negative. (Typically, the enumeration's +/// underlying type will either be fixed and unsigned, or it will be unfixed--- +/// and can thus default to a signed type---and all enumerators will have non- +/// negative values.) +/// +/// \param E the enumeration type. +/// \param M the all-bits-set value for the bit flags. +template<typename E, typename std::underlying_type<E>::type M> +struct is_typed_flags { + static_assert( + M >= 0, "is_typed_flags expects only non-negative bit values"); + + typedef E Self; + + class Wrap { + public: + typedef is_typed_flags Unwrapped; + + explicit constexpr Wrap(typename std::underlying_type<E>::type value): + value_(value) + { + assert(detail::isNonNegative(value)); + assert( + static_cast<typename std::underlying_type<E>::type>(~0) == M + // avoid "operands don't affect result" warnings when M + // covers all bits of the underlying type + || (value & ~M) == 0); + } + + constexpr operator E() const { return static_cast<E>(value_); } + + explicit constexpr operator typename std::underlying_type<E>::type() + const + { return value_; } + + explicit constexpr operator bool() const { return value_ != 0; } + + private: + typename std::underlying_type<E>::type value_; + }; + + static typename std::underlying_type<E>::type const mask = M; +}; + +} + +template<typename E> +constexpr typename o3tl::typed_flags<E>::Wrap operator ~(E rhs) { + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(rhs))); + return static_cast<typename o3tl::typed_flags<E>::Wrap>( + o3tl::typed_flags<E>::mask + & ~o3tl::to_underlying(rhs)); +} + +template<typename E> constexpr typename o3tl::typed_flags<E>::Wrap operator ~( + typename o3tl::typed_flags<E>::Wrap rhs) +{ + return static_cast<typename o3tl::typed_flags<E>::Wrap>( + o3tl::typed_flags<E>::mask + & ~o3tl::to_underlying<E>(rhs)); +} + +template<typename E> constexpr typename o3tl::typed_flags<E>::Wrap operator ^( + E lhs, E rhs) +{ + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(lhs))); + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(rhs))); + return static_cast<typename o3tl::typed_flags<E>::Wrap>( + o3tl::to_underlying(lhs) + ^ o3tl::to_underlying(rhs)); +} + +template<typename E> constexpr typename o3tl::typed_flags<E>::Wrap operator ^( + E lhs, typename o3tl::typed_flags<E>::Wrap rhs) +{ + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(lhs))); + return static_cast<typename o3tl::typed_flags<E>::Wrap>( + o3tl::to_underlying(lhs) + ^ o3tl::to_underlying<E>(rhs)); +} + +template<typename E> constexpr typename o3tl::typed_flags<E>::Wrap operator ^( + typename o3tl::typed_flags<E>::Wrap lhs, E rhs) +{ + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(rhs))); + return static_cast<typename o3tl::typed_flags<E>::Wrap>( + o3tl::to_underlying<E>(lhs) + ^ o3tl::to_underlying(rhs)); +} + +template<typename W> constexpr +typename o3tl::typed_flags<typename W::Unwrapped::Self>::Wrap operator ^( + W lhs, W rhs) +{ + return static_cast<W>( + o3tl::to_underlying<typename W::Unwrapped::Self>(lhs) + ^ o3tl::to_underlying<typename W::Unwrapped::Self>(rhs)); +} + +template<typename E> +constexpr typename o3tl::typed_flags<E>::Wrap operator &(E lhs, E rhs) { + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(lhs))); + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(rhs))); + return static_cast<typename o3tl::typed_flags<E>::Wrap>( + o3tl::to_underlying(lhs) + & o3tl::to_underlying(rhs)); +} + +template<typename E> constexpr typename o3tl::typed_flags<E>::Wrap operator &( + E lhs, typename o3tl::typed_flags<E>::Wrap rhs) +{ + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(lhs))); + return static_cast<typename o3tl::typed_flags<E>::Wrap>( + o3tl::to_underlying(lhs) + & o3tl::to_underlying<E>(rhs)); +} + +template<typename E> constexpr typename o3tl::typed_flags<E>::Wrap operator &( + typename o3tl::typed_flags<E>::Wrap lhs, E rhs) +{ + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(rhs))); + return static_cast<typename o3tl::typed_flags<E>::Wrap>( + o3tl::to_underlying<E>(lhs) + & o3tl::to_underlying(rhs)); +} + +template<typename W> constexpr +typename o3tl::typed_flags<typename W::Unwrapped::Self>::Wrap operator &( + W lhs, W rhs) +{ + return static_cast<W>( + o3tl::to_underlying<typename W::Unwrapped::Self>(lhs) + & o3tl::to_underlying<typename W::Unwrapped::Self>(rhs)); +} + +template<typename E> +constexpr typename o3tl::typed_flags<E>::Wrap operator |(E lhs, E rhs) { + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(lhs))); + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(rhs))); + return static_cast<typename o3tl::typed_flags<E>::Wrap>( + o3tl::to_underlying(lhs) + | o3tl::to_underlying(rhs)); +} + +template<typename E> constexpr typename o3tl::typed_flags<E>::Wrap operator |( + E lhs, typename o3tl::typed_flags<E>::Wrap rhs) +{ + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(lhs))); + return static_cast<typename o3tl::typed_flags<E>::Wrap>( + o3tl::to_underlying(lhs) + | o3tl::to_underlying<E>(rhs)); +} + +template<typename E> constexpr typename o3tl::typed_flags<E>::Wrap operator |( + typename o3tl::typed_flags<E>::Wrap lhs, E rhs) +{ + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(rhs))); + return static_cast<typename o3tl::typed_flags<E>::Wrap>( + o3tl::to_underlying<E>(lhs) + | o3tl::to_underlying(rhs)); +} + +template<typename W> constexpr +typename o3tl::typed_flags<typename W::Unwrapped::Self>::Wrap operator |( + W lhs, W rhs) +{ + return static_cast<W>( + o3tl::to_underlying<typename W::Unwrapped::Self>(lhs) + | o3tl::to_underlying<typename W::Unwrapped::Self>(rhs)); +} + +template<typename E> +inline typename o3tl::typed_flags<E>::Self operator &=(E & lhs, E rhs) { + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(lhs))); + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(rhs))); + lhs = lhs & rhs; + return lhs; +} + +template<typename E> +inline typename o3tl::typed_flags<E>::Self operator &=( + E & lhs, typename o3tl::typed_flags<E>::Wrap rhs) +{ + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(lhs))); + lhs = lhs & rhs; + return lhs; +} + +template<typename E> +inline typename o3tl::typed_flags<E>::Self operator |=(E & lhs, E rhs) { + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(lhs))); + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(rhs))); + lhs = lhs | rhs; + return lhs; +} + +template<typename E> +inline typename o3tl::typed_flags<E>::Self operator |=( + E & lhs, typename o3tl::typed_flags<E>::Wrap rhs) +{ + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(lhs))); + lhs = lhs | rhs; + return lhs; +} + +template<typename E> +inline typename o3tl::typed_flags<E>::Self operator ^=(E & lhs, E rhs) { + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(lhs))); + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(rhs))); + lhs = lhs ^ rhs; + return lhs; +} + +template<typename E> +inline typename o3tl::typed_flags<E>::Self operator ^=( + E & lhs, typename o3tl::typed_flags<E>::Wrap rhs) +{ + assert( + o3tl::detail::isNonNegative( + o3tl::to_underlying(lhs))); + lhs = lhs ^ rhs; + return lhs; +} + +#endif /* INCLUDED_O3TL_TYPED_FLAGS_SET_HXX */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/o3tl/underlyingenumvalue.hxx b/include/o3tl/underlyingenumvalue.hxx new file mode 100644 index 0000000000..a913ab3853 --- /dev/null +++ b/include/o3tl/underlyingenumvalue.hxx @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_O3TL_UNDERLYINGENUMVALUE_HXX +#define INCLUDED_O3TL_UNDERLYINGENUMVALUE_HXX + +#include <sal/config.h> + +#include <type_traits> + +namespace o3tl +{ +// An implementation of C++23 std::to_underlying +// For a value e of an enumeration type T, return the corresponding value of T's underlying type: +template <typename T> constexpr std::underlying_type_t<T> to_underlying(T e) +{ + return static_cast<std::underlying_type_t<T>>(e); +} +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/o3tl/unit_conversion.hxx b/include/o3tl/unit_conversion.hxx new file mode 100644 index 0000000000..54eb8cd246 --- /dev/null +++ b/include/o3tl/unit_conversion.hxx @@ -0,0 +1,269 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <o3tl/safeint.hxx> +#include <sal/types.h> + +#include <array> +#include <cassert> +#include <numeric> +#include <utility> +#include <type_traits> + +namespace o3tl +{ +// Length units +enum class Length +{ + mm100 = 0, // 1/100th mm + mm10, // 1/10 mm, corresponds to MapUnit::Map10thMM + mm, // millimeter + cm, // centimeter + m, // meter + km, // kilometer + emu, // English Metric Unit: 1/360000 cm, 1/914400 in + twip, // "Twentieth of a point" aka "dxa": 1/20 pt + pt, // Point: 1/72 in + pc, // Pica: 1/6 in, corresponds to FieldUnit::PICA and MeasureUnit::PICA + in1000, // 1/1000 in, corresponds to MapUnit::Map1000thInch + in100, // 1/100 in, corresponds to MapUnit::Map100thInch + in10, // 1/10 in, corresponds to MapUnit::Map10thInch + in, // inch + ft, // foot + mi, // mile + master, // PPT Master Unit: 1/576 in + px, // "pixel" unit: 15 twip (96 ppi), corresponds to MeasureUnit::PIXEL + ch, // "char" unit: 210 twip (14 px), corresponds to FieldUnit::CHAR + line, // "line" unit: 312 twip, corresponds to FieldUnit::LINE + count, // <== add new units above this last entry + invalid = -1 +}; + +// If other categories of units would be needed (like time), a separate scoped enum +// should be created, respective conversion array prepared in detail namespace, and +// respective md(NewUnit, NewUnit) overload introduced, which would allow using +// o3tl::convert(), o3tl::convertSaturate() and o3tl::getConversionMulDiv() with the +// new category in a type-safe way, without mixing unrelated units. + +namespace detail +{ +// Common utilities + +// A special function to avoid compiler warning comparing signed and unsigned values +template <typename I> constexpr bool isBetween(I n, sal_Int64 min, sal_Int64 max) +{ + assert(max > 0 && min < 0); + if constexpr (std::is_signed_v<I>) + return n >= min && n <= max; + else + return n <= sal_uInt64(max); +} + +// Ensure correct rounding for both positive and negative integers +template <typename I, std::enable_if_t<std::is_integral_v<I>, int> = 0> +constexpr sal_Int64 MulDiv(I n, sal_Int64 m, sal_Int64 d) +{ + assert(m > 0 && d > 0); + assert(isBetween(n, (SAL_MIN_INT64 + d / 2) / m, (SAL_MAX_INT64 - d / 2) / m)); + return (n >= 0 ? (n * m + d / 2) : (n * m - d / 2)) / d; +} +template <typename F, std::enable_if_t<std::is_floating_point_v<F>, int> = 0> +constexpr double MulDiv(F f, sal_Int64 m, sal_Int64 d) +{ + assert(m > 0 && d > 0); + return f * (double(m) / d); +} + +template <typename I, std::enable_if_t<std::is_integral_v<I>, int> = 0> +constexpr sal_Int64 MulDiv(I n, sal_Int64 m, sal_Int64 d, bool& bOverflow, sal_Int64 nDefault) +{ + if (!isBetween(n, (SAL_MIN_INT64 + d / 2) / m, (SAL_MAX_INT64 - d / 2) / m)) + { + bOverflow = true; + return nDefault; + } + bOverflow = false; + return MulDiv(n, m, d); +} + +template <typename I, std::enable_if_t<std::is_integral_v<I>, int> = 0> +constexpr sal_Int64 MulDivSaturate(I n, sal_Int64 m, sal_Int64 d) +{ + if (sal_Int64 d_2 = d / 2; !isBetween(n, (SAL_MIN_INT64 + d_2) / m, (SAL_MAX_INT64 - d_2) / m)) + { + if (n >= 0) + { + if (m > d && std::make_unsigned_t<I>(n) > sal_uInt64(SAL_MAX_INT64 / m * d - d_2)) + return SAL_MAX_INT64; // saturate + return saturating_add<sal_uInt64>(n, d_2) / d * m; // divide before multiplication + } + else if constexpr (std::is_signed_v<I>) // n < 0; don't compile for unsigned n + { + if (m > d && n < SAL_MIN_INT64 / m * d + d_2) + return SAL_MIN_INT64; // saturate + return saturating_sub<sal_Int64>(n, d_2) / d * m; // divide before multiplication + } + } + return MulDiv(n, m, d); +} + +template <class M, class N> constexpr std::common_type_t<M, N> asserting_gcd(M m, N n) +{ + auto ret = std::gcd(m, n); + assert(ret != 0); + return ret; +} + +// Packs integral multiplier and divisor for conversion from one unit to another +struct m_and_d +{ + sal_Int64 m; // multiplier + sal_Int64 d; // divisor + constexpr m_and_d(sal_Int64 _m, sal_Int64 _d) + : m(_m / asserting_gcd(_m, _d)) // make sure to use smallest quotients here because + , d(_d / asserting_gcd(_m, _d)) // they will be multiplied when building final table + { + assert(_m > 0 && _d > 0); + } +}; + +// Resulting static array N x N of all quotients to convert between all units. The +// quotients are minimal to allow largest range of converted numbers without overflow. +// Maybe o3tl::enumarray could be used here, but it's not constexpr yet. +template <int N> constexpr auto prepareMDArray(const m_and_d (&mdBase)[N]) +{ + std::array<std::array<sal_Int64, N>, N> a{}; + for (int i = 0; i < N; ++i) + { + a[i][i] = 1; + for (int j = 0; j < i; ++j) + { + assert(mdBase[i].m < SAL_MAX_INT64 / mdBase[j].d); + assert(mdBase[i].d < SAL_MAX_INT64 / mdBase[j].m); + const sal_Int64 m = mdBase[i].m * mdBase[j].d, d = mdBase[i].d * mdBase[j].m; + const sal_Int64 g = asserting_gcd(m, d); + a[i][j] = m / g; + a[j][i] = d / g; + } + } + return a; +} + +// A generic template used for fundamental arithmetic types +template <typename U> constexpr sal_Int64 md(U i, U /*j*/) { return i; } + +// Length units implementation + +// Array of conversion quotients for mm, used to build final conversion table. Entries +// are { multiplier, divider } to convert respective unit *to* mm. Order of elements +// corresponds to order in o3tl::Length enum (Length::count and Length::invalid omitted). +constexpr m_and_d mdBaseLen[] = { + { 1, 100 }, // mm100 => mm + { 1, 10 }, // mm10 => mm + { 1, 1 }, // mm => mm + { 10, 1 }, // cm => mm + { 1000, 1 }, // m => mm + { 1000000, 1 }, // km => mm + { 1, 36000 }, // emu => mm + { 254, 10 * 1440 }, // twip => mm + { 254, 10 * 72 }, // pt => mm + { 254, 10 * 6 }, // pc => mm + { 254, 10000 }, // in1000 => mm + { 254, 1000 }, // in100 => mm + { 254, 100 }, // in10 => mm + { 254, 10 }, // in => mm + { 254 * 12, 10 }, // ft => mm + { 254 * 12 * 5280, 10 }, // mi => mm + { 254, 10 * 576 }, // master => mm + { 254 * 15, 10 * 1440 }, // px => mm + { 254 * 210, 10 * 1440 }, // ch => mm + { 254 * 312, 10 * 1440 }, // line => mm +}; +static_assert(std::size(mdBaseLen) == static_cast<int>(Length::count), + "mdBaseL must have an entry for each unit in o3tl::Length"); + +// The resulting multipliers and divisors array +constexpr auto aLengthMDArray = prepareMDArray(mdBaseLen); + +// an overload taking Length +constexpr sal_Int64 md(Length i, Length j) +{ + const int nI = static_cast<int>(i), nJ = static_cast<int>(j); + assert(nI >= 0 && o3tl::make_unsigned(nI) < aLengthMDArray.size()); + assert(nJ >= 0 && o3tl::make_unsigned(nJ) < aLengthMDArray.size()); + return aLengthMDArray[nI][nJ]; +} + +// here might go overloads of md() taking other units ... +} + +// Unchecked conversion. Takes a number value, multiplier and divisor +template <typename N> constexpr auto convert(N n, sal_Int64 mul, sal_Int64 div) +{ + return detail::MulDiv(n, mul, div); +} + +// Unchecked conversion. Takes a number value and units defined in this header +template <typename N, typename U> constexpr auto convert(N n, U from, U to) +{ + return convert(n, detail::md(from, to), detail::md(to, from)); +} + +// Convert to twips - for convenience as we do this a lot +template <typename N> constexpr auto toTwips(N number, Length from) +{ + return convert(number, from, Length::twip); +} + +// Returns nDefault if intermediate multiplication overflows sal_Int64 (only for integral types). +// On return, bOverflow indicates if overflow happened. nDefault is returned when overflow occurs. +template <typename N, typename U> +constexpr auto convert(N n, U from, U to, bool& bOverflow, sal_Int64 nDefault = 0) +{ + return detail::MulDiv(n, detail::md(from, to), detail::md(to, from), bOverflow, nDefault); +} + +// Conversion with saturation (only for integral types). For too large input returns SAL_MAX_INT64. +// When intermediate multiplication would overflow, but the end result is in sal_Int64 range, the +// precision is decreased because of inversion of multiplication and division. +template <typename N, typename U> constexpr auto convertSaturate(N n, U from, U to) +{ + return detail::MulDivSaturate(n, detail::md(from, to), detail::md(to, from)); +} + +// Conversion with saturation (only for integral types), optimized for return types smaller than +// sal_Int64. In this case, it's easier to clamp input values to known bounds, than to do some +// preprocessing to handle too large input values, just to clamp the result anyway. Use it like: +// +// sal_Int32 n = convertNarrowing<sal_Int32, o3tl::Length::mm100, o3tl::Length::emu>(m); +template <typename Out, auto from, auto to, typename N, + std::enable_if_t< + std::is_integral_v<N> && std::is_integral_v<Out> && sizeof(Out) < sizeof(sal_Int64), + int> = 0> +constexpr Out convertNarrowing(N n) +{ + constexpr sal_Int64 nMin = convertSaturate(std::numeric_limits<Out>::min(), to, from); + constexpr sal_Int64 nMax = convertSaturate(std::numeric_limits<Out>::max(), to, from); + if (static_cast<sal_Int64>(n) > nMax) + return std::numeric_limits<Out>::max(); + if (static_cast<sal_Int64>(n) < nMin) + return std::numeric_limits<Out>::min(); + return convert(n, from, to); +} + +// Return a pair { multiplier, divisor } for a given conversion +template <typename U> constexpr std::pair<sal_Int64, sal_Int64> getConversionMulDiv(U from, U to) +{ + return { detail::md(from, to), detail::md(to, from) }; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/o3tl/unreachable.hxx b/include/o3tl/unreachable.hxx new file mode 100644 index 0000000000..604f7a2f77 --- /dev/null +++ b/include/o3tl/unreachable.hxx @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_O3TL_UNREACHABLE_HXX +#define INCLUDED_O3TL_UNREACHABLE_HXX + +#include <sal/config.h> + +#include <cassert> +#include <utility> + +// An approximation of C++23 std::unreachable +// (<http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0627r6.pdf> "Function to mark +// unreachable code"). + +#if defined __cpp_lib_unreachable + +#define O3TL_UNREACHABLE (::std::unreachable()) + +#else + +// This fallback implementation is inspired by LLVM's LLVM_BUILTIN_UNREACHABLE +// (llvm/include/llvm/Support/Compiler.h). + +#if defined _MSC_VER +#define O3TL_UNREACHABLE_detail __assume(false) +#else // assuming Clang or GCC with support for: +#define O3TL_UNREACHABLE_detail __builtin_unreachable() +#endif + +#define O3TL_UNREACHABLE \ + do \ + { \ + assert(false); \ + O3TL_UNREACHABLE_detail; \ + } while (false) + +#endif + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/o3tl/unsafe_downcast.hxx b/include/o3tl/unsafe_downcast.hxx new file mode 100644 index 0000000000..b49994eff1 --- /dev/null +++ b/include/o3tl/unsafe_downcast.hxx @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_O3TL_UNSAFE_DOWNCAST_HXX +#define INCLUDED_O3TL_UNSAFE_DOWNCAST_HXX + +#include <cassert> +#include <type_traits> + +namespace o3tl +{ +// Do a downcast from polymorphic `BasePtr` to `DerivedPtr` when it is known that `p` is actually +// pointing to an object of derived type (or is a nullptr). This is potentially cheaper than +// dynamic_cast and helps to avoid Coverity warnings about unchecked dynamic_cast. +template <typename DerivedPtr, typename BasePtr> +std::enable_if_t< + (std::is_pointer_v< + DerivedPtr> && std::is_pointer_v<BasePtr> && std::is_base_of_v<std::remove_pointer_t<BasePtr>, std::remove_pointer_t<DerivedPtr>>), + DerivedPtr> +unsafe_downcast(BasePtr p) +{ + assert(p == nullptr || dynamic_cast<DerivedPtr>(p) != nullptr); + return static_cast<DerivedPtr>(p); +} +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/o3tl/vector_pool.hxx b/include/o3tl/vector_pool.hxx new file mode 100644 index 0000000000..eec1a1a3c5 --- /dev/null +++ b/include/o3tl/vector_pool.hxx @@ -0,0 +1,123 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_O3TL_VECTOR_POOL_HXX +#define INCLUDED_O3TL_VECTOR_POOL_HXX + +#include <utility> +#include <vector> + +namespace o3tl +{ + namespace detail + { + template<typename ValueType, class Container> class simple_pool_impl : + public Container + { + typedef typename Container::value_type value_type; + std::ptrdiff_t mnFirstFreeIndex; + + public: + simple_pool_impl() : + mnFirstFreeIndex(-1) + {} + + std::ptrdiff_t alloc() + { + return store(ValueType()); + } + + std::ptrdiff_t store(const ValueType& rCopy) + { + if( mnFirstFreeIndex != -1 ) + { + std::ptrdiff_t nIdx=mnFirstFreeIndex; + mnFirstFreeIndex = this->at(mnFirstFreeIndex).nextFree; + this->at(nIdx).value = rCopy; + this->at(nIdx).nextFree = -1; + + return nIdx; + } + else + { + this->push_back(value_type(rCopy)); + return this->size()-1; + } + } + + void free( std::ptrdiff_t nIdx ) + { + this->at(nIdx).nextFree = mnFirstFreeIndex; + mnFirstFreeIndex = nIdx; + } + + const ValueType& get( std::ptrdiff_t nIdx ) const + { + return this->operator[](nIdx).value; + } + ValueType& get( std::ptrdiff_t nIdx ) + { + return this->operator[](nIdx).value; + } + }; + + template< typename ValueType > struct struct_from_value + { + struct type + { + type() : + value(), + nextFree(-1) + {} + explicit type( ValueType val ) : + value(std::move(val)), + nextFree(-1) + {} + + ValueType value; + std::ptrdiff_t nextFree; + }; + }; + } + + /** Simple vector-based memory pool allocator + + This template can be used to provide simple pooled memory + allocation from a container class that adheres to the stl + random access container concept. Note that alloc/free works + with _indices_ into the container! + + @example + <pre> +vector_pool<type> myPool; +int nIdx=myPool.alloc(); +myPool[nIdx] = myVal; + ... do stuff ... +myPool.free(nIdx); + </pre> + */ + template<typename ValueType> struct vector_pool : + public detail::simple_pool_impl<ValueType, + std::vector<typename detail::struct_from_value<ValueType>::type > > + {}; +} + +#endif /* INCLUDED_O3TL_VECTOR_POOL_HXX */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/o3tl/vector_utils.hxx b/include/o3tl/vector_utils.hxx new file mode 100644 index 0000000000..1fe34bcaf4 --- /dev/null +++ b/include/o3tl/vector_utils.hxx @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +#pragma once + +#include <algorithm> +#include <unordered_set> +#include <vector> + +namespace o3tl +{ +// removes duplicated elements in a vector +template <typename T> void remove_duplicates(std::vector<T>& rVector) +{ + std::unordered_set<T> aSet; + auto aEnd = std::copy_if(rVector.begin(), rVector.end(), rVector.begin(), + [&aSet](T const& rElement) { return aSet.insert(rElement).second; }); + rVector.erase(aEnd, rVector.end()); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |