Могу ли я применить это требование к количеству вызовов операторов во время компиляции?

У меня есть класс, который с точки зрения интерфейса так же прост:

struct Foo
{
inline Foo & operator << (int i)
{
return *this;
}
};

Затем я могу использовать его следующим образом:

Foo foo;
foo << 1 << 2 << 3 << 4;

Теперь я хотел бы ограничить использование этого оператора. Например, Я хотел бы, чтобы он вызывался четное количество раз между точками последовательности.

В настоящее время я решаю эту проблему, используя внутренний прокси-класс. Создается временный объект, который в конце последовательности управления уничтожается и проверяет, сколько раз был вызван оператор:

struct Foo
{
inline Foo() : m_count(0) {}

private:
struct FooProxy
{
friend struct Foo;

inline ~FooProxy();
inline struct Foo & operator << (int i);

private:
inline FooProxy(struct Foo &foo) : m_foo(foo) {}
struct Foo &m_foo;
};

public:
inline FooProxy operator << (int i);

private:
int m_count;
};

inline Foo::FooProxy Foo::operator << (int i)
{
++m_count;
return FooProxy(*this);
}

inline Foo & Foo::FooProxy::operator << (int i)
{
++m_foo.m_count;
return m_foo;
}

inline Foo::FooProxy::~FooProxy()
{
assert(m_foo.m_count % 2 == 0);
}

Есть несколько предостережений, но это в основном делает работу:

Foo foo;
foo << 1 << 2 << 3 << 4; /* is OK */
foo << 1 << 2 << 3; /* triggers an assert */

Теперь мне интересно, есть ли способ применить это во время компиляции, используя тот же метод прокси или другую стратегию.

Еще один пример того, чего я хотел бы достичь: принудительное толкание хотя бы одного int после любого числа float были переданы оператору:

foo << 1 << 2 << 3.f << 4.f << 5; /* is OK */
foo << 1 << 2 << 3.f << 4.f; /* illegal because one `int` is needed */

4

Решение

Вы можете использовать шаблонный прокси, который будет кодировать состояние как его параметр шаблона, а не как член.

Однако, если вы не используете конечное возвращаемое значение для чего-либо, вы можете проверить только некоторые условия, но не другие. Например, вы можете проверить, что int был вставлен перед float или что в строке не вставлено два float, но вы не можете проверить, вставлен ли int после любых float.

Обычно вы можете обнаружить любое условие, которое должно быть выполнено перед следующей вставкой, просто специализировав оператор вставки на что-то недопустимое для недопустимых состояний. Но вы не можете проверить конечное состояние, потому что все прокси должны быть разрушаемыми (каждый отличается, поэтому все промежуточные будут уничтожены).

1

Другие решения

Почему бы не использовать что-то вроде FooPair для обеспечения четности:

struct FooPair
{
int m_x, m_y;

FooPair(int x, int) : m_x(x), m_y(y)
{
}
};

А также:

inline Foo & operator << (const FooPair &pair)
{
return *this;
}

так что люди должны называть это как:

Foo foo;
foo << FooPair(1,2) << FooPair(3,4);

Это более многословно, но гарантирует, что передается четное количество значений.

2