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.
- Map is initialized as non-const variable. I understand that this will not work, it is not constant variable nor compile time initialized
- 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.
- 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)
- 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.
- 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.
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 declaredconst
is not a constant expression because the rules don't say that it is (except for certain cases of integers, which predateconstexpr
). 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 thoseconst
integer exceptions). And only constant expressions can appear instatic_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 writtenconstexpr
. After all, you might be in some template code where someone gave you aT
that just so happens to beconst
, and you create aconstinit 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 beconst
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.