11

Basically, I'd like to implement a float16 type. But this question is not about the details of how to do that, but instead how to set things up so that my new float16 type behaves appropriately with float, double, and all the integer types.

What I would like is for my float16 type to convert analogously as to float or double. For instance, it should implicitly cast to both of these types. It should also have std::common_type (http://en.cppreference.com/w/cpp/types/common_type) behaving analogously for it as it std::common_types behaves for the other float types. This means that std::common_type<my_float16, float>::type = float, std::common_type<my_float16, double>::type = double, and std::common_type<my_float16, T>::type = my_float16 where T is any integer type.

What constructors and cast operators do I need to write to make this work? Any help would be appreciated!

My other recent question might be related.

EDIT: Okay, I've built a minimal example like Anton did. It is

struct float16 {
    explicit operator int() {
        return 0;
    }

    operator float() {
        return 0.0f;
    }
};

This has the right common type for float and float16, but the common type for int and float16 is still int. I do not understand that.

Community
  • 1
  • 1
user2333829
  • 1,301
  • 1
  • 15
  • 25
  • 2
    Trying to answer your question I've found a [nice bug in GCC](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66615) which can make the whole idea very dangerous – Anton Savin Jun 21 '15 at 13:31
  • May I ask why one would ever need a 16 bits float? Is this just an exercise? – DarioP Jun 21 '15 at 17:13
  • It's called half-precision, and there are applications in graphics and other things -- see https://en.wikipedia.org/wiki/Half-precision_floating-point_format. I also want to implement a float128 type, to which the same question applies. – user2333829 Jun 21 '15 at 18:42

3 Answers3

4

I don't think you can get this exactly, because the C++ core language treats natively defined conversion (e.g. char to int) differently from user defined conversion (even if they are implicit).

For example after

struct float16 { float16(int) {} }; // cast int->float16 is implicit

struct Foo { Foo(const double&){} };  // constructor accepts a double&
struct Bar { Bar(const float16&){} }; // constructor accepts a float16&

void foo(const Foo&) {}
void bar(const Bar&) {}

the call foo(3) is valid because the integer 3 can be converted implicitly to a double and foo accepts a Foo instance that can be constructed implicitly from a double by a user-defined conversion (the Foo constructor that is not explicit).

However bar(3) is not valid because such a call would require TWO implicit user-defined conversions (intfloat16 and float16Bar) and this is not allowed.

6502
  • 112,025
  • 15
  • 165
  • 265
  • Thank @6502. I'm worried you might be right, but I'm not sure. – user2333829 Jun 21 '15 at 13:17
  • would you be able to point to a place in the standard which shows this is the case? If it's impossible, I'd like to clearly understand where that is decreed. – user2333829 Jun 21 '15 at 18:46
  • @user2333829: see edit, I've added an example. Note that exact rules for overload resolution of C++ are (at least for me) too complex to bother learning them precisely and I don't exclude more intricate cases are present. – 6502 Jun 21 '15 at 19:44
2

If you implement it as a struct/class type, provide overloads of all the relevant operators and constructors. Look up the guidelines for what a class needed if it supports conversion both to and from other types.

If you want your class to play nicely with standard templates like std::common_type and std::numeric_limits, you will need to provide appropriate specialisation of those templates. It is probably best to read the actual standards for a description of requirements of such specialisations, but there is probably some tutorial material around (I have seen good introductory material for specialising std::numeric_limits, but not for std::common_type).

There will always be some limitations on how your type fits in with built-in standard types (int, float, etc) unless you use compiler-specific types.

Peter
  • 35,646
  • 4
  • 32
  • 74
  • Thanks, Peter. My question is really exactly which constructors and operators need to be implemented to make this work. I have a class with many of them implemented, but I'm still not getting the exact behavior I want (which is what I specified above). As for overloading std::common_type, that is a last resort to me -- I'd rather make things work so that std::common_type works naturally using the return type of the ternary operator. – user2333829 Jun 21 '15 at 13:16
  • Off top of my head, minimum is all numeric operators (`operator+()`, etc), all `op=` operators (`operator+=()`, etc), conversion constructors and corresponding `operator=()` [all overloaded for all basic types], all conversion constructors (`operator float()`, etc). For C++11, move constructors are advisable, depending on the internals of your class. Don't supply bitwise operators. Supply stream insertion and extraction operators. Operators that can be chained (like `=` and `+=`) return a reference, others return by value. – Peter Jun 21 '15 at 13:34
  • Hey Peter. To get std::common_type working, I think one only needs to implement constructors (not even sure about move constructors) and cast operators. The rest is for these particular operations which, of course, are useful but don't affect things like std::common_type I believe. – user2333829 Jun 21 '15 at 13:40
  • Yeah, but there is more to "behaves appropriately with float, double, and all the integer types" than just getting it to work with `std::common_type`. `std::common_type` captures a subset of the requirements. – Peter Jun 21 '15 at 13:53
  • That's true! But std::common_type is what's confusing me. – user2333829 Jun 21 '15 at 13:56
0

I'm going to provide my own answer to this question.

I now agree with 6502, and I do not think this is possible in an easy way. The only way I found to do this was to provide operator overloads for every possible combination of types. This can be done in a slightly nicer way with templates (and Boost operators, http://www.boost.org/doc/libs/1_59_0/libs/utility/operators.htm, is a good example of this), but that is still a good amount of work and boilerplate code.

user2333829
  • 1,301
  • 1
  • 15
  • 25