std::array support
All dense vector classes offer a constructor for an initialization with a dynamic or static array. Is it also possible to offer an additional templated constructor for an initialization (constexpr
for StaticVector
) with a std::array
? The latter is ideal for storage purposes and more flexible than a static array due to its additional STL functionality and the possibility to use it as a base class (to extend it with proper constructors).
#include <array>
#include <blaze/math/StaticVector.h>
#include <iostream>
int main() {
constexpr std::array< float, 4u > a = { 0.0f, 1.0f, 2.0f, 3.0f };
constexpr float b[] = { 0.0f, 1.0f, 2.0f, 3.0f };
blaze::StaticVector< float, 4u, blaze::rowVector > va(a);
blaze::StaticVector< float, 4u, blaze::rowVector > vb(b);
std::cout << va << std::endl;
std::cout << vb << std::endl;
return 0;
}
Furthermore, I also wonder why
explicit inline constexpr StaticVector( initializer_list<Type> list );
template< typename Other, size_t Dim >
explicit inline StaticVector( const Other (&array)[Dim] );
both are declared explicit
?
Comments (13)
-
-
reporter Hi Klaus,
Thanks for the answers! You're right about the C++20
std::span
generalization for general contiguous sequences. (In the meantime, you can consider usinggsl::span
as that was the originalstd::span
proposal.) Apparently,std::span
hasconstexpr
constructors which still enable the compile-time construction ofblaze::StaticVector
from anstd::array
.What I now also wonder about, as
std::span
is going to be the preferred way to construct Blaze vectors from STL kind of containers, is whether there is a preferred way (e.g., explicit cast operators) of going back to STL kind of containers without using a free function or relying on the container itself to do the conversion. My problem with e.g., DirectXMath are explicit load/store barriers to/from the SIMD registers as there is still a need of non-SIMD storage containers of which the size is exactly the same across all platforms for storage and GPU packing purposes:// Pseudo C++: StorageVectorType stored = ... ; ComputeVectorType computed = Load(stored); // do all computations stored = Store(computed);
-
reporter Note that
std::span
could not be used to go back to STL containers via a cast operator asstd::span
merely implements a (mutable) view to a contiguous sequence. -
Hi Klaus and Matthias,
Preface: list-initialization is a huge reason I am exploring Blaze over Eigen. I had a longer discussion that was more technical than I am comfortable with, regarding internals of C++ and the STL, so I'll leave this as a use-case consideration and not a technical suggestion.
The issue with
explicit
came directly to me today as I started exploring Blaze, so I am glad I found this discussion. I’m developing a library which avoid exposing explicitly the underlying storage types. This model is already working well as most changes from Eigen to Blaze were quite simple.Consider an example API in which it does not matter to the end user what the underlying storage looks like.
class Thing { typedef Vector_t = ... // std::array<int, 3> or blaze::StaticVector<int, 3> void process(const Vector_t& external) { ... } }; // Valid for std::array but not for blaze Thing::process({1,2,3}); // Valid for both, but unnecessary extra work for the user Thing::Vector_t v{1,2,3}; Thing::process(v); // Valid for std::array but not blaze, still unnecessary extra work for the user Thing::Vector_t v = {1,2,3}; Thing::process(v);
If I had only the second/third use-case above, there would be no issue, as they are essentially the same from a user perspective, but the first case is a motivator for me.
To make the first case work, I have to overload
process
to take a specific STL container, then use the StaticVector constructor on size and data to create a vector which can be passed to the cannonicalprocess
function. ( A similar hack works in Eigen, but still requires an additional copy, usingEigen::Map
.) I will proceed this way, but it would be really nice to essentially treat StaticVector as a mathematically enhanced drop-in replacement for std::array.Related, speaking with respect to copies and overhead, why is the argument here not a const reference?
explicit inline constexpr StaticVector( initializer_list<Type> list );
-
To answer your question:
std::initializer_list
contains a pointer and a size (see cppreference for more details). That is why at cppreference or also in the C++ Core Guidelines you will always find examples where anstd::initializer_list
is passed by value.I understand your example as a request to rethink the
explicit
specifier on thestd::initializer_list
constructors. That is an interesting point, especially since the standard library itself uses non-explicit
constructors. We will consider the this change.Best regards,
Klaus!
-
reporter Normally, I would prefer the
explicit
keyword for pretty much all my C++ constructors (to avoid chains of implicit type conversions), but for mathematical concepts such as vectors, matrices and tensors it feels more convenient and familiar to have implicit constructors to write statements such asMyVector v = { 1, 2, 3 };
std::array
seems to me like the most portable standard C++ adapter to transfer data between libraries in both ways (the Vc std proposal seems to use it as well). Though, one probably will construct a wrapper anyway, becausestd::array
has no constructors and uses aggregate initialization (which starts to look different and odd for initializing aggregates of aggregates).std::span
can be used as well to transfer data to a library, as it provides an implicit constructor accepting astd::array
, but not always to transfer data from a library (e.g., implicit/explicit cast operator), as it is merely a view.In order to avoid adding Blaze dependencies to my core library while still having
std::array
that can be constructed from Blaze static vectors and matrices. I created a class that inherits fromstd::array
and adds constructors (instead of using aggregate initialization). Apart from the usual default, copy and move constructor, I only added one variadic template argument constructor which can be evaluated at compile-time and roughly acts as follows:-
For 1D arrays:
- Flatten (up to one level for tuple-like arguments) and concatenate the given flattened arguments to obtain the array to construct (padded with zeros if needed).
-
For nD > 1D arrays:
- If there is only one argument of the same dimension, and smaller or equal size as the array to construct, convert the argument to the array to construct (padded with zeros if needed). This assumes the argument is tuple-like.
- If there are multiple arguments of smaller dimension, and smaller or equal size of the element of the array to construct, convert each argument to the element of the array to construct (padded with zeros if needed) and combine all of these elements into the array to construct (padded with zeros if needed). This basically relies on recursion.
The essence consists of using something like the following to decide on “tuple-like”:
template< typename T, typename = void > struct is_tuple : std::false_type {}; template< typename T > struct is_tuple< T, std::conditional_t< false, decltype(std::tuple_size< std::decay_t< T > >::value), void > > : std::true_type {}; template< typename T > constexpr bool is_tuple_v = is_tuple< T >::value;
This allows me to use
std::array
,std::pair
,std::tuple
and basically anything supporting structure binding as arguments.
Note that Blaze already kept 1D arrays (vectors) and 2D arrays (matrices separate).
-
-
In commit b786653 we have removed the
explicit
specifier from all vector and matrix constructors taking aninitializer_list
. You should now be able to use the desired syntax. -
reporter Really nice feature which is consistent with the STD containers with
initializer_list
(non-explicit) constructors. -
-
assigned issue to
-
assigned issue to
-
- changed status to open
-
- changed status to resolved
Summary
The feature has been implemented, tested, optimized, and documented as required. It is immediately available via cloning the Blaze repository and will be officially released in Blaze 3.7.
Array Construction
All dense vector classes offer a constructor for an initialization with a dynamic or static array, or with a
std::array
. If the vector is initialized from a dynamic array, the constructor expects the actual size of the array as first argument, the array as second argument. In case of a static array orstd::array
, the fixed size of the array is used:const unique_ptr<double[]> array1( new double[2] ); // ... Initialization of the dynamic array blaze::StaticVector<double,2UL> v13( 2UL, array1.get() ); const int array2[4] = { 4, -5, -6, 7 }; blaze::StaticVector<int,4UL> v14( array2 ); const std::array<float,3UL> array3{ 1.1F, 2.2F, 3.3F }; blaze::StaticVector<float,3UL> v15( array3 );
Array Assignment
Dense vectors can also be assigned a static array or
std::array
:blaze::StaticVector<float,2UL> v1; blaze::DynamicVector<double,rowVector> v2; const float array1[2] = { 1.0F, 2.0F }; const std::array<double,5UL> array2{ 2.1, 4.0, -1.7, 8.6, -7.2 }; v1 = array1; v2 = array2;
-
reporter This is great! Adding C++20’s
std::span
eventually will now be trivial in the future as the most important constructors are already explicitly present now. Also, switching back and forth between my own array-like container types is very convenient and transparant now: (i) the structure binding support allows me to construct instances of these types from the Blaze vectors without having any Blaze dependencies, and (ii) thestd::array
constructor allows me to construct Blaze vectors from instances of these types without Blaze having any dependencies to my code. So the only common glue isstd::tuple
/std::array
.One odd thing imho, are the constructors being
explicit
, while having implicit assignment operators forstd::array
. Assuming the latter is the desired behavior, the constructor should be implicit as well for consistency. Furthermore, the overloadedstd::array
assignment operator can be removed in which case the implicit constructor will be called to make a temporary vector which will then be assigned using the move assignment operator.There is, however, one caveat, I got to think of,
std::array
can be initialized with fewer elements than the size. On the other hand, there is thestd::initializer_list
constructor as well, so that one will be called in case of brace initialization. -
Hi Matthias!
You have convinced me (again) to remove the
explicit
keyword from the new dense vector constructors accepting astd::array
(see commit fcb74d2). Thanks for pointing out this inconsistency,Best regards,
Klaus!
- Log in to comment
Hi Matthias!
Thanks a lot for creating this proposal. You are correct, it would be convenient to have a constructor that enables you to pass a
std::array
or alsostd::vector
directly instead of passing its size and data:However, there shouldn't be a special purpose constructor for every possible container, but a constructor based on
std::span
. Sincestd::span
is proposed for C++20, we are considering to alternatively provide a stand-in untilstd::span
becomes available.The two constructors are marked
explicit
as the C++ Core Guideline C.46 suggests that all single argument constructors should be explicit. We believe that in both cases an explicit constructor call is reasonable since it involves a copy operation (i.e. runtime overhead). This shouldn't go unnoticed in an implicit conversion.Thanks again for creating the proposal,
Best regards,
Klaus!