Reputation: 2890
I'm just into metaprogramming and was watching Cppcon channel on youtube, and saw this std::integral_constant
, but cannot find its use.
As far as i understood, it is a way to "pack" a value along with its type, it can be instantiated with
std::integral_constant<int, 42> i;
std::integral_constant<bool, true> b;
std::integral_constant<float, 3.14> f;
...
and each of those instances can be used just like the value they contains, i.e.: can be passed around, used in math operations, comparisons, etc.
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 (i.e.: int
, bool
and float
) at runtime to do stuff with it.
Can someone present a simple example of practical use of this feature? An example that explain its usage difference from using the actual values?
Upvotes: 22
Views: 3146
Reputation: 217235
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>{}), ...);
}
Upvotes: 1
Reputation: 73186
std::integral_constant
is mainly used as a utility type for writing meta-programming traits, particularly by encoding a type with a type and a value. By letting a custom trait inherit from specializations of std::integral_constant
we get easy, idiomatic access to a stored non-type template parameter through the static member constant value
, as well as e.g. the value type of this constant via the member typedef value_type
.
std::integral_constant
could be used for, say, writing a dimensionality trait for a matrix type
using index_t = int;
template <index_t M, index_t N, typename S> struct mat {
// matrix implementation
};
as
#include <type_traits>
// Default dimensionality 0.
template <class T, typename = void>
struct dimensionality : std::integral_constant<index_t, 0> {};
template <typename S>
struct dimensionality<S, std::enable_if_t<std::is_arithmetic_v<S>>>
: std::integral_constant<index_t, 1> {};
template <index_t M, index_t N, typename S>
struct dimensionality<mat<M, N, S>> : std::integral_constant<index_t, M * N> {};
template <class T>
inline constexpr index_t dimensionality_v = dimensionality<T>::value;
DEMO.
However, a more common use case is to use the two helper typedefs std::true_type
and std::false_type
for the common case where T
is bool
.
Type Definition ---- ---------- true_type std::integral_constant<bool, true> false_type std::integral_constant<bool, false>
E.g. as
#include <type_traits>
struct Foo {};
template <typename T>
struct is_foo : public std::false_type {};
template<>
struct is_foo<Foo> : public std::true_type {};
template<typename T>
constexpr bool is_foo_v = is_foo<T>::value;
static_assert(is_foo_v<Foo>, "");
static_assert(!is_foo_v<int>, "");
Upvotes: 15