view-only types

View-only types are kind of const types, but more relaxed way. Once constructed, their value can’t be changed but they provide useful bits over the data they hold.

Here is such a type I write when implementing an interpreter for the Lox language, from the Crafting Interpreters:

class ASTPrinter : public boost::static_visitor<std::string> {
private:
std::vector<Stmt> m_statements{};

public:
ASTPrinter() = default;
explicit ASTPrinter(const std::vector<Stmt> &statements)
: m_statements(statements) {}

void print(std::ostream &os) const;

private:
std::string visit(const Stmt &stmt) const;

std::string operator()(const BlockStmt &stmt) const;
std::string operator()(const ClassStmt &stmt) const;
std::string operator()(const ExpressionStmt &stmt) const;
std::string operator()(const FunctionStmt &stmt) const;
std::string operator()(const IfStmt &stmt) const;
std::string operator()(const PrintStmt &stmt) const;
std::string operator()(const ReturnStmt &stmt) const;
std::string operator()(const VariableStmt &stmt) const;
std::string operator()(const WhileStmt &stmt) const;

std::string visit(const Expr &expr) const;

std::string operator()(const AssignExpr &expr) const;
std::string operator()(const BinaryExpr &expr) const;
std::string operator()(const CallExpr &expr) const;
std::string operator()(const GetExpr &expr) const;
std::string operator()(const GroupingExpr &expr) const;
std::string operator()(const LiteralExpr &expr) const;
std::string operator()(const LogicalExpr &expr) const;
std::string operator()(const SetExpr &expr) const;
std::string operator()(const SuperExpr &expr) const;
std::string operator()(const ThisExpr &expr) const;
std::string operator()(const UnaryExpr &expr) const;
std::string operator()(const VariableExpr &expr) const;
std::string operator()(const auto & /*unused*/) const;
};

Stmt is a polymorphic type created using Boost.Variant. An array of statements are given when constructed and only a const member function print exposed:

void ASTPrinter::print(std::ostream &os) const {
for (const auto &stmt : m_statements)
os << visit(stmt);
os << '\n';
}

Visits are recursively applied:

std::string ASTPrinter::visit(const Stmt &stmt) const {
return boost::apply_visitor(*this, stmt);
}
std::string ASTPrinter::visit(const Expr &expr) const {
return boost::apply_visitor(*this, expr);
}

For example, when recursion hits a ReturnStmt, it does this:

std::string ASTPrinter::operator()(const ReturnStmt &stmt) const {
if (stmt.value.which() == 0)
return "(return)";

std::ostringstream sout;
sout << '(';
sout << "return " << visit(stmt.value);
sout << ')';
return sout.str();
}

None of the member functions change the state of the object, they just walk through m_statements, so all of them are const.

View-only types available since the C++98, C++17 adds std::string_view, and C++20 adds std::span. Thanks for reading :)