Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ensure at compile time that a method is called in exactly one place

Tags:

c++

I am curious about whether it is possible to ensure at compile time that a method is called in exactly one place.

Note that it is OK if the function is called more than once (e.g. in a loop) - but it should not be called in two separate loops.

This can be broken into two parts, I am also interested in solutions that cover either part:
(a) ensure a method is called in at least one place
(b) ensure a method is called in at most one place

I have full control over the structure of the code, and different idioms that achieve the same idea are welcome.

// class.h

class MyClass {
  public:
    void my_method();
}

The following should not compile (never called)

#include "class.h"

int main() {
  MyClass my_class;
}

The following should not compile (called in more than one place)

#include "class.h"

int main() {
  MyClass my_class;
  my_class.my_method();
  while(true) {
    my_class.my_method();
  }
}

The following should compile (called in exactly one place):

#include "class.h"

int main() {
  MyClass my_class;
  while(true) {
    my_class.my_method();
  }
}
like image 773
yoyoy Avatar asked Mar 27 '20 03:03

yoyoy


People also ask

What is known at compile time?

In computer science, compile time (or compile-time) describes the time window during which a computer program is compiled. The term is used as an adjective to describe concepts related to the context of program compilation, as opposed to concepts related to the context of program execution (runtime).

What is used for a function to execute at compile time?

In computing, compile-time function execution (or compile time function evaluation, or general constant expressions) is the ability of a compiler, that would normally compile a function to machine code and execute it at run time, to execute the function at compile time.

Is sizeof determined at compile time?

sizeof is evaluated at compile time, but if the executable is moved to a machine where the compile time and runtime values would be different, the executable will not be valid.


2 Answers

Low Tech Approach:

Since you have control over the code structure (which includes the build system, I assume), here is a low tech solution:

  • make the function name sufficiently unique
  • grep for the function name in your code. You are expecting it twice (assuming that you declaration and definition are colocated):
    • Once in the header
    • Once at the single call site

Alternatively:

If you really, really, really want to solve it with C++, then you could try

  • Use a compile time counter to figure out the number of uses within a compilation units
  • Make sure that the function would violate ODR if the header is included in multiple compilation units.

However, compile time counters are black magic (says I, and I really like TMP), and forcing ODR violations for this purpose seems like similar voodoo (at least you would require a test case that fails to link).

But seriously:

Don't do this. Whatever you do, it can be perverted with almost no effort by a wrapper function:

auto call_my_method(MyClass& o)
{
   return o.my_method();
}

MyClass::my_method() is called only in the wrapper. Everybody else just calls the wrapper which is probably even inlined by the compiler.

As others suggested: It might be much more helpful if you would explain what you are trying to do.

like image 122
Rumburak Avatar answered Oct 13 '22 08:10

Rumburak


There is a partial solution to this question using the C preprocessor and GNU inline assembly:

Header file a.h:

struct A {
    // Do not call this method directly, use the macro below to call it
    int _method_vUcaJB5NKSD3upQ(int i, int j);
};

// Use inline assembly to ensure that this macro is used at most once
#define method_vUcaJB5NKSD3upQ(args...) \
    _method_vUcaJB5NKSD3upQ(args); \
    asm (".global once_vUcaJB5NKSD3upQ; once_vUcaJB5NKSD3upQ:");

Implementation file a.cc:

#include <iostream>
#include "a.h"

int A::_method_vUcaJB5NKSD3upQ(int i, int j) { return i+j+5; }

// Ensure that the macro is used at least once
extern "C" const char once_vUcaJB5NKSD3upQ;
static const char get_vUcaJB5NKSD3upQ = once_vUcaJB5NKSD3upQ;

int main() {
    A a;
    for(int i=0; i<7; i++) {
        // Use a separate statement to call the method
        // (terminated by a semicolon, it cannot be a sub-expression)
        auto x = a.method_vUcaJB5NKSD3upQ(2, 3);
        std::cout << x << std::endl;
    }
    return 0;
}

This solution is partial in the sense that it does not prevent the program to call the method beginning with the underscore directly without using the wrapper macro.

like image 22
atomsymbol Avatar answered Oct 13 '22 08:10

atomsymbol