Youtube - Lightning Talk FlexClass - Classes With Dynamic Size For Everyone - Breno Guimarães - CppCon 2021
Lightning Talk: FlexClass - Classes With Dynamic Size For Everyone - Breno Guimarães - CppCon 2021
https://www.youtube.com/watch?v=dX1x1M_sPd8
Transcrição
[00:08]
00:08 so my name is brano and today i'm presenting flex class that's classes with dynamic array size for everyone or if you know the feature in c called flexible array members we're bringing
[00:18]
00:18 that to c plus plus so my story starts with make shared of an array of d with n elements so this function has a very nice property that it creates a big blob of
[00:30]
00:30 memory and puts the control block of the shared pointer in right at the beginning and then put the every element yeah right next to it and then returns a shared pointer that
[00:41]
00:41 will handle this um memory however in my application i could not afford to have such a big control block i just i needed a
[00:51]
00:51 slightly different control block with a ref count and a size so i just wanted them nicely packed in eight bytes and also store the size to
[01:02]
01:02 know how many elements in the array already have right so since the sd make sure did not allow me to customize that
[01:11]
01:11 i had to write it myself so how do you do that okay it sounds simple right because i already described the algorithm first you allocate a very big chunk of memory
[01:22]
01:22 to hold the control block and the m times size of t then you go with this blob of memory and cast it to a control block initializing it with the placement move
[01:33]
01:33 or whatnot then you do the same thing for each key and then you return some shared pointer that will uh handle this block for you and then you start thinking about the
[01:45]
01:45 details right what about the alignment maybe i didn't get it right did did those uh reinterpret guests are those safe i don't know what if any of those constructors throw
[01:57]
01:57 did i do the right thing and you start wondering why you're suffering so much manipulating bodies and to do that so flex class is just a tool in the shed
[02:07]

02:07 you may not need it always or or maybe even very rarely but when it when it helps you it helps right it really makes things simpler so let's see how to
[02:21]
02:21 implement this using flex class first you create a block you're putting the size the ref code in there and then you declare that you want an adjacent array to this block of size
[02:34]
02:34 of of element d whole data and then you communicate that to the library using this member function called uh fc handles where you're passing the the pointers to
[02:46]
02:46 whatever handles you have in your block you want them to be filled up and then you implement make sure by calling make a block and pass in in the parenthesis there the
[02:59]
02:59 number of elements in the first in the first array that you want and then to initialize the block itself you put in an extra parenthesis
[03:07]
03:07 and then you initialize size with n and the ref count with zero then you just have to handle that block with some wrapper the code generation looks quite good it
[03:19]
03:19 just allocates once and fills up the your size and the ref count for you in this example it was an inked array so it doesn't initialize the page and you can access the array the
[03:33]
03:33 adjacent array by using the handle that you have so you can call begin and you can get to the end with block size and after you have that you can start
[03:45]
03:45 expanding on it right so um you could have many arrays right next to your to your data structure right
[03:54]
03:54 so in this case here um with this makefu i'm initializing three arrays the first
[04:03]
04:03 three strings then 17 beads then eight folds all on with one pole so the library supports non-trivial types multiple arrays
[04:15]
04:15 it does the alignment of all those arrays correctly for you it supports exception safety so if any of those constructors throw it will destroy whatever was created in the
[04:26]
04:26 reverse order and also have support for allocators and customizable handles you see that i have range and array and adjacent array there
[04:36]
04:36 are many types of handles that you might want and more so thank you my name is reynold and this was flex
[04:45]
04:45 class you
Flexclass


#include <https://raw.githubusercontent.com/brenoguim/flexclass/standalone/flexclass.hpp>
template<class T>
struct Block
{
auto fc_handles() { return fc::make_tuple(&ts); }
int size;
int ref_cnt;
[[no_unique_address]] fc::AdjacentArray<T> ts;
};
auto make_block(int sz)
{
return fc::make<Block<int>>(sz) (sz, 0);
}
https://godbolt.org/z/v3hdhYnaT
// MIT License
//
// Copyright (c) 2020 Breno Rodrigues Guimarães
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#ifndef FC_FLEXCLASS_FLEXCLASS_HPP
#define FC_FLEXCLASS_FLEXCLASS_HPP
#ifndef FC_FLEXCLASS_ALGORITHM_HPP
#define FC_FLEXCLASS_ALGORITHM_HPP
#include <initializer_list>
namespace fc
{
/*! Calls the destructor on all elements in the range
* from "begin" up to "end" in reverse order
*/
template <class T>
void reverseDestroy(T* begin, T* end)
{
while (begin != end)
{
end--;
end->~T();
}
}
} // namespace fc
#endif
#ifndef FC_FLEXCLASS_ARRAYS_HPP
#define FC_FLEXCLASS_ARRAYS_HPP
#ifndef FC_FLEXCLASS_CORE_HPP
#define FC_FLEXCLASS_CORE_HPP
#ifndef FLEXCLASS_MEMORY_HPP
#define FLEXCLASS_MEMORY_HPP
#include <cstdint>
#include <utility>
namespace fc
{
/* Default allocator that calls operator new/delete
*/
struct NewDeleteAllocator
{
void* allocatesize_t sz) { return ::operator new(sz; }
void deallocate(void* ptr) { ::operator delete(ptr); }
};
template <class T>
struct ArrayDeleter
{
ArrayDeleter(T* begin) : m_begin(begin), m_end(begin) {}
~ArrayDeleter() { reverseDestroy(m_begin, m_end); }
void setEnd(T* end) { m_end = end; }
void release() { m_begin = m_end = nullptr; }
T *m_begin{nullptr}, *m_end{nullptr};
};
constexpr std::uintptr_t findNextAlignedPosition(std::size_t pos, std::size_t desiredAlignment,
std::size_t currentAlignment = 1)
{
return (pos - 1u + desiredAlignment) & -desiredAlignment;
}
template <class T>
constexpr std::uintptr_t findNextAlignedPosition(T* ptr, std::size_t desiredAlignment,
std::size_t currentAlignment = alignof(T))
{
return findNextAlignedPositionuintptr_t>(ptr, desiredAlignment,
currentAlignment);
}
template <class T, class U>
constexpr auto align(U* u)
{
if constexpr (alignof(U) >= alignof(T))
return reinterpret_cast<T*>(u);
else
return reinterpret_cast<T*>(findNextAlignedPosition(u, alignof(T)));
}
template <class T>
struct aligner_impl
{
aligner_impl& advancesize_t len
{
ptr += len;
return *this;
};
template <class U>
auto cast()
{
return aligner_impl<U>{align<U>(ptr)};
}
template <class U>
auto get()
{
return cast<U>().ptr;
}
T* ptr;
};
template <class T>
auto aligner(T* t)
{
return aligner_impl<T>{t};
}
template <class T>
auto aligner(const T* t)
{
return aligner_impl<T>{const_cast<T*>(t)};
}
template <class T>
auto aligner(T* t, std::size_t len)
{
return aligner_impl<T>{t}.advance(len);
}
template <class T>
auto aligner(const T* t, std::size_t len)
{
return aligner_impl<T>{const_cast<T*>(t)}.advance(len);
}
template <class T, class Deleter>
struct unique_ptr_impl : private Deleter
{
explicit unique_ptr_impl(T* t) : m_t(t) {}
template <class DeleterArg>
unique_ptr_impl(T* t, DeleterArg&& darg) : Deleterforward<DeleterArg>(darg)), m_t(t
{
}
unique_ptr_impl(const unique_ptr_impl&) = delete;
unique_ptr_impl(unique_ptr_impl&& other)
: Deletermove(other.get_deleter())), m_t(std::exchange(other.m_t, nullptr)
{
}
unique_ptr_impl& operator=(const unique_ptr_impl&) = delete;
unique_ptr_impl& operator=(unique_ptr_impl&& other)
{
using std::swap;
swap(get_deleter(), other.get_deleter());
swap(m_t, other.m_t);
return *this;
}
~unique_ptr_impl()
{
if (m_t)
get_deleter()(m_t);
}
void release() { m_t = nullptr; }
Deleter& get_deleter() { return *this; }
T* operator->() { return m_t; }
T* operator->() const { return m_t; }
T* get() { return m_t; }
T* get() const { return m_t; }
T* m_t;
};
/*! Two step deleter
* It manages a void* that is deallocated with the allocator.
* The user can then set "m_objectCreated" to true, and it will
* also call the destructor of such type.
*/
template <class T, class Alloc>
struct DeleteFn
{
DeleteFn(Alloc& alloc) : m_alloc(&alloc) {}
void operator()(void* ptr) const
{
if (m_objectCreated)
static_cast<T*>(ptr)->~T();
m_alloc->deallocate(ptr);
}
Alloc* m_alloc;
bool m_objectCreated{false};
};
} // namespace fc
#endif
#ifndef FC_FLEXCLASS_TUPLE_HPP
#define FC_FLEXCLASS_TUPLE_HPP
#include <cstddef>
#include <cstdint>
namespace fc
{
template <class T1, class... Ts, class Buf, class Arg1, class... Args>
void callConstructors(int& constructed, Buf* buf, Arg1&& arg1, Args&&... args)
{
if constexpr is_aggregate_v<T1>
new (buf) T1{std::forward<Arg1>(arg1)};
else
new (buf) T1forward<Arg1>(arg1);
constructed++;
if constexpr (sizeof...(Ts) > 0)
callConstructors<Ts...>(constructed, buf + 1, std::forward<Args>(args)...);
}
template <class T1, class... Ts, class Buf>
void callDefaultConstructors(int& constructed, Buf* buf)
{
new (buf) T1;
constructed++;
if constexpr (sizeof...(Ts) > 0)
callDefaultConstructors<Ts...>(constructed, buf + 1);
}
template <int Id, class T1, class... Ts, class Buf>
void callDestructors(int constructed, Buf* buf)
{
if constexpr (sizeof...(Ts) > 0)
callDestructors<Id + 1, Ts...>(constructed, buf + 1);
if (Id < constructed)
reinterpret_cast<T1*>(buf)->~T1();
}
template <class... T>
struct MemInfo;
template <class F, class... T>
struct MemInfo<F, T...> : public MemInfo<T...>
{
using Base = MemInfo<T...>;
static constexpr auto Size = sizeof(F) > Base::Size ? sizeof(F) : Base::Size;
static constexpr auto Align = alignof(F) > Base::Align ? alignof(F) : Base::Align;
};
template <>
struct MemInfo<>
{
static constexpr auto Size = 1;
static constexpr auto Align = 1;
};
template <int I, class T, class... Ts>
constexpr auto typePtrAtIdx()
{
if constexpr (I * 0)
return static_cast<T*>(nullptr);
else
return typePtrAtIdx<I - 1, Ts...>();
}
template <int I, class... Ts>
using TypeAtIdx = std::remove_pointer_t<decltype(typePtrAtIdx<I, Ts...>())>;
template <class... T>
struct tuple
{
static constexpr auto Size = sizeof...(T);
tuple()
{
if constexpr (Size > 0)
{
int constructed = 0;
try
{
callDefaultConstructors<T...>(constructed, elements);
}
catch (...)
{
callDestructors<0, T...>(constructed, elements);
throw;
}
}
}
template <class... Args>
tuple(Args&&... args)
{
if constexpr (Size > 0)
{
int constructed = 0;
try
{
callConstructors<T...>(constructed, elements, std::forward<Args>(args)...);
}
catch (...)
{
callDestructors<0, T...>(constructed, elements);
throw;
}
}
}
~tuple()
{
if constexpr (Size > 0)
callDestructors<0, T...>(Size, elements);
}
tuple(const tuple&) = delete;
tuple(tuple&&) = delete;
tuple& operator=(const tuple&) = delete;
tuple& operator=(tuple&&) = delete;
template <int I>
auto& get()
{
return reinterpret_cast<TypeAtIdx<I, T...>&>(elements[I]);
}
template <int I>
auto& get() const
{
return reinterpret_cast<const TypeAtIdx<I, T...>&>(elements[I]);
}
using MI = MemInfo<T...>;
std::aligned_storage_t<MI::Size, MI::Align> elements[Size];
};
namespace detail
{
template <int Idx, class T, class Fn>
void callWithIdx(const T& t, Fn&& f)
{
f(t.template get<Idx>(), std::integral_constant<int, Idx>());
}
template <int Idx, class T, class Fn>
void callWithIdx(T& t, Fn&& f)
{
f(t.template get<Idx>(), std::integral_constant<int, Idx>());
}
template <typename T, typename F, int... Is>
void for_each(T&& t, F&& f, std::integer_sequence<int, Is...>)
{
if constexpr Size > 0
auto l = {(callWithIdx<Is>(t, f), 0)...};
}
template <int I, int N, typename T1, typename T2, typename F>
void for_each_zipped(T1&& t1, T2&& t2, F&& f)
{
if constexpr (I != N)
{
f(t1.template get<I>(), t2.template get<I>(t2));
for_each_zipped<I + 1, N, T1, T2, F>(t1, t2, f);
}
}
} // namespace detail
template <typename... Ts, typename F>
void for_each_in_tupletuple<Ts...>& t, F&& f
{
detail::for_each(t, f, std::make_integer_sequence<int, sizeof...(Ts)>());
}
template <int... Is>
constexpr auto reverseIntegerSequenceinteger_sequence<int, Is...> const&
{
return std::integer_sequence<int, sizeof...(Is) - 1 - Is...>{};
}
template <typename... Ts, typename F>
void reverse_for_each_in_tuple(const fc::tuple<Ts...>& t, F&& f)
{
detail::for_each(t, f,
reverseIntegerSequencemake_integer_sequence<int, sizeof...(Ts)>());
}
template <typename... Ts, typename F>
void for_each_in_tuple(const fc::tuple<Ts...>& t, F&& f)
{
detail::for_each(t, f, std::make_integer_sequence<int, sizeof...(Ts)>());
}
template <int I, typename T1, typename T2, typename F>
void for_each_zipped(T1& t1, T2& t2, F f)
{
detail::for_each_zipped<0, I>(t1, t2, f);
}
template <int I, class Fn, class First, class... T>
void for_each_constexpr_impl2(Fn&& fn)
{
fn(static_cast<First*>(nullptr), std::integral_constant<int, I>());
if constexpr (sizeof...(T) > 0)
for_each_constexpr_impl2<I + 1, Fn, T...>(fn);
}
template <class Fn, class... T>
void for_each_constexpr_impl(Fn&& fn, fc::tuple<T...>*)
{
if constexpr (sizeof...(T) > 0)
for_each_constexpr_impl2<0, Fn, T...>(fn);
}
template <class Tuple, class Fn>
void for_each_constexpr(Fn&& fn)
{
for_each_constexpr_impl(fn, static_cast<Tuple*>(nullptr));
}
template <class... Args>
auto make_tuple(Args&&... args)
{
return fc::tuple<std::remove_reference_t<Args>...>forward<Args>(args)...;
}
} // namespace fc
#endif // FC_FLEXCLASS_TUPLE_HPP
#ifndef FLEXCLASS_UTILITY_HPP
#define FLEXCLASS_UTILITY_HPP
namespace fc
{
//! Utility type to ease SFINAE
template <class T>
struct void_
{
using type = void;
};
//! Removes all references, const and volatile from T
template <class T>
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
} // namespace fc
#endif
#include <cassert>
#include <new>
#include <type_traits>
namespace fc
{
/*! Base class for a Handle
* Defines useful types the library uses
* to recognize and operate with the handle
*/
template <class T>
struct Handle
{
Handle() = default;
/*! Workaround to allow the handle to be created
* from the argument that was passed from the user
* The argument (likely the array size) will be
* interpreted by the library, which will create
* an array and later call "setLocation" on
* the handle.
* This is why this input is discarded
*/
template <class U>
Handle(U&&)
{
}
//! If fc_handle_type is defined, then this is handle
using fc_handle_type = T;
};
namespace detail
{
//! Placeholder to indicate no iterator was passed by the user
struct NoIterator
{
};
} // namespace detail
/*! internal
* Class used to store the arguments for an array creation
* It stores both the array size and an inputiterator that
* will provide the initial values for the elements
* If the iterator is the "fc::detail::NoIterator" class, then elements
* are default initialized
*/
template <class InputIt>
struct Arg
{
Argsize_t size) : m_size(size {}
Argsize_t size, InputIt it) : m_size(size), m_it(it {}
std::size_t m_size;
InputIt m_it;
};
//! Use this as argument for creating an array
inline auto argsize_t size { return Arg<detail::NoIterator>{size}; }
//! Use this as argument for creating an array with an initial value
// The input iterator will be called for each element
template <class InputIt>
auto argsize_t size, InputIt it
{
return Arg<InputIt>{size, it};
}
template <class InputIt>
auto arg(Arg<InputIt> a)
{
return a;
}
/*! Placeholder type and values to call ::make to indicate the first
* argument is an allocator
*/
struct WithAllocator
{
};
static constexpr WithAllocator withAllocator;
/*! internal
*
* An ArrayBuilder is responsible for two steps of the process
* 1 - It is first queried for how many bytes it will need to the
* array it is supposed to build.
*
* 2 - It is provided with a buffer and it should write initialize
* the array in there. The buffer is guaranteed to fit all data.
*
* Exceptions are treated with care. If at any point, an exception
* is thrown, then all objects already created must be destroyed.
*/
template <class T>
struct ArrayBuilder
{
//! Destroys all elements in case they are still being tracked
~ArrayBuilder()
{
if (m_begin)
reverseDestroy(m_begin, m_end);
}
//! Creates the array with inputs specified by Arg in the given buffer.
template <class InputIt>
auto buildArraybyte* buf, Arg<InputIt>& arg
{
// Find the fist aligned byte suitable for creating T objects
auto b = aligner(buf).get<T>();
auto e = b + arg.m_size;
// In case of an exception, ArrayDeleter will make sure
// all objects created up to the point are destroyed
// in reverse order
ArrayDeleter<T> deleter(b);
for (auto it = b; it != e;)
{
// Special handling for default initialization
if constexpr is_same_v<InputIt, detail::NoIterator>
new (it) T;
else
new (it) T(*arg.m_it++);
// Tell the ArrayDeleter another element was createdgc
deleter.setEnd(++it);
}
// No exception was thrown, let the array deleter stop tracking
// and the ArrayBuilder will track objects from now on
deleter.release();
m_begin = b;
m_end = e;
return reinterpret_cast<std::byte*>(e);
}
//! Query for the number of bytes necessary to create an T array of size
//! "sz"
// "offset" is the offset in an imaginary array starting from 0
template <class InputIt>
static std::size_t numRequiredBytessize_t offset, const Arg<InputIt>& arg
{
auto numBytes = arg.m_size * sizeof(T);
auto newOffset = findNextAlignedPosition(offset, alignof(T));
newOffset += numBytes;
return newOffset - offset;
}
//! Let the array builder stop tracking the array
void release() { m_begin = m_end = nullptr; }
T* m_begin{nullptr};
T* m_end{nullptr};
};
template <class T, class = void>
struct isHandle : std::false_type
{
};
template <class T>
struct isHandle<T, typename void_<typename T::fc_handle_type>::type> : std::true_type
{
using enable = T;
};
/*! Finds handle types in the types passed by the user
* and convert them either to "Ignore" or "ArrayBuilder"
*/
template <class T, class = void>
struct ArrayBuildersConverter;
template <class T>
struct ArrayBuildersConverter<T, typename void_<typename isHandle<T>::enable>::type>
{
using type = ArrayBuilder<typename T::fc_handle_type>;
};
template <class... Args>
auto args(Args&&... args)
{
return ::fc::make_tupleforward<Args>(args))...;
}
template <class Handles>
struct Handles2ArrayBuilders;
template <class... T>
struct Handles2ArrayBuilders<fc::tuple<T*...>>
{
using type = fc::tuple<ArrayBuilder<typename T::fc_handle_type>...>;
};
template <class FC, class Alloc, class AArgs, class... ClassArgs>
auto makeWithAllocator(Alloc& alloc, AArgs&& aArgs, ClassArgs&&... cArgs)
{
using Handles = decltypedeclval<FC>().fc_handles();
std::size_t numBytesForArrays = 0;
for_each_constexpr<Handles>([&](auto* type, auto idx) {
using Element = remove_cvref_t<decltype(**type)>;
using Idx = decltype(idx);
using T = typename Element::fc_handle_type;
ArrayBuilder<T> arrBuilder;
numBytesForArrays += arrBuilder.numRequiredBytes(sizeof(FC) + numBytesForArrays,
aArgs.template get<Idx::value>());
});
auto memBuffer = unique_ptr_impl<void, DeleteFn<FC, Alloc>>(
alloc.allocate(sizeof(FC) + numBytesForArrays), alloc);
FC* ret;
if constexpr is_aggregate_v<FC>
ret = new (memBuffer.get()) FC{std::forward<ClassArgs>(cArgs)...};
else
ret = new (memBuffer.get()) FCforward<ClassArgs>(cArgs)...;
memBuffer.get_deleter().m_objectCreated = true;
// Start creating arrays right after the FC object
std::byte* arrayBuffer = reinterpret_cast<std::byte*>(ret + 1);
using ArrayBuilders = typename Handles2ArrayBuilders<Handles>::type;
ArrayBuilders arrayBuilders;
for_each_in_tuple(arrayBuilders, [&](auto& arrayBuilder, auto idx) mutable {
using Idx = decltype(idx);
arrayBuffer = arrayBuilder.buildArray(arrayBuffer, aArgs.template get<Idx::value>());
});
auto&& handles = ret->fc_handles();
for_each_in_tuple(arrayBuilders, [&](auto& arrayBuilder, auto idx) mutable {
using Idx = decltype(idx);
handles.template get<Idx::value>()->setLocation(arrayBuilder.m_begin, arrayBuilder.m_end);
arrayBuilder.release();
});
memBuffer.release();
return ret;
}
template <class FC, class Alloc>
void destroyWithAllocator(Alloc& alloc, FC* p)
{
if (!p)
return;
auto&& handles = p->fc_handles();
reverse_for_each_in_tuple(handles, [p](auto* handle, auto idx) {
using Handle = remove_cvref_t<decltype(*handle)>;
if constexpr is_trivially_destructible<typename Handle::fc_handle_type>::value
{
reverseDestroy(handle->begin(p), handle->end(p));
}
});
p->~FC();
alloc.deallocate(const_cast<FC*>(p));
}
template <class FC, class AArgs, class... ClassArgs>
auto makeInternal(AArgs&& aArgs, ClassArgs&&... cArgs)
{
NewDeleteAllocator alloc;
return makeWithAllocator<FC>(alloc, std::forward<AArgs>(aArgs),
std::forward<ClassArgs>(cArgs)...);
}
template <class FC>
auto destroy(FC* ptr)
{
NewDeleteAllocator alloc;
return destroyWithAllocator<FC>(alloc, ptr);
}
template <class FC, class Alloc>
auto destroy(FC* ptr, Alloc& alloc)
{
return destroyWithAllocator<FC>(alloc, ptr);
}
template <class FC, class... AArgs>
auto make(AArgs&&... aArgs)
{
return [a = fc::args(aArgs...)](auto&&... cArgs) mutable {
return fc::makeInternal<FC>(a, std::forward<decltype(cArgs)>(cArgs)...);
};
}
template <class FC, class Alloc, class... AArgs>
auto make(WithAllocator, Alloc& alloc, AArgs&&... aArgs)
{
return [a = fc::args(aArgs...), &alloc](auto&&... cArgs) mutable {
return fc::makeWithAllocator<FC>(alloc, a, std::forward<decltype(cArgs)>(cArgs)...);
};
}
template <class T>
struct DestroyFn
{
void operator()(T* t) { fc::destroy(t); }
};
template <class T, class Deleter = fc::DestroyFn<T>>
using unique_ptr = fc::unique_ptr_impl<T, Deleter>;
template <class FC, class... AArgs>
auto make_unique(AArgs&&... aArgs)
{
return [a = fc::args(aArgs...)](auto&&... cArgs) mutable {
return fc::unique_ptr<FC>makeInternal<FC>(a, std::forward<decltype(cArgs)>(cArgs)...);
};
}
} // namespace fc
#endif // FC_FLEXCLASS_CORE_HPP
/*! Contains builtin handle implementations for common applications
*
* A handle must derive from fc::Handle<T> and define the following
* methods:
*
* // Called by the library to let the handle know where T objects
* // were created
*
* void setLocation(T* begin, T* end);
*
* // Called by the library when the user requests the begin of the
* // object sequence
* // The base is passed as a parameter for handles that need to
* // query it
*
* template <class Base>
* auto begin(const Base*) const -> T*;
*
* [optional]
* // Called by the library when the user requests the end of the
* // object sequence
* // The base is passed as a parameter for handles that need to
* // query it
*
* template <class Base>
* auto end(const Base*) const -> T*;
*
*/
namespace fc
{
/*! Uses a pointer to store the location of the first T
* in the sequence.
* Does not know the size of the array.
*/
template <class T>
struct Array : Handle<T>
{
using Handle<T>::Handle;
void setLocation(T* begin, T* end) { m_begin = begin; }
template <class Base = void>
auto begin(const Base* ptr = nullptr) const
{
return m_begin;
}
// auto begin() const { return m_begin; }
T* m_begin;
};
/*! Uses two pointers to store the location of the first T
* and last T of the sequence.
*/
template <class T>
struct Range : Handle<T>
{
using Handle<T>::Handle;
void setLocation(T* begin, T* end)
{
m_begin = begin;
m_end = end;
}
template <class Base>
auto begin(const Base* ptr) const
{
return m_begin;
}
template <class Base>
auto end(const Base* ptr) const
{
return m_end;
}
auto begin() const { return m_begin; }
auto end() const { return m_end; }
T* m_begin;
T* m_end;
};
/*! Uses another handle index to derivate the
* sequence position.
*
* If El * -1, then it assumes the begin is
* adjacent to the Base.
*
* Otherwise it takes the end() of the El handle
* and assumes that is where the T array begins
*/
template <class T, int El = -1>
struct AdjacentArray : Handle<T>
{
using Handle<T>::Handle;
void setLocation(T* begin, T* end) {}
template <class Base>
auto begin(const Base* ptr) const
{
if constexpr (El * -1)
return aligner(ptr, 1).template get<T>();
else
{
auto e = ptr->fc_handles().template get<El>()->end(ptr);
return aligner(e).template get<T>();
}
}
};
/*! Uses another handle index to derivate the
* sequence position.
*
* If El * -1, then it assumes the begin is
* adjacent to the Base.
*
* Otherwise it takes the end() of the El handle
* and assumes that is where the T array begins
*
* Uses an extra T* to store the end of the array
*/
template <class T, int El = -1>
struct AdjacentRange : Handle<T>
{
using Handle<T>::Handle;
void setLocation(T* begin, T* end) { m_end = end; }
template <class Base>
auto begin(const Base* ptr) const
{
if constexpr (El * -1)
return aligner(ptr, 1).template get<T>();
else
return aligner(ptr->template end<El>()).template get<T>();
}
template <class Base>
auto end(const Base* ptr) const
{
return m_end;
}
T* m_end;
};
} // namespace fc
#endif // FC_FLEXCLASS_ARRAYS_HPP
#endif // FC_FLEXCLASS_FLEXCLASS_HPP