What's the reason behind having std::integral_constant?

There are a few cases where std::integral_constant is very useful.

One of them is tag dispatch. For example, std::true_type and std::false_type are simply std::integral_constant<bool, true> and std::integral_constant<bool, false> respectively. Every type trait derives from std::true_type or std::false_type, which enables tag dispatch:

template <typename T>
int foo_impl(T value, std::true_type) {
    // Implementation for arithmetic values
}

template <typename T>
double foo_impl(T value, std::false_type) {
    // Implementation for non-arithmetic values
}

template <typename T>
auto foo(T value) {
    // Calls the correct implementation function, which return different types.
    // foo's return type is `int` if it calls the `std::true_type` overload
    // and `double` if it calls the `std::false_type` overload
    return foo_impl(value, std::is_arithmetic<T>{});
}

Also, template metaprogramming libraries typically only have algorithms on lists of types rather than lists of values. If you wanted to use those algorithms for values instead, you'd have to use something like std::integral_constant


2 is value, while two_t is a type. They are two different abstractions. Each has its purpose.

  • You can't use 2 where a type is expected.
  • You can't use two_t where an integral value is expected.

More importantly, std::true_type and std::false_type are the most widely used specializations of std::integral_constant. They are extensively used in type_traits.


Below code snippet is one way I used std::integral_constant to create an api which takes a generic value but it also checks at compile time that the value you are providing is valid or not.

#include<iostream>

struct Value {};
struct Color {};
struct Size {};
struct Point {};

enum class Property {
    Color,
    StrokeColor,
    Opacity,
    Size,
    Position,
};

class Dom {
public:
    // give a single api to setValue
    template<Property prop, typename AnyValue>
    void setValue(const std::string &obj, AnyValue value){
        setValue(std::integral_constant<Property, prop>{}, obj, value);
    }
private:
    // specialization for each property and value type pair.
    void setValue(std::integral_constant<Property, Property::Color> type,
                  const std::string &obj,
                  Color col) {std::cout<<" update color property\n";}
    void setValue(std::integral_constant<Property, Property::StrokeColor> type,
                  const std::string &obj,
                  Color col){std::cout<<" update stroke color property\n";}
    void setValue(std::integral_constant<Property, Property::Opacity> type,
                  const std::string &obj,
                  Value opacity){std::cout<<" update opacity property\n";}
};

int main()
{
    Dom domObj;
    // try to update the color property of rect1 object inside layer1
    domObj.setValue<Property::Color>("layer1.rect1", Color());

    // compile time error expects Color value but given Size value
    //domObj.setValue<Property::Color>("layer1.rect1", Size());
    return 0;
}

Tags:

C++

Std