std::matrix<...> // A matrix class with storage
std::matrix_span<...> // A mutable view into a matrix.
std::matrix_view<...> // A non-mutable view into a matrix.subspan(src, pos, size);template<typename A, typename DataType, typename Properties..., typename P, typename S>
auto subspan(mdspan< DataType, Properties ... > const & u, P pos, S size);
template<size_t...sizes, typename DataType, typename Properties..., typename P, typename S> auto subspan(mdspan< DataType, Properties ... > const & u, P pos, S size);// A column vector for all of the 3:rd column. 3 is the only remaining pos dimension, there is no dynamic size.
subspan<0, std::all>(mat, 3);
// A row vector for all of the third row. 3 is the only remaining pos dimension, there is no dynamic size.
subspan<std::all, 0>(mat, 3);
Here are some comments on the p0009r4 proposal for a multi-dimensional span.A multi-dimensional matrix and view into it is a very common and important data structure. P0009 proposes only a mutable view (aka span) and not any actual matrix with storage. I think this is a pity as it will grow a plethora of almost equal implementations of matrix classes more or less successful in using the optimization potential for fixed size matrices.
Another argument for standardizing both is how spans are to be created. Not having a consistent way of creating a subspan from a matrix and a parent span is a big drawback. Without a standardized matrix and with std::subspan() function the prescribed way of creating a subspan from a parent span the only way to roll your own matrix is to overload subspan function in your namespace. This smells awfully similar to the std::swap problems.*Starting with the obvious the name mdspan is very unintuitive. I have never encountered md as an acronym for multi-dimensional before. Looking through the name bikeshedding list I don't see the word matrix being considered.
If it has been discarded it would be nice to discuss why, as it is such an obvious candidate:std::matrix<...> // A matrix class with storage
std::matrix_span<...> // A mutable view into a matrix.
std::matrix_view<...> // A non-mutable view into a matrix.Maybe someone wants to argue that matrix must necessarily be two dimensional, but I think that is a rather weak argument as both MTL and Eigen call their n-dimensional data types matrix.
* We already have a similar problem with strings: If you have a string and make substr you get a string, but if you have a string_view you get another string_view. That the like named function returns different kinds of results depending on the source is surprising.
It would have been much better if substr() always returned a string and view() always returned a string_view. That ship sailed recently, though.
On Sunday, November 19, 2017 at 7:16:02 PM UTC-5, Bengt Gustafsson wrote:Here are some comments on the p0009r4 proposal for a multi-dimensional span.A multi-dimensional matrix and view into it is a very common and important data structure. P0009 proposes only a mutable view (aka span) and not any actual matrix with storage. I think this is a pity as it will grow a plethora of almost equal implementations of matrix classes more or less successful in using the optimization potential for fixed size matrices.1: It's not the job of every proposal to solve every problem. It is not the job of a view class proposal to solve the problem of the propagation of container types (outside of the view class becoming a lingua franca type). P0009 is just fine tackling the view part; if you feel that we need a tensor container library, you can propose one yourself.
2: We already have a "plethora of almost equal implementations of matrix classes". I fail to see how this proposal will create more of these.
Another argument for standardizing both is how spans are to be created. Not having a consistent way of creating a subspan from a matrix and a parent span is a big drawback. Without a standardized matrix and with std::subspan() function the prescribed way of creating a subspan from a parent span the only way to roll your own matrix is to overload subspan function in your namespace. This smells awfully similar to the std::swap problems.*Starting with the obvious the name mdspan is very unintuitive. I have never encountered md as an acronym for multi-dimensional before. Looking through the name bikeshedding list I don't see the word matrix being considered.
If it has been discarded it would be nice to discuss why, as it is such an obvious candidate:std::matrix<...> // A matrix class with storage
std::matrix_span<...> // A mutable view into a matrix.
std::matrix_view<...> // A non-mutable view into a matrix.Maybe someone wants to argue that matrix must necessarily be two dimensional, but I think that is a rather weak argument as both MTL and Eigen call their n-dimensional data types matrix.Yes, and C++ calls their one-dimensional dynamically extensible array type "vector", while calling their one-dimensional statically sized array type "array". People use stupid names for things all the time; that should not be taken as justification for propagating it.
* We already have a similar problem with strings: If you have a string and make substr you get a string, but if you have a string_view you get another string_view. That the like named function returns different kinds of results depending on the source is surprising.... that would only be surprising if they were non-member functions. That member functions on different classes might return different types is not, and should not be, a surprise.
Den måndag 20 november 2017 kl. 01:41:42 UTC+1 skrev Nicol Bolas:On Sunday, November 19, 2017 at 7:16:02 PM UTC-5, Bengt Gustafsson wrote:Here are some comments on the p0009r4 proposal for a multi-dimensional span.A multi-dimensional matrix and view into it is a very common and important data structure. P0009 proposes only a mutable view (aka span) and not any actual matrix with storage. I think this is a pity as it will grow a plethora of almost equal implementations of matrix classes more or less successful in using the optimization potential for fixed size matrices.1: It's not the job of every proposal to solve every problem. It is not the job of a view class proposal to solve the problem of the propagation of container types (outside of the view class becoming a lingua franca type). P0009 is just fine tackling the view part; if you feel that we need a tensor container library, you can propose one yourself.Well, that's the first time we have a view and nothing to view...
would you think string_view without a string class would be a good idea too?
2: We already have a "plethora of almost equal implementations of matrix classes". I fail to see how this proposal will create more of these.Because the intent of mdspan is not to view hyperslabs out of other n-dimensional data structures but to _convert_ a 1-dimensional data structure to a n-dimensional one. So it will be easy to tuck a mdspan on top of a vector<T>, unique_ptr<T> or array<T,N> or a raw T* or string<T> and create your own tensor class, not quite the same as your colleague's.
If it has been discarded it would be nice to discuss why, as it is such an obvious candidate:std::matrix<...> // A matrix class with storage
std::matrix_span<...> // A mutable view into a matrix.
std::matrix_view<...> // A non-mutable view into a matrix.Maybe someone wants to argue that matrix must necessarily be two dimensional, but I think that is a rather weak argument as both MTL and Eigen call their n-dimensional data types matrix.Yes, and C++ calls their one-dimensional dynamically extensible array type "vector", while calling their one-dimensional statically sized array type "array". People use stupid names for things all the time; that should not be taken as justification for propagating it.So mdspan is less stupid than matrix_view because in math a matrix is always two dimensional and mdspan is never heard of before?
* We already have a similar problem with strings: If you have a string and make substr you get a string, but if you have a string_view you get another string_view. That the like named function returns different kinds of results depending on the source is surprising.... that would only be surprising if they were non-member functions. That member functions on different classes might return different types is not, and should not be, a surprise.Except if you write template code taking strings or string_views and want to get a string_view to some part of the parameter value.
Then you may try using substr() on your parameter and get a performance hit when instantiating for string, or you may try to construct the string_view but there are no constructors from string_views OR strings that take pos,len parameters. The only agnostic way I could find would be the less elegant:string_view(parameter.data() + pos, len)
`mdview` can work with `vector` and `array` as they currently exist. So we very much already have something to view.
To me, the whole point of `mdview` is so that people don't have to write multidimensional array containers ever. Instead, you use a single-dimensional container, and you use `mdview` to pretend it is multidimensional. So not having a type specifically for this is not a bad thing.would you think string_view without a string class would be a good idea too?If we were back in C++98 world, and someone had come up with `string_span` instead of `std::string`, it wouldn't be a bad idea to incorporate `string_span` in the absence of `std::string`. Indeed, it could have saved us a decade+ of COW `std::string` "optimizations", and it would have given us a strong impetus to develop a specific SSO-vector type.
2: We already have a "plethora of almost equal implementations of matrix classes". I fail to see how this proposal will create more of these.Because the intent of mdspan is not to view hyperslabs out of other n-dimensional data structures but to _convert_ a 1-dimensional data structure to a n-dimensional one. So it will be easy to tuck a mdspan on top of a vector<T>, unique_ptr<T> or array<T,N> or a raw T* or string<T> and create your own tensor class, not quite the same as your colleague's.Right, but that's a problem which already exists. How does adding `mdspan` in any way make it more likely that the two of us will create different tensor classes? If I need a tensor class, either the standard library provides one or it doesn't. And if it doesn't, I'll write one (or use someone else's where possible), whether `mdspan` exists or not.And so long as your colleague gives their type an `mdspan` interface, then at least you can have some interop between them.
If it has been discarded it would be nice to discuss why, as it is such an obvious candidate:std::matrix<...> // A matrix class with storage
std::matrix_span<...> // A mutable view into a matrix.
std::matrix_view<...> // A non-mutable view into a matrix.Maybe someone wants to argue that matrix must necessarily be two dimensional, but I think that is a rather weak argument as both MTL and Eigen call their n-dimensional data types matrix.Yes, and C++ calls their one-dimensional dynamically extensible array type "vector", while calling their one-dimensional statically sized array type "array". People use stupid names for things all the time; that should not be taken as justification for propagating it.So mdspan is less stupid than matrix_view because in math a matrix is always two dimensional and mdspan is never heard of before?... yes. Better to invent a new name than to misuse an existing one. See "vector" for an example. In mathematics, a particular vector has fixed dimensionality, while in C++, they have dynamic dimensionality.
* We already have a similar problem with strings: If you have a string and make substr you get a string, but if you have a string_view you get another string_view. That the like named function returns different kinds of results depending on the source is surprising.... that would only be surprising if they were non-member functions. That member functions on different classes might return different types is not, and should not be, a surprise.Except if you write template code taking strings or string_views and want to get a string_view to some part of the parameter value.Why would I do that? Why would I not simply write a non-template function that takes `string_view`, on the assumption that the user can simply convert their string-type-of-choice into `string_view`?
After all, if my algorithm is non-mutating, why would I want to exclude users of string types that don't follow the standard's conventions? `string_view`-based interfaces makes all string types equal.Then you may try using substr() on your parameter and get a performance hit when instantiating for string, or you may try to construct the string_view but there are no constructors from string_views OR strings that take pos,len parameters. The only agnostic way I could find would be the less elegant:string_view(parameter.data() + pos, len)Or just let the implicit conversion do its job. Indeed, this is why we have implicit conversion to `string_view`. And again, this requires that `parameter` use `std::string`'s interface. And that interface is not exactly popular outside of the standard library.
Why would a generic view-based matrix multiply operation return a *value*
rather than write to an output parameter? Views are references, not values.
So it's not clear why you would expect `multiply(view, view)` to return
some container. And which container would that be? Sure, you can certainly
write that with some form of template parameter: `multiply<array<T,
X>>(view, view)`.
But generally speaking, operations on views write to a view that is
provided; they don't return values. This is how standard library algorithms
work. `std::copy` does not return a copy as a container; it writes to a
given iterator.
A container-based matrix multiply would naturally return a container. But a
view-based one would operate on an output view parameter.
Now, I imagine what you're going to immediately say is that this breaks
guaranteed elision, because the container multiply should defer to the view
multiply. But if you do that, then the container multiply has to create a
container on the stack, then fill in its values through the view multiply.
And therefore it cannot take advantage of guaranteed elision.
Well, my feeling on the matter is this: if you're using a compiler that
cannot provide NRVO for this case:
Typename some_val;
fill_in_type(some_val);
return some_val;
Then you need to stop using that compiler post-haste.
If it has been discarded it would be nice to discuss why, as it is such an
>>>>> obvious candidate:
>>>>>
>>>>> std::matrix<...> // A matrix class with storage
>>>>> std::matrix_span<...> // A mutable view into a matrix.
>>>>> std::matrix_view<...> // A non-mutable view into a matrix.
>>>>>
>>>>> Maybe someone wants to argue that matrix must necessarily be two
>>>>> dimensional, but I think that is a rather weak argument as both MTL and
>>>>> Eigen call their n-dimensional data types matrix.
>>>>>
>>>>
>>>> Yes, and C++ calls their one-dimensional dynamically extensible array
>>>> type "vector", while calling their one-dimensional statically sized array
>>>> type "array". People use stupid names for things all the time; that should
>>>> not be taken as justification for propagating it.
>>>>
>>> So mdspan is less stupid than matrix_view because in math a matrix is
>>> always two dimensional and mdspan is never heard of before?
>>>
>>
>> ... yes. Better to invent a new name than to misuse an existing one. See
>> "vector" for an example. In mathematics, a particular vector has fixed
>> dimensionality, while in C++, they have dynamic dimensionality.
>>
>
> No, better to use well known names which give intuitive understanding and
> learn that details may vary (everyone knows this anyway).
>
"Intuitive understanding" is in the eye of the beholder. Multi-dimensional
views or array is a generic name befitting a generic concept. "Matrix" is
not a generic name; it is a specific math construct, like "vector". And
"set".
Repeating the same mistake over and over again is foolish.
* We already have a similar problem with strings: If you have a string and
>>>>> make substr you get a string, but if you have a string_view you get another
>>>>> string_view. That the like named function returns different kinds of
>>>>> results depending on the source is surprising.
>>>>>
>>>>
>>>> ... that would only be surprising if they were non-member functions.
>>>> That member functions on different classes might return different types is
>>>> not, and should not be, a surprise.
>>>>
>>> Except if you write template code taking strings or string_views and
>>> want to get a string_view to some part of the parameter value.
>>>
>>
>> Why would I do that? Why would I not simply write a non-template function
>> that takes `string_view`, on the assumption that the user can simply
>> convert their string-type-of-choice into `string_view`?
>>
> In my particular case this was due to char type conversions that had to be
> done, but only if necessary. As you know it is hard to get a string_view to
> some other char type, while a string can be copied with conversion.
>
... huh? How can it be harder to copy a string type than to copy a
string_view, when performing the exact same conversion? The conversion is a
loop over the characters, right? How exactly is it harder to loop over a
`std::string` than a `string_view`?
Unless you're talking about some special-case string type that has this
conversion built in. In which case, your API clearly takes that string, not
`std::string` and certainly not a conceptualized `T`.
I forgot the exact details but it had to do with optimizing the use of
> string literals. What I do remember was that I was disappointed at how
> seldom string_view was actually possible to use, I introduced them and then
> had to go back to strings on numerous occasions.
>
> The world is full of problems which don't lend themselves to simple rules.
>
And your solution is to get rid of simple rules? Exceptional circumstances
are *exceptional*. That doesn't mean that simple rules don't apply to most
cases.
It's so boring to hear "don't do that, use the standard solution" when
> obviously the standard solution was the first thing you tried and it didn't
> work. I think we must appreciate that there are good reasons to do most
> things, even though we can't foresee them. In the case of matrix vs
> matrix_view it is very easy to find cases where matrices are needed but
> that doesn't seem to help much against lofty principles.
>
OK, since you didn't seem to understand this point the first time, let me
say it again.
I'm not saying that the standard should not have a multidimensional
container. I'm saying that the lack of such a container type is *not* a
flaw in `mdspan`.
If you want to propose an mdcontainer of some form, go right ahead. But
that is *orthogonal* to mdspan. Obviously they should be reconciled to have
similar interfaces and conversions where appropriate. But one should not
otherwise impact the other.
You personally, for your use cases, may not have much use for mdspan
without a dedicated multidimensional container, but other people do.
After all, if my algorithm is non-mutating, why would I want to *exclude*
>> users of string types that don't follow the standard's conventions?
>> `string_view`-based interfaces makes all string types equal.
>>
>> Then you may try using substr() on your parameter and get a performance
>>> hit when instantiating for string, or you may try to construct the
>>> string_view but there are no constructors from string_views OR strings that
>>> take pos,len parameters. The only agnostic way I could find would be the
>>> less elegant:
>>>
>>> string_view(parameter.data() + pos, len)
>>>
>>
>> Or just let the implicit conversion do its job. Indeed, this is *why* we
>> have implicit conversion to `string_view`. And again, this requires that
>> `parameter` use `std::string`'s interface. And that interface is not
>> exactly popular outside of the standard library.
>>
>
> So again you are telling me that I run into problems inside my function
> implementation _because I tried to write such a function_.
>
Well, since you put it that way... *yes*.
The committee never conceptualized `std::string`'s interface. There was
never a promise that any type which used this interface would do so with
the exact same meaning. That every type that used the interface would be a
value type rather than a reference type.
You wrote a function under an assumption that is no longer valid. But, that
assumption was *never* valid, since `string_view` types that mimic
`std::string`'s interface are actually fairly common. And *not one of them*
has their `substr` function return a value rather than a view.
Functions that operate on values are not expected to be able to operate on
references/views. If your function operates on value types, then that is
effectively a part of that function's interface. That your value-oriented
function fails when passed a view is natural and expected.
So yes, you ran into problems because you wrote a value-oriented function
that got passed an API-compatible view. C++ doesn't really have a way to
stop that, to recognize that a type is a value type rather than a reference
type. But that was a possibility from the day you first wrote that function.
You can either keep it as is and remind people not to pass views, or you
can turn it into a view-oriented function. But you don't change a view type
to act like a value type.
And yes, I value "lofty principles" over the occasional pain point, because
sticking to those "lofty principles" is precisely why the STL *works*. The
container/algorithm/iterator design is an incredibly useful and powerful
abstraction. And Ranges only makes it moreso. But that can only come about
due to an unwavering commitment to that abstraction, to those "lofty
principles".
When you *don't* stick to "lofty principles", you get nonsense like
`std::async`, where its return `future` object has fundamentally different
behavior from *every other* `future` object. Why? Because somebody didn't
want to write a second `future` type with a nearly identical API but
different destructor behavior.
I understand valuing expediency in the moment over forming a genuine model
or abstraction. But expediency often leads to compromised design.
This is the gist of most replies on Stack Overflow: "don't try to do that,
> it is the wrong thing to want to do". This is not a very productive point
> of view unless you point at a better solution to the same problem. Most
> often, as in your case, replies point at solutions to other, simpler
> problems.
>
> After 40 years in computing and 23 with C++ I can say that I do something
> it is usually for a good reason. If I complain about the APIs being hard to
> use to do what I need done it is probably because they are, and that I see
> a way to improve them. Being patted on the head and told to go and solve a
> simpler problem that the API supports is not a helpful answer.
>
No amount of experience stops programmers from writing themselves into
corners. And no amount of experience changes the fact that the best
solution to that is to avoid writing yourself into a corner in the first
place.
The problem I have is that your "way to improve them" doesn't "improve"
them at all; it *redefines* them. Making `string_view.substr` return a
`std::string` is as absurd as making `std::copy` return a `vector`.
Declaring yourself an expert is not a particular convincing argument. A
more convincing one would be showing problems that a significant number of
users experience with `string_view`. Not just those who have their own
string types or are doing some conversion or something.