Ответ 1
Поскольку вопрос отмечен С++ 1z, здесь простое решение, использующее некоторые блестящие новые возможности С++ 17 (по линиям, обсуждаемым в комментариях выше):
template<class T, auto... PMs> struct composite_property : property<T>
{
using const_reference = typename property<T>::const_reference;
composite_property(const_reference v = {}) : property<T>(v)
{
(... , (this->value.*PMs).value_changed.connect([this](auto&&)
{
if(listen_to_members) this->value_changed.emit(this->value);
}));
}
composite_property& operator=(const_reference& other)
{
listen_to_members = false;
property<T>::operator=(other);
listen_to_members = true; // not exception-safe, should use RAII to reset
return *this;
}
private:
bool listen_to_members = true;
};
Из чистой лени я внес изменения в ваш property<T>
: я сделал публичный value
. Конечно, есть несколько способов избежать этого, но они не связаны с проблемой, поэтому я решил сохранить все просто.
Мы можем протестировать решение, используя этот игрушечный пример:
struct position2d
{
property<int> x;
property<int> y;
position2d& operator=(const position2d& other)
{
x = other.x.value;
y = other.y.value;
return *this;
}
};
bool operator!=(const position2d& lhs, const position2d& rhs) { return lhs.x.value != rhs.x.value || lhs.y.value != rhs.y.value; }
int main()
{
composite_property<position2d, &position2d::x, &position2d::y> pos = position2d{0, 0};
pos.value.x.value_changed.connect([](int x) { std::cout << " x value changed to " << x << '\n'; });
pos.value.y.value_changed.connect([](int y) { std::cout << " y value changed to " << y << '\n'; });
pos.value_changed.connect([](auto&& p) { std::cout << " pos value changed to {" << p.x << ", " << p.y << "}\n"; });
std::cout << "changing x\n";
pos.value.x = 7;
std::cout << "changing y\n";
pos.value.y = 3;
std::cout << "changing pos\n";
pos = {3, 7};
}
Здесь живой пример со всеми необходимыми определениями (мой код внизу).
При необходимости явно указывать элементы в качестве аргументов шаблона composite_property
может раздражать, он также обеспечивает довольно гибкость. Мы можем иметь, например, класс с тремя свойствами элемента и определять различные составные свойства по различным парам свойств членов. Содержащий класс не затрагивается ничем из этого и может также работать независимо от каких-либо составных свойств, причем элементы используются как автономные свойства.
Обратите внимание, что существует причина для предоставленного пользователем оператора назначения копирования position2d
: если мы оставим его по умолчанию, он скопирует сами свойства элемента, который не испускает сигналы, а скорее дублирует исходные свойства.
Код работает на соединительной линии Clang в режиме С++ 1z. Это также вызывает ICE на стволе GCC; мы должны попытаться уменьшить пример, чтобы получить то, что может быть представлено в отчете об ошибке.
Важнейшей функцией С++ 17 здесь является auto
параметры шаблона непигового типа. В предыдущих версиях языка существуют несколько более уродливые альтернативы, например, перенос указателей на члены в виде ptr_to_mem<decltype(&position2d::x), &position2d::x>
, возможно, с использованием макроса, чтобы избежать повторения.
В реализации конструктора composite_property
существует также выражение fold ,
, но это также можно сделать (несколько более подробным образом), инициализируя фиктивный массив.