Подтвердить что ты не робот

Указатель на член: работает в GCC, но не в VS2015

Я пытаюсь внедрить систему "свойство" для преобразования экземпляров С++ в JSON и наоборот. Я взял часть кода от Guillaume Racicot в этом вопросе (С++ JSON Serialization) и упростил его.

Вот как я продолжаю. У меня есть класс Property:

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 указывает на определенный член Class

Предположим, что я хочу определить свойства для класса User, я хотел бы иметь возможность продолжить это, чтобы иметь возможность назначать членам имя свойства:

class User
{
public:
    int age;

    constexpr static auto properties = std::make_tuple(
        Property<User, int>(&User::age, "age")
    );
}

Этот код компилируется и работает корректно в GCC (http://coliru.stacked-crooked.com/a/276ac099068579fd), но не в обновлении Visual Studio 2015 3. Я получаю эти ошибки:

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

Будет ли обходной путь, чтобы он работал в Visual Studio 2015 Update 3?

4b9b3361

Ответ 1

Мое предпочтительное временное решение - это просто заменить данные элемента properties функцией-членом properties:

class User
{
public:
    int age;

    constexpr static auto properties() { return std::make_tuple(
        Property<User, int>(&User::age, "age")
    ); }
};

Это работает, потому что в определении функции-члена класс считается полностью определенным. Он также имеет желаемый атрибут, который properties не обязательно должен быть определен отдельно, если используется odr.

Ответ 2

MSVC не знает достаточно о User, когда он хочет рассчитать тип properties, чтобы знать, что он имеет член age.

Мы можем решить эту проблему.

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")
  );
}

В коде отражения JSON просто замените std::decay_t<T>::properties на get_properties( tag<std::decay_t<T>> ).

Это имеет несколько преимуществ. Сначала вы можете модифицировать некоторые классы, которыми вы не владеете, или хотите легко изменять свойства. При использовании осторожного использования пространства имен и ADL, позволяющих использовать точку вызова, вы можете даже сделать это для (некоторых) типов внутри std (только с общими членами, не менее парой).

Во-вторых, он избегает возможных требований ODR-использования к свойствам. Свойства теперь представляют собой возвращаемое значение constexpr, а не некоторые глобальные данные, которые могут потребовать хранения.

В-третьих, он позволяет свойствам быть выписанными вне строки с определением класса, как указано выше, или встроенным как friend внутри класса, для максимальной гибкости.

Ответ 3

Если абсолютно необходимо, чтобы свойства были определены в классе User, возможно, вы могли бы использовать вспомогательную templated функцию constexpr, например:

#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()
{
}

Кажется, компилируется в webcompiler i.e VС++ 19.00.23720.0