-2

I am experimenting with complex FSM frameworks. For one of the approaches I am considering I would like to have two types of function: plain function and special function. The exact meaning does not matter. I want to have compile-time enforcement that there is no more than one special function per block. I can play with function types (e.g. make special actions to return value), signatures, attribute, template meta-programming, wrapper macro, function objects etc.

Each block is very simple just a plain sequence of a few function calls:

{
    funcA();
    funcB();
    ..
    funcC();
}
zzz777
  • 456
  • 1
  • 5
  • 11

2 Answers2

3

I don't think you can do this comfortably. You are trying to implement your own language on top of C++, but C++ doesn't really have this kind of extensibility.

The next best thing is a fluent interface. With a fluent interface, we can encode a grammar of our design into the type system. We can't enforce that only one special function per C++ block is called, but we can enforce that only one special method in our cascade is invoked. For example:

// Mixin for end() operation
struct can_end {
  auto end() && -> void {}
};

// Mixin for normal methods
template<typename next_state>
struct can_normal {
  auto normal_1() && -> next_state { return {}; }
  auto normal_2() && -> next_state { return {}; }
};

// State after special method: only end and normal methods allowed
struct after_special : can_end, can_normal<after_special> {
  after_special() = default;
  after_special(after_special const&) = delete;
};

// State before special method: end, normal, and special allowed
struct before_special : can_end, can_normal<before_special> {
  before_special() = default;
  before_special(before_special const&) = delete;

  auto special() && -> after_special { return {}; }
};

// Entry point
auto block() -> before_special { return {}; }

Now we can write a method cascade like:

block()
  .normal_1()
  .special()
  .normal_2()
  .end();

But will be prevented from invoking the special method twice:

block()
  .special()
  .special()  // type error
  .end();

By deleting the copy ctor, we prevent using a temporary variable to capture the pre-special state:

auto temp = block();  // compilation error: deleted copy ctor
temp.special();
temp.special().end();

As all methods take an rvalue reference, this also discourages trickery like binding the state to a reference and then invoking the special method twice. But I'm sure the C++ type system has enough escape hatches to do that if you're really determined.

amon
  • 132,749
  • 27
  • 279
  • 375
3

Maybe I've overlooked something, but given your strict requirements on blocks the following should be enough:

#define specialFunction(...) \
    int SPECIAL_FUNCTION_ALREADY_CALLED = 0; \
    specialFunction(__VA_ARGS__)

Trying to call specialFunction twice in the same block will redefine the variable, thus triggering a compile-time error.

main.cpp:6:9: error: redeclaration of 'int SPECIAL_FUNCTION_ALREADY_CALLED'
     int SPECIAL_FUNCTION_ALREADY_CALLED = 0; \
         ^

Caveat: does not work across nested blocks, breaks all uses of the function call as an expression. But it does seem to fit the bill.

Quentin
  • 1,465
  • 9
  • 10