I'm trying to use cereal (C++ serialization library) in the MFC project.
It works fine if I add an "internal serialize function" inside of the custom struct that I want to serialize. But, if I separately define "external serialize function" outside of the struct, then it gives a compilation error: "cereal could not find any output serialization functions for the provided type and archive combination."
So, it seems the external serialize functions that I defined are not found correctly, and the documentation says:
External serialization functions should be placed either in the same namespace as the types they serialize or in the cereal namespace so that the compiler can find them properly.
But, my custom struct and the external serialize functions are commonly defined in the project's View class header file:
class CmyprojectView : public CView
{
// other declarations...
// Custom struct that I want to serialize.
struct MyStruct { ... }
// External serialize function
template <class Archive>
void serialize(Archive & ar, CmyprojectView::MyStruct & s){
ar(
CEREAL_NVP(s.x),
CEREAL_NVP(s.y),
CEREAL_NVP(s.z)
);
}
}
Any advice on why my serialize functions are not found?
Thanks to @IInspectable's comments, I decided to let the custom struct (to be serialized) stay in the View class header file (CmyprojectView.h), and move the "external serialize function"(a term coined by cereal library) to another header file, wrapped in cereal namespace (it also works without the namespace, though).
One remaining question is, even if the struct to be serialized is declared as private, the external serialize function can access it - how is this possible?
Any additional advice would be appreciated!
P.S. When using cereal, don't forget to use the RAII concept, i.e. wrap it with { } such as {cereal::JSONOutputArchive oarchive(ss); oarchive(s);}, although it's not shown in IInspectable's sample code since the main function's scope is enough and also for brevity I guess.
Intro
The
cereallibrary is a flexible serialization framework. One of its features is extensibility, making its algorithms readily available to user-defined types. Extensibility support is generously provided by repurposing C++'s overload resolution rules.Problem Statement
The code in question fails to play by those rules.
Before diving in too deep, here's a minimal version of the code that reproduces the issue1:
Dumping the above into a newly created console application project (and setting up the cereal library) produces the following compiler diagnostic:
The diagnostic is spot-on but doesn't otherwise elucidate where the compiler went looking for a serialization function. Thankfully, the documentation covers the rules for "external serialization":
This is a bit hand-wavy2 but gets the main point across: If the
serialize()function template is not a class member of the class being serialized it needs to be a free function (template) that lives in one of two namespaces. In the sample above, however, theserialize()function template is a class member of an unrelated class.Speculation
I suppose part of the confusion here is that C++ uses the same token (
::) to delineate class and namespace hierarchy boundaries.CmyprojectView::serializecould refer to either a free function (template) in theCmyprojectViewnamespace, or a class member of theCmyprojectViewclass. In the sample above it is the latter, andcerealwon't consider it as an external serialization function for theMyStructclass (whose fully qualified name is::CmyprojectView::MyStruct).Solution
To resolve the issue, the
serialize()function template needs to be either a free function template in the same namespace thatMyStructlives in, or a class member of theMyStructclass.Establishing the first option is as simple as slapping the
friendkeyword onto the function template definition:This accomplishes two things:
serialize()access to theprivateparts of the enclosing class (CmyprojectView).With just this change, things will (magically4) start to compile. While there is nothing technically wrong with this code I will say that it is difficult to read than it needs to be. A solution that's easier to comprehend is to make
serialize()a class member ofMyStuct. This is whatcerealcalls "internal serialization":This moves the serialization code into the class it is operating on, making it easy to discover. The code will also continue to work regardless of whether
MyStructis a nested class, or lives in an arbitrary namespace.The only wrinkle with this design is that the
serialize()function template needs to be publicly visible, making it look like it was part of the class' regular interface as opposed to an artifact of the serialization framework.This can be addressed by making
cereal::accessa friend:That's better but feels a bit clumsy still.
serialize()is a class member ofMyStruct, and shows up in documentation or code completion hints for this class. We can fix this by going full circle to the first solution above, and re-apply the hidden friend idiom. Except, this time around the implementation goes into theMyStructclass:Nothing changed concerning readability: This is still a challenge to comprehend.
serialize()looks a lot like a class member, but isn't.There are, however, several benefits to this:
serialize()has full access to all internal data,public,protected, andprivate.serialize()is not a class member and doesn't show up in documentation or code completion hints.serialize()function template "follows" the class around. IfMyStructis nested inside a class, the function template will show up in the outer class' namespace. IfMyStructis moved to an arbitrary namespace, the function template will show up in that namespace.serialize()inadvertently.Conclusion
All proposed solutions solve the issue at hand. There are trade-offs to be made, and the choice of design is primarily down to personal preference.
1 MFC types have been stripped. The issue is unrelated to the MFC, and the solution(s) work with or without the MFC getting involved.
2 I don't know the exact semantics of "should", nor why the global namespace is (allegedly) not considered.
3 Into some namespace. I'm confident that the rules that govern this are well-defined and were implemented in good faith. Honestly, though, life is too short to follow down that rabbit hole.
4 See The Power of Hidden Friends in C++.