Comments on P0009 mdspan.

275 views
Skip to first unread message

Bengt Gustafsson

unread,
Nov 19, 2017, 7:16:02 PM11/19/17
to ISO C++ Standard - Future Proposals
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.

Restricting indexing with an object (as opposed to a number of integers) to std::array seems unnecessary, any type which has get<R>() functions defined for the rank first R values should be accepted. This is especially important as std::array does not provide any arithmetic operations and many algorithms (for instance in image processing) rely heavily on doing arithmetic between matrix positions. Doing this with a std::array is tedious.

My brain stumbles on the use of extent for the size in each dimension. I would much prefer bound as extent for me is both the lower and upper bound, so extents would be a hyperslab i.e. what you need to send to subspan() to select what part to cut out. I know that earlier proposals used bound(s) so why the change?

Speaking of subspan() and its way of defining the hyperslab to cut out it seems very awkward to use a make_pair per dimension. Without doing a big research into existing code I think that it is more often that you want to select by a n-dimensional starting position and a n-dimensional size:

subspan(src, pos, size);


There is some optimization potential in subspan() as there are common use cases where the size of the subspan is known at compile time. While there are also cases where the position of the subspan is known at compile time the potential performance gain from exploiting this is almost nil as the gain happens when you loop over the elements of the created span, which lends itself to better unrolling strategies when the size is known. Thus it seems logical that the subspan function could take explicit template parameters for the sizes as to be able to fix the size of the returned span in zero or more dimensions. As far as I can see subspan as it stands does not provide for any fixed size functionality and thus always returns a span which is dynamically sized in all dimensions. Only way of providing this functionality (given the functionality in the appendix) would be to overload the function with a version which has an extra undeducable template parameter first:

template<typename A, typename DataType, typename Properties..., typename P, typename S>
auto subspan(mdspan< DataType, Properties ... > const & u, P pos, S size);


In the signature above A must be an array type with empty [] denoting dynamically sized dimensions. P is a datatype with get<I> for I up to the rank of u, while S is a datatype with get<J> for a J up to the number of empty [] in A. It is unfortunately redundant to have a base type before the first bracket but this seems inevitable to gain consistency with the use of T[][] to specify the matrix size. An alternative declaration is:

template<size_t...sizes, typename DataType, typename Properties..., typename P, typename S>
auto subspan(mdspan< DataType, Properties ... > const & u, P pos, S size);

But then you're back to having to use the ugly std::dynamic_extent again**. You can specify up to rank numbers in sizes, but if you specify fewer than rank the rest are dynamic (which provides a good default avoiding special overloads for the all-dynamic case). In addition to std::dynamic_extent and a static size each sizes number could be allowed to be 0 to mean that this dimension is removed and is not to be mentioned when indexing the subspan, or numeric_limits<ptrdiff_t>::max() to indicate "all", which instead removes the dimension when specifying the pos of the subspan. This provides easy ways to get row and column vectors out of a matrix:

// 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);  


These examples use a joint list of integer pos values followed by size valuess, each part containing as many values as needed. This would require some serious TMP to get right, and may also be deemed too error prone. But note that most matrices will be of only 2-3 dimensions where this should be understandable.

I also have some ideas on how the Properties parameters could be arranged to allow for other storage orders such as triangular or banded matrices but it is getting too late to write more today.

Bengt


* 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.

** But if you feel free to occupy the short name std::all, why not the short name std::dyn?



Nicol Bolas

unread,
Nov 19, 2017, 7:41:42 PM11/19/17
to ISO C++ Standard - Future Proposals
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.

It shouldn't be. A "matrix" in this context is a two-dimensional construct. "Tensor" is the proper term for an equivalent of arbitrary dimensions.

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.
 
It would have been much better if substr() always returned a string and view() always returned a string_view. That ship sailed recently, though.

And a good thing, too. `string_view` functions should not be returning `string`s or any other specific string type. If you want to copy from a `string_view`, then you make a `string` or use whatever your favorite string type is to do so.

Bengt Gustafsson

unread,
Nov 22, 2017, 2:56:29 PM11/22/17
to ISO C++ Standard - Future Proposals


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.


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.

It shouldn't be. A "matrix" in this context is a two-dimensional construct. "Tensor" is the proper term for an equivalent of arbitrary dimensions.

In the world of mathematics this may be true, but reading up on the subject on Wikipedia I see that tensors are typically very small like 3x3 tied to a particular physics problem formulation. The word has more semantic value than just a n-dimensional matrix (as opposed to the word matrix, which carries no such semantics to a mathematician). Tensor is like the mathematical vector (with one number per dimension in a room) generalized, while the mathematical matrix is a general math tool often of variable size such as in simultaneous equations. Tensor fields, as used for instance in FE modelling have one variable size, matrix in typically our three usual dimensions with each element being a tensor, maybe 3x3x3. Noone would call this a tensor of tensors. So I don't think taking math terminology too far is going to be of value. 

That said I don't see tensors or fields on the bikeshedding list either. I guess  my main issue would be with 2D matrices being called tensors which is really uncommon. My assumption is that even though we are talking n dimensions n is most often going to be 2.



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)

Nicol Bolas

unread,
Nov 22, 2017, 5:47:01 PM11/22/17
to ISO C++ Standard - Future Proposals
On Wednesday, November 22, 2017 at 2:56:29 PM UTC-5, Bengt Gustafsson wrote:
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...

`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.


Bengt Gustafsson

unread,
Nov 25, 2017, 6:08:50 AM11/25/17
to ISO C++ Standard - Future Proposals

`mdview` can work with `vector` and `array` as they currently exist. So we very much already have something to view.
Try that when implementing for instance matrix multiply operator. As we don't have a matrix class there is nothing we can return. We can't return a span as there is nothing left it can refer to, we can't return an array or vector as the caller would not have a way to access it as a matrix.

 

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.
So there we go again: COW strings are inefficient. Everyone says so, but I have never seen any proof. On the other hand I made one myself around 1998 as the std::string we had was too slow (same problem as all current std::strings). It is still in use with the same benefit (in a heavily multithreaded application). It is 99% std::string compatible except for the 1% that makes it impossible to make a COW string fully compatible...
 

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.

Except when returning by value and so on.
 

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).



* 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. 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. 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.

 

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_. 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.

Bjorn Reese

unread,
Nov 25, 2017, 11:22:54 AM11/25/17
to std-pr...@isocpp.org
On 11/25/2017 12:08 PM, Bengt Gustafsson wrote:

> So there we go again: COW strings are inefficient. Everyone says so, but
> I have never seen any proof. On the other hand I made one myself around

http://www.gotw.ca/publications/optimizations.htm

Nicol Bolas

unread,
Nov 25, 2017, 12:28:27 PM11/25/17
to ISO C++ Standard - Future Proposals
On Saturday, November 25, 2017 at 6:08:50 AM UTC-5, Bengt Gustafsson wrote:
>
>
>>>>> `mdview` can work with `vector` and `array` as they currently exist.
>> So we very much already have something to view.
>>
> Try that when implementing for instance matrix multiply operator. As we
> don't have a matrix class there is nothing we can return. We can't return a
> span as there is nothing left it can refer to, we can't return an array or
> vector as the caller would not have a way to access it as a matrix.
>

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.

Reply all
Reply to author
Forward
0 new messages