方括号和圆括号操作器,如何选择重载

Square and round brackets operator, how to choose overloads?

本文关键字:选择 重载 何选择 圆括号 操作器 方括号      更新时间:2023-10-16

我想使用 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
}