使自定义类型"tie-able"(与 std::tie 兼容)
Make custom type "tie-able" (compatible with std::tie)
考虑我有一个自定义类型(我可以扩展(:
struct Foo {
int a;
string b;
};
如何使该对象的实例可分配给std::tie
,即 参考资料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
"。我想这是因为"转换为任何类型 X + 推断 operator= 的模板参数 X"不能一起工作,一次只能使用其中一个。
不完美的尝试:
因此,我尝试为领带的确切类型实现一个转换运算符:
-
operator tuple<int&,string&>() const
演示 -
operator tuple<int&,string&>()
演示
该赋值现在可以工作,因为类型现在(转换后(完全相同,但这不适用于我想支持的三种情况:
- 如果 tie 绑定了不同但可转换类型的变量(即在客户端将
int a;
更改为long long a;
(,则由于类型必须完全匹配,因此失败。这与通常使用将元组分配给允许可转换类型的引用元组相矛盾。(1( - 转换运算符需要返回一个必须给予左值引用的平局。这不适用于临时值或常量成员。(二(
- 如果转换运算符不是 const,则右侧
const Foo
的赋值也会失败。要实现转换的 const 版本,我们需要破解 const 主题成员的一致性。这是丑陋的,可能会被滥用,导致未定义的行为。
只看到提供我自己的tie
函数+类以及我的"可绑定"对象的替代方案,这使我被迫复制我不喜欢的std::tie
的功能(不是我觉得这样做很难,但必须这样做感觉不对(。
我认为归根结底,结论是这是仅库元组实现的一个缺点。它们并不像我们希望的那样神奇。
编辑:
事实证明,似乎没有解决上述所有问题的真正解决方案。一个很好的答案可以解释为什么这是无法解决的。特别是,我希望有人阐明为什么"失败的尝试"不可能奏效。
(1(:一个可怕的黑客是将转换编写为模板,并在转换运算符中转换为请求的成员类型。这是一个可怕的黑客,因为我不知道在哪里存储这些转换后的成员。在这个演示中,我使用静态变量,但这不是线程重入的。
(2(:可以应用与(1(中相同的黑客。
为什么当前尝试失败
std::tie(a, b)
产生std::tuple<int&, string&>
。此类型与std::tuple<int, string>
等无关。
std::tuple<T...>
有几个赋值运算符:
- 默认赋值运算符,采用
std::tuple<T...>
- 具有类型参数包的元组转换赋值运算符模板
U...
,需要std::tuple<U...>
- 具有两个类型参数的配对转换赋值运算符模板
U1, U2
,需要std::pair<U1, U2>
对于这三个版本,存在复制和移动变体;将const&
或&&
添加到它们采用的类型中。
运算符模板必须从函数参数类型(即赋值表达式的 RHS 类型(推导出其模板参数。
如果没有Foo
中的转换运算符,这些赋值运算符都不适合std::tie(a,b) = foo
。如果将转换运算符添加到 Foo
,那么只有默认的赋值运算符才可行:模板类型扣除不会考虑用户定义的转化。也就是说,不能从类型 Foo
推断赋值运算符模板的模板参数。
由于隐式转换序列中只允许一个用户定义的转换,因此转换运算符转换为的类型必须与默认赋值运算符的类型完全匹配。也就是说,它必须使用完全相同的元组元素类型作为 std::tie
的结果。
为了支持元素类型的转换(例如,将Foo::a
分配给long
(,Foo
的转换运算符必须是模板:
struct Foo {
int a;
string b;
template<typename T, typename U>
operator std::tuple<T, U>();
};
但是,std::tie
的元素类型是引用。由于不应返回对临时的引用,运算符模板中的转换选项非常有限(堆、双关类型、静态、线程本地等(。
只有两种方法可以尝试:
- 使用模板化赋值运算符:
您需要从模板化赋值运算符完全匹配的类型公开派生。 - 使用非模板化赋值运算符:
提供非模板化复制运算符期望的非explicit
转换,以便使用它。 - 没有第三种选择。
在这两种情况下,您的类型都必须包含要分配的元素,这是无法解决的。
#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';
}
关于大肠杆菌:http://coliru.stacked-crooked.com/a/315d4a43c62eec8d
正如其他答案已经解释的那样,您必须从tuple
继承(为了匹配赋值运算符模板(或转换为完全相同的引用tuple
(为了匹配非模板化赋值运算符tuple
相同类型的引用(。
如果你从元组继承,你将失去命名的成员,即 foo.a
不再可能。
在这个答案中,我提出了另一种选择:如果你愿意支付一些空间开销(每个成员的常量(,你可以通过继承常量引用的元组来同时拥有命名成员和元组继承,即对象本身的常量领带:
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}
{}
};
这种"附加领带"可以分配一个(非常量! Foo
到可转换组件类型的领带。由于"附加领带"是引用的元组,因此它会自动分配成员的当前值,即使您在构造函数中对其进行了初始化也是如此。
为什么"附加领带"const
?因为否则,可以通过其连接的领带修改const Foo
。
非精确组件类型的领带用法示例(请注意long long
与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.
这种可以做你想要的对吗?(假设您的值可以链接到课程的类型...
#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;
}
主要缺点是每个成员只能由其类型访问,现在,您可以使用其他机制解决此问题(例如,为每个成员定义一个类型,并通过要访问的成员类型包装对该类型的引用。
其次,转换运算符不是显式的,它将转换为请求的任何元组类型(可能您不希望这样做......
主要优点是您不必明确指定转换类型,这一切都是推断出来的......
这段代码对我有用。如果有人能指出它的任何错误,我会很高兴。
编译器资源管理器上的简单版本
编译器资源管理器上的更多通用版本
#include <tuple>
#include <cassert>
struct LevelBounds final
{
int min;
int max;
operator std::tuple<int&, int&>() { return {min, max}; }
};
int main() {
int a, b;
auto lb = LevelBounds{30, 40};
std::tie(a, b) = lb;
assert(30 == a);
assert(40 == b);
return 0;
}
- 码头化的C++应用程序是否向后兼容早期的内核版本
- 我收到同义重复编译器错误。我应该如何修复"类型"X"的参数与类型"X"的参数不兼容?
- 布局兼容类型的并集
- 字符类型转换不兼容
- Qt:如何使不兼容的发送方/接收方参数兼容?
- 视觉工作室 2017;启用 /permissive 时,类型 "const wchar_t *" 的参数与类型 "PWSTR" 的参数不兼容
- 指针问题:从不兼容的类型"int"分配给"int *"
- 如何以与 API 兼容的方式重命名类成员?
- 使用不兼容的分配器复制分配无序列图
- 分配兼容的向量?
- 自定义 STL 兼容迭代器,用于迭代 2D 数组类的列
- 类型为 "int*" 的参数与 C++ 中错误类型"int**"参数不兼容
- 该对象具有与成员函数不兼容的类型限定符.为什么会出现此错误?
- ios_base::sync_with_stdio(false); cin.tie(NULL);
- 我正在尝试将表的地址传递给要在另一个函数中使用的指针,但得到不兼容的指针类型
- 为什么范围算法与 std 的迭代器不兼容?
- C/C++:POSIX 兼容方式查找默认网络接口上/下
- Winpcap Findalldevs const char * 与 char * 不兼容
- 在 std::tie 中使用 std::weak_ptr::lock()
- 使自定义类型"tie-able"(与 std::tie 兼容)