Is there a way to raise a compile time error when calling a given function several times in C++?

318 views Asked by At

Is there a way in C++ to design a function / add some "attributes" to it in such a way that calling it several times in the code would raise a compile time error?

To give a bit of background / motivation: I was programming on Mbed-OS and I did a couple of mistakes that look like:

rtos::Thread thread;

[lots of code]

thread.start(persistent_function_1);

[lots of code in a setup function]

thread.start(persistent_function_2);

This had the (logical) consequence that the persistent_function_1, which should have been allowed to execute for the lifetime of the program, only got to execute until the thread was re-purposed to run persistent_function_2. It took me a long time to find this bug, and I was wondering if I can do something to my thread.start function to make sure I get a compiler error if I make this sort of mistake again.

3

There are 3 answers

1
Williham Totland On

One option is to wrap the thread in a new manager object, with the rough shape of

class thread_manager {
  rtos::Thread thread;
  const std::function<...> execution_function;
  /* .
     .
     . */

public:
  thread_manager(rtos::Thread _thread, std::function<...> function, ...)
    : thread { _thread }
    , execution_function { function }
    , ...

  void start();
}

and disallowing any other usage of threading (which can be justified on the basis of encapsulation, although as pointed out in comments, yahoos are always a risk).

2
Dietmar Kühl On

I don't think there is a way to coerce the C++ language directly to detect double invocation of start() at compile time (put differently, I don't think @user4581301's suggestion would work): to statically assert a property you'd need to somehow change the entity. I'm sure you could write a custom checker using clang but I guess that isn't what you are after. It would, obviously, be possible to have a run-time assertion which reports that an already start()ed thread is started again. Again, that doesn't seem to be what you are after.

The "obvious" solution is no to have "[lots of code]" in a function to start with. In fact, std::thread entirely side-steps that issue by enforcing that there is no code between the object declaration and its start: the std::thread is started upon construction. The setup with "[lots of code]" between the object declaration and the start would be something like

my::thread thread([&]{
        [lots of code]
        return persistent_function_1;
    }());

The caveat is that you'd need to set up your various variables sort of out of order. That is, the preferred approach would be to declare the thread object at the site where it is actually started:

[lots of code]
my::thread thread(persistent_function_1);

In both of these cases my::thread would be a trivial wrapper around rtos::thread which doesn't expose a separate start() method. As I don't know why rtos::thread separates construction and start() and a plausible reason could be the ability to set up various thread parameters, it may be reasonable to actually use two separate arguments to my::thread's constructor:

  1. A function taking a my::thread::properties entity as parameter which allows the necessary manipulations of the thread object.
  2. The function to be started.

That is, something like

my::thread thread([](my::thread::properties& properties) {
    [lots of code manipulating the properties]
    },
    persistent_function_1);

This way, it remains possible to manipulate the thread but you can't possible start() a thread twice.

0
Passer By On

There is no current mechanism for detecting an expression that appears twice. But you can torture the compiler to get something close

namespace
{
    template<int>
    struct once
    {
        once() {}
        friend void redefine() {}
    };
}

#define ONCE(expr) (once<__COUNTER__>{}, (expr))

If ONCE ever appear twice in the same TU, the compiler will complain about redefining redefine.

ONCE(thread.start(persistent_function_1));  // ok
ONCE(thread.start(persistent_function_2));  // error