Structured bindings for your own type that isn’t a struct or a tuple(via public member function)
There are many problems here.
First, in order to qualify for structured bindings, you need to specialize tuple_size
:
namespace std {
template <> struct tuple_size<foobar> : std::integral_constant<size_t, 2> { };
}
Next, your specializations of tuple_element
also have to be in namespace std
:
namespace std {
template <> struct tuple_size<foobar> : std::integral_constant<size_t, 2> { };
template <> struct tuple_element<0,foobar> { using type = int; };
template <> struct tuple_element<1,foobar> { using type = std::string; };
}
Next, your get
must be declared as a friend
function if you're going to access private members, as per usual:
class foobar {
template <int I> friend auto get(foobar const& );
};
Lastly, get()
really had better return references, otherwise your bindings will end up doing surprising things:
template<int I>
auto const& get(const foobar&x) {
if constexpr(I == 0) return x._ival;
else if constexpr(I == 1) return x.s;
}
Rather than dealing with friend
ship, it's easier to just make get()
a public member, and then write the three overloads you need:
class foobar {
public:
template <size_t I>
auto& get() & {
if constexpr (I == 0) return _ival;
else if constexpr (I == 1) return s;
}
template <size_t I>
auto const& get() const& {
if constexpr (I == 0) return _ival;
else if constexpr (I == 1) return s;
}
template <size_t I>
auto&& get() && {
if constexpr (I == 0) return std::move(_ival);
else if constexpr (I == 1) return std::move(s);
}
};
Also ival()
as a function doesn't make sense. Your constructor should just take arguments.
Fixing the errors in Sutter's example
I think it's a typo/glitch in Herb Sutter's blog post: He should have made those members public, or provided getters for them, or made the std::get()
function a friend.
Also, it looks like Herb forgot to put "x" in the function signature...
Explanation of the get function
The function you quote is similar to how std::get()
works for tuples. If I have
std::tuple<int, std::string> t;
then
auto x { std::get<0>(t) }; // x is an integer
auto y { std::get<1>(t) }; // y is an std::string
and in Herb's example, he needs to have the same work for the S
class, i.e. have std::get<0>(s)
return the first member of s
, std::get<1>(s)
return the second member etc. This is necessary, because otherwise, you can't use S
for initializing a structured binding.
The "magic" in Hebr's implementation is that he's returning values of different types from different points in his function. This "magic" is the effect of an if constexpr
. It means, essentially, that the compiler ignores everything except the syntax of the irrelevant branches. So for I = 0
, the function is:
auto get(const S&) {
if (true) return x.i;
/* else if constexpr(I == 1) return string_view{x.c};
else if constexpr(I == 2) return x.d;
*/
}
for I = 1
it's
template<int I>
auto get(const S&) {
if (false) {/* return x.i; */ ; }
else if (true) return string_view{x.c};
/* else if constexpr(I == 2) return x.d; */
}
}
etc. And the auto
chooses the appropriate type.