为什么这个返回语句似乎需要一个const_cast?

编程语言 2026-07-11

我有一个 Chess 类,用来把棋盘上格子的状态编码成4 位整数。为了把这点抽象出来,我正在做我认为是标准做法的那种:创建一个能够模拟引用的类,以便能够使用调用运算符和下标运算符。

问题在于 const OptPieceRef 的重载编译失败。使用 const_cast 可以;但那没有意义。非 const OptPieceRef 的重载不需要这些花招,而且我一直很坚信 const 函数和对象不会在它们各自的成员变量上赋予任何 const 性质。我一直以为类的实现者可以任意地去做,只不过 const 对象只能调用 const 函数。

我有两个疑问,分别是前文提到的为什么,以及我是否引入了未定义行为?我不认为自己引入过未定义行为,因为除了代码能够工作之外,这个Stack Overflow的回答 也说,只要底层对象不是 constconst_cast 就可以。话虽如此,我还是想确认。

为了使对虚拟的(口语中的说法,而非关键字)Piece 结构体的成员有更直接的访问能力(关于程序员接口方面),我添加了一个 PieceRef 类,其方式类似于Remy Lebeau的建议。

#include "Chess.h"
#include <iostream>

int main() {
    Chess::Piece piece = Chess::Piece();
    piece.color = Chess::Piece::Color::White;
    piece.kind = Chess::Piece::Kind::Rook;

    Chess chess = Chess();
    chess.m_board(3, 3) = piece;

    std::cout << "1.  " << chess.m_board(3, 3).has_value() << std::endl;
    std::cout << "2.  " << chess.m_board(3, 4).has_value() << std::endl;
    std::cout << "3.  " << (chess.m_board(3, 3).value().kind == Chess::Piece::Kind::Rook) << std::endl;
    std::cout << "4.  " << (chess.m_board(3, 3).value().kind == Chess::Piece::Kind::Knight) << std::endl;
    std::cout << "5.  " << (chess.m_board(3, 3).value().color == Chess::Piece::Color::White) << std::endl;
    std::cout << "6.  " << (chess.m_board(3, 3).value().color == Chess::Piece::Color::Black) << std::endl;
    std::cout << "7.  " << (chess.m_board(3, 3) == chess.m_board(3, 3)) << std::endl;
    std::cout << "8.  " << (chess.m_board(3, 3) == chess.m_board(3, 4)) << std::endl;
    std::cout << "9.  " << (chess.m_board(4, 3) == chess.m_board(3, 4)) << std::endl;
    std::cout << "10. " << (chess.m_board(3, 3).value() == chess.m_board(3, 3).value()) << std::endl;
}
#pragma once

#include <optional>

#include <cstdint>
#include <cstddef>

class Chess {

    // Public for testing
    public: struct Piece {

        enum class Color { White, Black };
        enum class Kind { King, Queen, Rook, Bishop, Knight, Pawn };

        Color color;
        Kind kind;

        bool operator ==(const Piece& piece)const;

    };

    // Public for testing
    public: class PieceRef {

        public: class ColorRef {

            private: uint32_t& m_array;
            private: const size_t m_index;

            public: ColorRef() = delete;

            public: explicit ColorRef(uint32_t& array, const size_t& index);
            public: ColorRef(ColorRef& color_ref);

            public: ColorRef& operator =(const Piece::Color& color);
            public: ColorRef& operator =(const ColorRef& color_ref);

            public: operator Piece::Color()const;

        };

        public: class KindRef {

            private: uint32_t& m_array;
            private: const size_t m_index;

            public: KindRef() = delete;

            public: explicit KindRef(uint32_t& array, const size_t& index);
            public: KindRef(KindRef& kind_ref);

            public: KindRef& operator =(const Piece::Kind& kind);
            public: KindRef& operator =(const KindRef& kind_ref);

            public: operator Piece::Kind()const;

        };

        public: ColorRef color;
        public: KindRef kind;

        private: uint32_t& m_array;
        private: const size_t m_index;

        public: PieceRef() = delete;

        public: explicit PieceRef(uint32_t& array, const size_t& index);
        public: PieceRef(PieceRef& piece_ref);

        public: PieceRef& operator =(const Piece& piece);
        public: PieceRef& operator =(const PieceRef& piece_ref);

        public: bool operator ==(const PieceRef& piece_ref)const;

        public: operator Piece()const;

    };

    // Public for testing
    public: class OptPieceRef {

        private: uint32_t& m_array;
        private: const size_t m_index;

        public: OptPieceRef() = delete;

        public: explicit OptPieceRef(uint32_t& array, const size_t& index);
        public: OptPieceRef(OptPieceRef& opt_piece_ref);

        public: OptPieceRef& operator =(const std::optional<Piece>& opt_piece);
        public: OptPieceRef& operator =(const OptPieceRef& opt_piece_ref);

        public: bool operator ==(const OptPieceRef& opt_piece_ref)const;

        public: bool has_value()const;

        public: PieceRef value();
        public: const PieceRef value()const;

        public: operator std::optional<Piece>()const;

    };

    private: class Board {

        private: uint32_t m_array[8];

        public: Board();

        public: OptPieceRef operator ()(const size_t& row, const size_t& col);
        public: const OptPieceRef operator ()(const size_t& row, const size_t& col)const;

    };

    // Public for testing
    public: Board m_board;

};

#include "Chess.cpp"
#include <optional>

#include <cstdint>
#include <cstddef>

bool Chess::Piece::operator ==(const Piece& piece) const {
    return color == piece.color & kind == piece.kind;
}

Chess::PieceRef::ColorRef::ColorRef(uint32_t& array, const size_t& index):
    m_array(array),
    m_index(index) {}

Chess::PieceRef::ColorRef::ColorRef(ColorRef& color_ref):
    m_array(color_ref.m_array),
    m_index(color_ref.m_index) {}

Chess::PieceRef::ColorRef& Chess::PieceRef::ColorRef::operator =(const Piece::Color& color) {
    Piece::Kind kind;
    {
        uint32_t key = (m_array >> (4 * m_index)) & 0b1111;
        kind = static_cast<Piece::Kind>(key % 6);
    }

    uint32_t key = 6 * static_cast<uint32_t>(color) + static_cast<uint32_t>(kind);
    m_array = m_array & ~(UINT32_C(0b1111) << (4 * m_index)) | (key << (4 * m_index));

    return *this;
}

Chess::PieceRef::ColorRef& Chess::PieceRef::ColorRef::operator =(const ColorRef& color_ref) {
    Piece::Color color;
    {
        uint32_t key = (color_ref.m_array >> (4 * color_ref.m_index)) & 0b1111;
        color = static_cast<Piece::Color>(key / 6);
    }

    Piece::Kind kind;
    {
        uint32_t key = (m_array >> (4 * m_index)) & 0b1111;
        kind = static_cast<Piece::Kind>(key % 6);
    }

    uint32_t key = 6 * static_cast<uint32_t>(color) + static_cast<uint32_t>(kind);
    m_array = m_array & ~(UINT32_C(0b1111) << (4 * m_index)) | (key << (4 * m_index));

    return *this;
}

Chess::PieceRef::ColorRef::operator Piece::Color() const {
    uint32_t key = (m_array >> (4 * m_index)) & 0b1111;
    return static_cast<Piece::Color>(key / 6);
}

Chess::PieceRef::KindRef::KindRef(uint32_t& array, const size_t& index):
    m_array(array),
    m_index(index) {}

Chess::PieceRef::KindRef::KindRef(KindRef& kind_ref):
    m_array(kind_ref.m_array),
    m_index(kind_ref.m_index) {}

Chess::PieceRef::KindRef& Chess::PieceRef::KindRef::operator =(const Piece::Kind& kind) {
    Piece::Color color;
    {
        uint32_t key = (m_array >> (4 * m_index)) & 0b1111;
        color = static_cast<Piece::Color>(key / 6);
    }

    uint32_t key = 6 * static_cast<uint32_t>(color) + static_cast<uint32_t>(kind);
    m_array = m_array & ~(UINT32_C(0b1111) << (4 * m_index)) | (key << (4 * m_index));

    return *this;
}

Chess::PieceRef::KindRef& Chess::PieceRef::KindRef::operator =(const KindRef& kind_ref) {
    Piece::Color color;
    {
        uint32_t key = (m_array >> (4 * m_index)) & 0b1111;
        color = static_cast<Piece::Color>(key / 6);
    }

    Piece::Kind kind;
    {
        uint32_t key = (kind_ref.m_array >> (4 * kind_ref.m_index)) & 0b1111;
        kind = static_cast<Piece::Kind>(key % 6);
    }

    uint32_t key = 6 * static_cast<uint32_t>(color) + static_cast<uint32_t>(kind);
    m_array = m_array & ~(UINT32_C(0b1111) << (4 * m_index)) | (key << (4 * m_index));

    return *this;
}

Chess::PieceRef::KindRef::operator Piece::Kind() const {
    uint32_t key = (m_array >> (4 * m_index)) & 0b1111;
    return static_cast<Piece::Kind>(key % 6);
}

Chess::PieceRef::PieceRef(uint32_t& array, const size_t& index):
    color(array, index),
    kind(array, index),
    m_array(array),
    m_index(index) {}

Chess::PieceRef::PieceRef(PieceRef& piece_ref):
    color(piece_ref.m_array, piece_ref.m_index),
    kind(piece_ref.m_array, piece_ref.m_index),
    m_array(piece_ref.m_array),
    m_index(piece_ref.m_index) {}

Chess::PieceRef& Chess::PieceRef::operator =(const Piece& piece) {
    uint32_t key = 6 * static_cast<uint32_t>(piece.color) + static_cast<uint32_t>(piece.kind);
    m_array = m_array & ~(UINT32_C(0b1111) << (4 * m_index)) | (key << (4 * m_index));

    return *this;
}

Chess::PieceRef& Chess::PieceRef::operator =(const PieceRef& piece_ref) {
    uint32_t key = (piece_ref.m_array >> (4 * piece_ref.m_index)) & 0b1111;
    m_array = m_array & ~(UINT32_C(0b1111) << (4 * m_index)) | (key << (4 * m_index));

    return *this;
}

bool Chess::PieceRef::operator ==(const PieceRef& piece_ref) const {
    return ((m_array >> (4 * m_index)) & 0b1111) == ((piece_ref.m_array >> (4 * piece_ref.m_index)) & 0b1111);
}

Chess::PieceRef::operator Piece() const {
    uint32_t key = (m_array >> (4 * m_index)) & 0b1111;

    Piece piece = Piece();
    {
        piece.color = static_cast<Piece::Color>(key / 6);
        piece.kind = static_cast<Piece::Kind>(key % 6);
    }

    return piece;
}

Chess::OptPieceRef::OptPieceRef(uint32_t& array, const size_t& index):
    m_array(array),
    m_index(index) {}

Chess::OptPieceRef::OptPieceRef(OptPieceRef& opt_piece_ref):
    m_array(opt_piece_ref.m_array),
    m_index(opt_piece_ref.m_index) {}

Chess::OptPieceRef& Chess::OptPieceRef::operator =(const std::optional<Piece>& opt_piece) {
    uint32_t key = !opt_piece.has_value() ? 12 : 6 * static_cast<uint32_t>(opt_piece.value().color) + static_cast<uint32_t>(opt_piece.value().kind);
    m_array = m_array & ~(UINT32_C(0b1111) << (4 * m_index)) | (key << (4 * m_index));

    return *this;
}

Chess::OptPieceRef& Chess::OptPieceRef::operator =(const OptPieceRef& opt_piece_ref) {
    uint32_t key = (opt_piece_ref.m_array >> (4 * opt_piece_ref.m_index)) & 0b1111;
    m_array = m_array & ~(UINT32_C(0b1111) << (4 * m_index)) | (key << (4 * m_index));

    return *this;
}

bool Chess::OptPieceRef::operator ==(const OptPieceRef& opt_piece_ref) const {
    return ((m_array >> (4 * m_index)) & 0b1111) == ((opt_piece_ref.m_array >> (4 * opt_piece_ref.m_index)) & 0b1111);
}

bool Chess::OptPieceRef::has_value() const {
    uint32_t key = (m_array >> (4 * m_index)) & 0b1111;
    return key != 12;
}

Chess::PieceRef Chess::OptPieceRef::value() {
    uint32_t key = (m_array >> (4 * m_index)) & 0b1111;
    if (key == 12) {
        throw std::bad_optional_access();
    }

    return PieceRef(m_array, m_index);
}

const Chess::PieceRef Chess::OptPieceRef::value() const {
    uint32_t key = (m_array >> (4 * m_index)) & 0b1111;
    if (key == 12) {
        throw std::bad_optional_access();
    }

    return PieceRef(m_array, m_index);
}

Chess::OptPieceRef::operator std::optional<Piece>() const {
    uint32_t key = (m_array >> (4 * m_index)) & 0b1111;
    if (key == 12) {
        return {};
    }

    Piece piece = Piece();
    {
        piece.color = static_cast<Piece::Color>(key / 6);
        piece.kind = static_cast<Piece::Kind>(key % 6);
    }

    return piece;
}

Chess::Board::Board() {
    for (size_t index = 0; index < 8; index++) {
        m_array[index] = UINT32_C(0xCCCCCCCC);
    }
}

Chess::OptPieceRef Chess::Board::operator ()(const size_t& row, const size_t& col) {
    return OptPieceRef(m_array[row], col);
}

const Chess::OptPieceRef Chess::Board::operator ()(const size_t& row, const size_t& col) const {
    // Requires const_cast
    return OptPieceRef(const_cast<uint32_t&>(m_array[row]), col);
}

解决方案

如果你对这一设计坚持下去,你应该为 ConstPieceRef 等等创建类。你不应该能够从 const Board 构造出一个 PieceRef

你也应该重新考虑 operator=(PieceRef),以及把引用作为数据成员。做任意一项都会使这些类型不再是 regular 的。赋值运算符尤其危险,因为从语法上看该类型似乎是常规的,但如果有人使用它,底层的 Board 会造成混乱。

如果你的目标是C++20或更高版本,可以在几乎不需要修改的情况下实现这一点;在那之前,你可以通过继承来实现类似的效果。

template <bool isConst>
class OptPieceRef {
    using array_t = std::conditional_t<isConst, const uint32_t&, uint32_t&>;
    private: array_t m_array;
    private: size_t m_index;

    public: OptPieceRef() = delete;

    public: explicit OptPieceRef(array_t array, size_t index);
    public: OptPieceRef(OptPieceRef& opt_piece_ref);

    public: OptPieceRef& operator =(const std::optional<Piece>& opt_piece) requires (!isConst);
    public: OptPieceRef& operator =(const OptPieceRef& opt_piece_ref) requires (!isConst);

    public: bool operator ==(const OptPieceRef& opt_piece_ref)const;

    public: bool has_value()const;

    public: PieceRef<false> value();
    public: PieceRef<true> value() const;

    public: operator std::optional<Piece>()const;

};

完整示例在此处

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章