strong_type
strong_type copied to clipboard
value_of destroys strong type system to much
I am evaluating which strong type library we could use in your environment.
I really like the simplistic of your implementation. But one thing botters me. Maybe you can help me, if I oversee a function or oversee the concrete concept here.
Is the ADL function value_of the only way to get the underlying value? If so, consider following example:
using my_strong_type = strong::type<int, struct foo>;
my_strong_type value{10};
int i = value_of(value);
Everything looks fine, but if someone later changes the underlying value to double, the narrowing is maybe only visible through compiler warnings.
If I could explicit (like a static_cast in other strong_type implementations) define the target type, the compiler wouldn't even compile this code.
using my_strong_type = strong::type<double, struct foo>;
my_strong_type value{10};
int i = value_of<int>(value); // Whoops!
I see what you mean. I have taken the meaning of value_of to be "I hereby leave the realm of safety and go to the underlying types, warts and all."
I've thought some about this, though, but I just haven't been able to figure out how to do what you want, in a way that doesn't completely break perfectly reasonable code. If you have an implementation idea in mind, I'd love to learn about your thoughts.
So one idea is to not use value_of at all (as a coding-style rule) and add strong::convertible_to<T> to every strong type. So the static_cast<int> fails if the strong type is no longer an int. Doesn't really work if some want multiple convertible_to options.
Another idea would be to allow value_of to take a type. For the member function this is not breaking, since we can have both functions, with an without a type. For the friend function, we have to pass the type via another object.
template<typename T>
struct identitiy {};
template<typename U, typename = std::enable_if_t<std::is_same<T, U>::value>>
STRONG_NODISCARD
constexpr T& value_of() & noexcept { return val;}
template<typename U, typename = std::enable_if_t<std::is_same<T, U>::value>>
STRONG_NODISCARD
friend constexpr T& value_of(identity<U>, type& t) noexcept { return t.val;}
/// ...
using my_strong_type = strong::type<double, struct foo>;
my_strong_type value{10};
int i = value.value_of<int>();
int j = value_of(strong::identity<int>{}, value);
If the ADL version wouldn't look that ugly, I would send a PR right away. ;) How can we do better?
It's not only ugly, it also breaks all code currently using the library, which (IMO) is not acceptable. I've been toying with returning a proxy object that does the conversion in its turn, which works fine for your example, but fails miserable for auto val = value_of(my_typed_obj). Given how gladly C++ makes unsafe implicit conversions when initializing variables, auto is (quite ironically) the only type safe way, so making that one fail, is also a no-go.
Another way around this is, of course, to not rely 100% on a library, but to lean on your compiler. If you compile with -Wconversion -Werror (gcc/clang), which you absolutely should if you care the least about type safety, you're already home and dry (I believe).
I would have provide both overloads (type safe and not) so this wouldn't break, I think.
Maybe I just go with value_of(...) and/or static_cast.
Hello, I think depending on what comes next in the code there are 3 solutions:
- implicit cast to int (give up safety)
- using
autoto store the return ofvalue_of(generic code) - adding a
static_assertto check the underlying type (so you can come back and think about what needs to be changed)