Consider following code snippet which introduces a custom Test type with support to structured binding:
struct Test {
int member;
};
template<>
struct std::tuple_size<::Test>
{
static constexpr size_t value = 1;
};
template<>
struct std::tuple_element<0, ::Test>
{
using type = int;
};
template <size_t Index>
const int& get(const Test& test)
{
// Omit Index for the sake of brevity
return test.member;
}
int main() {
Test test = Test{1234};
auto [member] = test;
return 0;
}
The code does not compile with following error:
error: binding reference of type ‘std::tuple_element<0, Test>::type&’ {aka ‘int&’} to ‘const int’ discards qualifiers
According to my understanding the structured binding should expand to (test is converted to xvalue and passed to get function):
In these initializer expressions, e is an lvalue if the type of the entity e is an lvalue reference (this only happens if the ref-qualifier is & or if it is && and the initializer expression is an lvalue) and an xvalue otherwise (this effectively performs a kind of perfect forwarding), i is a std::size_t prvalue, and is always interpreted as a template parameter list.
https://en.cppreference.com/w/cpp/language/structured_binding
Test test = Test{1234};
Test hidden = Test(test);
const int& member = get<0UL>(static_cast<Test &&>(hidden));
which compiles fine. However, it looks like the const qualifier is left out and the member's type is int& which for obvious reasons cannot compile and produce similar error message.
Rather than const int &member, you get int &member:
cppreference
[the type is] reference to
std::tuple_element<i, E>::type, ... lvalue reference if its corresponding initializer is an lvalue, rvalue reference otherwise
Where E is std::remove_reference_t<decltype((hidden))>, aka Test. And the "corresponding initializer" is what your get() returns.
std::tuple_element automatically propagates const from its template parameter to the resulting type, but it doesn't help, because E isn't const in the first place.
This boils down to get not being written correctly. You need 4 overloads, for all combinations of const, non-const, & and &&.
Those can be compacted to:
template <size_t Index, typename T>
requires std::is_same_v<std::remove_cvref_t<T>, Test>
auto &&get(T &&test)
{
return std::forward<T>(test).member;
}
Alternatively, if you intend your type to be read-only when using tuple protocol, force tuple_element to always be const:
template<>
struct std::tuple_element<0, ::Test>
{
using type = const int;
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With