The origin of this problem is that I'm designing a 2-dimensional container implemented by std::vector
. The result type of operator[]
is a proxy class that has a fixed number of elements, and then I want to use structured binding with this proxy class, just like std::array
. This is a simple example for it:
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;
};
}
In this case, I tried:
Container2D<2> container;
/* init... */
auto [a, b] = container[0];
/* get a copy of each element */
auto& [c, d] = container[0];
/* compile error */
But the compiler said "Non-const lvalue reference to type 'Reference<...>' cannot bind to a temporary of type 'Reference<...>'"
So if I want to modify the element by structured binding, I have to:
template<size_t stride>
struct tuple_element<Reference<stride>>{
/* 2 choices: */
/* second: tuple_element_t<...> = T& */
typedef int& type;
};
and then:
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? */
But in this case, if I want to get a copy, I have to declare some variables to copy these reference variables. It's exactly not what I want. Is there some better way that can deal with these two situations easily and correctly?
The following is in addition to this question:
I know that the implementation of structured binding is:
"auto" [const] [volatile] [&/&&] "[" <vars> "]" "=" <expression>
and may be implemented as (in a tuple-like case, simplifying some edge cases):
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)));
...
in which the grammar implies you can replace the [a, b, c, ...]
with some variable name like e
, and then the type of a
, b
and c
follows a weird deduction rule.
However, this anonymous variable is always not what we want, but the a
, b
and c
will be. So why not ensure the type of a
, b
and c
? It can just apply the cv-qualifier and ref-operator to the std::tuple_element_t<I, E>
for a
, b
and c
, use auto&& e
and std::forward(e)
for the expression, and others are treated as before.