Мне нужно хранить универсальные ссылки внутри класса (я уверен, что ссылочные значения переживут класс). Есть ли канонический способ сделать это?
Вот минимальный пример того, что я придумал. Кажется, это работает, но я не уверен, правильно ли я понял.
template <typename F, typename X>
struct binder
{
template <typename G, typename Y>
binder(G&& g, Y&& y) : f(std::forward<G>(g)), x(std::forward<Y>(y)) {}
void operator()() { f(std::forward<X>(x)); }
F&& f;
X&& x;
};
template <typename F, typename X>
binder<F&&, X&&> bind(F&& f, X&& x)
{
return binder<F&&, X&&>(std::forward<F>(f), std::forward<X>(x));
}
void test()
{
int i = 1;
const int j = 2;
auto f = [](int){};
bind(f, i)(); // X&& is int&
bind(f, j)(); // X&& is const int&
bind(f, 3)(); // X&& is int&&
}
Верны ли мои рассуждения или это приведет к незаметным ошибкам? Кроме того, есть ли лучший (то есть, более краткий) способ написания конструктора? binder(F&& f, X&& x)
не будет работать, так как это ссылки на r-значение, что запрещает binder(f, i)
,
Вы не можете «сохранить универсальную ссылку», потому что такой вещи нет, есть только ссылки на rvalue и ссылки на lvalue. «Универсальная ссылка» — это удобный термин Скотта Мейерса для описания синтаксической функции, но это не часть системы типов.
Чтобы посмотреть на конкретные детали кода:
template <typename F, typename X>
binder<F&&, X&&> bind(F&& f, X&& x)
Вот вам экземпляр binder
со ссылочными типами в качестве аргументов шаблона, поэтому в определении класса нет необходимости объявлять члены как rvalue-ссылки, потому что они уже являются ссылочные типы (или lvalue или rvalue как выведено bind
). Это означает, что у вас всегда есть больше &&
токены, чем нужно, которые являются избыточными и исчезают из-за свертывания ссылок.
Если ты уверен binder
всегда будут созданы вашими bind
функция (и, следовательно, всегда создаются с помощью ссылочных типов), то вы можете определить ее следующим образом:
template <typename F, typename X>
struct binder
{
binder(F g, X y) : f(std::forward<F>(g)), x(std::forward<X>(y)) {}
void operator()() { f(std::forward<X>(x)); }
F f;
X x;
};
В этой версии типы F
а также X
являются ссылочными типами, поэтому использовать их избыточно F&&
а также X&&
потому что они либо уже lvalue ссылки (так что &&
ничего не делает) или они Rvalue ссылки (так что &&
в этом случае тоже ничего не делает!)
В качестве альтернативы, вы могли бы сохранить binder
как у тебя это и поменяй bind
чтобы:
template <typename F, typename X>
binder<F, X> bind(F&& f, X&& x)
{
return binder<F, X>(std::forward<F>(f), std::forward<X>(x));
}
Теперь вы создаете экземпляр binder
либо с ссылочным типом lvalue, либо с типом объекта (т.е. без ссылки), а затем внутри binder
Вы объявляете членов с дополнительным &&
поэтому они являются либо ссылочными типами lvalue, либо ссылочными типами rvalue.
Кроме того, если вы думаете об этом, вам не нужно хранить rvalue ссылочных членов. Это прекрасно для хранения объектов по ссылкам lvalue, все, что важно, это то, что вы вперед они правильно, как lvalues или rvalues в operator()
функция. Так что ученики могут быть просто F&
а также X&
(или в любом случае, когда вы всегда создаете экземпляр типа со ссылочными аргументами, F
а также X
)
Поэтому я бы упростил код до этого:
template <typename F, typename X>
struct binder
{
binder(F& g, X& y) : f(g), x(y) { }
void operator()() { f(std::forward<X>(x)); }
F& f;
X& x;
};
template <typename F, typename X>
binder<F, X> bind(F&& f, X&& x)
{
return binder<F, X>(f, x);
}
Эта версия сохраняет нужный тип в параметрах шаблона. F
а также X
и использует правильный тип в std::forward<X>(x)
выражение, которое является единственным местом, где это необходимо.
Наконец, я считаю более точным и более полезным думать в терминах выведенного типа, а не только (свернутого) ссылочного типа:
bind(f, i)(); // X is int&, X&& is int&
bind(f, j)(); // X is const int&, X&& is const int&
bind(f, 3)(); // X is int, X&& is int&&
binder(F&& f, X&& x)
работает отлично.
F
а также X
являются ссылочными типами (они являются типами, указанными в экземпляре шаблона binder<F&&, X&&>
), так что f
а также x
стать всеобщим благоговением, следуя обычным правилам свертывания ссылок.
Кроме этого, код выглядит хорошо (если вы действительно можете гарантировать, что ссылочные переменные переживут механизм связывания).