2

I'm developing a C++ library, there are two functions: func1 and func2.

I want to generate an compile-time error if developer forgets to call func1 before calling func2:

This will be OK:

func1();
func2();  // OK

But this would fail:

func2();  // ERROR, you forget to call func1()

Of course, it's very easy to generate a runtime error but I would like to generate a compile-time error.

I've tried as below but it's not working because I can't modify a constexpr variable:

static constexpr bool b {false};

void func1() {
    b = true; // ERROR!
}

typename<std::enable_if_t<b == true>* = nullptr>
void func2() {}

I'm not very good at metaprogramming. I want to know if it's possible to generate a compile-time error in this case.

Yves
  • 11,597
  • 17
  • 83
  • 180
  • 7
    I don't understand why folks keep looking for language solutions to design smells. If the functions are so coupled, why are they separate? – StoryTeller - Unslander Monica Dec 07 '21 at 07:24
  • 2
    Look into how constexpr counters are made (e.g. [this one](https://stackoverflow.com/q/51601439/2752075)). The first function instantiates a template class with a friend function definition in it, and the second function checks if that friend function exists or not. – HolyBlackCat Dec 07 '21 at 07:26
  • @StoryTeller-UnslanderMonica In the real case, I'm developing a Singleton pattern, so there are two functions: `init` with parameters and `getInstance`. Well, I want to make sure that `init` must be called before `getInstance`. – Yves Dec 07 '21 at 07:27
  • @Yves - The language has constructors you know... They are *force invoked* before the object is used. Public `init` functions are an even worse smell, and I'm amazed by how many developers go by it despite the stench. – StoryTeller - Unslander Monica Dec 07 '21 at 07:28
  • 2
    Settle down, mate. The point StoryTeller is making here is that there are real ways to solve the problem using actual language constructs. The way you posed your question is XY problem. – paddy Dec 07 '21 at 07:31
  • 3
    No we don't, *dude*. Parameterized constructors and singletons are in use for decades already, despite insistence to the contrary by the uninitiated. – StoryTeller - Unslander Monica Dec 07 '21 at 07:32
  • Use Meyers' singleton for this. – Suthiro Dec 07 '21 at 07:32
  • @Suthiro Well, that would be a runtime error, instead of compile-time error. – Yves Dec 07 '21 at 07:52
  • @StoryTeller-UnslanderMonica So what. They have been used for decades already so I can't ask about how to generate a compile error? – Yves Dec 07 '21 at 07:54
  • @paddy Not at all. All of existing methods will make a runtime error, instead of compile-time error. And I have no idea why it makes the question XY problem. I can't simply ask this question regardless of Singleton pattern??? – Yves Dec 07 '21 at 07:56
  • @paddy XY problem means you are asking about X but you are actually asking about Y. For me, I ask: if it's possible to generate a compile-time error if I forget to call some function. That's it. If it's not possible, just tell me. – Yves Dec 07 '21 at 07:59
  • @HolyBlackCat Thanks man. The link helps a lot. I think a compile-time counter may help me. I'll try. – Yves Dec 07 '21 at 08:16
  • 1
    @paddy I can't accept that you are saying this is a XY problem. Obviously, C++ provides many ways to play with metaprogramming. For example, you can check if a constexpr variable equals to 1, if not, an compile-time error would be generated. So my question is reasonable. I mentioned the Singleton, but that's not what I really want to ask man. Because my question doesn't ONLY concern about the Singleton pattern. – Yves Dec 07 '21 at 08:37
  • 1
    Your very first comment says you specifically are motivated by singletons, but it doesn't matter as that's splitting hairs anyway. The same kinds of techniques can be applied to general case. "A before B" ordering can easily be encapsulated in a stateful design by making B only callable via some kind of result or other effect of A. This is an engineering choice. I would trust a well-engineered stateful design much more than I would trust that what you're trying to do isn't somehow the Halting Problem in disguise. – paddy Dec 07 '21 at 08:53
  • 3
    This is definitely an XY problem. You need `func1()` to be called at least once before `func2()` is called, and believe getting the compiler to diagnose such cases is the solution. The compiler cannot do such things, particularly if `func1()`, `func2()`, and the functions that call either of them are in different source files. Instead, you need to redesign `func2()` so it calls `func1()` and, if necessary, tracks the fact it has done so (which allows avoiding calling `func1()` again if that is desired). Even better, don't make `func1()` available to other code. – Peter Dec 07 '21 at 09:17
  • @Peter Thanks for your comment and I don't think this is a XY problem at all. This is obviously a common question, if it's possible, many places can use this technique. The Singleton pattern is only one case. – Yves Dec 07 '21 at 09:51
  • 1
    @Yves - You are leaving out a lot of details on the requirements (*exactly* one call to `func1`, or what?), but one option is to make `func2` a member of a class, where `func1` is the constructor. Noone can call the member function without first creating a class object. – BoP Dec 07 '21 at 10:38

2 Answers2

6

Make temporal coupling explicit:

  • make func1 return a type which should be consumed by func2:
struct func1_result {
// Possibly constructors private, and friendship to `func1`.
//...
};

func1_result func1();
void func2(const func1_result&);

So you cannot call func2 without creating first func1_result (returned by func1).

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • 1
    Problem with this is that there is nothing preventing another function being defined as `const func1_result &foo() {}`. Then `func2(foo())` will result in no diagnostic, even if `func1()` is never called. Even worse, `foo()` causes undefined behaviour for which no diagnostic is required - which, practically, often means the program will compile AND link without diagnostics. – Peter Dec 07 '21 at 10:13
  • @Peter: `const func1_result &foo() {}` is UB by itself, compilers warns about that construct with appropriate level though. *"C++ tries to guard against Murphy, not Machiavelli."* – Jarod42 Dec 07 '21 at 10:26
  • Both fair points. Could probably be settled by just making the result class have public copy/assignment semantics but private construction and be a friend of `func1`, then pass it by value to `func2`. The only way to obtain a value that can be copied is by calling the only thing that can create it. I use that approach when I need it. – paddy Dec 07 '21 at 10:32
5

It is not possible at least in corner cases. Look at the following code (headers and test for scanf return value omitted for brievety):

int main() {
    int code;
    printf("Enter code value: ");
    scanf("%d", &code);
    if (code > 0) func1();
    func2();               // has func1 be called ?
    return 0;
}

Here, even the best static analysis tool will be unable to state whether func1 is called before func2 because it depends on a value which will only be known at runtime. Ok, this is a rather stupid example, but real world programs often read their config data at run time and that config can change even part of their initialization.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • Hmm... I know what you meant. Now I'm thinking if a compile-time counter can help... – Yves Dec 07 '21 at 08:17