问题描述
这个问题的根源是我正在设计一个由std::vector
实现的二维容器。结果类型operator[]
是一个具有固定数量元素的代理类,然后我想将此结构化绑定与此代理类一起使用,就像std::array
一样。这是一个简单的示例:
template<size_t stride>
struct Reference{
Container2D<stride>* container;
size_t index;
template<size_t I>
decltype(auto) get(){
return container->data()[I + index * stride];
}
};
/* the object means `stride` elements in container,starting at `index * stride` */
template<size_t stride>
struct Container2D{
std::vector<int>& data();
/* implemented by std::vector,simplify the template argument T */
Reference operator[](size_t index);
/* operator[] just constructs an object of Reference */
/* so it returns a rvalue */
};
namespace std{
template<size_t stride>
struct tuple_size<Reference<stride>>{
static constexpr size_t value = stride;
};
template<size_t stride>
struct tuple_element<Reference<stride>>{
/* 2 choices: */
/* first: tuple_element_t<...> = T */
typedef int type;
};
}
在这种情况下,我尝试过:
Container2D<2> container;
/* init... */
auto [a,b] = container[0];
/* get a copy of each element */
auto& [c,d] = container[0];
/* compile error */
但是编译器说 “对类型为'Reference <...>'的非常量左值引用不能绑定到类型为'Reference <...>'的临时对象”
因此,如果要通过结构化绑定修改元素,则必须:
template<size_t stride>
struct tuple_element<Reference<stride>>{
/* 2 choices: */
/* second: tuple_element_t<...> = T& */
typedef int& type;
};
然后:
Container2D<2> container;
/* init... */
auto [a,b] = container[0];
/* get a reference to each element */
// auto& [c,d] = container[0];
/* still compile error,but who cares? */
但是在这种情况下,如果要获取副本,则必须声明一些变量以复制这些参考变量。 这不是我想要的。有没有更好的方法可以轻松,正确地处理这两种情况?
以下是此问题的补充:
我知道结构化绑定的实现是:
"auto" [const] [volatile] [&/&&] "[" <vars> "]" "=" <expression>
并可以实现为(在类似元组的情况下,简化一些边缘情况):
auto [const] [volatile] [&/&&] e = <expression>;
std::tuple_element_t<0,std::remove_reference_t<decltype(e)>> var_0(get<0>(std::forward(e)));
std::tuple_element_t<1,std::remove_reference_t<decltype(e)>> var_1(get<1>(std::forward(e)));
...
其中的语法暗示您可以用诸如[a,b,c,...]
之类的变量名替换e
,然后用a
,b
和c
的类型替换遵循一个怪异的推论规则。
但是,此匿名变量始终不是我们想要的,而是a
,b
和c
。那么,为什么不确保a
,b
和c
的类型呢?它仅可以将std::tuple_element_t<I,E>
,a
和b
的cv限定词和引用运算符应用于c
,使用auto&& e
和std::forward(e)
用于表达,其他与以前一样。
解决方法
这是一个穿着新衣服的非常老的C ++疣:
std::vector<bool> x;
auto& rx = x[0]; // does not compile
代理是二等公民。通过operator[]
返回值并使用auto&
使用结构化绑定来绑定它是不兼容的。
没有权衡的解决方案。
要使auto&
绑定按原样工作,operator[]
可能必须存在某个地方,auto&
才能返回引用(例如,作为容器成员)。当受auto
约束时,该事物的行为必须不同于受auto&
约束的行为(例如,复制后,它进入“复制”模式)。应该可以做到这一点,并使这种 exact 用法正常工作,但这将无法维护。
更合理的方法是放弃auto [a,b] = container[0]; // copy
auto [a,b] = container[0].ref(); // reference-like
绑定。在这种情况下,您可以提供以类似价值和类似参考的方式行事的代理,例如像这样的东西:
operator[]
为此,get()
返回一个代理,.ref()
将为其返回副本,并对其调用get()
返回一个代理,其中auto&&
将为其返回引用。 / p>
这个问题本身就很有趣。此语言功能存在一些有趣的紧张关系。我不在委员会中,但是我可以列举一些可能会朝这个方向倾斜的动机:(1)一致性(2)不同的推论语义,(3)效率,(4)可教导性和(5)生活
请注意,问题中的增加掩盖了重要区别。绑定的名称不是引用,而是别名。它们是所指事物的新名称。这是一个重要的区别,因为位域与结构化绑定一起工作,但是无法形成对它们的引用。
通过(1),我的意思是,如果像元组一样的绑定是引用,则它们现在与类情况下的结构化绑定不同(除非我们做不同的操作并损害位域上的功能)。现在,我们在结构化绑定的工作方式上存在非常细微的矛盾。
通过(2),我的意思是,auto&& [...]
在该语言的任何地方都有一种类型推断。如果将auto&&
转换为绑定名称为auto [...] = ...
的版本,则将有N个不同的推论,并可能具有不同的左值/右值性。这使它们比以前更加复杂(这非常复杂)
通过(3),我的意思是,如果我们写[...]
,我们期望得到一个副本,但不能得到N个副本。在提供的示例中,差异不大,因为复制聚合与复制每个成员相同,但这不是固有属性。成员可以使用聚合共享某些共同状态,否则他们将需要拥有自己的副本。进行多个复制操作可能会令人惊讶。
通过(4),我的意思是,您可以通过说“他们就像将bool IsGameEnded()
{
static int i = 0;
i++;
if (i == 10)
return true;
return false;
}
int main()
{
bool GameEnd = false;
float ElapsedTime = 0;
while(!GameEnd)
{
chrono::steady_clock::time_point StartingTime = chrono::steady_clock::now();
if (ElapsedTime > 10)
{
ElapsedTime = 0;
draw();
}
GameEnd = IsGameEnded();
chrono::steady_clock::time_point EndingTime = chrono::steady_clock::now();
ElapsedTime = ElapsedTime + chrono::duration_cast<chrono::milliseconds>(EndingTime - StartingTime).count();
}
return 0;
}
替换为对象名一样工作,而绑定名是该部分的新名称,您可以开始教某人结构化绑定“。