Как в c++ создать тип данных "целое число либо одна из констант"
Есть класс, который позволяет перемещать каретку (нечто вроде сопла 3д принтера) в произвольную позицию:
AxesMover am;
am.move({1, 2, 3, 4}); // метод принимает вектор целых чисел - координаты точки
Задаётся последовательность движений. Иногда нужно двигать не все координаты. Например:
am.move({1, 1, 1, 1});
am.move({1, 1, 1, 2}); // сдвинули только последнюю координату
am.move({2, 2, 1, 2}); // сдвинули первые две
Иногда нужно сдвигать не в конкретную позицию, а в максимум для конкретной координаты.
Хотелось бы писать так:
am.move({1, DONT_MOVE, 3, MAX}) // не двигать вторую координату, 4 координату в максимум
Вижу два варианта
- Сделать отдельный класс "Позиция". Спец случаи вынести в перечисление:
enum class SpecialPos {
DONT_MOVE,
MIN,
MAX
}
сlass Positions {
public:
Position(unsigned long n) : _coordinate(n), special(false),
_sp(SpecialPos::DONT_MOVE) { }
Position(SpecialPos sp) : _coordinate(0), special(true), _sp(sp) { }
bool operator==(Positions const & rhs) const {
if (_special) return _sp == rhs._sp;
else return _coordinate == rhs._coordinate;
}
private:
unsigned long const _coordinate;
bool const _special;
SpecialPos const _sp;
}
В таком варианте не очень нравится использование констант: SpecialPos::DONT_MOVE Да и доп приватные поля сомнительно смотрятся
- Спец случаями считать числа вида MAX_LONG - n (MAX_LONG: не двигать, MAX_LONG - 1: минимум и т.д.)
В таком варианте не нравится интерфейс (нужно будет в комментариях описывать, что эти числа отведены под спецслучаи)
Больше склоняюсь к первому варианту. Есть альтернативные предложения?
Ответы (4 шт):
1 вариант) сделать как всегда это делается:
enum class SpecialPos {
DONT_MOVE = 12341,
MIN,
MAX
}
и
if(val>=DONT_MOVE)
2 вариант) сделать как надо:
enum class SpecialPos {
DONT_MOVE,
MIN,
MAX
}
variant<int, SpecialPos> val
И долго потом пытаться что-то с этим написать.
Первый вариант если нужно чтобы работало, а второй чтобы было веселее кодить, visit там побзать например.
(нужно будет в комментариях описывать, что эти числа отведены под спецслучаи)
Ну так и делается. Написать такой коммент самая меньшая из проблем в C++.
Например, можно использовать std::variant, который является типобезопасной альтернативой union, и как раз и предназначен, чтобы держать значение одного из нескольких типов. При этом специальные значения можно поместить в enum:
#include <variant>
enum class SpecialPosition
{
DONT_MOVE,
MIN,
MAX
};
using Position = std::variant<unsigned long, SpecialPosition>;
Пользоваться так:
void move(std::array<Position, 4> positions)
{
for (auto& p : positions)
{
if (std::holds_alternative<unsigned long>(p))
{
std::cout << "Moving to position " << std::get<unsigned long>(p) << std::endl;
}
else
{
switch (std::get<SpecialPosition>(p))
{
case SpecialPosition::DONT_MOVE:
std::cout << "Not moving" << std::endl;
break;
case SpecialPosition::MIN:
std::cout << "Moving to min" << std::endl;
break;
case SpecialPosition::MAX:
std::cout << "Moving to max" << std::endl;
break;
}
}
}
}
int main(int argc, char* argv[])
{
move({ 1ul, SpecialPosition::DONT_MOVE, 2ul, SpecialPosition::MAX });
return 0;
}
Выдаёт:
Moving to position 1 Not moving Moving to position 2 Moving to max
Вот мой вариант с сохранением текущей позиции:
#include <array>
#include <limits>
#include <iostream>
using
t_Pos = ::std::array<int, 3>;
class
t_AxesMover
{
public: void
move(t_Pos const pos)
{
::std::cout << "moving to " <<
pos[0] << " " << pos[1] << " " << pos[2] <<
::std::endl;
return;
}
};
class
t_MoveHelper
{
private: t_AxesMover & m_mover;
private: t_Pos m_pos;
public: explicit
t_MoveHelper(t_AxesMover & mover) noexcept
: m_mover{mover}
, m_pos{}
{
return;
}
public: t_MoveHelper &
operator ()(void)
{
m_mover.move(m_pos);
return *this;
}
public: t_MoveHelper &
operator ()(t_Pos const pos)
{
m_pos = pos;
m_mover.move(m_pos);
return *this;
}
public: t_MoveHelper &
x(int const value) noexcept
{
m_pos[0] = value;
return *this;
}
public: t_MoveHelper &
xmax(void) noexcept
{
return x(::std::numeric_limits<int>::max());
}
public: t_MoveHelper &
y(int const value) noexcept
{
m_pos[1] = value;
return *this;
}
public: t_MoveHelper &
ymax(void) noexcept
{
return y(::std::numeric_limits<int>::max());
}
public: t_MoveHelper &
z(int const value) noexcept
{
m_pos[2] = value;
return *this;
}
public: t_MoveHelper &
zmax(void) noexcept
{
return z(::std::numeric_limits<int>::max());
}
};
int main()
{
t_AxesMover mover{};
t_MoveHelper helper{mover};
helper
// двигаем по всем координатам, передавая объект координаты
(t_Pos{1, 1, 1})
// двигаем по всем координатам, задавая координаты в любом порядке
.y(3).x(2).z(4)()
// двигаем только по x и z
.x(5).z(7)()
// двигаем по y до упора
.ymax()();
return 0;
}
Эзотерический пятничный вариант с FORTH-style командами:
helper,
// двигаем по всем координатам, передавая объект координаты
t_Pos{1, 1, 1},
// двигаем по всем координатам, задавая координаты в любом порядке
3, y, 2, x, 4, z, move,
// двигаем только по x и z
5, x, 7, z, move,
// двигаем по y до упора
max, y, move;
код:
#include <array>
#include <limits>
#include <iostream>
#include <cstddef>
using
t_Pos = ::std::array<int, 3>;
class
t_AxesMover
{
public: void
Move(t_Pos const pos)
{
::std::cout << "moving to " <<
pos[0] << " " << pos[1] << " " << pos[2] <<
::std::endl;
return;
}
};
struct
t_Flusher final
{
// ничего...
};
struct
t_Max final
{
// ничего...
};
struct
t_Axis final
{
::std::size_t const index;
};
class
t_MoveHelper final
{
private: t_AxesMover & m_mover;
private: t_Pos m_pos;
public: explicit
t_MoveHelper(t_AxesMover & mover) noexcept
: m_mover{mover}
, m_pos{}
{
return;
}
public: t_MoveHelper &
operator ,([[maybe_unused]] t_Flusher const & dummy) noexcept
{
m_mover.Move(m_pos);
return *this;
}
private: class
t_ValueApply final
{
private: t_MoveHelper & m_helper;
private: int const m_value;
public: explicit constexpr
t_ValueApply(t_MoveHelper & helper, int const value) noexcept
: m_helper{helper}
, m_value{value}
{
return;
}
public: t_MoveHelper &
operator ,(t_Axis const axis) const && noexcept
{
m_helper.m_pos[axis.index] = m_value;
return m_helper;
}
};
public: t_ValueApply
operator ,([[maybe_unused]] t_Max const & dummy) noexcept
{
return t_ValueApply(*this, ::std::numeric_limits<int>::max());
}
public: t_ValueApply
operator ,(int const value) noexcept
{
return t_ValueApply(*this, value);
}
public: t_MoveHelper &
operator ,(t_Pos const pos) noexcept
{
m_pos = pos;
m_mover.Move(m_pos);
return *this;
}
};
int main()
{
constexpr t_Flusher const move{};
constexpr t_Max const max{};
constexpr t_Axis const x{0};
constexpr t_Axis const y{1};
constexpr t_Axis const z{2};
t_AxesMover mover{};
t_MoveHelper helper{mover};
helper,
// двигаем по всем координатам, передавая объект координаты
t_Pos{1, 1, 1},
// двигаем по всем координатам, задавая координаты отдельно, в любом порядке
3, y, 2, x, 4, z, move,
// двигаем только по x и z
5, x, 7, z, move,
// двигаем по y до упора
max, y, move;
return 0;
}