Make the custom type "bound" (compatible with std :: tie)

I have a custom type (which I can extend):

struct Foo { int a; string b; }; 

How can I create an instance of this object that can be assigned std::tie , i.e. std::tuple links?

 Foo foo = ...; int a; string b; std::tie(a, b) = foo; 

Failed Attempts:

Overloading the assignment operator for tuple<int&,string&> = Foo not possible, because the assignment operator is one of the binary operators that must be members of the left-side object.

So I tried to solve this problem by running the appropriate tuple conversion operator . The following versions failed to complete:

  • operator tuple<int,string>() const
  • operator tuple<const int&,const string&>() const

They lead to an assignment error, saying that " operator = not overloaded for tuple<int&,string&> = Foo ". I think this is due to the fact that "converting to any pattern X + the pattern parameter X for the operator =" does not work together, only one of them at once.

Failed attempt:

Therefore, I tried to implement a conversion operator for the exact type of binding :

  • operator tuple<int&,string&>() const Demo
  • operator tuple<int&,string&>() Demo

The assignment now works, because the types are now (after conversion) exactly the same, but this will not work for the three scenarios that I would like to support:

  • If the connection is related to variables of different but convertible types (i.e. change int a; to long long a; on the client side), it fails, because the types must fully match. This contradicts the usual use of tuple assignment in a link tuple, which allows you to convert types. (one)
  • The conversion operator should return a link for which lvalue references should be given. This will not work for temporary values ​​or constant members. (2)
  • If the conversion operator is not const, assignment also fails for const Foo on the right side. To implement the const version of the transformation, we need to remove the constant of the const object members. This is ugly and can be abused, resulting in undefined behavior.

I see only an alternative in providing my own tie function + class function along with my "binding-related" objects, which makes me duplicate std::tie functions that I don’t like (not that I find it difficult, but it’s not good for him do it).

I think the conclusion at the end of the day is that this is one of the drawbacks of implementing the library for tuples only. They are not as magical as we would like.

EDIT:

As it turned out, there is no real solution to all of the above problems. A very good answer would explain why this is not solvable. In particular, I would like someone to shed light on why "unsuccessful attempts" cannot work.




(1): A terrible hack is to write the transformation as a template and convert to the requested member types in the conversion operator. This is a terrible hack because I don’t know where to store these converted elements. In this demo, I use static variables, but it is not thread-reentrant.

(2): You can apply the same hacking as in (1).

+46
c ++ c ++ 11 std tuples
09 Oct '14 at 11:13
source share
4 answers

Why current attempts do not work

std::tie(a, b) creates a std::tuple<int&, string&> . This type is not associated with std::tuple<int, string> etc.

std::tuple<T...> have several assignment operators:

  • The default assignment operator that accepts std::tuple<T...>
  • Template-operator-transformation of a tuple with a package of parameters of type U... that takes std::tuple<U...>
  • A pair conversion of an assignment-operator template with two parameters of type U1, U2 , which takes std::pair<U1, U2>

For these three versions, there are options for copying and moving; add either const& or && tags to the types they accept.

The destination operator templates must derive their template arguments from the function argument type (i.e., the RHS type of the destination expression).

Without the conversion operator in Foo none of these assignment operators is viable for std::tie(a,b) = foo . If you add a conversion operator to Foo , then only the default assignment operator becomes available: Calculation of the template type does not take into account user conversions. That is, you cannot infer template arguments for assignment-operator patterns from the Foo type.

Since only one user transformation is allowed in an implicit sequence of transformations, the type that transforms the transformation operator must exactly match the type of the default assignment operator. That is, it should use the same types of tuple elements as the result of std::tie .

To support element type conversions (e.g. assigning Foo::a to long ), the Foo conversion operator must be a template:

 struct Foo { int a; string b; template<typename T, typename U> operator std::tuple<T, U>(); }; 

However, the std::tie element types are links. Since you should not return a link to a temporary one, the conversion options inside the operator template are rather limited (heap, punning type, static, local stream, etc.).

+18
09 Oct '14 at 12:22
source share

There are only two ways you can try:

  • Using template assignment operators:
    You need to publicly infer from a type that exactly matches the assignment-operator pattern.
  • Use custom assignment operators:
    Suggest converting non- explicit to the type that the non-template copy operator expects, so it will be used.
  • The third option is missing.

In both cases, your type must contain the elements that you want to assign, in no way supporting 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

+6
Oct 09 '14 at 11:44
source share

As the other answers already explain, you either need to inherit from tuple (to match the pattern of the assignment operator), or convert to the same link tuple (to match the non-templated assignment operator, accepting a tuple links of the same type).

If you inherit a tuple, you will lose named members, i.e. foo.a no longer possible.

In this answer, I propose another option: if you are willing to pay for overhead (constant per member), you can have named members, as well as inheritance of tuples, inheriting from a const tuple of links, i.e. the constant of the object itself:

 struct Foo : tuple<const int&, const string&> { int a; string b; Foo(int a, string b) : tuple{std::tie(this->a, this->b)}, a{a}, b{b} {} }; 

This “bound binding” allows you to assign (not const!) Foo binding to the types of convertible components. Since an “attached binding” is a collection of links, it automatically assigns current values ​​to members, although you initialized it in the constructor.

Why is the "tied tie" const ? Since otherwise a const Foo could be modified using an attached binding.

An example of use with inaccurate composite binding types (note the long long vs int ):

 int main() { Foo foo(0, "bar"); foo.a = 42; long long a; string b; tie(a, b) = foo; cout << a << ' ' << b << '\n'; } 

will print

 42 bar 

Live demo

Thus, this solves the problems 1. + 3. by entering spatial overhead.

+4
09 Oct '14 at 13:16
source share

Does this look do what you want correctly? (it is assumed that your values ​​may be related to course types ...)

 #include <tuple> #include <string> #include <iostream> #include <functional> using namespace std; struct Foo { int a; string b; template <template<typename ...Args> class tuple, typename ...Args> operator tuple<Args...>() const { return forward_as_tuple(get<Args>()...); } template <template<typename ...Args> class tuple, typename ...Args> operator tuple<Args...>() { return forward_as_tuple(get<Args>()...); } private: // This is hacky, may be there is a way to avoid it... template <typename T> T get() { static typename remove_reference<T>::type i; return i; } template <typename T> T get() const { static typename remove_reference<T>::type i; return i; } }; template <> int& Foo::get() { return a; } template <> string& Foo::get() { return b; } template <> int& Foo::get() const { return *const_cast<int*>(&a); } template <> string& Foo::get() const { return *const_cast<string*>(&b); } int main() { Foo foo { 42, "bar" }; const Foo foo2 { 43, "gah" }; int a; string b; tie(a, b) = foo; cout << a << ", " << b << endl; tie(a, b) = foo2; cout << a << ", " << b << endl; } 

The main drawback is that each member can be accessed only by their types, now you can get around this using some other mechanism (for example, determine the type for each member and wrap the link to the type according to the type of participant you want to access. .)

Secondly, the conversion operator is not explicit, it will be converted to any requested type of tuple (you may not want this).

The main advantage is that you do not need to explicitly specify the type of transformation, all inferred ...

+2
09 Oct '14 at 13:18
source share



All Articles