1

Consider the following class foo:

class foo {
private:
    int x = 0;
    int y = 0;

public:
    template <std::size_t I>
    int const& get() const
    {
        if constexpr (I == 0) {
            return x;
        }
        else return y;
    }
};

I would like to implement structured bindings for foo, so I added the following:

namespace std {
    template <>
    struct tuple_size<foo> : std::integral_constant<std::size_t, 2u> {};

    template <std::size_t I>
    struct tuple_element<I, foo> {
        using type = int;
    };
}

Now, if the user is to perform structure bindings, he/she has to explicitly add a const qualifier:

int main() {
    foo f;
    auto [a, b] = f;            // Won't compile
    auto& [x, y] = f;           // Won't compile
    const auto [c_a, c_b] = f;  // Compiles
    const auto& [c_x, c_y] = f; // Compiles
    return 0;
}

(Because we cannot bind int& to a int const&)

Or, alternatively, I can make tuple_element typedef a const int. Now all four statements compiles:

namespace std {
    template <>
    struct tuple_size<foo> : std::integral_constant<std::size_t, 2u> {};

    template <std::size_t I>
    struct tuple_element<I, foo> {
        using type = const int;         // <=== const added here
    };
}

int main() {
    foo f;
    auto [a, b] = f;            // Compiles
    auto& [x, y] = f;           // Compiles
    const auto [c_a, c_b] = f;  // Compiles
    const auto& [c_x, c_y] = f; // Compiles
    return 0;
}

Now note that even if no const qualifier is present in their declarations, a, b, x, and y are all int const. This might be confusing to the user.

So my question is: which one of the above is considered a better practice?

ph3rin
  • 4,426
  • 1
  • 18
  • 42
  • 1
    Some consider introduction of only one name per line to be a best practice. And structured bindings in this sense are even worse than old mixed declarations (such as `int x, * px, * const * ppx = 0;`) because types of the variables may be completely different and because they rely on compiler to generate some magic code behind your back. – user7860670 Dec 23 '19 at 09:07
  • @user7860670 This qualifies as an answer. Make it an answer so I can accept it. – ph3rin Dec 23 '19 at 09:21
  • 1
    The `auto` of structure binding doesn't apply to variable inside `[]` but to the structure (which is IMO a source of confusion). To answer to your question, better to mimic behavior of `tuple` (think about `auto [x, y] = std::tie(var1, var2)`). – Jarod42 Dec 23 '19 at 09:30

1 Answers1

2

So my question is: which one of the above is considered a better practice?

Prefer second method to try to mimic what happens with built-in and standard:

class foo {
public:
    const int x = 0;
    const int y = 0;
};

int main() {
    foo f;
    auto [a, b] = f;            // Compiles
    auto& [x, y] = f;           // Compiles
    const auto [c_a, c_b] = f;  // Compiles
    const auto& [c_x, c_y] = f; // Compiles
}

And from binding_a_tuple-like_type

std::tuple<float&,char&&,int> tpl(x,std::move(y),z);
const auto& [a,b,c] = tpl;
// a names a structured binding that refers to x; decltype(a) is float&
// b names a structured binding that refers to y; decltype(b) is char&&
// c names a structured binding that refers to the 3rd element of tpl; decltype(c) is const int

Jarod42
  • 203,559
  • 14
  • 181
  • 302