Consider I have a custom type (which I can extend):
struct Foo {
int a;
string b;
};
How can I make an instance of this object assignable to a std::tie
, i.e. std::tuple
of references?
Foo foo = ...;
int a;
string b;
std::tie(a, b) = foo;
Failed attempts:
Overloading the assignment operator for tuple<int&,string&> = Foo
is not possible, since assignment operator is one of the binary operators which have to be members of the left hand side object.
So I tried to solve this by implementing a suitable tuple-conversion operator. The following versions fail:
operator tuple<int,string>() const
operator tuple<const int&,const string&>() const
They result in an error at the assignment, telling that "operator =
is not overloaded for tuple<int&,string&> = Foo
". I guess this is because "conversion to any type X + deducing template parameter X for operator=" don't work together, only one of them at once.
Imperfect attempt:
Hence I tried to implement a conversion operator for the exact type of the tie:
operator tuple<int&,string&>() const
Demo
operator tuple<int&,string&>()
Demo
The assignment now works since types are now (after conversion) exactly the same, but this won't work for three scenarios which I'd like to support:
int a;
to long long a;
on the client side), it fails since the types have to fully match. This contradicts the usual use of assigning a tuple to a tuple of references which allows convertible types.(1)
const Foo
on the right hand side. To implement a const version of the conversion, we need to hack away const-ness of the members of the const subject. This is ugly and might be abused, resulting in undefined behavior.I only see an alternative in providing my own tie
function + class together with my "tie-able" objects, which makes me force to duplicate the functionality of std::tie
which I don't like (not that I find it difficult to do so, but it feels wrong to have to do it).
I think at the end of the day, the conclusion is that this is one drawback of a library-only tuple implementation. They're not as magic as we'd like them to be.
As it turns out, there doesn't seem to be a real solution addressing all of the above problems. A very good answer would explain why this isn't solvable. In particular, I'd like someone to shed some light on why the "failed attempts" can't possibly work.
(1): A horrible hack is to write the conversion as a template and convert to the requested member types in the conversion operator. It's a horrible hack because I don't know where to store these converted members. In this demo I use static variables, but this is not thread-reentrant.
(2): Same hack as in (1) can be applied.
std::tie(a, b)
produces a std::tuple<int&, string&>
.
This type is not related to std::tuple<int, string>
etc.
std::tuple<T...>
s have several assignment-operators:
std::tuple<T...>
U...
, that takes a std::tuple<U...>
U1, U2
, that takes a std::pair<U1, U2>
For those three versions exist copy- and move-variants; add either a const&
or a &&
to the types they take.
The assignment-operator templates have to deduce their template arguments from the function argument type (i.e. of the type of the RHS of the assignment-expression).
Without a conversion operator in Foo
, none of those assignment-operators are viable for std::tie(a,b) = foo
.
If you add a conversion operator to Foo
,
then only the default assignment-operator becomes viable:
Template type deduction does not take user-defined conversions into account.
That is, you cannot deduce template arguments for the assignment-operator templates from the type Foo
.
Since only one user-defined conversion is allowed in an implicit conversion sequence, the type the conversion operator converts to must match the type of the default assignment operator exactly. That is, it must use the exact same tuple element types as the result of std::tie
.
To support conversions of the element types (e.g. assignment of Foo::a
to a long
), the conversion operator of Foo
has to be a template:
struct Foo {
int a;
string b;
template<typename T, typename U>
operator std::tuple<T, U>();
};
However, the element types of std::tie
are references.
Since you should not return a reference to a temporary,
the options for conversions inside the operator template are quite limited
(heap, type punning, static, thread local, etc).
There are only two ways you can try to go:
explicit
conversion to the type the non-templated copy-operator expects, so it will be used.In both cases, your type must contain the elements you want to assign, no way around it.
#include <iostream>
#include <tuple>
using namespace std;
struct X : tuple<int,int> {
};
struct Y {
int i;
operator tuple<int&,int&>() {return tuple<int&,int&>{i,i};}
};
int main()
{
int a, b;
tie(a, b) = make_tuple(9,9);
tie(a, b) = X{};
tie(a, b) = Y{};
cout << a << ' ' << b << '\n';
}
On coliru: http://coliru.stacked-crooked.com/a/315d4a43c62eec8d
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