3

I run into the following problem and idk if it can be solved in an elegant way:

  1. I need to statically initialize an interface wifi and sntp in main. I can't do it in global space because they are dependent on a task scheduler which is started only when main is called.
  2. The wifi API takes a callback which requires the sntp interface to be present to start it, thus capturing it by reference.
  3. The sntp service depends on the wifi service to be started as the wifi service will start up the network interface that sntp needs (I know this is bad design but I'm kind of fixed on this solution right now).

So I'm landing in cyclic dependency land once again. My initial thought to fight through this was to just forward declare the static variable but it turns out this doesn't work in the way that I thought, as declaring it "extern" conflicts with the later definition.

Here's the code:

Demo

#include <cstdio>
#include <functional>

struct wifi_config {
    std::function<void()> callback;
};

struct wifi {
    wifi(const wifi_config& cfg) {} 
};

struct sntp {
    sntp()  = default;

    auto start() -> void { printf("SNTP start!\n"); }
};

int main() {

    extern sntp mysntp;

    static wifi mywifi(wifi_config{
        .callback = [&]() -> void {
            mysntp.start();
        }
    });

    static sntp mysntp;
}

And here's the error:

<source>:28:17: error: 'sntp mysntp' conflicts with a previous declaration
   28 |     static sntp mysntp;
      |           

How do I get around this?

3
  • I may be missing something, but you already need the initialization of mysntp to happen before the callback is called (or calling start is definitely undefined). So why can't it be declared first, again? Commented Apr 11, 2023 at 14:05
  • 1
    In above example sntp doesn't depends of wifi, you don't have cycle. Commented Apr 11, 2023 at 14:11
  • There is no such thing like "Forward declaring a static variable". "Forward declarations" are for types (struct/class) and its templates. What exactly are you trying achieve from end user point of view? Commented Apr 11, 2023 at 17:09

3 Answers 3

2

It if it is extern you are saying that mysntp is defined in another compilation unit. By declaring it static you are declaring it and saying it is only available in this compilation unit (cpp file). Those are not compatible.

Sign up to request clarification or add additional context in comments.

5 Comments

not correct, extern can refer to same translation unit.
Nono that just Emits a weak symbol to the linker afaik. For example you can do this: godbolt.org/z/6dcYnY9rG
@glades it doesn't refer to function local static though.
@appleapple Unfortunately
@glades not exactly, you have order inside same function. globals are another thing.
1

The most painless way is to put them both as members of the same struct. The order the members are declared in the struct is the order that they are constructed in:

int main() {
    struct statics {
        wifi mywifi{
            wifi_config{
                .callback = [this]() -> void {
                    mysntp.start();
                }
            }
        };

        sntp mysntp;
    };
    static auto [ mywifi, mysntp ] = statics();

}

This has very slightly different semantics if one of the constructors were to throw, but this shouldn't be a problem in main().

Also, a static in main is kind of useless because they will be destroyed when main exits, very similarly to if they had been automatic variables. If you were to switch to automatic variables, you could just remove the static in static auto [ ....


To answer your question literally, it is possible to "forward declare" static variables (get a reference to them before they have been initialized):

int main() {
    // fake "forward declaration"
    constexpr auto get_mysntp = [](sntp init()){
        static sntp mysntp(init());
        return std::integral_constant<sntp*, &mysntp>{};
    };
    constexpr sntp& mysntp = *decltype(get_mysntp(nullptr))::value;

    static wifi mywifi(wifi_config{
        .callback = [&]() -> void {
            mysntp.start();
        }
    });

    // fake "definition" (actually construct the static variable)
    get_mysntp([]{ return sntp{}; });
}

Which may be useful if your example is more complicated. The struct of statics is probably enough.

3 Comments

"Also, a static in main is kind of useless because they will be destroyed when main exits" Are you sure about this? I thought the point of all statics is to be preserved across function calls.
@glades main is the first function to be called and the last function to be left, all statics are destroyed after it finishes
Ah forgot to explain. Yes in blank cpp, but I'm running this on an RTOS microprocessor which has a app_main() instead of main() which is invoked by main(). The statics remain valid throughout the program.
0

Does this work? (TBH, I'm not sure if it does, it depends when the callback is called.)

int main() {
    static sntp *mysntp_ptr;

    static wifi mywifi(wifi_config{
        .callback = [&]() -> void {
            mysntp_ptr->start();
        }
    });

    static sntp mysntp;
    mysntp_ptr = &mysntp;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.