openPMD-api
IOTask.hpp
1 /* Copyright 2017-2021 Fabian Koller
2  *
3  * This file is part of openPMD-api.
4  *
5  * openPMD-api is free software: you can redistribute it and/or modify
6  * it under the terms of of either the GNU General Public License or
7  * the GNU Lesser General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * openPMD-api is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License and the GNU Lesser General Public License
15  * for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * and the GNU Lesser General Public License along with openPMD-api.
19  * If not, see <http://www.gnu.org/licenses/>.
20  */
21 #pragma once
22 
23 #include "openPMD/ChunkInfo.hpp"
24 #include "openPMD/Dataset.hpp"
25 #include "openPMD/IterationEncoding.hpp"
26 #include "openPMD/Streaming.hpp"
27 #include "openPMD/auxiliary/Export.hpp"
28 #include "openPMD/auxiliary/Memory.hpp"
29 #include "openPMD/auxiliary/Variant.hpp"
30 #include "openPMD/backend/Attribute.hpp"
31 #include "openPMD/backend/ParsePreference.hpp"
32 
33 #include <map>
34 #include <memory>
35 #include <string>
36 #include <utility>
37 #include <variant>
38 #include <vector>
39 
40 namespace openPMD
41 {
42 class Attributable;
43 class Writable;
44 
45 Writable *getWritable(Attributable *);
46 
50  CREATE_FILE,
51  CHECK_FILE,
52  OPEN_FILE,
53  CLOSE_FILE,
54  DELETE_FILE,
55 
56  CREATE_PATH,
57  CLOSE_PATH,
58  OPEN_PATH,
59  DELETE_PATH,
60  LIST_PATHS,
61 
62  CREATE_DATASET,
63  EXTEND_DATASET,
64  OPEN_DATASET,
65  DELETE_DATASET,
66  WRITE_DATASET,
67  READ_DATASET,
68  LIST_DATASETS,
69  GET_BUFFER_VIEW,
70 
71  DELETE_ATT,
72  WRITE_ATT,
73  READ_ATT,
74  LIST_ATTS,
75 
76  ADVANCE,
77  AVAILABLE_CHUNKS,
78  KEEP_SYNCHRONOUS,
79  DEREGISTER
81 }; // note: if you change the enum members here, please update
82  // docs/source/dev/design.rst
83 
84 namespace internal
85 {
86  /*
87  * The returned strings are compile-time constants, so no worries about
88  * pointer validity.
89  */
90  OPENPMDAPI_EXPORT std::string operationAsString(Operation);
91 } // namespace internal
92 
93 struct OPENPMDAPI_EXPORT AbstractParameter
94 {
95  virtual ~AbstractParameter() = default;
96  AbstractParameter() = default;
97 
98  virtual std::unique_ptr<AbstractParameter> to_heap() && = 0;
99 
100 protected:
101  // avoid object slicing
102  // by allow only child classes to use these things for defining their own
103  // copy/move constructors/assignment operators
104  AbstractParameter(const AbstractParameter &) = default;
105  AbstractParameter &operator=(const AbstractParameter &) = default;
106  AbstractParameter(AbstractParameter &&) = default;
107  AbstractParameter &operator=(AbstractParameter &&) = default;
108 };
109 
119 template <Operation>
120 struct OPENPMDAPI_EXPORT Parameter : public AbstractParameter
121 {
122  Parameter() = delete;
123  Parameter(Parameter const &) = delete;
124  Parameter(Parameter &&) = delete;
125 };
126 
127 template <>
128 struct OPENPMDAPI_EXPORT Parameter<Operation::CREATE_FILE>
129  : public AbstractParameter
130 {
131  Parameter() = default;
132  Parameter(Parameter &&) = default;
133  Parameter(Parameter const &) = default;
134  Parameter &operator=(Parameter &&) = default;
135  Parameter &operator=(Parameter const &) = default;
136 
137  std::unique_ptr<AbstractParameter> to_heap() && override
138  {
139  return std::unique_ptr<AbstractParameter>(
140  new Parameter<Operation::CREATE_FILE>(std::move(*this)));
141  }
142 
143  std::string name = "";
144  IterationEncoding encoding = IterationEncoding::groupBased;
145 };
146 
147 template <>
148 struct OPENPMDAPI_EXPORT Parameter<Operation::CHECK_FILE>
149  : public AbstractParameter
150 {
151  Parameter() = default;
152  Parameter(Parameter &&) = default;
153  Parameter(Parameter const &) = default;
154  Parameter &operator=(Parameter &&) = default;
155  Parameter &operator=(Parameter const &) = default;
156 
157  std::unique_ptr<AbstractParameter> to_heap() && override
158  {
159  return std::unique_ptr<AbstractParameter>(
160  new Parameter<Operation::CHECK_FILE>(std::move(*this)));
161  }
162 
163  std::string name = "";
164  enum class FileExists
165  {
166  DontKnow,
167  Yes,
168  No
169  };
170  std::shared_ptr<FileExists> fileExists =
171  std::make_shared<FileExists>(FileExists::DontKnow);
172 };
173 
174 template <>
175 struct OPENPMDAPI_EXPORT Parameter<Operation::OPEN_FILE>
176  : public AbstractParameter
177 {
178  Parameter() = default;
179  Parameter(Parameter &&) = default;
180  Parameter(Parameter const &) = default;
181  Parameter &operator=(Parameter &&) = default;
182  Parameter &operator=(Parameter const &) = default;
183 
184  std::unique_ptr<AbstractParameter> to_heap() && override
185  {
186  return std::unique_ptr<AbstractParameter>(
187  new Parameter<Operation::OPEN_FILE>(std::move(*this)));
188  }
189 
190  std::string name = "";
191  /*
192  * The backends might need to ensure availability of certain features
193  * for some iteration encodings, e.g. availability of ADIOS steps for
194  * variableBased encoding.
195  */
196  IterationEncoding encoding = IterationEncoding::groupBased;
197  using ParsePreference = internal::ParsePreference;
198  std::shared_ptr<ParsePreference> out_parsePreference =
199  std::make_shared<ParsePreference>(ParsePreference::UpFront);
200 };
201 
202 template <>
203 struct OPENPMDAPI_EXPORT Parameter<Operation::CLOSE_FILE>
204  : public AbstractParameter
205 {
206  Parameter() = default;
207  Parameter(Parameter &&) = default;
208  Parameter(Parameter const &) = default;
209  Parameter &operator=(Parameter &&) = default;
210  Parameter &operator=(Parameter const &) = default;
211 
212  std::unique_ptr<AbstractParameter> to_heap() && override
213  {
214  return std::unique_ptr<AbstractParameter>(
215  new Parameter<Operation::CLOSE_FILE>(std::move(*this)));
216  }
217 };
218 
219 template <>
220 struct OPENPMDAPI_EXPORT Parameter<Operation::DELETE_FILE>
221  : public AbstractParameter
222 {
223  Parameter() = default;
224  Parameter(Parameter &&) = default;
225  Parameter(Parameter const &) = default;
226  Parameter &operator=(Parameter &&) = default;
227  Parameter &operator=(Parameter const &) = default;
228 
229  std::unique_ptr<AbstractParameter> to_heap() && override
230  {
231  return std::unique_ptr<AbstractParameter>(
232  new Parameter<Operation::DELETE_FILE>(std::move(*this)));
233  }
234 
235  std::string name = "";
236 };
237 
238 template <>
239 struct OPENPMDAPI_EXPORT Parameter<Operation::CREATE_PATH>
240  : public AbstractParameter
241 {
242  Parameter() = default;
243  Parameter(Parameter &&) = default;
244  Parameter(Parameter const &) = default;
245  Parameter &operator=(Parameter &&) = default;
246  Parameter &operator=(Parameter const &) = default;
247 
248  std::unique_ptr<AbstractParameter> to_heap() && override
249  {
250  return std::unique_ptr<AbstractParameter>(
251  new Parameter<Operation::CREATE_PATH>(std::move(*this)));
252  }
253 
254  std::string path = "";
255 };
256 
257 template <>
258 struct OPENPMDAPI_EXPORT Parameter<Operation::CLOSE_PATH>
259  : public AbstractParameter
260 {
261  Parameter() = default;
262  Parameter(Parameter &&) = default;
263  Parameter(Parameter const &) = default;
264  Parameter &operator=(Parameter &&) = default;
265  Parameter &operator=(Parameter const &) = default;
266 
267  std::unique_ptr<AbstractParameter> to_heap() && override
268  {
269  return std::unique_ptr<AbstractParameter>(
270  new Parameter<Operation::CLOSE_PATH>(std::move(*this)));
271  }
272 };
273 
274 template <>
275 struct OPENPMDAPI_EXPORT Parameter<Operation::OPEN_PATH>
276  : public AbstractParameter
277 {
278  Parameter() = default;
279  Parameter(Parameter &&) = default;
280  Parameter(Parameter const &) = default;
281  Parameter &operator=(Parameter &&) = default;
282  Parameter &operator=(Parameter const &) = default;
283 
284  std::unique_ptr<AbstractParameter> to_heap() && override
285  {
286  return std::unique_ptr<AbstractParameter>(
287  new Parameter<Operation::OPEN_PATH>(std::move(*this)));
288  }
289 
290  std::string path = "";
291 };
292 
293 template <>
294 struct OPENPMDAPI_EXPORT Parameter<Operation::DELETE_PATH>
295  : public AbstractParameter
296 {
297  Parameter() = default;
298  Parameter(Parameter &&) = default;
299  Parameter(Parameter const &) = default;
300  Parameter &operator=(Parameter &&) = default;
301  Parameter &operator=(Parameter const &) = default;
302 
303  std::unique_ptr<AbstractParameter> to_heap() && override
304  {
305  return std::unique_ptr<AbstractParameter>(
306  new Parameter<Operation::DELETE_PATH>(std::move(*this)));
307  }
308 
309  std::string path = "";
310 };
311 
312 template <>
313 struct OPENPMDAPI_EXPORT Parameter<Operation::LIST_PATHS>
314  : public AbstractParameter
315 {
316  Parameter() = default;
317  Parameter(Parameter &&) = default;
318  Parameter(Parameter const &) = default;
319  Parameter &operator=(Parameter &&) = default;
320  Parameter &operator=(Parameter const &) = default;
321 
322  std::unique_ptr<AbstractParameter> to_heap() && override
323  {
324  return std::unique_ptr<AbstractParameter>(
325  new Parameter<Operation::LIST_PATHS>(std::move(*this)));
326  }
327 
328  std::shared_ptr<std::vector<std::string>> paths =
329  std::make_shared<std::vector<std::string>>();
330 };
331 
332 template <>
333 struct OPENPMDAPI_EXPORT Parameter<Operation::CREATE_DATASET>
334  : public AbstractParameter
335 {
336  Parameter() = default;
337  Parameter(Parameter &&) = default;
338  Parameter(Parameter const &) = default;
339  Parameter &operator=(Parameter &&) = default;
340  Parameter &operator=(Parameter const &) = default;
341 
342  std::unique_ptr<AbstractParameter> to_heap() && override
343  {
344  return std::unique_ptr<AbstractParameter>(
345  new Parameter<Operation::CREATE_DATASET>(std::move(*this)));
346  }
347 
348  std::string name = "";
349  Extent extent = {};
350  Datatype dtype = Datatype::UNDEFINED;
351  std::string options = "{}";
352 
359  template <typename TracingJSON>
360  static void warnUnusedParameters(
361  TracingJSON &,
362  std::string const &currentBackendName,
363  std::string const &warningMessage);
364 };
365 
366 template <>
367 struct OPENPMDAPI_EXPORT Parameter<Operation::EXTEND_DATASET>
368  : public AbstractParameter
369 {
370  Parameter() = default;
371  Parameter(Parameter &&) = default;
372  Parameter(Parameter const &) = default;
373  Parameter &operator=(Parameter &&) = default;
374  Parameter &operator=(Parameter const &) = default;
375 
376  std::unique_ptr<AbstractParameter> to_heap() && override
377  {
378  return std::unique_ptr<AbstractParameter>(
379  new Parameter<Operation::EXTEND_DATASET>(std::move(*this)));
380  }
381 
382  Extent extent = {};
383 };
384 
385 template <>
386 struct OPENPMDAPI_EXPORT Parameter<Operation::OPEN_DATASET>
387  : public AbstractParameter
388 {
389  Parameter() = default;
390  Parameter(Parameter &&) = default;
391  Parameter(Parameter const &) = default;
392  Parameter &operator=(Parameter &&) = default;
393  Parameter &operator=(Parameter const &) = default;
394 
395  std::unique_ptr<AbstractParameter> to_heap() && override
396  {
397  return std::unique_ptr<AbstractParameter>(
398  new Parameter<Operation::OPEN_DATASET>(std::move(*this)));
399  }
400 
401  std::string name = "";
402  std::shared_ptr<Datatype> dtype = std::make_shared<Datatype>();
403  std::shared_ptr<Extent> extent = std::make_shared<Extent>();
404 };
405 
406 template <>
407 struct OPENPMDAPI_EXPORT Parameter<Operation::DELETE_DATASET>
408  : public AbstractParameter
409 {
410  Parameter() = default;
411  Parameter(Parameter &&) = default;
412  Parameter(Parameter const &) = default;
413  Parameter &operator=(Parameter &&) = default;
414  Parameter &operator=(Parameter const &) = default;
415 
416  std::unique_ptr<AbstractParameter> to_heap() && override
417  {
418  return std::unique_ptr<AbstractParameter>(
419  new Parameter<Operation::DELETE_DATASET>(std::move(*this)));
420  }
421 
422  std::string name = "";
423 };
424 
425 template <>
426 struct OPENPMDAPI_EXPORT Parameter<Operation::WRITE_DATASET>
427  : public AbstractParameter
428 {
429  Parameter() = default;
430 
431  Parameter(Parameter &&) = default;
432  Parameter(Parameter const &) = delete;
433  Parameter &operator=(Parameter &&) = default;
434  Parameter &operator=(Parameter const &) = delete;
435 
436  std::unique_ptr<AbstractParameter> to_heap() && override
437  {
438  return std::unique_ptr<AbstractParameter>(
439  new Parameter<Operation::WRITE_DATASET>(std::move(*this)));
440  }
441 
442  Extent extent = {};
443  Offset offset = {};
444  Datatype dtype = Datatype::UNDEFINED;
446 };
447 
448 template <>
449 struct OPENPMDAPI_EXPORT Parameter<Operation::READ_DATASET>
450  : public AbstractParameter
451 {
452  Parameter() = default;
453  Parameter(Parameter &&) = default;
454  Parameter(Parameter const &) = default;
455  Parameter &operator=(Parameter &&) = default;
456  Parameter &operator=(Parameter const &) = default;
457 
458  std::unique_ptr<AbstractParameter> to_heap() && override
459  {
460  return std::unique_ptr<AbstractParameter>(
461  new Parameter<Operation::READ_DATASET>(std::move(*this)));
462  }
463 
464  Extent extent = {};
465  Offset offset = {};
466  Datatype dtype = Datatype::UNDEFINED;
467  std::shared_ptr<void> data = nullptr;
468 };
469 
470 template <>
471 struct OPENPMDAPI_EXPORT Parameter<Operation::LIST_DATASETS>
472  : public AbstractParameter
473 {
474  Parameter() = default;
475  Parameter(Parameter &&) = default;
476  Parameter(Parameter const &) = default;
477  Parameter &operator=(Parameter &&) = default;
478  Parameter &operator=(Parameter const &) = default;
479 
480  std::unique_ptr<AbstractParameter> to_heap() && override
481  {
482  return std::unique_ptr<AbstractParameter>(
483  new Parameter<Operation::LIST_DATASETS>(std::move(*this)));
484  }
485 
486  std::shared_ptr<std::vector<std::string>> datasets =
487  std::make_shared<std::vector<std::string>>();
488 };
489 
490 template <>
491 struct OPENPMDAPI_EXPORT Parameter<Operation::GET_BUFFER_VIEW>
492  : public AbstractParameter
493 {
494  Parameter() = default;
495  Parameter(Parameter &&) = default;
496  Parameter(Parameter const &) = default;
497  Parameter &operator=(Parameter &&) = default;
498  Parameter &operator=(Parameter const &) = default;
499 
500  std::unique_ptr<AbstractParameter> to_heap() && override
501  {
502  return std::unique_ptr<AbstractParameter>(
503  new Parameter<Operation::GET_BUFFER_VIEW>(std::move(*this)));
504  }
505 
506  // in parameters
507  Offset offset;
508  Extent extent;
509  Datatype dtype = Datatype::UNDEFINED;
510  bool update = false;
511  // out parameters
512  struct OutParameters
513  {
514  bool backendManagedBuffer = false;
515  unsigned viewIndex = 0;
516  void *ptr = nullptr;
517  };
518  std::shared_ptr<OutParameters> out = std::make_shared<OutParameters>();
519 };
520 
521 template <>
522 struct OPENPMDAPI_EXPORT Parameter<Operation::DELETE_ATT>
523  : public AbstractParameter
524 {
525  Parameter() = default;
526  Parameter(Parameter &&) = default;
527  Parameter(Parameter const &) = default;
528  Parameter &operator=(Parameter &&) = default;
529  Parameter &operator=(Parameter const &) = default;
530 
531  std::unique_ptr<AbstractParameter> to_heap() && override
532  {
533  return std::unique_ptr<AbstractParameter>(
534  new Parameter<Operation::DELETE_ATT>(std::move(*this)));
535  }
536 
537  std::string name = "";
538 };
539 
540 template <>
541 struct OPENPMDAPI_EXPORT Parameter<Operation::WRITE_ATT>
542  : public AbstractParameter
543 {
544  Parameter() = default;
545  Parameter(Parameter &&) = default;
546  Parameter(Parameter const &) = default;
547  Parameter &operator=(Parameter &&) = default;
548  Parameter &operator=(Parameter const &) = default;
549 
550  std::unique_ptr<AbstractParameter> to_heap() && override
551  {
552  return std::unique_ptr<AbstractParameter>(
553  new Parameter<Operation::WRITE_ATT>(std::move(*this)));
554  }
555 
556  std::string name = "";
557  Datatype dtype = Datatype::UNDEFINED;
558  /*
559  * If true, this attribute changes across IO steps.
560  * It should only be written in backends that support IO steps,
561  * otherwise writing should be skipped.
562  * The frontend is responsible for handling both situations.
563  */
564  bool changesOverSteps = false;
565  Attribute::resource resource;
566 };
567 
568 template <>
569 struct OPENPMDAPI_EXPORT Parameter<Operation::READ_ATT>
570  : public AbstractParameter
571 {
572  Parameter() = default;
573  Parameter(Parameter &&) = default;
574  Parameter(Parameter const &) = default;
575  Parameter &operator=(Parameter &&) = default;
576  Parameter &operator=(Parameter const &) = default;
577 
578  std::unique_ptr<AbstractParameter> to_heap() && override
579  {
580  return std::unique_ptr<AbstractParameter>(
581  new Parameter<Operation::READ_ATT>(std::move(*this)));
582  }
583 
584  std::string name = "";
585  std::shared_ptr<Datatype> dtype = std::make_shared<Datatype>();
586  std::shared_ptr<Attribute::resource> resource =
587  std::make_shared<Attribute::resource>();
588 };
589 
590 template <>
591 struct OPENPMDAPI_EXPORT Parameter<Operation::LIST_ATTS>
592  : public AbstractParameter
593 {
594  Parameter() = default;
595  Parameter(Parameter &&) = default;
596  Parameter(Parameter const &) = default;
597  Parameter &operator=(Parameter &&) = default;
598  Parameter &operator=(Parameter const &) = default;
599 
600  std::unique_ptr<AbstractParameter> to_heap() && override
601  {
602  return std::unique_ptr<AbstractParameter>(
603  new Parameter<Operation::LIST_ATTS>(std::move(*this)));
604  }
605 
606  std::shared_ptr<std::vector<std::string>> attributes =
607  std::make_shared<std::vector<std::string>>();
608 };
609 
610 template <>
611 struct OPENPMDAPI_EXPORT Parameter<Operation::ADVANCE>
612  : public AbstractParameter
613 {
614  Parameter() = default;
615  Parameter(Parameter &&) = default;
616  Parameter(Parameter const &) = default;
617  Parameter &operator=(Parameter &&) = default;
618  Parameter &operator=(Parameter const &) = default;
619 
620  std::unique_ptr<AbstractParameter> to_heap() && override
621  {
622  return std::unique_ptr<AbstractParameter>(
623  new Parameter<Operation::ADVANCE>(std::move(*this)));
624  }
625 
629  std::shared_ptr<AdvanceStatus> status =
630  std::make_shared<AdvanceStatus>(AdvanceStatus::OK);
631 };
632 
633 template <>
634 struct OPENPMDAPI_EXPORT Parameter<Operation::AVAILABLE_CHUNKS>
635  : public AbstractParameter
636 {
637  Parameter() = default;
638  Parameter(Parameter &&) = default;
639  Parameter(Parameter const &) = default;
640  Parameter &operator=(Parameter &&) = default;
641  Parameter &operator=(Parameter const &) = default;
642 
643  std::unique_ptr<AbstractParameter> to_heap() && override
644  {
645  return std::unique_ptr<AbstractParameter>(
646  new Parameter<Operation::AVAILABLE_CHUNKS>(std::move(*this)));
647  }
648 
649  // output parameter
650  std::shared_ptr<ChunkTable> chunks = std::make_shared<ChunkTable>();
651 };
652 
653 template <>
654 struct OPENPMDAPI_EXPORT Parameter<Operation::KEEP_SYNCHRONOUS>
655  : public AbstractParameter
656 {
657  Parameter() = default;
658  Parameter(Parameter &&) = default;
659  Parameter(Parameter const &) = default;
660  Parameter &operator=(Parameter &&) = default;
661  Parameter &operator=(Parameter const &) = default;
662 
663  std::unique_ptr<AbstractParameter> to_heap() && override
664  {
665  return std::make_unique<Parameter<Operation::KEEP_SYNCHRONOUS>>(
666  std::move(*this));
667  }
668 
669  Writable *otherWritable;
670 };
671 
672 template <>
673 struct OPENPMDAPI_EXPORT Parameter<Operation::DEREGISTER>
674  : public AbstractParameter
675 {
676  Parameter() = default;
678  {}
679 
680  std::unique_ptr<AbstractParameter> to_heap() && override
681  {
682  return std::make_unique<Parameter<Operation::DEREGISTER>>(
683  std::move(*this));
684  }
685 };
686 
695 class OPENPMDAPI_EXPORT IOTask
696 {
697 public:
706  template <Operation op>
707  explicit IOTask(Writable *w, Parameter<op> p)
708  : writable{w}, operation{op}, parameter{std::move(p).to_heap()}
709  {}
710 
711  template <Operation op>
712  explicit IOTask(Attributable *a, Parameter<op> p)
713  : writable{getWritable(a)}
714  , operation{op}
715  , parameter{std::move(p).to_heap()}
716  {}
717 
718  explicit IOTask(IOTask const &other)
719  : writable{other.writable}
720  , operation{other.operation}
721  , parameter{other.parameter}
722  {}
723 
724  IOTask &operator=(IOTask const &other)
725  {
726  writable = other.writable;
727  operation = other.operation;
728  parameter = other.parameter;
729  return *this;
730  }
731 
732  Writable *writable;
733  Operation operation;
734  std::shared_ptr<AbstractParameter> parameter;
735 }; // IOTask
736 } // namespace openPMD
Self-contained description of a single IO operation.
Definition: IOTask.hpp:695
AdvanceMode mode
input parameter
Definition: IOTask.hpp:627
Definition: Memory.hpp:174
AdvanceMode
In step-based mode (i.e.
Definition: Streaming.hpp:34
IOTask(Writable *w, Parameter< op > p)
Constructor for self-contained description of single IO operation.
Definition: IOTask.hpp:707
Datatype
Concrete datatype of an object available at runtime.
Definition: Datatype.hpp:45
Public definitions of openPMD-api.
Layer to mirror structure of logical data and persistent data in file.
Definition: Writable.hpp:64
Typesafe description of all required arguments for a specified Operation.
Definition: IOTask.hpp:120
OPENPMDAPI_EXPORT_ENUM_CLASS(Operation)
Type of IO operation between logical and persistent data.
Definition: IOTask.hpp:49
Definition: IOTask.hpp:93
IterationEncoding
Encoding scheme of an Iterations Series&#39;.
Definition: IterationEncoding.hpp:32
Layer to manage storage of attributes associated with file objects.
Definition: Attributable.hpp:93