方括号和圆括号运算符,如何选择重载?

时间:2017-01-25 10:51:56

标签: c++ operator-overloading

我想使用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(),我想知道是否有更多我不了解的替代方案?

4 个答案:

答案 0 :(得分:7)

我认为使用命名方法比在您的情况下使用operator[]要好得多,因为通过阅读可以更容易理解正在访问两个单独的缓冲区源代码。

无论如何,如果您想使用operator[]方法,可以使用strong typedefsuser 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];

wandbox example

答案 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
}