Structured bindings for your own type that isn’t a struct or a tuple(via public member function)

8.1k views Asked by At

I am going through the Herb Sutter's

A journey: Toward more powerful and simpler C++ programming

Structure Binding section

In order to understand the concept .Best is to write a program I tried but getting some error

Just want to try how to use structure binding on class with private data .Please ignore the below example.if any example you can provide

#include<iostream>
#include<string>
using namespace std;

class foobar {
public:
    foobar() { cout << "foobar::foobar()\n"; }
    ~foobar() { cout << "foobar::~foobar()\n"; }

    foobar( const foobar &rhs )
    { cout << "foobar::foobar( const foobar & )\n"; }
    void ival( int nval, string new_string ) { _ival = nval;s=new_string; }

private:
    int _ival;
    string s;
};

foobar f( int val,string new_string ) {
    foobar local;
    local.ival( val,new_string );
    return local;
}

template<> struct tuple_element<0,foobar> { using type = int; };
template<> struct tuple_element<1,foobar> { using type = string; };



 // 2. Now add get<> support (using C++17, because why not; it’s better
 // than =delete’ing the primary get<> template and adding specializations)
 template<int I>
 auto get(const foobar&x) {
 if      constexpr(I == 0) return x._ival;//'_ival' is a private member of 'foobar'
 else if constexpr(I == 1) return x.s;//'s' is a private member of 'foobar'
 }


int main(){
    foobar ml = f( 1024,"hello" );
    auto [ n, s] = f( 1024,"hello" );//Cannot decompose non-public member '_ival' o
    return 0;
}

Error

if constexpr(I == 0) return x._ival;//'_ival' is a private member of 'foobar'

else if constexpr(I == 1) return x.s;//'s' is a private member of 'foobar'

auto [ n, s] = f( 1024,"hello" );//Cannot decompose non-public

Help required

1.If anyone can elaborate what he is actually trying to do on these lines (please refer the link provided)

// 2. Now add get<> support (using C++17, because why not; it’s better
// than =delete’ing the primary get<> template and adding specializations)
template<int I>
auto get(const S&) {
   if      constexpr(I == 0) return x.i;
   else if constexpr(I == 1) return string_view{x.c}; }
   else if constexpr(I == 2) return x.d;
}

2.Any suggestion how to fix the error for the above example

2

There are 2 answers

1
einpoklum On BEST ANSWER

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.

8
Barry On

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 friendship, 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.