Commit 94943e29 authored by Edwin Carlinet's avatar Edwin Carlinet Committed by Michaël Roynard
Browse files

Type-erased interface for ndimage

parent 0b00c490
......@@ -53,7 +53,7 @@ option(PYLENE_BUILD_BENCHMARKS "Require Google Benchmark library. Set to YES to
option(PYLENE_BUILD_LIBS_ONLY "ON to build only the library (packaging)" OFF)
option(PYLENE_BUILD_TESTING "ON to build the test suite" ON)
#### GCC + Clang Compiler configuration
# Compiler configurations
if ((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") OR (CMAKE_CXX_COMPILER_ID STREQUAL "GNU"))
add_compile_options(
"-Wextra"
......
#pragma once
#include <mln/core/image/image2d.hpp>
#include <mln/core/image/experimental/ndimage.hpp>
void Sum(const mln::image2d<mln::uint8>& input, mln::image2d<mln::uint8>& output);
......@@ -9,11 +10,11 @@ void Erosion(const mln::image2d<mln::uint8>& input, mln::image2d<mln::uint8>& ou
void Isotropic_Diffusion(const mln::image2d<mln::uint8>& input, mln::image2d<mln::uint8>& output);
void Anisotropic_Diffusion(const mln::image2d<mln::uint8>& input, mln::image2d<mln::uint8>& output);
void Sum_New(const mln::image2d<mln::uint8>& input, mln::image2d<mln::uint8>& output);
void Average_New(const mln::image2d<mln::uint8>& input, mln::image2d<mln::uint8>& output);
void Erosion_New(const mln::image2d<mln::uint8>& input, mln::image2d<mln::uint8>& output);
void Isotropic_Diffusion_New(const mln::image2d<mln::uint8>& input, mln::image2d<mln::uint8>& output);
void Anisotropic_Diffusion_New(const mln::image2d<mln::uint8>& input, mln::image2d<mln::uint8>& output);
void Sum_New(const mln::experimental::image2d<mln::uint8>& input, mln::experimental::image2d<mln::uint8>& output);
void Average_New(const mln::experimental::image2d<mln::uint8>& input, mln::experimental::image2d<mln::uint8>& output);
void Erosion_New(const mln::experimental::image2d<mln::uint8>& input, mln::experimental::image2d<mln::uint8>& output);
void Isotropic_Diffusion_New(const mln::experimental::image2d<mln::uint8>& input, mln::experimental::image2d<mln::uint8>& output);
void Anisotropic_Diffusion_New(const mln::experimental::image2d<mln::uint8>& input, mln::experimental::image2d<mln::uint8>& output);
void Sum_C(const mln::uint8* __restrict ibuffer, mln::uint8* __restrict obuffer, int width, int height,
std::ptrdiff_t stride);
......
......@@ -27,7 +27,7 @@ void Sum(const mln::image2d<mln::uint8>& input, mln::image2d<mln::uint8>& output
}
}
void Sum_New(const mln::image2d<mln::uint8>& input, mln::image2d<mln::uint8>& output)
void Sum_New(const mln::experimental::image2d<mln::uint8>& input, mln::experimental::image2d<mln::uint8>& output)
{
auto pixels = mln::ranges::view::zip(input.new_pixels(), output.new_pixels());
for (auto rows : pixels.rows())
......@@ -79,7 +79,7 @@ void Sum_C(const mln::uint8* __restrict ibuffer, mln::uint8* __restrict obuffer,
}
}
void Average_New(const mln::image2d<mln::uint8>& input, mln::image2d<mln::uint8>& output)
void Average_New(const mln::experimental::image2d<mln::uint8>& input, mln::experimental::image2d<mln::uint8>& output)
{
auto pixels = mln::ranges::view::zip(input.new_pixels(), output.new_pixels());
for (auto rows : pixels.rows())
......@@ -160,7 +160,7 @@ void Erosion(const mln::image2d<mln::uint8>& input, mln::image2d<mln::uint8>& ou
}
}
void Erosion_New(const mln::image2d<mln::uint8>& input, mln::image2d<mln::uint8>& output)
void Erosion_New(const mln::experimental::image2d<mln::uint8>& input, mln::experimental::image2d<mln::uint8>& output)
{
auto pixels = mln::ranges::view::zip(input.new_pixels(), output.new_pixels());
for (auto rows : pixels.rows())
......@@ -231,7 +231,7 @@ void Isotropic_Diffusion(const mln::image2d<mln::uint8>& input, mln::image2d<mln
}
}
void Isotropic_Diffusion_New(const mln::image2d<mln::uint8>& input, mln::image2d<mln::uint8>& output)
void Isotropic_Diffusion_New(const mln::experimental::image2d<mln::uint8>& input, mln::experimental::image2d<mln::uint8>& output)
{
auto pixels = mln::ranges::view::zip(input.new_pixels(), output.new_pixels());
for (auto rows : pixels.rows())
......@@ -296,7 +296,7 @@ void Anisotropic_Diffusion(const mln::image2d<mln::uint8>& input, mln::image2d<m
}
}
void Anisotropic_Diffusion_New(const mln::image2d<mln::uint8>& input, mln::image2d<mln::uint8>& output)
void Anisotropic_Diffusion_New(const mln::experimental::image2d<mln::uint8>& input, mln::experimental::image2d<mln::uint8>& output)
{
auto pixels = mln::ranges::view::zip(input.new_pixels(), output.new_pixels());
for (auto rows : pixels.rows())
......
#include <mln/core/extension/fill.hpp>
#include <mln/core/image/image2d.hpp>
#include <mln/core/image/experimental/ndimage.hpp>
#include <mln/io/imread.hpp>
#include <fixtures/ImageCompare/image_compare.hpp>
......@@ -18,49 +19,67 @@ class CoreNeighborhood : public ::testing::Test
mln::image2d<mln::rgb8> tmp(0, 0);
mln::io::imread(filename, tmp);
mln::resize(m_input, tmp);
mln::copy(mln::red(tmp), m_input);
mln::extension::fill(m_input, 0);
mln::resize(m_tmp, tmp);
mln::copy(mln::red(tmp), m_tmp);
mln::extension::fill(m_tmp, 0);
m_h = m_input.nrows();
m_w = m_input.ncols();
m_stride = m_input.strides()[0];
m_ibuffer = &m_input.at(0, 0);
m_input = mln::experimental::image2d<uint8_t>::from(m_tmp);
m_h = m_input.height();
m_w = m_input.width();
m_stride = m_input.stride();
m_ibuffer = m_input.buffer();
}
protected:
using fun1_t = std::function<void(const mln::image2d<mln::uint8>&, mln::image2d<mln::uint8>&)>;
using fun1_t = std::function<void(const mln::experimental::image2d<mln::uint8>&, mln::experimental::image2d<mln::uint8>&)>;
using fun2_t = std::function<void(const mln::uint8*, mln::uint8*, int, int, std::ptrdiff_t)>;
using fun3_t = std::function<void(const mln::image2d<mln::uint8>&, mln::image2d<mln::uint8>&)>;
mln::image2d<mln::uint8> run_with(fun1_t f) const;
mln::image2d<mln::uint8> run_with(fun2_t f) const;
mln::experimental::image2d<mln::uint8> run_with(fun1_t f) const;
mln::image2d<mln::uint8> run_with(fun3_t f) const;
mln::experimental::image2d<mln::uint8> run_with(fun2_t f) const;
private:
int m_w, m_h;
std::ptrdiff_t m_stride;
mln::uint8* m_ibuffer;
mln::image2d<mln::uint8> m_input;
mln::image2d<mln::uint8> m_tmp;
mln::experimental::image2d<mln::uint8> m_input;
};
mln::image2d<mln::uint8> CoreNeighborhood::run_with(fun1_t f) const
mln::experimental::image2d<mln::uint8> CoreNeighborhood::run_with(fun1_t f) const
{
mln::image2d<mln::uint8> output;
mln::experimental::image2d<mln::uint8> output;
mln::resize(output, m_input);
f(m_input, output);
return output;
}
mln::image2d<mln::uint8> CoreNeighborhood::run_with(fun2_t f) const
mln::image2d<mln::uint8> CoreNeighborhood::run_with(fun3_t f) const
{
mln::image2d<mln::uint8> output;
mln::resize(output, m_tmp);
f(m_tmp, output);
return output;
}
mln::experimental::image2d<mln::uint8> CoreNeighborhood::run_with(fun2_t f) const
{
mln::experimental::image2d<mln::uint8> output;
mln::resize(output, m_input);
mln::uint8* obuffer = &output.at(0, 0);
mln::uint8* obuffer = output.buffer();
f(m_ibuffer, obuffer, m_w, m_h, m_stride);
return output;
}
TEST_F(CoreNeighborhood, Algorithm_Sum)
{
auto ref = this->run_with(Sum_C);
......@@ -68,9 +87,9 @@ TEST_F(CoreNeighborhood, Algorithm_Sum)
auto ima2 = this->run_with(Sum);
#ifndef PYLENE_GCC8_WORKAROUND
ASSERT_IMAGES_EQ(ima1, ref);
ASSERT_IMAGES_EQ_EXP(ima1, ref);
#endif
ASSERT_IMAGES_EQ(ima2, ref);
ASSERT_IMAGES_EQ_EXP(mln::experimental::image2d<uint8_t>::from(ima2), ref);
}
......@@ -80,8 +99,8 @@ TEST_F(CoreNeighborhood, Algorithm_Average)
auto ima1 = this->run_with(Average_New);
auto ima2 = this->run_with(Average);
ASSERT_IMAGES_EQ(ima1, ref);
ASSERT_IMAGES_EQ(ima2, ref);
ASSERT_IMAGES_EQ_EXP(ima1, ref);
ASSERT_IMAGES_EQ_EXP(mln::experimental::image2d<uint8_t>::from(ima2), ref);
}
......@@ -91,8 +110,8 @@ TEST_F(CoreNeighborhood, Algorithm_Erosion)
auto ima1 = this->run_with(Erosion_New);
auto ima2 = this->run_with(Erosion);
ASSERT_IMAGES_EQ(ima1, ref);
ASSERT_IMAGES_EQ(ima2, ref);
ASSERT_IMAGES_EQ_EXP(ima1, ref);
ASSERT_IMAGES_EQ_EXP(mln::experimental::image2d<uint8_t>::from(ima2), ref);
}
TEST_F(CoreNeighborhood, Algorithm_Isotropic_Diffusion)
......@@ -101,8 +120,8 @@ TEST_F(CoreNeighborhood, Algorithm_Isotropic_Diffusion)
auto ima1 = this->run_with(Isotropic_Diffusion_New);
auto ima2 = this->run_with(Isotropic_Diffusion);
ASSERT_IMAGES_EQ(ima1, ref);
ASSERT_IMAGES_EQ(ima2, ref);
ASSERT_IMAGES_EQ_EXP(ima1, ref);
ASSERT_IMAGES_EQ_EXP(mln::experimental::image2d<uint8_t>::from(ima2), ref);
}
TEST_F(CoreNeighborhood, Algorithm_Anisotropic_Diffusion)
......@@ -111,6 +130,6 @@ TEST_F(CoreNeighborhood, Algorithm_Anisotropic_Diffusion)
auto ima1 = this->run_with(Anisotropic_Diffusion_New);
auto ima2 = this->run_with(Anisotropic_Diffusion);
ASSERT_IMAGES_EQ(ima1, ref);
ASSERT_IMAGES_EQ(ima2, ref);
ASSERT_IMAGES_EQ_EXP(ima1, ref);
ASSERT_IMAGES_EQ_EXP(mln::experimental::image2d<uint8_t>::from(ima2), ref);
}
......@@ -44,6 +44,20 @@ See :doc:`core/images` for a description of the image concepts and image basics.
| :cpp:func:`resize(g,f) <mln::resize>` | Resize `g` to the *geometry* of `f` |
+-------------------------------------------------+------------------------------------------------------------------------------------------+
.. topic:: Fundamental types
.. table::
:class: full
:widths: auto
+----------------------------------------------+--------------------------------------------------------------------------+
| :cpp:class:`ndpoint` :cpp:class:`ndpointref` | Generic :doc:`point <core/point>` that hold *n* coordinates |
+----------------------------------------------+--------------------------------------------------------------------------+
| :cpp:class:`ndbox` :cpp:class:`ndboxref` | Generic :doc:`core/box` in *n* dimension |
+----------------------------------------------+--------------------------------------------------------------------------+
.. topic:: Fundamental image types
.. table::
......@@ -273,5 +287,3 @@ Fundamental primitives for basic image manipulation. Those are grouped by:
:doc:`core/stl` are the fundamentals concepts of the C++ standard library that we are building our concept upon. They are our building blocks.
ND Points
#########
Include :file:`<mln/core/point.hpp>`
.. image:: /figures/core/ndpoint.svg
:width: 100%
Class hierarchy diagrams for ndpoints. They all implement the :cpp:class:`__PointInterface` but differ in the storage and the compile-time number of dimension:
* `ndpoint` provide points type with value semantics (they own the coordinates array). `Point` uses a dynamic array while `point*N*d` use a static array storage
* `nbpointref` provide points type with reference semantics (they borrow the coordinates array). `PointRef` and `ConstPointRef` supports dynamic array bound while `pointNd_ref` and `const_pointNd_ref` provide compile-time bound checking.
.. cpp:class:: template <int ndim, class T> ndpoint
`ndpoint` represents a n-dimensional points (coordinates) over a grid. The number of dimensions can be known at
compile time or dynamic ``ndim = -1``. It is a container (own the value).
.. cpp:function:: ndpoint() = default
Default constructor.
.. cpp:function:: ndpoint(const P& other)
Converting constructor from any point implementing the :cpp:class:`__PointInterface`. This overload is enabled only if `P` is compatible with ``ndpoint`` i.e. if ``P::value_type`` is convertible to ``T`` and
``ndim == (-1 || other.ndim)``.
.. cpp:function:: ndpoint(int dim)
Construction with the number of dimensions given dynamically. Only available when ``ndim == -1``.
.. cpp:class:: template <int ndim, class T> ndpointref
`ndpointref` represents a n-dimensional points (coordinates) over a grid. The number of dimensions can be known at
compile time or dynamic ``ndim = -1``. It has a reference semantic and should be used as a function parameter only.
1. .. cpp:function:: ndpointref(P& other)
2. .. cpp:function:: ndpointref(const P& other)
Converting constructor from any point implementing the :cpp:class:`__PointInterface`. This overload is enabled only if `P` is compatible with ``ndpointref``, i.e. if ``P::value_type*`` is convertible to ``T*`` and ``ndim == (-1 || other.ndim)``.
.. cpp:function:: ndpoint(int dim, T* coordinates)
Constructor from an array in the form of a pair (pointer, size).
Aliases
=======
.. cpp:type:: Point = ndpoint<-1, int>
.. cpp:type:: point1d = ndpoint<1, int>
.. cpp:type:: point2d = ndpoint<2, int>
.. cpp:type:: point3d = ndpoint<3, int>
.. cpp:type:: PointRef = ndpointref<-1, int>
.. cpp:type:: point1d_ref = ndpointref<1, int>
.. cpp:type:: point2d_ref = ndpointref<2, int>
.. cpp:type:: point3d_ref = ndpointref<3, int>
.. cpp:type:: ConstPointRef = ndpointref<-1, const int>
.. cpp:type:: const_point1d_ref = ndpointref<1, const int>
.. cpp:type:: const_point2d_ref = ndpointref<2, const int>
.. cpp:type:: const_point3d_ref = ndpointref<3, const int>
Read-Only Point Interface
=========================
.. cpp:class:: template <int ndim, class T> __PointInterface
.. cpp:type:: value_type = T
.. cpp:function:: __PointInterface(std::initializer_list<T> coordinates)
Construction from a list of coordinates
.. cpp:function:: const T* data() const
Return a pointer to the coordinates array.
.. cpp:function:: int dim() const
return the number of dimension
.. cpp:function:: T operator[] (int k) const
Return the coordinate in the k-th dimension.
.. cpp:function:: T x()
Get the x-coordinate (available when ``dim() >= 1``)
.. cpp:function:: T y()
Get the y-coordinate (available when ``dim() >= 2``)
.. cpp:function:: T z()
Get the z-coordinate (available when ``dim() >= 3``)
.. rubric:: Point Interopability
Any two points p₁ and p₂ of types P₁ and P₂ are said *compatible* if their value types are *compatible* and if they have the same number of dimensions (``p₁.dim() == p₂.dim()``). They thus share a `common_reference` (:cpp:concept:`std::CommonReference˂P₁,P₂˃ <std::CommonReference>`).
* Conversion
Two *compatible* points are convertible to each other (:cpp:concept:`std::Convertible`).
Example::
mln::point2d p1 = {x, y};
mln::ndpoint<2, long> p2 = p1; // Ok (int to long conversion)
mln::Point p3 = p1; // Ok (static to dynamic conversion)
mln::point2d p4 = p3; // Ok (dynamic to static conversion with run-time dim assertion)
* Comparison
Two *compatible* points are comparable and totally ordered (:cpp:concept:`std::StrictTotallyOrdered`) using the lexicographical ordering.
* Arithmetics
Two *compatible* points are :cpp:concept:`Addable` using usual arithmetic rules.
Mutable Point Interface
=======================
.. cpp:class:: template <int ndim, class T> __MutablePointInterface
.. cpp:type:: value_type = T
.. cpp:function:: __MutablePointInterface(std::initializer_list<T> coordinates)
Construction from a list of coordinates
.. cpp:function:: T* data()
Return a pointer to the mutable coordinates array.
.. cpp:function:: int dim() const
return the number of dimension
.. cpp:function:: T& operator[] (int k)
Return the coordinate in the k-th dimension.
.. cpp:function:: T& x()
Get the x-coordinate (available when ``dim() >= 1``)
.. cpp:function:: T& y()
Get the y-coordinate (available when ``dim() >= 2``)
.. cpp:function:: T& z()
Get the z-coordinate (available when ``dim() >= 3``)
This diff is collapsed.
\documentclass[svgnames,tikz]{standalone}
\usetikzlibrary{positioning,arrows.meta,calc, backgrounds}
\usepackage{lmodern}
\input{uml.tikz}
\begin{document}
\begin{tikzpicture}[
every node/.style={font=\tt},
edge from parent path={[latex-] (\tikzparentnode.south) -- ++(0,-.5) -| (\tikzchildnode)}
]
\node[align=center,below] (PI) at (0,0) {\_\_BoxInterface\\};
\node[align=center] (MPI) [below left=0cm and 1.5 cm of PI, anchor=north] {\_\_MutableBoxInterface\\};
\node[class, template={ndim}] (ndbox) [below left=1cm and 1.5 cm of PI, anchor=north] {ndbox};
\node[class, template={ndim}] (ndboxref) [below right=1cm and 1.5 cm of PI, anchor=north] {ndboxref};
\node[class, below left=2cm and 0.25cm of ndbox.south, minimum width=1.5cm] (Box){Box};
\node[class, below right=2cm and 0.25cm of ndbox.south, minimum width=1.5cm] (boxnd){box\emph{N}d};
\draw[realization] (Box) -- (ndbox);
\draw[realization] (boxnd) -- (ndbox);
\node[above=0.25cm of Box, font=\small, align=center] { <<$\mathrm{ndim}=-1$>> };
\node[above=0.25cm of boxnd, font=\small, align=center] { <<$\mathrm{ndim}=N$>> };
\node[class,below left=2cm and 0.25cm of ndboxref.south] (BoxRef) {ConstBoxRef};
\node[class,below right=2cm and 0.25cm of ndboxref.south] (boxndref) {const\_boxNd\_ref};
\draw[realization](BoxRef) -- (ndboxref);
\draw[realization](boxndref) -- (ndboxref);
\node[above=0.25cm of BoxRef, font=\small, align=center] { <<$\mathrm{ndim}=-1$>> };
\node[above=0.25cm of boxndref, font=\small, align=center] { <<$\mathrm{ndim}=N$>> };
\begin{scope}[on background layer]
\draw[fill=LightGreen] (MPI.west |- PI.north) rectangle (boxndref.south east);
\draw[fill=LightBlue] (MPI.north west) rectangle (MPI.east |- boxnd.south);
\end{scope}
% \draw [latex-] (PI.340) -- ++(0,-.5) -| (CPR);
\end{tikzpicture}
\end{document}
\ No newline at end of file
This diff is collapsed.
\documentclass[svgnames,tikz]{standalone}
\usetikzlibrary{positioning,arrows.meta,calc}
\usepackage{lmodern}
\input{uml.tikz}
\begin{document}
\begin{tikzpicture}[
every node/.style={font=\tt},
edge from parent path={[latex-] (\tikzparentnode.south) -- ++(0,-.5) -| (\tikzchildnode)}
]
\draw[fill=LightGreen] (-5,1) rectangle (14,-6);
\node[align=center,below] (PI) at (4.5,1) {\_\_PointInterface\\};
% \node (CPR) at (5,-2) {pointref<ndim, const T>};
\draw[fill=LightBlue] (-5,-.5) rectangle (6,-6);
\node[align=center,below] (PI) at (0.5,-.5) {\_\_MutablePointInterface\\};
\node[class, template={ndim,T}] (ndpoint) at (-3,-2){ndpoint};
\node[class, template={ndim,T}] (ndpointref) at (+3,-2){ndpointref};
\node[class, below left=2cm and 0.25cm of ndpoint.south] (Point){Point};
\node[class, below right=2cm and 0.25cm of ndpoint.south, name=pointnd] (pointnd){point\emph{N}d};
\draw[realization] (Point) -- (ndpoint);
\draw[realization] (pointnd) -- (ndpoint);
\node[above=0.25cm of Point, font=\small, align=center] { <<$\mathrm{ndim}=-1$>>\\ <<T=int>> };
\node[above=0.25cm of pointnd, font=\small, align=center] { <<$\mathrm{ndim}=N$>>\\ <<T=int>> };
\node[class,below left=2cm and 0.25cm of ndpointref.south] (PointRef) {PointRef};
\node[class,below right=2cm and 0.25cm of ndpointref.south] (pointndref) {pointNd\_ref};
\draw[realization](PointRef) -- (ndpointref);
\draw[realization](pointndref) -- (ndpointref);
\node[above=0.25cm of PointRef, font=\small, align=center] { <<$\mathrm{ndim}=-1$>>\\ <<T=int>> };
\node[above=0.25cm of pointndref, font=\small, align=center] { <<$\mathrm{ndim}=N$>>\\ <<T=int>> };
\node[class,below right=2cm and 4cm of ndpointref.south] (ConstPointRef) {ConstPointRef};
\node[class,right=0.5cm of ConstPointRef] (constpointndref) {const\_pointNd\_ref};
\coordinate (U) at ($ (ConstPointRef)!0.5!(constpointndref) + (0,1.5) $);
\draw[realization](ConstPointRef) |- (U) -- ++(0,0.5) -| (ndpointref);
\draw[realization](constpointndref) |- (U) -- ++(0,0.5) -| (ndpointref);
\node[above=0.25cm of ConstPointRef, font=\small, align=center] { <<$\mathrm{ndim}=-1$>>\\ <<T=const int>> };
\node[above=0.25cm of constpointndref, font=\small, align=center] { <<$\mathrm{ndim}=N$>>\\ <<T=const int>> };
% \draw [latex-] (PI.340) -- ++(0,-.5) -| (CPR);
\end{tikzpicture}
\end{document}
\ No newline at end of file
......@@ -73,7 +73,7 @@ namespace fixtures::ImageCompare
static_assert(mln::is_a<ImageRhs, mln::experimental::Image>());
auto f_dom = f.domain();
auto g_dom = f.domain();
auto g_dom = g.domain();
auto f_dom_b = ::ranges::begin(f_dom);
auto g_dom_b = ::ranges::begin(g_dom);
......
......@@ -46,6 +46,10 @@ target_sources(Pylene PRIVATE
src/core/se/rect2d.cpp
src/core/se/mask2d.cpp
src/core/trace.cpp
src/core/init_list.cpp
src/core/image_format.cpp
src/core/ndbuffer_image_data.cpp
src/core/ndbuffer_image.cpp
)
# Compiler configurations
......
......@@ -28,7 +28,7 @@ namespace mln
template <typename OutputImage, typename Value>
void iota(OutputImage output, Value val)
{
static_assert(mln::is_a<OutputImage, Image>());
static_assert(mln::is_a<OutputImage, mln::Image>::value || mln::is_a<OutputImage, mln::experimental::Image>::value);
static_assert(std::is_convertible_v<Value, image_value_t<OutputImage>>);
auto&& vals = output.new_values();
......
#pragma once
#include <mln/core/rangev3/multi_indices.hpp>
#include <mln/core/experimental/point.hpp>
#include <type_traits>
#ifdef PYLENE_CONCEPT_TS_ENABLED
#include <stl2/type_traits.hpp>
#endif
namespace mln::experimental
{
// Forward declaration
template <class Impl>
class _box;
namespace impl
{
template <int D, typename T = int>
struct _bstorage;
template <int D, class T>
struct _bref;
};
/******************************************/
/**** Box Types and Types Aliases ****/
/******************************************/
using Box = _box<impl::_bstorage<-1, int>>;
using ConstBoxRef = _box<impl::_bref<-1, const int>>;
template <int dim>
using ndbox = _box<impl::_bstorage<dim, int>>;
template <int dim>
using ndboxref = _box<impl::_bref<dim, const int>>;
using box1d = ndbox<1>;
using box2d = ndbox<2>;
using box3d = ndbox<3>;
using const_box1d_ref = ndboxref<1>;
using const_box2d_ref = ndboxref<2>;
using const_box3d_ref = ndboxref<3>;
/******************************************/
/**** Box Interface *****/