I have a class with copy & move ctor deleted.
struct A
{
A(int a):data(a){}
~A(){ std::cout << "~A()" << this << " : " << data << std::endl; }
A(A const &obj) = delete;
A(A &&obj) = delete;
friend std::ostream & operator << ( std::ostream & out , A const & obj);
int data;
};
And I want to create a tuple with objects of this class. But the following does not compile:
auto p = std::tuple<A,A>(A{10},A{20});
On the other hand, the following does compile, but gives a surprising output.
int main() {
auto q = std::tuple<A&&,A&&>(A{100},A{200});
std::cout << "q created\n";
}
Output
~A()0x22fe10 : 100
~A()0x22fe30 : 200
q created
It means that dtor for objects is called as soon as tuple construction line ends. So, what is significance of a tuple of destroyed objects?
This is bad:
auto q = std::tuple<A&&,A&&>(A{100},A{200});
you are constructing a tuple
of rvalue references to temporaries that get destroyed at the end of the expression, so you're left with dangling references.
The correct statement would be:
std::tuple<A, A> q(100, 200);
However, until quite recently, the above was not supported by the standard. In N4296, the wording around the relevant constructor for tuple
is [tuple.cnstr]:
template <class... UTypes> constexpr explicit tuple(UTypes&&... u);
Requires:
sizeof...(Types) == sizeof...(UTypes)
.is_constructible<Ti, Ui&&>::value
is true for alli
.
Effects: Initializes the elements in the tuple with the corresponding value instd::forward<UTypes>(u)
.
Remark: This constructor shall not participate in overload resolution unless each type inUTypes
is implicitly convertible to its corresponding type inTypes
.
So, this constructor was not participating in overload resolution because int
is not implicitly convertible to A
. This has been resolved by the adoption of Improving pair
and tuple
, which addressed precisely your use-case:
struct D { D(int); D(const D&) = delete; };
std::tuple<D> td(12); // Error
The new wording for this constructor is, from N4527:
Remarks: This constructor shall not participate in overload resolution unless
sizeof...(Types) >= 1
andis_constructible<Ti, Ui&&>::value
is true for alli
. The constructor is explicit if and only ifis_convertible<Ui&&, Ti>::value
isfalse
for at least one i.
And is_constructible<A, int&&>::value
is true.
To present the difference another way, here is an extremely stripped down tuple implementation:
struct D { D(int ) {} D(const D& ) = delete; };
template <typename T>
struct Tuple {
Tuple(const T& t)
: T(t)
{ }
template <typename U,
#ifdef USE_OLD_RULES
typename = std::enable_if_t<std::is_convertible<U, T>::value>
#else
typename = std::enable_if_t<std::is_constructible<T, U&&>::value>
#endif
>
Tuple(U&& u)
: t(std::forward<U>(u))
{ }
T t;
};
int main()
{
Tuple<D> t(12);
}
If USE_OLD_RULES
is defined, the first constructor is the only viable constructor and hence the code will not compile since D
is noncopyable. Otherwise, the second constructor is the best viable candidate and that one is well-formed.
The adoption was recent enough that neither gcc 5.2 nor clang 3.6 actually will compile this example yet. So you will either need a newer compiler than that (gcc 6.0 works) or come up with a different design.
Your problem is that you explicitly asked for a tuple of rvalue references, and a rvalue reference is not that far from a pointer.
So auto q = std::tuple<A&&,A&&>(A{100},A{200});
creates two A objects, takes (rvalue) references to them, build the tuple with the references... and destroys the temporary objects, leaving you with two dangling references
Even if it is said to be more secure than good old C and its dangling pointers, C++ still allows programmer to write wrong programs.
Anyway, the following would make sense (note usage of A& and not A&&):
int main() {
A a(100), b(100); // Ok, a and b will leave as long as main
auto q = tuple<A&, A&>(a, b); // ok, q contains references to a and b
...
return 0; // Ok, q, a and b will be destroyed
}
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