我想使用operator[]
访问某些类数据,但根据方括号中的索引类型返回一种数据或其他类型。作为简化示例:
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 };
};
无法编写short
字面值,因此选择short
重载的唯一方法是通过强制转换:
S s;
std::cout << s[9] << '\n'; // prints [i]9
std::cout << s[(short)9] << '\n'; // prints [s]999
但我不喜欢它,我想知道是否有不同的选择。
首先我尝试使用“标签”:
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 };
};
虽然有效,但仍然有点冗长:
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
作为一种疯狂的解决方法,我想尝试模拟运算符:
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]; }
模板版本的行为与原始代码相同,但没有简单的方法来指定类型参数以及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';
所描述的所有问题也发生在operator()
,我想知道是否有更多我不了解的替代方案?
答案 0 :(得分:7)
我认为使用命名方法比在您的情况下使用operator[]
要好得多,因为通过阅读可以更容易理解正在访问两个单独的缓冲区源代码。
无论如何,如果您想使用operator[]
方法,可以使用strong typedefs和user defined literals来保证类型安全,并且语法开销最小:
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}; }
然后您可以按如下方式调用您的方法:
S s;
auto& some_int = s[15_ii];
auto& some_short = s[4_si];
答案 1 :(得分:2)
我想我会使用std::tie
库中的<tuple>
,然后写一个小助手来找到正确的引用类型:
#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';
}
这样,代码是明确的,但仍然简洁。
预期产出:
short is : 555
int is : 5
short is : 666
int is : 6
在阅读了进一步的评论之后,我可能会选择以(例如)行方式存储矩阵,然后提供一个col-wise包装。
几乎没有功能的例子:
#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';
}
预期产出:
3
7
答案 2 :(得分:1)
我同意Vittorio Romeo最佳解决方案是命名方法。
然而,这是一个解决方案:
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 };
};
并使用:
S s;
return s.s_type()[2];
答案 3 :(得分:0)
如果i_type和s_type本身具有含义,则可以向operator []添加语义。像
这样的东西#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
}