Reputation: 41
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.
According to cppInsights, the generated code by compiler is same as for constinit const and constexpr.
Upvotes: 0
Views: 404
Reputation: 473174
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.
Upvotes: 3
Reputation: 302643
Map is initialized as
constinit const
variable. Now we have compile-time constant variable butstatic_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
.
Upvotes: 7
Reputation: 60208
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
).
Upvotes: 3