0

I'm using someone's class for bitmaps, which are ways of storing chess positions in 64-bit bitsets. I was wondering what the part with auto() operator does. Is "auto" used because it returns one bit, which is why a return-type isn't specified for the function? I get that it checks that x and y are in the bounds of the chess board, and asserts an error if they aren't. I also understand that it returns a bit that corresponds to the x,y value pair for the bitset. I also don't get why the function is defined like it is, with an extra pair of parentheses. Any help is appreciated!

class BitBoard {
    private:
        std::bitset<64> board;
    public:
        auto operator()(int x, int y) {
            assert(0<=x && x<=7);
            assert(0<=y && y<=7);
            return board[8*y+x];
        }
    }
};
  • 3
    It's commonly called a *function call operator*. It allows an instance of the class, ex: `BitBoard bb;` , to be used like: `bb(1,2);` . See the section "Function call operator" in this encompassing question and answer: [What are the basic rules and idioms for operator overloading?](https://stackoverflow.com/a/4421719/1322972) – WhozCraig Apr 18 '22 at 21:31
  • I can't believe there's no dupe for questions about what `operator()` means... – einpoklum Apr 18 '22 at 21:38
  • 2
    @einpoklum: There's the very general one Whoz linked, but `operator()` deserves additional detail about its use as a workaround for `operator[]` not accepting multiple arguments. – Ben Voigt Apr 18 '22 at 21:39
  • It’s not an extra set of parentheses. They’re part of the name of the operator, just like the `+` in `operator+`. The name of this operator is `operator()`, which is a function call operator. Yes, it’s confusing. – Pete Becker Apr 18 '22 at 22:00
  • `operator`-*something* in this context means you are overloading the *something* operator in your C++ class. – lurker Apr 18 '22 at 22:05

2 Answers2

4

The "extra" pair of parentheses are because you're defining operator(), which lets instances of your class behave like functions. So if you had a:

BitBoard board;

you could get the value for x=3, y=5 by doing:

board(3, 5)

instead of having a method you call on the board explicitly, like board.get_bit_at(3, 5).

The use of auto just means it deduces the return type from std::bitset<64>'s operator[]; since the method isn't const qualified, this means it's just deducing the std::bitset::reference type that std::bitset's operator[] uses to allow mutations via stuff like mybitset[5] = true;, even though you can't give "true" references to single bits. If reimplemented a second time as a const-qualified operator(), e.g.:

    auto operator()(int x, int y) const {
        assert(0<=x && x<=7);
        assert(0<=y && y<=7);
        return board[8*y+x];
    }

you could use auto again for consistency, though it doesn't save any complexity (the return type would be bool in that case, matching std::bitset's const-qualified operator[], no harder to type than auto).

The choice to use operator() is an old hack for multidimensional data structures to work around operator[] only accepting one argument; rather than defining operator[] to return a proxy type (which itself implements another operator[] to enable access to the second dimension), you define operator() to take an arbitrary number of arguments and efficiently perform the complete lookup with no proxies required.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • Are you sure about "for `const BitBoard` objects the return type would be `bool`" ? Having a deduced return type doesn't automatically make variations for different `this`-qualification, I believe this function just can't be used on a `const BitBoard` at all. – Ben Voigt Apr 18 '22 at 21:36
  • @BenVoigt: No, I'm not sure. As written, I believe you're correct that it's only valid on non-`const BitBoard`, but if you defined both the `const` and non-`const` versions of `operator()` you could use `auto` on both definition, and one would be seamlessly a `std::bitset::reference`, while the other would be `bool`, without needing to get into the nitty-gritty. I'll clarify. – ShadowRanger Apr 18 '22 at 21:43
  • The qualification "if you defined both the `const` and non-`const` versions of `operator()`" I agree with – Ben Voigt Apr 18 '22 at 21:44
  • @BenVoigt: I just confirmed, and yes, a `const BitBoard` doesn't work as written, as suspected (though it works fine as soon as you define `auto operator()(int x, int y) const { return board[8*y+x]; }`). The updated answer should be fully correct now. – ShadowRanger Apr 18 '22 at 21:51
1

operator() is the name of the function, which is then followed by another pair of parentheses listing the arguments. It is the function-call operator and overloading it allows you to make objects that act like functions/function pointers. In this case, it allows:

BitBoard thing;
thing(i, j); // looks like a function!

In this particular case, it's being used for indexing (like a[i]) but the subscript operator operator[] doesn't allow multiple indexes and the function-call operator does. So it was pretty common to see this for multi-dimensional arrays.

However, the new "preferred" style for multiple indexes in C++ is to pass a list to the subscript operator:

BitBoard thing;
std::cout << thing[{i, j}];

This would be accomplished by operator[](std::array<int, 2> xy).

But the author of this class has chosen the old way, that looks like a function call.

Overloaded operator() is also what makes lambda expressions tick inside.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720