Logo ROOT   6.30.04
Reference Guide
 All Namespaces Files Pages
RHistImpl.hxx
Go to the documentation of this file.
1 /// \file ROOT/RHistImpl.h
2 /// \ingroup Hist ROOT7
3 /// \author Axel Naumann <axel@cern.ch>
4 /// \date 2015-03-23
5 /// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
6 /// is welcome!
7 
8 /*************************************************************************
9  * Copyright (C) 1995-2015, Rene Brun and Fons Rademakers. *
10  * All rights reserved. *
11  * *
12  * For the licensing terms see $ROOTSYS/LICENSE. *
13  * For the list of contributors see $ROOTSYS/README/CREDITS. *
14  *************************************************************************/
15 
16 #ifndef ROOT7_RHistImpl
17 #define ROOT7_RHistImpl
18 
19 #include <cctype>
20 #include <functional>
21 #include "ROOT/RSpan.hxx"
22 #include "ROOT/RTupleApply.hxx"
23 
24 #include "ROOT/RAxis.hxx"
25 #include "ROOT/RHistBinIter.hxx"
26 #include "ROOT/RHistUtils.hxx"
27 
28 class TRootIOCtor;
29 
30 namespace ROOT {
31 namespace Experimental {
32 
33 template <int DIMENSIONS, class PRECISION, template <int D_, class P_> class... STAT>
34 class RHist;
35 
36 namespace Hist {
37 /// Iterator over n dimensional axes - an array of n axis iterators.
38 template <int NDIM>
39 using AxisIter_t = std::array<RAxisBase::const_iterator, NDIM>;
40 /// Range over n dimensional axes - a pair of arrays of n axis iterators.
41 template <int NDIM>
42 using AxisIterRange_t = std::array<AxisIter_t<NDIM>, 2>;
43 
44 /// Kinds of under- and overflow handling.
45 enum class EOverflow {
46  kNoOverflow = 0x0, ///< Exclude under- and overflows
47  kUnderflow = 0x1, ///< Include underflows
48  kOverflow = 0x2, ///< Include overflows
49  kUnderOver = 0x3, ///< Include both under- and overflows
50 };
51 
52 inline bool operator&(EOverflow a, EOverflow b)
53 {
54  return static_cast<int>(a) & static_cast<int>(b);
55 }
56 } // namespace Hist
57 
58 namespace Detail {
59 
60 /**
61  \class RHistImplPrecisionAgnosticBase
62  Base class for RHistImplBase that abstracts out the histogram's PRECISION.
63 
64  For operations such as painting a histogram, the PRECISION (type of the bin
65  content) is not relevant; painting will cast the underlying bin type to double.
66  To facilitate this, RHistImplBase itself inherits from the
67  RHistImplPrecisionAgnosticBase interface.
68  */
69 template <int DIMENSIONS>
70 class RHistImplPrecisionAgnosticBase {
71 public:
72  /// Type of the coordinate: a DIMENSIONS-dimensional array of doubles.
73  using CoordArray_t = Hist::CoordArray_t<DIMENSIONS>;
74  /// Range type.
75  using AxisIterRange_t = Hist::AxisIterRange_t<DIMENSIONS>;
76 
77  RHistImplPrecisionAgnosticBase() = default;
78  RHistImplPrecisionAgnosticBase(const RHistImplPrecisionAgnosticBase &) = default;
79  RHistImplPrecisionAgnosticBase(RHistImplPrecisionAgnosticBase &&) = default;
80  RHistImplPrecisionAgnosticBase(std::string_view title): fTitle(title) {}
81  virtual ~RHistImplPrecisionAgnosticBase() {}
82 
83  /// Number of dimensions of the coordinates
84  static constexpr int GetNDim() { return DIMENSIONS; }
85  /// Number of bins of this histogram, including all overflow and underflow
86  /// bins. Simply the product of all axes' number of bins.
87  virtual int GetNBins() const noexcept = 0;
88 
89  /// Get the histogram title.
90  const std::string &GetTitle() const { return fTitle; }
91 
92  /// Given the coordinate `x`, determine the index of the bin.
93  virtual int GetBinIndex(const CoordArray_t &x) const = 0;
94  /// Given the coordinate `x`, determine the index of the bin, possibly growing
95  /// axes for which `x` is out of range.
96  virtual int GetBinIndexAndGrow(const CoordArray_t &x) = 0;
97 
98  /// Get the center in all dimensions of the bin with index `binidx`.
99  virtual CoordArray_t GetBinCenter(int binidx) const = 0;
100  /// Get the lower edge in all dimensions of the bin with index `binidx`.
101  virtual CoordArray_t GetBinFrom(int binidx) const = 0;
102  /// Get the upper edge in all dimensions of the bin with index `binidx`.
103  virtual CoordArray_t GetBinTo(int binidx) const = 0;
104 
105  /// The bin's uncertainty. size() of the vector is a multiple of 2:
106  /// several kinds of uncertainty, same number of entries for lower and upper.
107  virtual double GetBinUncertainty(int binidx) const = 0;
108 
109  /// Whether this histogram's statistics provide storage for uncertainties, or
110  /// whether uncertainties are determined as poisson uncertainty of the content.
111  virtual bool HasBinUncertainty() const = 0;
112 
113  /// The bin content, cast to double.
114  virtual double GetBinContentAsDouble(int binidx) const = 0;
115 
116  /// Get a RAxisView on axis with index iAxis.
117  ///
118  /// \param iAxis - index of the axis, must be 0 <= iAxis < DIMENSION
119  virtual RAxisView GetAxis(int iAxis) const = 0;
120 
121  /// Get a AxisIterRange_t for the whole histogram, possibly restricting the
122  /// range to non-overflow bins.
123  ///
124  /// \param withOverUnder - specifies for each dimension whether under and
125  /// overflow should be included in the returned range.
126  virtual AxisIterRange_t GetRange(const std::array<Hist::EOverflow, DIMENSIONS> &withOverUnder) const = 0;
127 
128 private:
129  std::string fTitle; ///< Histogram title.
130 };
131 
132 /**
133  \class RHistImplBase
134  Interface class for RHistImpl.
135 
136  RHistImpl is templated for a specific configuration of axes. To enable access
137  through RHist, RHistImpl inherits from RHistImplBase, exposing only dimension
138  (`DIMENSION`) and bin type (`PRECISION`).
139  */
140 template <class DATA>
141 class RHistImplBase: public RHistImplPrecisionAgnosticBase<DATA::GetNDim()> {
142 public:
143  /// Type of the statistics (bin content, uncertainties etc).
144  using Stat_t = DATA;
145  /// Type of the coordinate: a DIMENSIONS-dimensional array of doubles.
146  using CoordArray_t = Hist::CoordArray_t<DATA::GetNDim()>;
147  /// Type of the bin content (and thus weights).
148  using Weight_t = typename DATA::Weight_t;
149 
150  /// Type of the Fill(x, w) function
151  using FillFunc_t = void (RHistImplBase::*)(const CoordArray_t &x, Weight_t w);
152 
153 private:
154  /// The histogram's bin content, uncertainties etc.
155  Stat_t fStatistics;
156 
157 public:
158  RHistImplBase() = default;
159  RHistImplBase(size_t numBins): fStatistics(numBins) {}
160  RHistImplBase(std::string_view title, size_t numBins)
161  : RHistImplPrecisionAgnosticBase<DATA::GetNDim()>(title), fStatistics(numBins)
162  {}
163  RHistImplBase(const RHistImplBase &) = default;
164  RHistImplBase(RHistImplBase &&) = default;
165 
166  virtual std::unique_ptr<RHistImplBase> Clone() const = 0;
167 
168  /// Interface function to fill a vector or array of coordinates with
169  /// corresponding weights.
170  /// \note the size of `xN` and `weightN` must be the same!
171  virtual void FillN(const std::span<const CoordArray_t> xN, const std::span<const Weight_t> weightN) = 0;
172 
173  /// Interface function to fill a vector or array of coordinates.
174  virtual void FillN(const std::span<const CoordArray_t> xN) = 0;
175 
176  /// Retrieve the pointer to the overridden Fill(x, w) function.
177  virtual FillFunc_t GetFillFunc() const = 0;
178 
179  /// Apply a function (lambda) to all bins of the histogram. The function takes
180  /// the bin reference.
181  virtual void Apply(std::function<void(RHistBinRef<const RHistImplBase>)>) const = 0;
182 
183  /// Apply a function (lambda) to all bins of the histogram. The function takes
184  /// the bin coordinate and content.
185  virtual void ApplyXC(std::function<void(const CoordArray_t &, Weight_t)>) const = 0;
186 
187  /// Apply a function (lambda) to all bins of the histogram. The function takes
188  /// the bin coordinate, content and uncertainty ("error") of the content.
189  virtual void ApplyXCE(std::function<void(const CoordArray_t &, Weight_t, double)>) const = 0;
190 
191  /// Get the bin content (sum of weights) for the bin at coordinate x.
192  virtual Weight_t GetBinContent(const CoordArray_t &x) const = 0;
193 
194  using RHistImplPrecisionAgnosticBase<DATA::GetNDim()>::GetBinUncertainty;
195 
196  /// Get the bin uncertainty for the bin at coordinate x.
197  virtual double GetBinUncertainty(const CoordArray_t &x) const = 0;
198 
199  /// Get the number of bins in this histogram, including possible under- and
200  /// overflow bins.
201  int GetNBins() const noexcept final { return fStatistics.size(); }
202 
203  /// Get the bin content (sum of weights) for bin index `binidx`.
204  Weight_t GetBinContent(int binidx) const { return fStatistics[binidx]; }
205 
206  /// Get the bin content (sum of weights) for bin index `binidx` (non-const).
207  Weight_t &GetBinContent(int binidx) { return fStatistics[binidx]; }
208 
209  /// Const access to statistics.
210  const Stat_t &GetStat() const noexcept { return fStatistics; }
211 
212  /// Non-const access to statistics.
213  Stat_t &GetStat() noexcept { return fStatistics; }
214 
215  /// Get the bin content (sum of weights) for bin index `binidx`, cast to
216  /// double.
217  double GetBinContentAsDouble(int binidx) const final { return (double)GetBinContent(binidx); }
218 
219  /// Add `w` to the bin at index `bin`.
220  void AddBinContent(int binidx, Weight_t w) { fStatistics[binidx] += w; }
221 };
222 } // namespace Detail
223 
224 namespace Internal {
225 /** \name Histogram traits
226  Helper traits for histogram operations.
227  */
228 ///\{
229 
230 /// \name Axis tuple operations
231 /// Template operations on axis tuple.
232 ///@{
233 template <int IDX, class AXISTUPLE>
234 struct RGetBinCount;
235 
236 template <class AXES>
237 struct RGetBinCount<0, AXES> {
238  int operator()(const AXES &axes) const { return std::get<0>(axes).GetNBins(); }
239 };
240 
241 template <int I, class AXES>
242 struct RGetBinCount {
243  int operator()(const AXES &axes) const { return std::get<I>(axes).GetNBins() * RGetBinCount<I - 1, AXES>()(axes); }
244 };
245 
246 template <class... AXISCONFIG>
247 int GetNBinsFromAxes(AXISCONFIG... axisArgs)
248 {
249  using axesTuple = std::tuple<AXISCONFIG...>;
250  return RGetBinCount<sizeof...(AXISCONFIG) - 1, axesTuple>()(axesTuple{axisArgs...});
251 }
252 
253 template <int IDX, class HISTIMPL, class AXES, bool GROW>
254 struct RGetBinIndex;
255 
256 // Break recursion
257 template <class HISTIMPL, class AXES, bool GROW>
258 struct RGetBinIndex<-1, HISTIMPL, AXES, GROW> {
259  int operator()(HISTIMPL *, const AXES &, const typename HISTIMPL::CoordArray_t &,
260  RAxisBase::EFindStatus &status) const
261  {
262  status = RAxisBase::EFindStatus::kValid;
263  return 0;
264  }
265 };
266 
267 template <int I, class HISTIMPL, class AXES, bool GROW>
268 struct RGetBinIndex {
269  int operator()(HISTIMPL *hist, const AXES &axes, const typename HISTIMPL::CoordArray_t &x,
270  RAxisBase::EFindStatus &status) const
271  {
272  constexpr const int thisAxis = HISTIMPL::GetNDim() - I - 1;
273  int bin = std::get<thisAxis>(axes).FindBin(x[thisAxis]);
274  if (GROW && std::get<thisAxis>(axes).CanGrow() && (bin < 0 || bin >= std::get<thisAxis>(axes).GetNBinsNoOver())) {
275  hist->GrowAxis(thisAxis, x[thisAxis]);
276  status = RAxisBase::EFindStatus::kCanGrow;
277 
278  // Abort bin calculation; we don't care. Let RHist::GetBinIndex() retry!
279  return bin;
280  }
281  return bin +
282  RGetBinIndex<I - 1, HISTIMPL, AXES, GROW>()(hist, axes, x, status) * std::get<thisAxis>(axes).GetNBins();
283  }
284 };
285 
286 template <int I, class AXES>
287 struct RFillIterRange;
288 
289 // Break recursion.
290 template <class AXES>
291 struct RFillIterRange<-1, AXES> {
292  void operator()(Hist::AxisIterRange_t<std::tuple_size<AXES>::value> & /*range*/, const AXES & /*axes*/,
293  const std::array<Hist::EOverflow, std::tuple_size<AXES>::value> & /*over*/) const
294  {}
295 };
296 
297 /** Fill `range` with begin() and end() of all axes, including under/overflow
298  as specified by `over`.
299 */
300 template <int I, class AXES>
301 struct RFillIterRange {
302  void operator()(Hist::AxisIterRange_t<std::tuple_size<AXES>::value> &range, const AXES &axes,
303  const std::array<Hist::EOverflow, std::tuple_size<AXES>::value> &over) const
304  {
305  if (over[I] & Hist::EOverflow::kUnderflow)
306  range[0][I] = std::get<I>(axes).begin_with_underflow();
307  else
308  range[0][I] = std::get<I>(axes).begin();
309  if (over[I] & Hist::EOverflow::kOverflow)
310  range[1][I] = std::get<I>(axes).end_with_overflow();
311  else
312  range[1][I] = std::get<I>(axes).end();
313  RFillIterRange<I - 1, AXES>()(range, axes, over);
314  }
315 };
316 
317 enum class EBinCoord {
318  kBinFrom, ///< Get the lower bin edge
319  kBinCenter, ///< Get the bin center
320  kBinTo ///< Get the bin high edge
321 };
322 
323 template <int I, int NDIM, class COORD, class AXES>
324 struct RFillBinCoord;
325 
326 // Break recursion.
327 template <int NDIM, class COORD, class AXES>
328 struct RFillBinCoord<-1, NDIM, COORD, AXES> {
329  void operator()(COORD & /*coord*/, const AXES & /*axes*/, EBinCoord /*kind*/, int /*binidx*/) const {}
330 };
331 
332 /** Fill `coord` with low bin edge or center or high bin edge of all axes.
333  */
334 template <int I, int NDIM, class COORD, class AXES>
335 struct RFillBinCoord {
336  void operator()(COORD &coord, const AXES &axes, EBinCoord kind, int binidx) const
337  {
338  constexpr const int thisAxis = NDIM - I - 1;
339  int axisbin = binidx % std::get<thisAxis>(axes).GetNBins();
340  switch (kind) {
341  case EBinCoord::kBinFrom: coord[thisAxis] = std::get<thisAxis>(axes).GetBinFrom(axisbin); break;
342  case EBinCoord::kBinCenter: coord[thisAxis] = std::get<thisAxis>(axes).GetBinCenter(axisbin); break;
343  case EBinCoord::kBinTo: coord[thisAxis] = std::get<thisAxis>(axes).GetBinTo(axisbin); break;
344  }
345  RFillBinCoord<I - 1, NDIM, COORD, AXES>()(coord, axes, kind, binidx / std::get<thisAxis>(axes).GetNBins());
346  }
347 };
348 
349 template <class... AXISCONFIG>
350 static std::array<RAxisView, sizeof...(AXISCONFIG)> GetAxisView(const AXISCONFIG &... axes) noexcept
351 {
352  std::array<RAxisView, sizeof...(AXISCONFIG)> axisViews = {{RAxisView(axes)...}};
353  return axisViews;
354 }
355 
356 ///\}
357 } // namespace Internal
358 
359 namespace Detail {
360 
361 template <class DATA, class... AXISCONFIG>
362 class RHistImpl final: public RHistImplBase<DATA> {
363  static_assert(sizeof...(AXISCONFIG) == DATA::GetNDim(), "Number of axes must equal histogram dimension");
364 
365  friend typename DATA::Hist_t;
366 
367 public:
368  using ImplBase_t = RHistImplBase<DATA>;
369  using CoordArray_t = typename ImplBase_t::CoordArray_t;
370  using Weight_t = typename ImplBase_t::Weight_t;
371  using typename ImplBase_t::FillFunc_t;
372  template <int NDIM = DATA::GetNDim()>
373  using AxisIterRange_t = typename Hist::AxisIterRange_t<NDIM>;
374 
375 private:
376  std::tuple<AXISCONFIG...> fAxes; ///< The histogram's axes
377 
378 public:
379  RHistImpl(TRootIOCtor *);
380  RHistImpl(AXISCONFIG... axisArgs);
381  RHistImpl(std::string_view title, AXISCONFIG... axisArgs);
382 
383  std::unique_ptr<ImplBase_t> Clone() const override {
384  return std::unique_ptr<ImplBase_t>(new RHistImpl(*this));
385  }
386 
387  /// Retrieve the fill function for this histogram implementation, to prevent
388  /// the virtual function call for high-frequency fills.
389  FillFunc_t GetFillFunc() const final { return (FillFunc_t)&RHistImpl::Fill; }
390 
391  /// Apply a function (lambda) to all bins of the histogram. The function takes
392  /// the bin reference.
393  void Apply(std::function<void(RHistBinRef<const ImplBase_t>)> op) const final
394  {
395  for (RHistBinRef<const ImplBase_t> binref: *this)
396  op(binref);
397  }
398 
399  /// Apply a function (lambda) to all bins of the histogram. The function takes
400  /// the bin coordinate and content.
401  void ApplyXC(std::function<void(const CoordArray_t &, Weight_t)> op) const final
402  {
403  for (auto binref: *this)
404  op(binref.GetCenter(), binref.GetContent());
405  }
406 
407  /// Apply a function (lambda) to all bins of the histogram. The function takes
408  /// the bin coordinate, content and uncertainty ("error") of the content.
409  virtual void ApplyXCE(std::function<void(const CoordArray_t &, Weight_t, double)> op) const final
410  {
411  for (auto binref: *this)
412  op(binref.GetCenter(), binref.GetContent(), binref.GetUncertainty());
413  }
414 
415  /// Get the axes of this histogram.
416  const std::tuple<AXISCONFIG...> &GetAxes() const { return fAxes; }
417 
418  /// Normalized axes access, converting the actual axis to RAxisConfig
419  RAxisView GetAxis(int iAxis) const final { return std::apply(Internal::GetAxisView<AXISCONFIG...>, fAxes)[iAxis]; }
420 
421  /// Gets the bin index for coordinate `x`; returns -1 if there is no such bin,
422  /// e.g. for axes without over / underflow but coordinate out of range.
423  int GetBinIndex(const CoordArray_t &x) const final
424  {
425  RAxisBase::EFindStatus status = RAxisBase::EFindStatus::kValid;
426  int ret =
427  Internal::RGetBinIndex<DATA::GetNDim() - 1, RHistImpl, decltype(fAxes), false>()(nullptr, fAxes, x, status);
428  if (status != RAxisBase::EFindStatus::kValid)
429  return -1;
430  return ret;
431  }
432 
433  /// Gets the bin index for coordinate `x`, growing the axes as needed and
434  /// possible. Returns -1 if there is no such bin,
435  /// e.g. for axes without over / underflow but coordinate out of range.
436  int GetBinIndexAndGrow(const CoordArray_t &x) final
437  {
438  RAxisBase::EFindStatus status = RAxisBase::EFindStatus::kCanGrow;
439  int ret = -1;
440  while (status == RAxisBase::EFindStatus::kCanGrow) {
441  ret = Internal::RGetBinIndex<DATA::GetNDim() - 1, RHistImpl, decltype(fAxes), true>()(this, fAxes, x, status);
442  }
443  return ret;
444  }
445 
446  /// Get the center coordinate of the bin.
447  CoordArray_t GetBinCenter(int binidx) const final
448  {
449  using RFillBinCoord = Internal::RFillBinCoord<DATA::GetNDim() - 1, DATA::GetNDim(), CoordArray_t, decltype(fAxes)>;
450  CoordArray_t coord;
451  RFillBinCoord()(coord, fAxes, Internal::EBinCoord::kBinCenter, binidx);
452  return coord;
453  }
454 
455  /// Get the coordinate of the low limit of the bin.
456  CoordArray_t GetBinFrom(int binidx) const final
457  {
458  using RFillBinCoord = Internal::RFillBinCoord<DATA::GetNDim() - 1, DATA::GetNDim(), CoordArray_t, decltype(fAxes)>;
459  CoordArray_t coord;
460  RFillBinCoord()(coord, fAxes, Internal::EBinCoord::kBinFrom, binidx);
461  return coord;
462  }
463 
464  /// Get the coordinate of the high limit of the bin.
465  CoordArray_t GetBinTo(int binidx) const final
466  {
467  using RFillBinCoord = Internal::RFillBinCoord<DATA::GetNDim() - 1, DATA::GetNDim(), CoordArray_t, decltype(fAxes)>;
468  CoordArray_t coord;
469  RFillBinCoord()(coord, fAxes, Internal::EBinCoord::kBinTo, binidx);
470  return coord;
471  }
472 
473  /// Fill an array of `weightN` to the bins specified by coordinates `xN`.
474  /// For each element `i`, the weight `weightN[i]` will be added to the bin
475  /// at the coordinate `xN[i]`
476  /// \note `xN` and `weightN` must have the same size!
477  void FillN(const std::span<const CoordArray_t> xN, const std::span<const Weight_t> weightN) final
478  {
479 #ifndef NDEBUG
480  if (xN.size() != weightN.size()) {
481  R__ERROR_HERE("HIST") << "Not the same number of points and weights!";
482  return;
483  }
484 #endif
485 
486  for (size_t i = 0; i < xN.size(); ++i) {
487  Fill(xN[i], weightN[i]);
488  }
489  }
490 
491  /// Fill an array of `weightN` to the bins specified by coordinates `xN`.
492  /// For each element `i`, the weight `weightN[i]` will be added to the bin
493  /// at the coordinate `xN[i]`
494  void FillN(const std::span<const CoordArray_t> xN) final
495  {
496  for (auto &&x: xN) {
497  Fill(x);
498  }
499  }
500 
501  /// Add a single weight `w` to the bin at coordinate `x`.
502  void Fill(const CoordArray_t &x, Weight_t w = 1.)
503  {
504  int bin = GetBinIndexAndGrow(x);
505  this->GetStat().Fill(x, bin, w);
506  }
507 
508  /// Get the content of the bin at position `x`.
509  Weight_t GetBinContent(const CoordArray_t &x) const final
510  {
511  int bin = GetBinIndex(x);
512  if (bin >= 0)
513  return ImplBase_t::GetBinContent(bin);
514  return 0.;
515  }
516 
517  /// Return the uncertainties for the given bin.
518  double GetBinUncertainty(int binidx) const final { return this->GetStat().GetBinUncertainty(binidx); }
519 
520  /// Get the bin uncertainty for the bin at coordinate x.
521  double GetBinUncertainty(const CoordArray_t &x) const final
522  {
523  const int bin = GetBinIndex(x);
524  return this->GetBinUncertainty(bin);
525  }
526 
527  /// Whether this histogram's statistics provide storage for uncertainties, or
528  /// whether uncertainties are determined as poisson uncertainty of the content.
529  bool HasBinUncertainty() const final { return this->GetStat().HasBinUncertainty(); }
530 
531  /// Get the begin() and end() for each axis.
532  ///
533  ///\param[in] withOverUnder - Whether the begin and end should contain over-
534  /// or underflow. Ignored if the axis does not support over- / underflow.
535  AxisIterRange_t<DATA::GetNDim()>
536  GetRange(const std::array<Hist::EOverflow, DATA::GetNDim()> &withOverUnder) const final
537  {
538  std::array<std::array<RAxisBase::const_iterator, DATA::GetNDim()>, 2> ret;
539  Internal::RFillIterRange<DATA::GetNDim() - 1, decltype(fAxes)>()(ret, fAxes, withOverUnder);
540  return ret;
541  }
542 
543  /// Grow the axis number `iAxis` to fit the coordinate `x`.
544  ///
545  /// The histogram (conceptually) combines pairs of bins along this axis until
546  /// `x` is within the range of the axis.
547  /// The axis must support growing for this to work (e.g. a `RAxisGrow`).
548  void GrowAxis(int /*iAxis*/, double /*x*/)
549  {
550  // RODO: Implement GrowAxis()
551  }
552 
553  /// \{
554  /// \name Iterator interface
555  using const_iterator = RHistBinIter<const ImplBase_t>;
556  using iterator = RHistBinIter<ImplBase_t>;
557  iterator begin() noexcept { return iterator(*this); }
558  const_iterator begin() const noexcept { return const_iterator(*this); }
559  iterator end() noexcept { return iterator(*this, this->GetNBins()); }
560  const_iterator end() const noexcept { return const_iterator(*this, this->GetNBins()); }
561  /// \}
562 };
563 
564 template <class DATA, class... AXISCONFIG>
565 RHistImpl<DATA, AXISCONFIG...>::RHistImpl(TRootIOCtor *)
566 {}
567 
568 template <class DATA, class... AXISCONFIG>
569 RHistImpl<DATA, AXISCONFIG...>::RHistImpl(AXISCONFIG... axisArgs)
570  : ImplBase_t(Internal::GetNBinsFromAxes(axisArgs...)), fAxes{axisArgs...}
571 {}
572 
573 template <class DATA, class... AXISCONFIG>
574 RHistImpl<DATA, AXISCONFIG...>::RHistImpl(std::string_view title, AXISCONFIG... axisArgs)
575  : ImplBase_t(title, Internal::GetNBinsFromAxes(axisArgs...)), fAxes{axisArgs...}
576 {}
577 
578 #if 0
579 // In principle we can also have a runtime version of RHistImpl, that does not
580 // contain a tuple of concrete axis types but a vector of `RAxisConfig`.
581 template <class DATA>
582 class RHistImplRuntime: public RHistImplBase<DATA> {
583 public:
584  RHistImplRuntime(std::array<RAxisConfig, DATA::GetNDim()>&& axisCfg);
585 };
586 #endif
587 
588 } // namespace Detail
589 
590 } // namespace Experimental
591 } // namespace ROOT
592 
593 #endif