自定义types“可绑定”(与std :: tie兼容)
考虑我有一个自定义types(我可以扩展):
struct Foo { int a; string b; };
我如何使这个对象的实例可分配给一个std::tie
,即std::tuple
引用的std::tuple
?
Foo foo = ...; int a; string b; std::tie(a, b) = foo;
失败的尝试:
为tuple<int&,string&> = Foo
重载赋值运算符是不可能的,因为赋值运算符是必须是左侧对象成员的二元运算符之一。
所以我试图通过实现一个合适的元组转换运算符来解决这个问题。 以下版本失败:
-
operator tuple<int,string>() const
-
operator tuple<const int&,const string&>() const
他们在赋值时导致错误,告诉“ operator =
不会重载tuple<int&,string&> = Foo
”。 我想这是因为“转换为任何types的X +演绎模板参数X for operator =”不能一起工作,只能同时使用其中的一个。
不完美的尝试:
因此,我试图为领带的确切types实现一个转换操作符:
-
operator tuple<int&,string&>() const
Demo -
operator tuple<int&,string&>()
Demo
现在的作业是可行的,因为types现在(转换后)完全一样,但是这不适用于我想支持的三种场景:
- 如果绑定具有不同但可转换types的variables绑定(即在客户端更改
int a;
long long a;
),则失败,因为types必须完全匹配。 这与将元组分配给允许可转换types的引用元组的通常使用相矛盾。 (1) - 转换运算符需要返回必须给出左值引用的联系。 这不适用于临时值或const成员。 (2)
- 如果转换运算符不是const,那么对于右边的
const Foo
,赋值也会失败。 要实现一个const转换的版本,我们需要破解const主体成员的const。 这是丑陋的,可能会被滥用,导致未定义的行为。
我只能看到另外一个select,就是提供我自己的tie
function+课程以及“tie-able”对象,这使我强制复制我不喜欢的std::tie
function(不是说我觉得很难这样做,但必须这样做感觉不对)。
我想在一天结束时,结论是,这是一个库的元组实现的一个缺点。 他们并不像我们希望的那样神奇。
编辑:
事实certificate,似乎没有解决所有上述问题的真正解决scheme。 一个很好的答案将解释为什么这是不可解的。 特别是,我希望有人澄清为什么“失败的尝试”不可能奏效。
(1):一个可怕的黑客是将转换作为模板写入,并在转换运算符中转换为请求的成员types。 这是一个可怕的黑客,因为我不知道在哪里存储这些转换后的成员。 在这个演示中,我使用静态variables,但这不是线程可重入的。
(2):可以使用与(1)中相同的方法。
为什么当前的尝试失败
std::tie(a, b)
产生一个std::tuple<int&, string&>
。 这个types与std::tuple<int, string>
等无关
std::tuple<T...>
有几个赋值运算符:
- 一个默认的赋值运算符,它接受一个
std::tuple<T...>
- 带有types参数包
U...
的元组转换赋值运算符模板 ,它接受一个std::tuple<U...>
- 具有两个types参数
U1, U2
的对转换赋值运算符模板 ,其采用std::pair<U1, U2>
对于这三个版本存在复制和移动变体; 添加一个const&
或一个&&
到他们所采取的types。
赋值运算符模板必须从函数参数types(即赋值expression式的RHStypes)中推导出它们的模板参数。
如果没有Foo
的转换运算符,则这些赋值运算符都不可用于std::tie(a,b) = foo
。 如果将转换运算符添加到Foo
,则只有默认的赋值运算符才可行:模板types扣除不考虑用户定义的转换。 也就是说,你不能从Foo
types推导赋值运算符模板的模板参数。
由于在隐式转换序列中只允许一个用户定义的转换,转换运算符转换的types必须完全匹配默认赋值运算符的types。 也就是说,它必须使用与std::tie
结果完全相同的元组元素types。
为了支持元素types的转换(例如将Foo::a
赋值为long
), Foo
的转换运算符必须是一个模板:
struct Foo { int a; string b; template<typename T, typename U> operator std::tuple<T, U>(); };
但是, std::tie
的元素types是引用。 既然你不应该返回一个临时的引用,运算符模板中的转换选项是相当有限的(堆,types双精度,静态,线程本地等)。
只有两种方法可以尝试去:
- 使用模板化的赋值运算符:
您需要从模板化的赋值运算符完全匹配的types公开派生。 - 使用非模板赋值运算符:
提供非模板化的复制操作符所期望的types的非explicit
转换,以便它将被使用。 - 没有第三种select。
在这两种情况下,你的types都必须包含你想要分配的元素,没有办法绕过它。
#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'; }
在coliru上: http ://coliru.stacked-crooked.com/a/315d4a43c62eec8d
正如其他答案已经解释过的,你必须从一个tuple
inheritance(为了匹配赋值运算符模板)或者转换成完全相同的引用tuple
(为了匹配非模板赋值运算符,相同的types)。
如果你从一个元组inheritance,你将失去指定的成员,即foo.a
不再是可能的。
在这个答案中,我提出了另一个select:如果你愿意付出一定的空间开销(每个成员的常量),你可以同时拥有两个命名成员以及一个元组inheritance,从一个const引用的元组inheritance,即一个const对象本身的领带:
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} {} };
这个“附加领带”可以将一个(非const!) Foo
分配给一系列可转换组件types。 由于“附加领带”是一个引用的元组,它会自动赋值成员的当前值,尽pipe你在构造函数中初始化它。
为什么“附加领带”是const
? 因为否则,一个const Foo
可以通过它附加的领带进行修改。
带有非精确元素types的示例用法(注意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'; }
将打印
42 bar
现场演示
所以这通过引入空间开销来解决问题1. + 3。
这种做你想做的事吧? (假设你的价值可以链接到课程的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; }
主要缺点是每个成员只能通过他们的types访问,现在,你可以用其他一些机制来解决这个问题(例如,为每个成员定义一个types,并用你想要的成员types来包装引用types访问..)
其次转换运算符是不明确的,它将转换为任何请求的元组types(可能是你不希望那..)
主要优点是你不必明确指定转换types,这一切都推断…