Can someone present a simple example of practical use of this feature? An example that explain its usage difference from using the actual values?
It is mostly to be used in generic (i.e template) code.
One practical usage is to transform runtime value to compile time value:
Given:
enum class E { A, B /*..*/ };
template <E e> void foo() {/*..*/}
template <E e> void bar() {/*..*/}
instead of
void foo(E e) {
switch (e) {
case E::A: return foo<E::A>();
case E::B: return foo<E::B>();
//..
}
std::unreachable();
}
void bar(E e) {
switch (e) {
case E::A: return bar<E::A>();
case E::B: return bar<E::B>();
//..
}
std::unreachable();
}
you might do:
std::variant<std::integral_constant<E, E::A>,
std::integral_constant<E, E::B>
// ..
>
to_integral_constant_var(E e) {
switch (e) {
case E::A: return std::integral_constant<E, E::A>();
case E::B: return std::integral_constant<E, E::B>();
//..
}
std::unreachable();
}
void foo(E e) { std::visit([](auto I){ foo<I>()}, to_integral_constant_var(e)); }
void bar(E e) { std::visit([](auto I){ bar<I>()}, to_integral_constant_var(e)); }
Another usage case is the possibility to overload instead of specialization:
So instead of
template <int I, int J>
void foo() { std::cout << "generic" << I << ", " << J << std::endl; }
// No partial specialization of method possible
// template <int J>
// void foo<42, J>() { std::cout << "partial special 42, " << J << std::endl; }
template <>
void foo<42, 42>() { std::cout << "full special 42 42\n"; }
you might have
// alias to shorten type
template <int N> using IntC = std::integral_constant<int, N>;
template <int I, int J>
void foo(IntC<I>, IntC<J>) { std::cout << "generic" << I << ", " << J << std::endl; }
template <int J>
void foo(IntC<42>, IntC<J>) { std::cout << "partial special 42, " << J << std::endl; }
void foo(IntC<42>, IntC<42>) { std::cout << "full special 42 42\n"; }
But I cannot understand why should I use those structs instead of the actual contained values, nor if I can actually access the value type
One case I see when you cannot use the value directly is with variadic template, as you cannot mix types and values, but the wrapping value in type allow to have that mix
template <typename T>
void foo(std::type_identity<T>)
{
std::cout << std::source_location::current().function_name() << std::endl;
}
template <typename T, T Value>
void foo(std::type_identity<std::integral_constant<T, Value>>)
{
std::cout << Value << std::endl;
}
template <typename... Ts>
void foos()
{
(foo(std::type_identity<Ts>{}), ...);
}
Demo
std::integral_constant<int, 42>is a type and42is a value. It's many times easier to deal with a type then a value in you template code. Additionally it hasvalue_typeandtypeand con convert to a conpile time constant of the value when needed.