C++ вариант класса члена хранится по ссылке
Я пытаюсь поэкспериментировать с STD:: вариант. Я храню STD:: вариант в качестве члена класса. В приведенном ниже коде все работает хорошо, если вариант хранится по значению, но не работает (для случая вектора, а также для пользовательских объектов), если вариант хранится по ссылке. Это почему?
#include <variant>
#include <vector>
#include <iostream>
template<typename T>
using VectorOrSimple = std::variant<T, std::vector<T>>;
struct Print {
void operator()(int v) { std::cout << "type = int, value = " << v << "\n"; }
void operator()(std::vector<int> v) const { std::cout << "type = vector<int>, size = " << v.size() << "\n"; }
};
class A {
public:
explicit A(const VectorOrSimple<int>& arg) : member(arg) {
print();
}
inline void print() const {
visit(Print{}, member);
}
private:
const VectorOrSimple<int> member; // const VectorOrSimple<int>& member; => does not work
};
int main() {
int simple = 1;
A a1(simple);
a1.print();
std::vector<int> vector(3, 1);
A a2(vector);
a2.print();
}
См. http://melpon.org/wandbox/permlink/vhnkAnZhqgoYxU1H для рабочей версии и http://melpon.org/wandbox/permlink/T5RCx0ImTLi4gk5e для сбойной версии с ошибкой: "завершение вызова после создания экземпляра" std::bad_variant_access' what(): неожиданный индекс "
Странно, что при написании версии кода boost:: variable с элементом, сохраненным в качестве ссылки, он работает как положено (печатает vector size = 3 дважды) с gcc7.0 (см. Здесь http://melpon.org/wandbox/permlink/eW3Bs1InG383vp6M) и не работает (печатает vector size = 3 в конструкторе, а затем vector size = 0 при последующем вызове print(), но без сбоев) с clang 4.0 (см. здесь http://melpon.org/wandbox/permlink/2GRf2y8RproD7XDM).
Это довольно запутанно. Может кто-нибудь объяснить, что происходит? Благодарю.
2 ответа
Это не работает, потому что это утверждение A a1(simple);
создает временный вариант объекта!
Затем вы продолжаете привязывать указанный временный объект к вашей постоянной ссылке. Но временный выходит за рамки сразу после строительства a1
закончилась, оставив вас с висящей ссылкой. Очевидно, что создание копии работает, поскольку она всегда предполагает работу с действительной копией.
Возможное решение (если вас всегда беспокоит производительность копирования) - принять вариант объекта по значению, а затем переместить его в локальную копию, например так:
explicit A(VectorOrSimple<int> arg) : member(std::move(arg)) {
print();
}
Это позволит вашему конструктору вызываться с lvalues или rvalues. Для вашего значения member
будет инициализирован путем перемещения копии исходного варианта и для rvalues
содержимое источника будет перемещено (максимум) дважды.
Варианты - это объекты. Они содержат один из набора типов, но они не являются одним из этих типов.
Ссылка на вариант - это ссылка на объект варианта, а не ссылка на один из содержащихся типов.
Вариант справочной обертки может быть тем, что вы хотите:
template<class...Ts>
using variant_ref=std::variant<std::reference_wrapper<Ts>...>;
template<typename T>
using VectorOrSimple = std::variant<T, std::vector<T>>;
template<typename T>
using VectorOrSimpleRef = variant_ref<T, std::vector<T>>;
template<typename T>
using VectorOrSimpleConstRef = variant_ref<const T, const std::vector<T>>;
Сейчас магазин VectorOfSimpleConstRef<int>
, (Не const&
). И взять один в конструкторе.
Также изменить Print
взять const&
чтобы избежать ненужного копирования этого std::vector
при печати.