The operator of the square and round brackets, how to choose overload?

I want to access some class data using operator[] , but depending on the type of index, one or more data is returned in square brackets. As a simplified example:

 struct S { int &operator []( int index ) { std::cout << "[i]"; return i_buffer[index]; } short &operator [](short index) { std::cout << "[s]"; return s_buffer[index]; } private: int i_buffer[10]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; short s_buffer[10]{ 0, 111, 222, 333, 444, 555, 666, 777, 888, 999 }; }; 

It is not possible to write a short literal, so the only way to choose short overload is casting:

 S s; std::cout << s[9] << '\n'; // prints [i]9 std::cout << s[(short)9] << '\n'; // prints [s]999 

But I do not like it, and I was wondering if there are other options.

What have i tried?

Marked Parameter

At first I tried using the "tags":

 struct S { enum class i_type : std::int32_t {}; enum class s_type : std::int32_t {}; int &operator [](i_type index) { std::cout << "[i]"; return i_buffer[static_cast<int>(index)]; } short &operator [](s_type index) { std::cout << "[s]"; return s_buffer[static_cast<int>(index)]; } private: int i_buffer[10]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; short s_buffer[10]{ 0, 111, 222, 333, 444, 555, 666, 777, 888, 999 }; }; 

This works, but still a little more verbose:

 S s; std::cout << s[9] << '\n'; // error, no possible overload to be taken std::cout << s[S::i_type{9}] << '\n'; // prints [i]9 std::cout << s[S::s_type{9}] << '\n'; // prints [s]999 

Template.

As a crazy workaround, I wanted to try the operator pattern:

 struct S { template <typename T> T &operator [](T) { std::cout << "???"; return 0; } private: int i_buffer[10]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; short s_buffer[10]{ 0, 111, 222, 333, 444, 555, 666, 777, 888, 999 }; }; template <> int &S::operator [](int index) { std::cout << "[i]"; return i_buffer[index]; } template <> short &S::operator [](short index) { std::cout << "[s]"; return s_buffer[index]; } 

The version of the template behaves like source code, but there is no easy way to specify a type parameter along with operator[] :

 S s; std::cout << s[9] << '\n'; // prints [i]9 like before std::cout << s[(short)9] << '\n'; // prints [s]999 like before std::cout << s<short>[9] << '\n'; // s is not template std::cout << s[9]<short> << '\n'; // nonsense // Correct but utterly verbose and hard to write and read std::cout << s.operator[]<short>(9) << '\n'; 

Question.

All the described problems also happen with operator() , I want to know if there are any other alternatives that I don't know about?

+6
source share
4 answers

I think using a named method is a much better idea than using operator[] in your situation, since it would be easier to understand that two separate buffers are accessed by reading the source code.

Regardless, if you want to use your operator[] approach, you can use strong typedefs and custom literals to have type safety with minimal overhead:

 BOOST_STRONG_TYPEDEF(std::size_t, int_index) BOOST_STRONG_TYPEDEF(std::size_t, short_index) struct S { auto& operator[](int_index i) { /* ... */ } auto& operator[](short_index i) { /* ... */ } }; auto operator "" _ii(unsigned long long int x) { return int_index{x}; } auto operator "" _si(unsigned long long int x) { return short_index{x}; } 

Then you can call your methods as follows:

 S s; auto& some_int = s[15_ii]; auto& some_short = s[4_si]; 

wandbox example

+7
source

I think I would use std::tie from the <tuple> library, and then write a little helper to find the correct reference type:

 #include <tuple> #include <iostream> template<class As, class...Ts> auto& as(std::tuple<const Ts&...>ts) { return std::get<As const&>(ts); }; template<class As, class...Ts> auto& as(std::tuple<Ts&...>ts) { return std::get<As &>(ts); }; struct S { // both cost and mutable version provided for completeness. auto operator[](std::size_t i) const { return std::tie(i_buffer[i], s_buffer[i]); } auto operator[](std::size_t i) { return std::tie(i_buffer[i], s_buffer[i]); } private: int i_buffer[10]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; short s_buffer[10]{ 0, 111, 222, 333, 444, 555, 666, 777, 888, 999 }; }; int main() { auto s = S(); const auto x = S(); std::cout << "short is : " << as<short>(s[5])<< '\n'; std::cout << "int is : " << as<int>(s[5])<< '\n'; std::cout << "short is : " << as<short>(x[6])<< '\n'; std::cout << "int is : " << as<int>(x[6])<< '\n'; } 

Thus, the code is explicit, but still concise.

expected output:

 short is : 555 int is : 5 short is : 666 int is : 6 

After reading further comments, I can choose to save the matrix in (say) a string form, and then provide a collective shell.

A practically ineffective example:

 #include <tuple> #include <iostream> #include <array> template<std::size_t Rows, std::size_t Cols> struct RowWiseMatrix { auto& operator[](std::size_t i) { return data_[i]; } std::array<std::array<double, Cols>, Rows> data_; }; template<std::size_t Rows, std::size_t Cols> struct ColumnProxy { ColumnProxy(std::array<std::array<double, Cols>, Rows>& data, std::size_t col) : data_(data), col_(col) { } auto& operator[](std::size_t i) { return data_[i][col_]; } std::array<std::array<double, Cols>, Rows>& data_; std::size_t col_; }; template<std::size_t Rows, std::size_t Cols> struct ColWiseProxy { ColWiseProxy(RowWiseMatrix<Rows, Cols>& mat) : underlying_(mat) {} auto operator[](std::size_t i) { return ColumnProxy<Rows, Cols> { underlying_.data_, i }; } RowWiseMatrix<Rows, Cols>& underlying_; }; template<std::size_t Rows, std::size_t Cols> auto& rowWise(RowWiseMatrix<Rows, Cols>& mat) { return mat; }; template<std::size_t Rows, std::size_t Cols> auto colWise(RowWiseMatrix<Rows, Cols>& mat) { return ColWiseProxy<Rows, Cols>(mat); }; int main() { auto m = RowWiseMatrix<3, 3> { std::array<double, 3>{ 1, 2, 3 }, std::array<double, 3>{ 4, 5, 6}, std::array<double, 3>{ 7, 8, 9} }; std::cout << rowWise(m)[0][2] << '\n'; std::cout << colWise(m)[0][2] << '\n'; } 

Expected Result:

 3 7 
+2
source

I agree with Vittorio Romeo that the best solution is a named method.

However, here is the solution:

 template <class T> struct S_proxy { T* data; T& operator[](std::size_t i) { return data[i]; } }; struct S { auto i_type() { return S_proxy<int>{i_buffer}; }; auto s_type() { return S_proxy<short>{s_buffer}; }; private: int i_buffer[10]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; short s_buffer[10]{ 0, 111, 222, 333, 444, 555, 666, 777, 888, 999 }; }; 

and use:

 S s; return s.s_type()[2]; 
+1
source

If i_type and s_type should matter by themselves, you can add semantics to the [] statements. Sort of

 #include <iostream> struct Month { explicit Month(int m) : m(m) { } int m; }; struct Day { explicit Day(short d) : d(d) { } short d; }; struct S { int& operator[](const Month& mes) { std::cout << "[i]"; return i_bufer[mes.m]; } short& operator[](const Day& dis) { std::cout << "[s]"; return s_bufer[dis.d]; } private: int i_bufer[10]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; short s_bufer[10]{ 0, 111, 222, 333, 444, 555, 666, 777, 888, 999 }; }; int main() { S s; std::cout << s[Month(9)] << '\n'; // muestra [i]9 std::cout << s[Day(9)] << '\n'; // muestra [s]999 } 
0
source

Source: https://habr.com/ru/post/1014411/


All Articles