C++ conversion details ====================== The C++ codebase has been primarily a conversion of the original Java codebase, with some additional helper functions and classes added where needed. The intention is that the basic interfaces and classes should be identical between the two languages unless this is prevented by fundamental differences between the languages. This section is intended to be useful for - Users of the existing Java interface, who wish to understand the differences between the two implementations - Developers who wish to work on the C++ interface In addition to documenting the specific language and class compatibility issues, this section also documents the idioms in use in the C++ code which might not be immediately clear by looking at the API reference, and which may not be familiar to Java developers. C++ and Java type incompatibility --------------------------------- While C++ and Java have some basic syntactical similarities, there are several basic differences in their type systems. Java types ^^^^^^^^^^ Java has primitive types and classes. .. code-block:: java int i; double d; - No unsigned primitive integer types .. code-block:: java Pixels pixels = new Pixels(); - All classes are derived from root :cpp:class:`Object` - Objects are by reference only - Objects and arrays are always allocated with ``new`` - Destruction is non-deterministic - All passing is by value (primitives and object references) .. code-block:: java Pixels[] array = new Pixels[5]; - Arrays have an intrinsic size. - Arrays are safe to index out of bounds (an exception is thrown). C++ types ^^^^^^^^^ C++ has primitive types, structures and classes. .. code-block:: cpp int16_t i1; uint32_t i2; double d; - Primitive integer types may be signed or unsigned. - Integer types are of defined size. .. code-block:: cpp // Allocate on the stack, or as a struct or class member: Pixels pixels; // Allocate on the heap Pixels *pixelsptr1 = new Pixels(); // Pointer to existing instance const Pixels *pixelsptr2 = &pixels; // Reference to existing instance Pixels& pixelsref(pixels); - Classes have no common root - All types may be instances, pointers or references - Object construction may be on the stack, on the heap using ``new`` or in place using placement ``new``. - Pointers and references may refer to :c:type:`const` type - Pointers may be :c:type:`const` - References are implicitly :c:type:`const` (similar to :c:type:`final`) - Destruction is deterministic - :c:type:`new` **should never be used** in modern C++ code (see below) .. code-block:: cpp Pixels array[5]; - Arrays “decay” to bare pointers - Arrays are not safe to index out of bounds - Size information lost at runtime - **Never use arrays** outside static initializers Simplified type names ^^^^^^^^^^^^^^^^^^^^^ :c:type:`typedef` is used to create an alias for an existing type. .. code-block:: cpp typedef std::vector string_list; string_list l; string_list::const_iterator i = l.begin(); // NOT std::vector::const_iterator typedef std::vector plist; plist pl(6); plist::size_type idx = 2; // size_type NOT unsigned int or uint32_t pl.at(idx) = ...; Used in standard container types e.g. :cpp:type:`size_type`, :cpp:type:`value_type` and in classes and class templates. Consistency is needed for generic programming—use the standard type names to enable interoperability with standard algorithms. Exception handling ------------------ Java ^^^^ :c:type:`throws` details which exceptions are thrown by a method. Java exceptions are also “checked”, requiring the caller to catch and handle all exceptions which might be thrown, aside from :cpp:class:`RuntimeException` and its subclasses. C++ ^^^ C++ has exception specifications like Java, however they are useless aside from :c:type:`nothrow`. This is because if an exception is thrown which does not match the specification, it will abort the program with a :cpp:class:`bad_exception` which makes them unusable in practice. Exceptions can be thrown at any point with the exception that they should **never be thrown in a destructor**. It is not necessary or typical to check exceptions except where needed. All code must be exception-safe given that an exception could be thrown at any point; the design considerations for exception safety are covered below. Interfaces ---------- Java supports single-inheritance, plus interfaces. C++ supports true multiple-inheritance, which is rather more flexible, at the expense of being rather more complicated and dangerous. However, the Java single-inheritance-plus-interfaces model can be implemented in C++ using a subset of the facilities provided by multiple inheritance. Rather than being enforced by the language, it is a set of idioms. These must be rigorously followed or else things will fail horribly! C++ interfaces are classes with: - No instance variables - Pure virtual methods - ``protected`` default constructor - ``public virtual`` destructor - Deleted copy constructor and assignment operator C++ classes implementing interfaces: - Use ``public`` inheritance for parent class - Use ``virtual public`` inheritance for implemented interfaces - Have a ``virtual`` destructor When compiled with optimization enabled, the interface classes should have zero storage overhead. If implementing classes do not use :c:type:`virtual public` inheritance, compilation will fail as soon as a second class in the inheritance hierarchy also implements the interface. Reference handling and memory management ---------------------------------------- Pointer problems ^^^^^^^^^^^^^^^^ Plain (or “dumb”) C++ pointers can be dangerous if used incorrectly. The OME-Files API make a point of never using them unless absolutely necessary. For automatic objects allocated on the stack, allocation and deallocation is automatic and safe: .. code-block:: cpp { Image i(filename); i.read_plane(); // Object destroyed when i goes out of scope } In this case, the object's destructor was run and the memory freed automatically. Looking at the case where a pointer is used to reference manually-allocated memory on the heap: .. code-block:: cpp { Image *i = new Image(filename); i->read_plane(); // Memory not freed when pointer i goes out of scope } In this case :c:type:`new` was not paired with the corresponding :c:type:`delete`, resulting in a *memory leak*. This is the code with the “leak” fixed: .. code-block:: cpp { Image *i = new Image(filename); i->read_plane(); // throws exception; memory leaked delete i; // never called } :c:type:`new` and :c:type:`delete` are now paired, but the code is not exception-safe. If an exception is thrown, memory will still be leaked. Manual memory management requires correct clean up for every exit point in the function, including both all :c:type:`return` statements and thrown exceptions. Here, we handle this correctly: .. code-block:: cpp { Image *i = new Image(filename); try { i->read_plane(); // throws exception } catch (const std::runtime_error& e) { delete i; // clean up throw; // rethrow } delete i; // never called for exceptions } However, this does not scale. This is painful and error prone when scaled to an entire codebase. Even within this simple function, there is only a single variable with a single exception and single return to deal with. Imagine the combinatorial explosion when there are several variables with different lifetimes and scopes, multiple return points and several exceptions to handle--this is easy to get wrong, so a more robust approach is needed. Use of ``new`` is not in the general case safe or sensible. The OME-Files API **never** passes pointers allocated with ``new``, nor requires any manual memory management. Instead, “smart” pointers are used throughout to manage memory safely and automatically. :cpp:class:`std::shared_ptr` as a “smart” pointer ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The unsafe example above, has been rewritten to use :cpp:class:`std::shared_ptr`: .. code-block:: cpp // Start of block { std::shared_ptr i(std::make_shared(filename)); i->read_plane(); // throws exception // Memory freed when i's destructor is // run at exit of block scope } Rather than managing the memory by hand, responsibility for this is delegated to a “smart” pointer, :cpp:class:`std::shared_ptr`. The memory is freed by the :cpp:class:`std::shared_ptr` destructor which is run at the end of the block scope, on explicit :c:type:`return`, or when cleaned up by exception stack unwinding. - :cpp:class:`shared_ptr` object lifetime manages the resource - ``new`` replaced with :cpp:class:`std::make_shared` - May be used as class members; lifetime is tied to class instance - Clean up for all exit points is automatic and safe - Allows ownership transfer and sharing - Allows reference without ownership using :cpp:class:`weak_ptr` - :cpp:class:`weak_ptr` references the object but does not prevent it being freed when the last :cpp:class:`shared_ptr` reference is lost; this is useful for cycle breaking and is used by the OME XML model objects for references Resource Acquisition Is Initialization ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Resource Acquisition Is Initialization (RAII) is a programming idiom used throughout modern C++ libraries and applications, including the Standard Library, - A class is a proxy for a resource - The resource is acquired when object is initialised - The resource is released when object is destroyed - Any resource may be managed (e.g. memory, files, locks, mutexes) - The C++ language and runtime guarantees make resource management deterministic and reliable - Safe for use in any scope - Exception safe - Used throughout modern C++ libraries and applications Because this relies implicitly upon the deterministic object destruction guarantees made by the C++ language, this is not used widely in Java APIs which often require manual management of resources such as open files. Used carefully, RAII will prevent resource leaks and result in robust, safe code. The :cpp:class:`FormatReader` API is currently not using RAII due to the use of the :cpp:func:`FormatHandler::setId()` interface. C++ reference variants ^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: cpp // Non-constant Constant // ----------------------------- -------------------------------------- // Pointer Image *i; const Image *i; Image * const i; const Image * const i; // Reference Image& i; const Image& i; // Shared pointer std::shared_ptr i; std::shared_ptr i; const std::shared_ptr i; const std::shared_ptr i; // Shared pointer reference std::shared_ptr& i; std::shared_ptr& i; const std::shared_ptr& i; const std::shared_ptr& i; // Weak pointer std::weak_ptr i; std::weak_ptr i; const std::weak_ptr i; const std::weak_ptr i; // Weak pointer reference std::weak_ptr& i; std::weak_ptr& i; const std::weak_ptr& i; const std::weak_ptr& i; Java has one reference type. Here, we have **22**. Clearly, not all of these will typically be used. Below, a subset of these are shown for use for particular purposes. Class member types: .. code-block:: cpp Image i; // Concrete instance std::shared_ptr i; // Reference std::weak_ptr i; // Weak reference Wherever possible, a concrete instance should be preferred. This is not possible for polymorphic types, where a reference is required. In this situation, an :cpp:class:`std::shared_ptr` is preferred if the class owns the member and/or needs control over its lifetime. If the class does not have ownership then an :cpp:class:`std::weak_ptr` will allow safe access to the object if it still exists. In circumstances where manual lifetime management is required, e.g. for performance, and the member is guaranteed to exist for the duration of the object's lifetime, a plain pointer or reference may be used. A pointer will be used if it is possible for it to be :cpp:class:`null`, or it may be reassigned more than once, or if is assigned after initial construction. If properly using RAII, using references should be possible and preferred over bare pointers in all cases. Argument types: .. code-block:: cpp // Ownership retained void read_plane(const Image& image); // Ownership shared or transferred void read_plane(const std::shared_ptr& image); Passing primitive types by value is acceptable. However, passing a struct or class by value will implicitly copy the object into the callee's stack frame, which may be expensive (and requires a copy constructor which will not be guaranteed or even possible for polymorphic types). Passing by reference avoids the need for any copying, and passing by :c:type:`const` reference will prevent the callee from modifying the object, also making it clear that there is no transfer of ownership. Passing using an :cpp:class:`std::shared_ptr` is possible but not recommended---the copy will involve reference counting overhead which can kill multi-threaded performance since it requires synchronization between all threads; use a :c:type:`const` reference to an :cpp:class:`std::shared_ptr` to avoid the overhead. If ownership should be transferred or shared with the callee, use a non-:c:type:`const` reference. To be absolutely clear, plain pointers are never used and are not acceptable for ownership transfer. A plain reference also makes it clear there is no ownership transfer. Return types: .. code-block:: cpp Image get_image(); // Ownership transferred Image& get_image(); // Ownership retained std::shared_ptr get_image(); // Ownership shared/trans If the callee does not retain a copy of the original object, it can't pass by reference since it can't guarantee the object remaining in scope after it returns, hence it must create a temporary value and pass by value. If the callee does retain a copy, it has the option of passing by reference. Passing by reference is preferred when possible. Passing by value implies ownership transfer. Passing by reference implies ownership retention. Passing an :cpp:class:`std::shared_ptr` by value or reference implies sharing ownership since the caller can retain a reference; if passing by value ownership *may* be transferred since this implies the callee is not retaining a reference to it (but this is not guaranteed). Again, to be absolutely clear, plain pointers are never used and are not acceptable for ownership transfer. A plain reference also makes it clear there is no ownership transfer. - Safety: References cannot be :cpp:class:`null` - Storing polymorphic types requires use of a :cpp:class:`shared_ptr` - Referencing polymorphic types *may* require use of a :cpp:class:`shared_ptr` - Safety: To avoid cyclic dependencies, use :cpp:class:`weak_ptr` - Safety: To allow object destruction while maintaining a safe reference, use :cpp:class:`weak_ptr` - :cpp:class:`weak_ptr` is not directly usable - :cpp:class:`weak_ptr` is convertible back to :cpp:class:`shared_ptr` for use *if the object is still in existence* - C++11 *move semantics* (``&&``) improve the performance of ownership transfer Containers ---------- Safe array passing ^^^^^^^^^^^^^^^^^^ C++ arrays are not safe to pass in or out of functions since the size is not known unless passed separately. .. code-block:: cpp class Image { // Unsafe; size unknown uint8_t[] getLUT(); void setLUT(uint8_t[]& lut); }; C++ arrays “decay” to “bare” pointers, and pointers have no associated size information. :cpp:class:`std::array` is a safe alternative. .. code-block:: cpp class Image { typedef std::array LUT; // Safe; size defined const LUT& getLUT() const; void setLUT(const LUT&); }; :cpp:class:`std::array` is a array-like object (a class which behaves like an array). Its type and size are defined in the template, and it may be passed around like any other object. Its :cpp:func:`array::at()` method provides strict bounds checking, while its index :cpp:func:`array::operator[]` provides unchecked access. Storing and passing unrelated types ----------------------------------- Types with a common base ^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: cpp std::vector> v; v.push_back(std::make_shared()); This can store any type derived from :cpp:class:`Base`. An :cpp:class:`std::shared_ptr` is **essential**. Without it, bare pointers to the base would be stored, and memory would be leaked when elements are removed from the container (unless externally managed [generally unsafe]). The same applies to passing polymorphic types. Java containers can be problematic: - Java can store root :cpp:class:`Object` in containers - Java can pass and return root :cpp:class:`Object` in methods. - This is not possible in C++: there is no root object. - An alternative approach is needed. Arbitrary types ^^^^^^^^^^^^^^^ :cpp:class:`boost::any` may be used to store any type: .. code-block:: cpp std::vector v; v.push_back(Anything); - Assign and store any type - Type erasure (similar to Java generics) - Use for containers of arbitrary types - Flexible, but need to cast to each type used to extract - Code will not be able to handle all possible types meaningfully This is the most flexible solution, but in order to get a value back out, requires casting it to its specific type. This can mean a situation could arise where values are stored of types which cannot be handled since it is not possible to write the code to handle every single possibility ahead of time. However, if the open-ended flexibility is needed, this is available. A fixed set of types ^^^^^^^^^^^^^^^^^^^^ :cpp:class:`boost::variant` may be used to store a limited set of different types: This avoids the :cpp:class:`boost::any` problem of not being able to handle all possible types, since the scope is limited to a set of allowed types, and a :cpp:type:`static_visitor` can ensure that all types are supported by the code at compile time. .. code-block:: cpp typedef boost::variant variants; std::vector v; v.push_back(43); v.push_back("ATTO 647N"); - Store a set of discriminated types - “External polymorphism” via :cpp:type:`static_visitor` - Used to store original metadata - Used to store nD pixel data of different pixel types This is not an alternative to a common root object. Instead, this is a discriminated union, which can store one of a defined set of “variant” types. A static visitor pattern may be used to generate code to operate on all of the supported types. The variant type may be used as a class member, passed by value, passed by reference or stored in a container like any other type. Due to the way it is implemented to store values, it does not necessarily need wrapping in an :cpp:class:`std::shared_ptr` since it can behave as a value type (depending upon the context). Java uses polymorphism to store and pass the root :cpp:class:`Object` around. The :cpp:class:`boost::variant` and :cpp:class:`boost::any` approaches use templates to (internally) create a common base and manage the stored objects. However, the end user does not need to deal with this complexity directly—the use of the types is quite transparent. Variant example: MetadataMap ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This example demonstrates the use of variants with a simple expansion for two different categories of type (scalars and vectors of scalars). The :cpp:class:`MetadataMap` class stores key-value pairs, where the value can be either a string, Boolean, or several integer and floating point types, or vectors of any of these types. When converting the data to other forms, it is necessary to flatten the vector types to a set of separate key-value pairs with the key having a numbered suffix, one for each element in the vector. .. code-block:: cpp { MetadataMap map; MetadataMap flat_map (map.flatten()); } A flattened map is created using the following method: .. code-block:: cpp MetadataMap MetadataMap::flatten() const { MetadataMap newmap; for (MetadataMap::const_iterator i = oldmap.begin(); i != oldmap.end(); ++i) { MetadataMapFlattenVisitor v(newmap, i->first); boost::apply_visitor(v, i->second); } return newmap; } The :cpp:class:`MetadataMapFlattenVisitor` is implemented thusly: .. code-block:: cpp // Flatten MetadataMap vector values struct MetadataMapFlattenVisitor : public boost::static_visitor<> { MetadataMap& map; // Map of flattened elements const MetadataMap::key_type& key; // Current key MetadataMapFlattenVisitor (MetadataMap& map, const MetadataMap::key_type& key): map(map), key(key) {} // Output a scalar value of arbitrary type. template void operator() (const T& v) const { map.set(key, v); } // Output a vector value of arbitrary type. template void operator() (const std::vector& c) const { typename std::vector::size_type idx = 1; for (typename std::vector::const_iterator i = c.begin(); i != c.end(); ++i, ++idx) { std::ostringstream os; os << key << " #" << idx; map.set(os.str(), *i); } } }; The :cpp:class:`MetadataMapFlattenVisitor` is derived from :cpp:class:`boost::static_visitor`, and its templated operator method is specialized and expanded once for each type supported by the variant type used by the map. In the above example, two separate overloaded operators are provided, one for scalar values which is a simple copy, and one for vector values which splits the elements into separate keys in the new map. The important part is the call to :cpp:func:`apply_visitor`, which takes as arguments the visitor object and the variant to apply it to. This could be done with a large set of conditionals using ``boost::get(value)`` for each supported type. The benefit of the :cpp:class:`boost::static_visitor` approach is that it ensures that all the types are supported *at compile time*, and in effect results in the same code. If any types are not supported, the code will fail to compile. Variant example: VariantPixelBuffer equality comparison ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This example demonstrates the use of variants with a combinatorial expansion of types. The :cpp:class:`VariantPixelBuffer` class can contain :cpp:class:`PixelBuffer` classes of various pixel types. Comparing for equality is only performed if the pixel types of the two objects are the same: .. code-block:: cpp { VariantPixelBuffer a, b; if (a == b) { // Buffers are the same. } } This is implemented using an overloaded equality operator: .. code-block:: cpp bool VariantPixelBuffer::operator == (const VariantPixelBuffer& rhs) const { return boost::apply_visitor(PBCompareVisitor(), buffer, rhs.buffer); } As before, this is implemented in terms of a :cpp:class:`boost::static_visitor`, but note that this time it is specialized for ``bool``, meaning that the return type of :cpp:func:`apply_visitor` will also be ``bool``, and the operator methods must also return this type. .. code-block:: cpp struct PBCompareVisitor : public boost::static_visitor { template bool operator() (const T& /* lhs */, const U& /* rhs */) const { return false; } template bool operator() (const T& lhs, const T& rhs) const { return lhs && rhs && (*lhs == *rhs); } }; Unlike the last example, the operator methods now have two arguments, both of which are variant types, and the :cpp:func:`apply_visitor` call is passed two variant objects in addition to the visitor object. This causes the templates to be expanded for all pairwise combinations of the possible types. When the types are not equal, the first templated operator is called, which always returns false. When the types are equal the second operator is called; this checks both operands are not null and then performs an equality comparison using the buffer contents. Given that all the operators are inline, we would hope that a good compiler would cause all the false cases to be optimized out after expansion. Variant example: VariantPixelBuffer SFINAE ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This example demonstrates the use of variants with SFINAE. C++ has a concept known as Substitution Failure Is Not An Error (SFINAE), which refers to it not being an error for a candidate template to fail argument substitution during overload resolution. While this is in and of itself a fairly obscure language detail, it enables overloading of a method not just on type, but different categories of type, for example integer and floating point types, signed and unsigned integer types, simple and complex types, or combinations of all of these. This is particularly useful when writing algorithms to process pixel data. Use of SFINAE has been made accessible through the creation of :cpp:class:`boost::enable_if` (:cpp:class:`std::enable_if` in C++11), and *type traits* (type category checking classes such as :cpp:class:`is_integer`). The following code is an example of how one might write a visitor for adapting an algorithm to separate integer, floating point, complex floating point and bitmask cases. .. code-block:: cpp struct TypeCategoryVisitor : public boost::static_visitor<> { typedef ::ome::files::PixelProperties<::ome::xml::model::enums::PixelType::BIT>::std_type bit_type; TypeCategoryVisitor() {} // Integer pixel types template typename boost::enable_if_c< boost::is_integral::value, void >::type operator() (std::shared_ptr<::ome::files::PixelBuffer>& buf) { // Integer algorithm. } // Floating point pixel types template typename boost::enable_if_c< boost::is_floating_point::value, void >::type operator() (std::shared_ptr<::ome::files::PixelBuffer>& buf) { // Floating point algorithm. } // Complex floating point pixel types template typename boost::enable_if_c< boost::is_complex::value, void >::type operator() (std::shared_ptr<::ome::files::PixelBuffer>& buf) { // Complex floating point algorithm. } // BIT/bool pixel type. Note this is a simple overload since it is // a simple type, not a category of different types. void operator() (std::shared_ptr<::ome::files::PixelBuffer>& buf) { // Boolean algorithm. } }; This visitor may be used with :cpp:func:`apply_visitor` in a similar manner to the previously demonstrated visitors. :cpp:class:`enable_if` has two parameters, the first being a conditional, the second being the return type (in this example, all the methods return ``void``). If the conditional is true, then the type expands to the return type and the template is successfully substituted. If the conditional is false (types do not match), then the substitution fails and the template will not be used. Note that the conditional is itself a type, which can be confusing, since all this logic is driven by conditional template expansion. Normal templates are specialized for a type. This approach allows specialization for different *categories* of type. Without this approach it would be necessary to write separate overloads for each individual type (each integer type, each floating point type, each complex type, etc.), even when the logic would be identical for e.g. the different integer types. This approach therefore removes the need for unnecessary code duplication, and the type traits checks make each type category explicit to the reader. Behavior differences -------------------- Pixel data buffering ^^^^^^^^^^^^^^^^^^^^ Pixel data is handled differently between the Java and C++ implementations. The primary reason for the difference is that the Java code uses raw ``byte[]`` arrays to contain pixel data. This could not be implemented in C++ due to the limitations of C++ arrays discussed above, as well as having a number of additional limitations: - they are not using the native pixel type, requiring conversion to the required type, potentially also including endian conversion - they are unstructured, having no dimension ordering or dimension size information The solution was to create a dedicated :cpp:class:`PixelBuffer` template class which could represent pixels of any type. This is contained by a :cpp:class:`VariantPixelBuffer` class which can contain any of the supported pixel types. This is therefore both flexible and strongly-typed. The C++ code is slightly more complex as a result, but it is safer, and the buffer can be passed around without the need for any additional metadata to describe its type, size and ordering. This can make passing pixel data between different libraries much more transparent. Pixel sample interleaving ^^^^^^^^^^^^^^^^^^^^^^^^^ Additional differences include the semantics of how the :cpp:func:`FormatReader::openBytes()` and :cpp:func:`FormatWriter::saveBytes()` methods are implemented. The API is the same, but the default behavior is a little different. All well-written code should cope with the differences, but code making assumptions may require some attention. The Java TIFF reader classes' :cpp:func:`FormatReader::isInterleaved()` method will always return ``false``, irrespective of the TIFF ``PlanarConfiguration`` tag. As a result, :cpp:func:`FormatReader::openBytes()` will always return pixel data with samples on separate contiguous planes. In contrast, the C++ TIFF reader classes' :cpp:func:`FormatReader::isInterleaved()` method will return ``true`` if the TIFF ``PlanarConfiguration`` is ``CONTIG`` and ``false`` if ``SEPARATE``, and the :cpp:func:`FormatReader::openBytes()` method will return pixel data with the appropriate interleaving, matching the same format in the TIFF file. The Java behavior is due to the implementation details of its TIFF reading code; the C++ code uses libtiff and simply passes back the pixel data without any rearrangement. Java code which assumes it will never receive interleaved data will need to be updated to cope with it when porting to C++. The Java TIFF writer will always set interleaving if the number of samples per pixel is one (which is the recommended behaviour), overriding :cpp:func:`FormatWriter::setInterleaved()`. The C++ TIFF writer will always set interleaving based upon :cpp:func:`FormatWriter::setInterleaved()`, and will not override the request of the caller. This discrepancy will be rectified in a future release to match the behavior of the Java reader; in practice there is no difference in the pixel ordering since interleaving is irrelevant when there is only one sample per pixel. To obtain the Java TIFF reader behavior in C++, i.e. to obtain non-interleaved pixel data, create a :cpp:class:`VariantPixelBuffer` with the desired pixel type and interleaving (use the :cpp:func:`PixelBufferBase::make_storage_order` helper method to create the dimension order without interleaving), and then assign the buffer filled by :cpp:func:`FormatRead::openBytes` to this buffer; the data will be transparently converted to the desired ordering on assignment.