USCiLab / cereal

A C++11 library for serialization
BSD 3-Clause "New" or "Revised" License
4.16k stars 748 forks source link

Ambiguous overload of JSONOutputArchive::saveValue() if int32_t is long #389

Open dmarks-ls opened 7 years ago

dmarks-ls commented 7 years ago

I'm running Cereal 1.2.2 on an NXP Kinetis K64 microcontroller (Cortex-M4) using Kinetis Design Studio 3 and KSDK v2.1. I've got it working great; I'm using it to parse and emit commands and responses for a JSON-formatted protocol.

I've found that there are only two modifications I have to make to Cereal to get it to work in my build and execution environment. One involves the chunk allocator, which I'll save for a different thread. The other involves the JSONOutputArchive class, specifically the saveValue() overloads.

The crux of the issue is this: in the KDS 3 build environment, int32_t has been defined as long, and uint32_t is unsigned long. This annoys me to no end because I have to change all my print specifiers from %d to %ld to avoid warnings, etc. Of course, size_t is just unsigned int, so uint32_t and size_t are not identical types. Grr.

Anyway, here's what happens on my platform if I compile Cereal as-is (output edited for clarity):

cereal/cereal/archives/json.hpp: In member function 'void cereal::JSONOutputArchive::saveLong(T)':
cereal/cereal/archives/json.hpp:262:69: error: call of overloaded 'saveValue(int32_t)' is ambiguous
       void saveLong(T l){ saveValue( static_cast<std::int32_t>( l ) ); }
cereal/cereal/archives/json.hpp:262:69: note: candidates are:
cereal/cereal/archives/json.hpp:233:12: note: void cereal::JSONOutputArchive::saveValue(bool)
       void saveValue(bool b)                { itsWriter.Bool(b);                                                         }
cereal/cereal/archives/json.hpp:235:12: note: void cereal::JSONOutputArchive::saveValue(int)
       void saveValue(int i)                 { itsWriter.Int(i);                                                          }
cereal/cereal/archives/json.hpp:237:12: note: void cereal::JSONOutputArchive::saveValue(unsigned int)
       void saveValue(unsigned u)            { itsWriter.Uint(u);                                                         }
cereal/cereal/archives/json.hpp:243:12: note: void cereal::JSONOutputArchive::saveValue(int64_t)
       void saveValue(int64_t i64)           { itsWriter.Int64(i64);                                                      }
cereal/cereal/archives/json.hpp:245:12: note: void cereal::JSONOutputArchive::saveValue(uint64_t)
       void saveValue(uint64_t u64)          { itsWriter.Uint64(u64);                                                     }
cereal/cereal/archives/json.hpp:247:12: note: void cereal::JSONOutputArchive::saveValue(double)
       void saveValue(double d)              { itsWriter.Double(d);                                                       }

The method saveLong() is casting the (verified by traits) signed 32-bit value to an int32_t, and then passing that to saveValue(). But since int32_t is long, none of the saveValue() methods match, so C++ can't decide which overload to use.

My workaround for this has been to add a couple more overloads:

      //! Saves a bool to the current node
      void saveValue(bool b)                { itsWriter.Bool(b);                                                         }
      //! Saves an int to the current node
      void saveValue(int i)                 { itsWriter.Int(i);                                                          }
      //! Saves a uint to the current node
      void saveValue(unsigned u)            { itsWriter.Uint(u);                                                         }
++    //! Saves a long to the current node
++    void saveValue(long i)                { itsWriter.Int(i);                                                          }
++    //! Saves an unsigned long to the current node
++    void saveValue(unsigned long u)       { itsWriter.Uint(u);                                                         }

However, I don't think this holds up if compiling on an architecture where sizeof(int) != sizeof(long), e.g. if long is 8 bytes. I tried modifying the static_cast<> invocations in saveLong() to call int and unsigned int instead of std::int32_t and std::uint32_t, but that only fixes the internal calls to saveLong(); application calls to saveValue() will still fail if passed a long type.

So... what's the correct/best way to fix this? How can I avoid having to edit cereal/archives/json.hpp every time I import cereal into a Kinetis project? I mean, it's not a big deal to add the two extra overloads above, but editing library code is always less than ideal. Thanks.

AzothAmmo commented 7 years ago

These issues have cropped up in the past - what helps to solve it is knowing, for all of the basic types, both the sizes used by the compiler, as well as testing std::is_equal against long. We have to try and come up with a solution that is unambiguous for everyone.

kantoniak commented 4 years ago

Any progress on this one? I use cereal as a dependency in a publicly accessible project, and modifying vendor library is not an option there.