Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explicit underlying cast #6

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

Guillaume227
Copy link

In my project I need to:

  • Enable value initialization
  • Enable explicit cast to underlying value

I thought that need is general enough that it would be useful to all NamedType users and that it's not taking away any existing feature, hence that PR.

@joboccara
Copy link
Owner

Guillaume,

Thanks a lot for contributing to NamedType. I am sorry for the late reply, I didn't see the PR earlier.
In the meantime, NamedType has had the FunctionCallable skill (see https://github.com/joboccara/NamedType/blob/master/underlying_functionalities.hpp#L84-L94), which is an opt-in for allowing implicit conversion to underlying type. Do you think this fills your need to casting to the underlying value?
Also, would you have an example of use case for the value initialization please?

@Guillaume227
Copy link
Author

Hi Jonathan,
thanks for getting back to me! I have moved on from my previous job where I introduced your lib but from what I can remember (I don't have access to the source code anymore) here is the use case behind that PR. I had a named type NT wrapping an int. Then I had a template that I wanted to call on int-convertible types, like NT or float, or an enum class so I was using static_cast. I didn't want to add a specialization just for the purpose of calling .get() instead of doing a straight static_cast. I thought the explicit nature of the constructor played nicely with the strong type nature.
So basically it boils down to a 'static_castable' skill.
I don't think FunctionCallable would help in my case.

@joboccara
Copy link
Owner

joboccara commented Mar 6, 2018

Guillaume, would the ImplicitlyConvertibleTo skill help more in your case then?
Here is the rationale and usage for that skill.

@Guillaume227
Copy link
Author

ImplicitlyConvertibleTo would defeat my purpose for using NamedType as I want a cast to be explicit.
Just curious: what do you think would the downsides be of adding that explicit conversion operator?
Not a big deal if we just ignore and close that PR.

@joboccara
Copy link
Owner

No major downside, it only gives more access to the underlying type, and the STL doesn't do it with unique_ptr. But I understand the need. Would it make sense for you if it was implemented as a skill "ExplicitlyConvertible"?

@Guillaume227
Copy link
Author

Yes, it would make good sense to have it as a skill! I need to find sometime to rework it then.

@arvidn
Copy link
Contributor

arvidn commented Mar 25, 2018

+1 on having an ExplicitlyConvertible skill

@arvidn
Copy link
Contributor

arvidn commented Mar 26, 2018

In fact, I would argue that making all NamedType explicitly convertible to its underlying type makes more sense than to expose the get() functions. static_cast<> is the standard protocol for getting at the underlying type, and it would make sense for interoperability to support this protocol.

One missing piece is a protocol for querying the underlying type, something like std::underlying_type. Unfortunately it seems the standard type trait doesn't allow specializations for custom types, and is really only meant for enums.

I suppose generic code could be made to support the direct member UnderlyingType, possibly by using the detection idiom, it would be nicer to have a type trait for this.

@joboccara
Copy link
Owner

Arvid, thanks for your suggestion. Are you thinking about particular examples that use static_cast<> as a standard protocol for getting the underlying value?

@arvidn
Copy link
Contributor

arvidn commented Mar 26, 2018

I think the most obvious example is for enums. static_cast<> is what you use to get the underlying integer value. I would argue that the fact that the language provides explicit conversion operators is an indication of making that the standard mechanism for conversions (and for explicit conversions, you use static_cast<> to invoke the conversion).

I would imagine that generic code use static_cast<> for this reason.

The other major example I have in mind is boost.endian. I'm using this to build network packets, to abstract alignment and endian requirements. Currently, boost.endian doesn't really have support for strong types, but only supports raw integers. However, I would greatly improve its type safety if the value_type used in boost.endian could be something more restricted than a raw integer. But since boost.endian is responsible for the actual binary representation of the value, it needs to deal with raw integer types, that it can shift, AND and OR. In a way, it deals with the storage of values, and the mapping of the storage to values. In order for boost.endian to support arbitrary value types, it would need to have a protocol to:

  1. cast a value to its underlying integer
  2. query the underlying integral type (via some type trait)

I'm interested in proposing a patch to boost.endian to support this.

@viboes
Copy link

viboes commented Apr 16, 2018

An alternative is to build on top of Boost.Endian an endian type that can take a UDT types instead of an integral builtin type.

@viboes
Copy link

viboes commented Apr 16, 2018

Yes, the standard conversion protocol uses static_cast.

I believe both skills (implicit and explicit conversion to) are useful and must not be hard coded in the strong type base type (NamedType in this case).

However, in order to get the underlying type I believe that conversion are cumbersome. We need something else. A underlying(st) non-memberfunction that returns a reference to theunderlying_type`. This should work for enums as for strong types that wrap an underlying type.

BTW, I believe that get is a function name that has no clear intent, IMHO is is misused in the standard. I believe we need something more specific for strong types as st.get_underlying() or st.underlying().

@arvidn
Copy link
Contributor

arvidn commented Apr 16, 2018

@viboes Yes, once I actually started working on it, with the intention to patch boost.endian, it turned out to be much simpler to build a new primitive on top of boost.endian that glues together both NamedType, enums and some other types that I had (that already supported a tag template parameter for unique types)

@viboes
Copy link

viboes commented Sep 9, 2018

Maybe it is worth understanding why std::duration hasn't an explicit cast to his representation (With H.Hinnant). Instead it has a count() member function. If we wanted to be able to implement std::duration using the library+patch, the user would need to delete the explicit conversion operator, which is weird.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants