I have a question about compile time functions. I understand that static_assert should work only with types, that can be evaluated/computed at compile time. So it does not work with std::string (yet, no support in gcc10 for constexpr std::string) but will work with std::array(when I know size at compile time). I am watching C++ Weekly from Jason Turner, so this snippet is from this episode https://www.youtube.com/watch?v=INn3xa4pMfg.

The code is here: https://godbolt.org/z/e3WPTP

#include <array>
#include <algorithm>

template<typename Key, typename Value, std::size_t Size>
struct Map final
{
    std::array<std::pair<Key, Value>, Size> _data;

    [[nodiscard]] constexpr Value getMappedKey(const Key& aKey) const
    {
        const auto mapIterator = std::ranges::find_if(_data, [&aKey](const auto& pair){ return pair.first == aKey;});

        if(mapIterator != _data.end())
        {
            return mapIterator->second;
        }
        else
        {
            throw std::out_of_range("Key is not in the map");
        }
    }
};

enum class OurEnum
{
    OUR_VALUE,
    OUR_VALUE2,
    OUR_VALUE3
};

enum class TheirEnum
{
    THEIR_VALUE,
    THEIR_VALUE2,
    THEIR_VALUE3
};


// This Fails non constant variable of course
/*
    Map<OurEnum, TheirEnum, 2> enumsConverter =
    {
        {
            {{OurEnum::OUR_VALUE, TheirEnum::THEIR_VALUE},
            {OurEnum::OUR_VALUE2, TheirEnum::THEIR_VALUE2}}
        }
    };
*/

// This fails, it is const, but this does not guarentee that it will be created in compile time
/*
    const Map<OurEnum, TheirEnum, 2> enumsConverter =
    {
        {
            {{OurEnum::OUR_VALUE, TheirEnum::THEIR_VALUE},
            {OurEnum::OUR_VALUE2, TheirEnum::THEIR_VALUE2}}
        }
    };
*/

// This works
/*
    constexpr Map<OurEnum, TheirEnum, 2> enumsConverter =
    {
        {
            {{OurEnum::OUR_VALUE, TheirEnum::THEIR_VALUE},
            {OurEnum::OUR_VALUE2, TheirEnum::THEIR_VALUE2}}
        }
    };
*/

//How come this does not work? Oh i see, missing const because constinit does not apply constness
/*
    constinit Map<OurEnum, TheirEnum, 2> enumsConverter =
    {
        {
            {{OurEnum::OUR_VALUE, TheirEnum::THEIR_VALUE},
            {OurEnum::OUR_VALUE2, TheirEnum::THEIR_VALUE2}}
        }
    };
*/

// Okay, I added const specifier but still this makes static_assert fail because of non-constant condition
// Why?

    constinit const Map<OurEnum, TheirEnum, 2> enumsConverter =
    {
        {
            {{OurEnum::OUR_VALUE, TheirEnum::THEIR_VALUE},
            {OurEnum::OUR_VALUE2, TheirEnum::THEIR_VALUE2}}
        }
    };


int main() 
{
    static_assert(enumsConverter.getMappedKey(OurEnum::OUR_VALUE) == TheirEnum::THEIR_VALUE);
}

I was playing with this sample and find out, that the static_assert does not work with constinit const initialized map. I commented out every possibility and I would like to explain them.

  1. Map is initialized as non-const variable. I understand that this will not work, it is not constant variable nor compile time initialized
  2. Map is initialized as const variable. This will not work either, even though the variable is constant, it is not guaranteed that it will be created at compile time.
  3. Map is initialized as constexpr variable. This guarantee that the variable will be initialized at compile time. Also it implies constness, so we have compile time const variable. This works correctly. (https://en.cppreference.com/w/cpp/language/constexpr)
  4. Map is initialized as constinit varibale. Now, constinit guarantees, that the expression is zero-intiialized or constant initialized. I initialize with constant so according this variable should be known at compile time (Sets the initial values of the static variables to a compile-time constant. - https://en.cppreference.com/w/cpp/language/constant_initialization) But it does not implies constness, so we have compile-time non-const variable, this static_assert can not work.
  5. Map is initialized as constinit const variable. Now we have compile-time constant variable but static_assert refuses to work. static_assert needs contextually converted constant expression of type bool (https://en.cppreference.com/w/cpp/language/static_assert) which is A converted constant expression of type T is an expression implicitly converted to type T, where the converted expression is a constant expression. Why this does not work?

According to cppInsights, the generated code by compiler is same as for constinit const and constexpr.

3

There are 3 answers

2
Nicol Bolas On BEST ANSWER

You are trying to address things from the wrong perspective. You see a variable declared as constinit const. You think that, because the object is non-modifiable, and because it is initialized by a constant expression, that this means that the object is a constant expression.

It's not.

Is its value knowable by the compiler? Absolutely. But that's not how "constant expression" is defined.

Something is a constant expression because the standard says that it is. A variable declared constinit is not a constant expression because the rules don't say that it is. A variable declared const is not a constant expression because the rules don't say that it is (except for certain cases of integers, which predate constexpr). And there's no special rule for the use of both of these markers.

A variable is a constant expression if it is declared constexpr (or one of those const integer exceptions). And only constant expressions can appear in static_assert.

That's the rule.

And there's no reason to have a special case of using constinit const because if you wanted a constant expression... you could have just written constexpr. After all, you might be in some template code where someone gave you a T that just so happens to be const, and you create a constinit T variable somewhere. You didn't ask it to be a constant expression; you just wanted a variable that was statically initialized. That the type just so happened to be const is just happenstance.

Now sure, the compiler is free to do all kinds of special optimizations with this knowledge. But this isn't about what the compiler is allowed to do; it's about what the language means. And if you wanted special meaning from that declaration, you should have said it correctly.

2
Barry On

Map is initialized as constinit const variable. Now we have compile-time constant variable but static_assert refuses to work

The second claim does not follow from the first. We do not have a compile-time constant variable, therefore the static_assert does not work.

constinit does not make your variable a constexpr variable, it only guarantees that you have constant initialization (hence the name constinit). Indeed, constinit does not even imply const:

constinit std::mutex m;

is a valid and motivating use for constinit, and still allows me to lock and unlock m.

The only way to have a constexpr variable is to declare your variable constexpr (with an unfortunate legacy carve out for integral types declared const, which doesn't apply here). If you want to have a constexpr map, you need to declare your map constexpr.

0
cigien On

Map is initialized as constinit const variable. ... but static_assert refuses to work. ... Why this does not work?

As you have observed, static_assert needs the expression to be known at compile time.

However, const only implies logical constness, and does not imply that the value is known at compile time (ignoring the case of const integral values initialized with constant expressions).

Similarly, constinit only implies static initialization; again, this does not imply that the value is known at compile time.

From the phrasing of your question, I guess you are expecting that:

const + constinit --> constexpr

But that's not the case. If you want the Map to be usable at compile time (i.e. inside a static_assert, you'll need to make it constexpr).