C++ Logo

std-proposals

Advanced search

Re: Explicit using --> Library for simple strong types

From: Peter Sommerlad (C++) <"Peter>
Date: Wed, 27 Jan 2021 13:54:07 +0100
Cher Yves

BAILLY Yves via Std-Proposals wrote on 27.01.21 11:51:
> Thank you for the information Peter 😊
>
> As nice and well-done your framework is, it is still "yet another" approach to provide strong typing to C++.
But it is different from many previous approaches since it does not
require defining constructors or own operators.
>
> Suppose I have created my own, and I use a library providing its own, and I use another library providing yours... Now I have at least 3 incompatible and competing "strong typing" frameworks in my software.

So what, that is always the case if different librarys share common
non-standardized abstractions. With that argument, we would be using
void* throughout any interface (in C++), fortunately we do not need that
anymore.

Complexity, confusion... similar situation when you have to use
geometric point types and you have to use several libraries, each
providing their own ones 😊 glm::vec3 and Eigen::Vector3f are basically
the same thing yet it's a pain if I have to combine them in a single
algorithm, because using 3rd party libraries.

Considering that specific example, what is the problem in those
libraries is that the same type is used for the affine space and the
vector space (with an origin) not distinguishing between a point and a
displacement, because those two distinct types share the same
representation. My framework provides a template (create_vector_space)
for 1D affine spaces that provides the ability to have two strong types
sharing the relationship of the same affine space with different origins
(see example on degrees Kelvin and Celsius).

I think that is the whole purpose of types from different libraries to
be distinct, because they have distinct semantics, even if they share
structure. If you follow this argumentation any two libraries that cover
a similar realm will need to standardize their common ground in the C++
standard, before they can be used. This puts a burden on both
standardization of so-called "vocabulary types" as well as the libraries
to reduce their outer face to just those types and not export any of
their own.

My goal is to ease the means to define specific semantic, where
otherwise `int` or `double` might be used, allowing too many operations
and only conveying intended semantics through variable/parameter names
and not their type.

If common things like `int` or `double` are used for distinct purposes
and thus different semantics, confusion and mistakes occur.

>
> This is to illustrate my opinion that there's a real need to somehow provide a standard way to achieve the desired goal - here, strong typing.

I just say, we already have a type system supporting strong types (may
be not as simple as in Ada, but that was not yet my goal).

And strong typing is not only about distinguishing types but also limit
the available operations to those that make sense for the intended
semantics, i.e., if you have a struct name:strong<std::string,name>{};
you would not expect a name to support mutations, for example.

>
> Maybe, probably it can be done at the library level. However, a library implementation involves some more or less heavy template machinery, making it not so easy to use.

Did you look at my attempt. I consider the user side not really template
heavy. You can have a single template to inherit from with two
obligatory arguments and may be some more for a select set of supported
operations.


Moreover it would probably increase compile time, which is usually not
desirable (I daily work on softwares that typically need hours to build).

Did you compare that to a language feature? Why wouldn't that increase
compile time?

The biggest fear I have about introducing new language features is the
unforseen interaction with the rest of the language. This could mean
that those language-defined strong types behave differently from the
existing types. We have such a wart already with enum types that are
kind of second-class user-defined types.


> Also a library implementation implies some kind of tag to distinguish between the "strongly type types", either a value or an empty type.

Again, you didn't seem to bother to look at my implementation. The type
itself is the tag, so no need to manage that, since you need to give a
name anyway. The only hurdle is that you have to repeat it once in its
own definition, so there is the copy-waste issue, but that would easily
result in a compile error.

The management of these tags is put on the user's responsability, making
it potentially difficult to avoid clashes with locally defined derived
types and ones from a third party library.

I do not see that issue with self-contained types used as their own tags.

>
> That's why I thought an extension to the language would be more practical - although probably more work for compilers writers.

A language change was proposed before and failed to reach consensus
https://wg21.link/p0109

Regards
Peter.
>
> Regards,
>
> Yves Bailly
> Development engineer
> Manufacturing Intelligence division
> Hexagon
> M: +33 (0) 6.82.66.09.01
> HexagonMI.com
>
>> -----Original Message-----
>> From: Peter Sommerlad (C++) <peter.cpp_at_[hidden]>
>> Sent: Wednesday, January 27, 2021 11:31
>> To: std-proposals_at_[hidden]; BAILLY Yves via Std-Proposals <std-
>> proposals_at_[hidden]>
>> Cc: BAILLY Yves <yves.bailly_at_[hidden]>
>> Subject: Re: [std-proposals] Explicit using --> Library for simple strong types
>>
>> This email is not from Hexagon’s Office 365 instance. Please be careful while
>> clicking links, opening attachments, or replying to this email.
>>
>>
>> FWIW, I created a very simple to use strong typing framework for C++17 that
>> allows to create strong types with respective selection of appropriate operators
>> on a single line, such as
>>
>> struct Word:strong<std::string,Word, Out, Add, Order>{};
>>
>> struct literGas:strong<double,literGas,Additive,Order,Out>{
>> constexpr static inline auto suffix=" l";
>> };
>> the suffix static member is for output customization.
>>
>> Available at
>> https://eur02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.c
>> om%2FPeterSommerlad%2FPSsst&amp;data=04%7C01%7C%7C134ffe8c24384
>> 7ad5e3e08d8c2aeaba5%7C1b16ab3eb8f64fe39f3e2db7fe549f6a%7C0%7C0%
>> 7C637473402750776770%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAw
>> MDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&amp;sdat
>> a=TjsDb4SSwerQuv8A8%2B2SniCDv00deU3KxA7VbSbqbtI%3D&amp;reserved=
>> 0
>>
>> So I see little need in the language change, but I also did not follow the
>> discussed details.
>>
>> Regards
>> Peter.
>>
>> BAILLY Yves via Std-Proposals wrote on 27.01.21 11:22:
>>> I think I get your point. After more thinking about it (which I should
>>> have done before, sorry for that), then in short, I would say when
>>> writing
>>>
>>> *using U = new T;*
>>>
>>> …then yes, *U* is a new type different from *T* – although referring
>>> to the same Platonic type as *T*.
>>>
>>> This implies that indeed, *std::is_same<T,U>* is different from
>>> *std::is_same<T,T>* and *std::is_same_v<T,U> == std::is_same_v<U,T> ==
>>> false*.
>>>
>>>> For example:
>>>
>>>> template<class A> void f(A a);
>>>
>>>> f(T{}); // #1
>>>
>>>> f(U{}); // #2
>>>
>>>> Does #1 call f<T>? (Yes, it must.)
>>>
>>> Yes.
>>>
>>>> Does #2 call f<U>? (I don't know but I think so.)
>>>
>>> Yes, because it’s given a *U*.
>>>
>>>> Is f<U> the same function as, or a different function from, f<T>? (I
>>>> don't know but I think so.)
>>>
>>> If *T* and *U* are two different types, then *f<U>* is a different
>>> function from *f<T>*.
>>>
>>> /However,/ because *U* and *T* refer to the same Platonic type and a
>>> *U* can be seen as a *T*, then if *f<U>* is syntactically correct,
>>> well-formed with regard to the restrictions put on U (for example,
>>> inside *f<>* there’s no assignment of a *T* to a *U* without an
>>> explicit cast), then the actual instantiation of *f<U>* can be the
>>> same as the instantiation of *f<T>* - in a way, *f<U> = f<T>*. Put in
>>> another way, if we assume *f<T>* has already been created, then the
>>> compiler would create a new “envelop” named *f<U>* but with the same
>>> contents as *f<T>* – unless of course some explicit specialization
>>> *f<U>* is provided by the user.
>>>
>>> This would apply to the *std::hash<>* specialization: when required to
>>> instantiate *std::hash<U>*, if it has not been explicitly specialized,
>>> then as you said the compiler may realize that *std::hash<U>* is the
>>> same (has the same contents although it doesn’t have the same
>>> identity) as *std::hash<T>* - again, if and only if the code for
>>> *std::hash<U>* is well-formed and there’s no explicit specialization
>>> defined provided by the user.
>>>
>>>> using Height = new int;
>>>
>>>> using Tilt = new int;
>>>
>>>> void increaseAltitude(Height h);
>>>
>>>> void increaseAttitude(Tilt t);
>>>
>>>> ...
>>>
>>>> Height h;
>>>
>>>> increaseAttitude(h); // oops — but, since increaseAttitude takes a Tilt, this
>> happily won't compile!
>>>
>>>>
>>>
>>>> However, it doesn't seem to protect us against
>>>
>>>> Height h;
>>>
>>>> Tilt t;
>>>
>>>> t += h; // oops — and this silently calls the built-in candidate
>>>> operator+=(int&, int)
>>>
>>> That’s true indeed, no more protection here than when using
>>> “traditional” typedefs. And I see how this can indeed be seen as a
>>> “massive inconsistency” between *t=h* (not OK) and *t+=h* (OK).
>>>
>>> However, I would argue the situation is still better than when using
>>> “standard” using declarations: at least the function call typo will be
>>> caught.
>>>
>>> If protection in above situations is desired, a solution would be to
>>> explicitly delete the operations we don’t want:
>>>
>>> *Tilt& operator+=(Tilt&,Height const&) = delete;*
>>>
>>> This would protect against the code you suggested, but the following
>>> would still be valid:
>>>
>>> *int h2; // “int”, not “Height”*
>>>
>>> *t += h2; // OK – and maybe oops, or not*
>>>
>>> Another solution would be to use the other suggested construct:
>>>
>>> *using Height = explicit int;*
>>>
>>> *using Tilt = explicit int;*
>>>
>>> *Height h{1}; // OK*
>>>
>>> *Tilt t{2}; // OK*
>>>
>>> *t += 1; // OK – literal used*
>>>
>>> *t += h; // ill-formed here thanks to the “explicit”*
>>>
>>> With the “explicit” construct, there’s no implicit interaction between
>>> T and U.
>>>
>>> Regards,
>>>
>>> *Yves Bailly**
>>> *Development engineer
>>> Manufacturing Intelligence division
>>> *Hexagon*
>>> *M: *+33 (0) 6.82.66.09.01
>>> HexagonMI.com
>>> <https://eur02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww
>>> .hexagonmi.com%2Ffr-
>> FR%2F&amp;data=04%7C01%7C%7C134ffe8c243847ad5e3e08
>>>
>> d8c2aeaba5%7C1b16ab3eb8f64fe39f3e2db7fe549f6a%7C0%7C0%7C6374734
>> 0275077
>>>
>> 6770%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luM
>> zIiLCJBT
>>>
>> iI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&amp;sdata=hTssL%2BXeNX2jh2q0Flp
>> 62KHwe
>>> 8L5Tyyv1l2%2B4pm3cZQ%3D&amp;reserved=0>
>>>
>>>
>>>
>>
>>
>> --
>> Peter Sommerlad
>>
>> Better Software: Consulting, Training, Reviews Modern, Safe & Agile C++
>>
>> peter.cpp_at_[hidden]
>> +41 79 432 23 32


-- 
Peter Sommerlad
Better Software: Consulting, Training, Reviews
Modern, Safe & Agile C++
peter.cpp_at_[hidden]
+41 79 432 23 32

Received on 2021-01-27 06:54:22