使自定义类型“可绑定"(与 std::tie 兼容)

2022-01-19 00:00:00 tuples c++ c++11 std

假设我有一个自定义类型(我可以扩展):

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

struct Foo {
    int a;
    string b;
};

如何使该对象的实例可分配给 std::tie,即 std::tuple 引用?

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;

尝试失败:

重载 tuple 是不可能的,因为赋值运算符是二进制运算符之一,它必须是左侧对象的成员.

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:

  • 运算符元组() const
  • 运算符元组

它们导致赋值错误,告诉operator = 没有为 tuple 重载".我猜这是因为转换为任何类型 X + 为 operator=" 推导模板参数 X 不能一起工作,一次只能其中一个.

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.

不完美的尝试:

因此我尝试为领带的确切类型实现转换运算符:

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:

  1. 如果 tie 绑定了不同但可转换类型的变量(即在客户端将 int a; 更改为 long long a;),它会失败,因为类型必须完全匹配.这与将元组分配给允许可转换类型的引用元组的通常用法相矛盾.(1)
  2. 转换运算符需要返回一个必须给定左值引用的关系.这不适用于临时值或 const 成员.(2)
  3. 如果转换运算符不是 const,则右侧的 const Foo 的赋值也会失败.要实现转换的 const 版本,我们需要去掉 const 主体成员的 const 特性.这很丑陋,可能会被滥用,从而导致未定义的行为.
  1. If the tie has variables of different but convertible types bound (i.e. change 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)
  2. The conversion operator needs to return a tie which has to be given lvalue references. This won't work for temporary values or const members.(2)
  3. If the conversion operator is not const, the assignment also fails for a 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.

我只看到提供我自己的 tie 函数 + 类以及我的tie-able"对象的替代方案,这使我不得不复制 std::tie<的功能/code> 我不喜欢(不是我觉得这样做很难,而是觉得必须这样做是不对的).

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):一个可怕的 hack 是将转换编写为模板并在转换运算符中转换为请求的成员类型.这是一个可怕的黑客,因为我不知道在哪里存储这些转换的成员.在 这个演示中,我使用静态变量,但这不是线程可重入的.

(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): 可以应用与 (1) 相同的技巧.

推荐答案

为什么当前尝试失败

std::tie(a, b) 产生一个 std::tuple.此类型与 std::tuple 等无关.

Why the current attempts fail

std::tie(a, b) produces a std::tuple<int&, string&>. This type is not related to std::tuple<int, string> etc.

std::tuples 有几个赋值运算符:

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

  • 一个默认的赋值操作符,它接受一个 std::tuple
  • 带有类型参数包U...的元组转换赋值运算符模板,它采用std::tuple
  • 具有两个类型参数 U1, U2 的对转换赋值运算符 模板,它采用 std::pair代码>
  • A default assignment-operator, that takes a std::tuple<T...>
  • A tuple-converting assignment-operator template with a type parameter pack U..., that takes a std::tuple<U...>
  • A pair-converting assignment-operator template with two type parameters U1, U2, that takes a std::pair<U1, U2>

对于这三个版本,存在复制和移动变体;将 const&&& 添加到它们采用的类型.

For those three versions exist copy- and move-variants; add either a const& or a && to the types they take.

赋值运算符模板必须从函数参数类型(即赋值表达式的 RHS 类型)推导出它们的模板参数.

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).

如果在 Foo 中没有转换运算符,这些赋值运算符对于 std::tie(a,b) = foo 都不可行.如果给 Foo 添加一个转换操作符,那么只有默认的赋值运算符变得可行:模板类型推导不考虑用户定义的转换.也就是说,您不能从 Foo 类型推断赋值运算符模板的模板参数.

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.

由于在隐式转换序列中只允许进行一次用户定义的转换,因此转换运算符转换为的类型必须与默认赋值运算符的类型完全匹配.也就是说,它必须使用与 std::tie 的结果完全相同的元组元素类型.

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.

为了支持元素类型的转换(例如将 Foo::a 分配给 long),Foo 的转换运算符具有做一个模板:

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>();
};

但是,std::tie 的元素类型是引用.由于您不应该返回对临时的引用,运算符模板内的转换选项非常有限(堆、类型双关语、静态、线程本地等).

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).

相关文章