Repository: heremaps/flatdata (language: python)

Issue:
Expose maximum possible value for primitive types in generated code
While playing with flatdata, I realized that if you encode a primitive field with a custom size like so:

```
struct Attribute {
    value : u32 : 3;
}
```

then in the generated code, the number of bytes of the field is not exposed anywhere.

This could be useful for example when adding a `static_assert` in C++ that internal enum values match the size of the field. I suppose in Rust, we could also leverage this for some safer fun!

What I would have to do now:

```cpp
static_assert( ::MyEnum::COUNT == 8, "Oops, make sure your flatdata schema is up-to-date!" );
```

what I'd expect:

```cpp
static_assert( ::MyEnum::COUNT == something_max_value(), "..." );
```

pinging @boxdot and @VeaaC to discuss the proposal before I get to the implementation!


Discussion / hints:
In C++ this should be easy: Just add as a method to the `Member` class (e.g. max()/min())

In Rust it is a bit more tricky, we have getter methods for each member, but they directly return the target type.
 The only thing I could think of would be additional getters with naming conventions, e.g. `xyz_max()/xyz_min()`. This could theoretically clash with names defined by the user.

@gferon are you going to do a C++ implementation?
Maybe this could even be generalized: How about enabling compile-time reflection over the schema? E.g.:
* check if new resources were added to an archive (e.g. to static_assert that something needs to be done)
* check min/max values
* check possible enum values
* etc?

Fix patch:
diff --git a/flatdata-cpp/include/flatdata/internal/BitHelpers.h b/flatdata-cpp/include/flatdata/internal/BitHelpers.h
index 032b0153..3357bcb6 100644
--- a/flatdata-cpp/include/flatdata/internal/BitHelpers.h
+++ b/flatdata-cpp/include/flatdata/internal/BitHelpers.h
@@ -80,6 +80,30 @@ struct IsSigned
     };
 };
 
+template < typename T, bool is_enum >
+struct UnderlyingTypeImpl
+{
+};
+
+template < typename T >
+struct UnderlyingTypeImpl< T, false >
+{
+    using type = T;
+};
+
+template < typename T >
+struct UnderlyingTypeImpl< T, true >
+{
+    using type = typename std::underlying_type< T >::type;
+};
+
+// Since std::underlying_type only works for enums we need to add support ourselves
+template < typename T >
+struct UnderlyingType
+{
+    using type = typename UnderlyingTypeImpl< T, std::is_enum< T >::value >::type;
+};
+
 template < typename ResultType, int num_bits, typename T >
 typename std::enable_if< IsSigned< ResultType >::value, ResultType >::type
 extend_sign( T value )
diff --git a/flatdata-cpp/include/flatdata/internal/Reader.h b/flatdata-cpp/include/flatdata/internal/Reader.h
index 9e294331..374e00f2 100644
--- a/flatdata-cpp/include/flatdata/internal/Reader.h
+++ b/flatdata-cpp/include/flatdata/internal/Reader.h
@@ -30,17 +30,31 @@ template < typename T, int offset = 0, int num_bits = sizeof( T ) * 8, int struc
 struct Reader
 {
     using StreamType = const unsigned char*;
-    using UnsignedType = typename BitsToUnsigned<
-        int_choice< num_bits, num_bits + offset % 8, num_bits + offset % 8 <= 64 >::value >::type;
+
     enum
     {
         bit_width = num_bits
     };
+    enum : typename UnderlyingType< T >::type
+    {
+        max = bit_width == 0 ? 0
+                             : ( bit_width - std::is_signed< T >::value
+                                         == sizeof( typename BitsToUnsigned< num_bits >::type ) * 8
+                                     ? typename BitsToUnsigned< num_bits >::type( -1 )
+                                     : ( typename BitsToUnsigned< num_bits >::type( 1 )
+                                         << ( bit_width - std::is_signed< T >::value ) )
+                                           - 1 ),
+        min = bit_width == 0 || !std::is_signed< T >::value ? 0 : -max - 1
+    };
 
     StreamType data;
 
     operator T( ) const
     {
+        using UnsignedType =
+            typename BitsToUnsigned< int_choice< num_bits, num_bits + offset % 8,
+                                                 num_bits + offset % 8 <= 64 >::value >::type;
+
         /* Does the following:
          * - takes the smallest data type available that can read the necessary amount of bytes
          *   (including offset in the beginning and empty space in the end)
@@ -122,7 +136,7 @@ struct Reader< Tagged< T, INVALID_VALUE >, offset, num_bits, struct_size_bytes >
         }
         else
         {
-            return boost::optional< U >(Reader< T, offset, num_bits >{data});
+            return boost::optional< U >( Reader< T, offset, num_bits >{data} );
         }
     }
 };


Test patch:
diff --git a/flatdata-cpp/test/WriterReaderTest.cpp b/flatdata-cpp/test/WriterReaderTest.cpp
index 6b87b444..1a1f04b3 100644
--- a/flatdata-cpp/test/WriterReaderTest.cpp
+++ b/flatdata-cpp/test/WriterReaderTest.cpp
@@ -196,6 +196,40 @@ TEST_CASE( "Range", "[Read/Write]" )
     REQUIRE( result.second == 32 );
 }
 
+TEST_CASE( "Min/Max", "[Reader]" )
+{
+    REQUIRE( Reader< uint32_t, 0, 0, 666 >::min == 0 );
+    REQUIRE( Reader< uint32_t, 0, 0, 666 >::max == 0 );
+    REQUIRE( Reader< int32_t, 0, 0, 666 >::min == 0 );
+    REQUIRE( Reader< int32_t, 0, 0, 666 >::max == 0 );
+
+    REQUIRE( Reader< uint32_t, 1, 1, 666 >::min == 0 );
+    REQUIRE( Reader< uint32_t, 1, 1, 666 >::max == 1 );
+    REQUIRE( Reader< int32_t, 1, 1, 666 >::min == -1 );
+    REQUIRE( Reader< int32_t, 1, 1, 666 >::max == 0 );
+
+    REQUIRE( Reader< uint32_t, 7, 7, 666 >::min == 0 );
+    REQUIRE( Reader< uint32_t, 7, 7, 666 >::max == 127 );
+    REQUIRE( Reader< int32_t, 7, 7, 666 >::min == -64 );
+    REQUIRE( Reader< int32_t, 7, 7, 666 >::max == 63 );
+
+    REQUIRE( Reader< uint32_t, 16, 16, 666 >::min == 0 );
+    REQUIRE( Reader< uint32_t, 16, 16, 666 >::max == 65535 );
+    REQUIRE( Reader< int32_t, 16, 16, 666 >::min == -32768 );
+    REQUIRE( Reader< int32_t, 16, 16, 666 >::max == 32767 );
+
+    REQUIRE( Reader< uint32_t, 32, 32, 666 >::min == 0 );
+    REQUIRE( Reader< uint32_t, 32, 32, 666 >::max == std::numeric_limits< uint32_t >::max( ) );
+    REQUIRE( Reader< int32_t, 32, 32, 666 >::min == std::numeric_limits< int32_t >::min( ) );
+    REQUIRE( Reader< int32_t, 32, 32, 666 >::max == std::numeric_limits< int32_t >::max( ) );
+
+    REQUIRE( Reader< bool, 16, 1, 666 >::min == false );
+    REQUIRE( Reader< bool, 16, 1, 666 >::max == true );
+
+    REQUIRE( Reader< TestEnum, 7, 7, 666 >::min == 0 );
+    REQUIRE( Reader< TestEnum, 7, 7, 666 >::max == 127 );
+}
+
 #if ( GENERATE_PYTHON_READER_TESTS )
 TEST_CASE( "Fail if python test output is enabled", "[Reader]" )
 {