方括号和圆括号操作器,如何选择重载
Square and round brackets operator, how to choose overloads?
我想使用 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()
,我想知道是否还有我不知道的更多替代方案?
我认为
使用命名方法比在您的情况下使用operator[]
要好得多,因为通过读取源代码更容易理解正在访问两个单独的缓冲区。
无论如何,如果你想使用你的operator[]
方法,你可以使用强 typedefs 和用户定义的文字,以最小的语法开销实现类型安全:
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];
魔杖盒示例
我想
我会使用 <tuple>
库中的std::tie
,然后编写一个小助手来查找正确的引用类型:
#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
阅读了进一步的评论后,我可能会选择以(例如)逐行形式存储矩阵,然后提供一个逐行包装器。
一个勉强起作用的例子:
#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
我同意维托里奥·罗密欧的观点,最好的解决方案是命名方法。
但是这里有一个解决方案:
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];
如果i_type和s_type本身应该有一个含义,则可以为运算符[]添加语义。类似的东西
#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
}
相关文章:
- 添加重载更改选择的重载
- 具有参数包和通用引用的重载选择
- 重载模板函数未为特定类型选择正确的版本
- 为什么选择转换运算符的重载?
- 为什么传递文字 3 会选择 int 重载而不是短重载?
- 模板函数重载(泛型类型与模板模板类型)选择正确的重载
- 为什么<iostream>运算符<<会选择明显错误的重载?
- C++:使用重载而不是动态强制转换通过基选择派生类
- 模板实例化失败:编译器选择不正确的重载函数
- 编译器选择错误的重载函数
- 为什么重载分辨率不选择模板函数的 std::vector 重载?
- 如何获取通过重载分辨率选择的函子签名
- 方括号和圆括号操作器,如何选择重载
- 手动选择重载函数为右值或左值类型
- 使用模板模板参数和enable_if正确选择重载
- visual Do C++lambdas没有正确选择重载函数
- 显式运算符<<选择重载'wrong'
- C++编译器无法选择重载运算符*
- 如何根据返回类型选择重载
- 当没有"使用 Base_class::function_name"时,如何选择重载的虚拟函数