I'm trying to implement a "property" system to convert C++ instances into JSON and vice versa. I took a part of the code from Guillaume Racicot's answer in this question (C++ JSON Serialization) and simplified it.
Here is how I proceed. I have a Property
class:
template <typename Class, typename T>
struct Property {
constexpr Property(T Class::* member, const char* name) : m_member(member), m_name(name) {}
T Class::* m_member;
const char* m_name;
};
m_member points to a specific member of Class
Let's say I want to define properties for a User
class, I would like to be able to proceed like this, to be able to assign members a property name:
class User
{
public:
int age;
constexpr static auto properties = std::make_tuple(
Property<User, int>(&User::age, "age")
);
}
This code compiles and works correctly in GCC(http://coliru.stacked-crooked.com/a/276ac099068579fd) but not in Visual Studio 2015 Update 3. I get those errors:
main.cpp(19) : error C2327 : 'User::age' : is not a type name, static, or enumerator
main.cpp(19) : error C2065 : 'age' : undeclared identifier
main.cpp(20) : error C2672 : 'std::make_tuple' : no matching overloaded function found
main.cpp(20) : error C2119 : 'properties' : the type for 'auto' cannot be deduced from an empty initializer
Would there be a workaround to make it work in Visual Studio 2015 Update 3?
My preferred workaround is just to replace the properties
member data with a properties
member function:
class User
{
public:
int age;
constexpr static auto properties() { return std::make_tuple(
Property<User, int>(&User::age, "age")
); }
};
This works because in the definition of a member function, the class is considered to be completely defined. It also has the desirable attribute that properties
doesn't need to be separately defined if odr-used.
MSVC doesn't know enough about User
when it wants to calculate the type of properties
to know it has a member age
.
We can work around the problem.
template<class T>struct tag_t{constexpr tag_t(){};};
template<class T>constexpr tag_t<T> tag{};
template<class T>
using properties = decltype( get_properties( tag<T> ) );
class User
{
public:
int age;
};
constexpr auto get_properties(tag_t<User>) {
return std::make_tuple(
Property<User, int>(&User::age, "age")
);
}
In the JSON reflection code, simply replace std::decay_t<T>::properties
with get_properties( tag<std::decay_t<T>> )
.
This has a few advantages. First you can retrofit some classes you do not own or wish to modify with properties seamlessly. With careful namespace use and ADL enabling the point of call, you can even do so for (some) types within std
(with oublic members only; pair at the least).
Second it avoids possible ODR-use requirements on properties. Properties is now a constexpr
return value not some global data that may require storage.
Third it permits properties to be written out-of-line with the class definition like above, or inline as a friend
within the class, for maximium flexibility.
If it is absolutely needed to properties be defined in User class maybe you could make use of helper templated constexpr function like:
#include <tuple>
template <typename Class, typename T>
struct Property {
constexpr Property(T Class::* const member) : m_member{ member } {}
T Class::* const m_member;
};
template <class T, class V>
constexpr Property<T, V> get_age_property() {
return Property<T, V>(&T::age);
}
class User
{
public:
int age;
constexpr static std::tuple<Property<User, int>> properties = std::make_tuple(
get_age_property<User, int>()
);
};
int main()
{
}
It seems to compile in webcompiler i.e. VC++ 19.00.23720.0
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With