Search code examples
c++templatesmetaprogrammingcompile-timestatic-assert

Is there a way to static_assert a variable reference given in a template parameter?


struct Config 
{
    int version = 1; 
};

template<Config& config /* , ... */>
struct Peripheral
{
    const Config config_ = config;
    static_assert(config_.version > 1, "Config version must be greater than 1");
    /* ... */
};

Config myConfig;

int main()
{
    myConfig.version = 5;
    Peripheral<myConfig> peripheral;
}

I want to check at compile-time if the configurations given to my template are correct.

So I am trying to cast my reference to a constant instance in order to try to use it in my static_assert, but I get the error: 'invalid use of non-static data member ...'

Is there a way to check the values of a non-type parameter at compile-time in this case? Or do you have other suggestions to achieve this goal?


Solution

  • If you want the value of myConfig to be used at compile-time, then you should mark it constexpr and give it its value directly in the initializer. Whether it is a static or automatic storage duration variable is then secondary:

    constexpr Config myConfig = { .version = 5 };
    // alternatively before C++20 for example
    // constexpr Config myConfig = { 5 };
    /*...*/
    Peripheral<myConfig> peripheral;
    

    Then the template should take the parameter by-value, not by-reference, and you should use that template parameter directly in the static_assert:

    template<Config config /* , ... */>
    struct Peripheral
    {
        static_assert(config.version > 1, "Config version must be greater than 1");
        /* ... */
    };
    

    Depending on what else is in Config or if you are not using C++20 or later, the type might not be allowed as by-value template parameter. In that case you can keep using a reference parameter (although I am not sure that this is good design) but you would need to make it const Config& instead to match the const implied by constexpr. In this case it does matter that myConfig has static storage duration (specifically it may be declared at namespace scope, as a static data member or since C++17 as static local variable).

    If you want to keep on using a local config_ copy to assert on, that copy should also be marked constexpr (const is not enough to make a variable usable at compile-time) and hence must also be marked static (because non-static data members cannot be declared constexpr).

    The value of the template parameter and static_assert cannot be (potentially) determined at run-time, so myConfig.version = 5; will never work.