21

What's the real use case of this?

std::integral_constant

I can understand this is a wrapper with value 2:

typedef std::integral_constant<int, 2> two_t

But why not just use 2 or define a const int value with 2?

0

3 Answers 3

21

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

4
  • Is it equivalent as adding an if-else check, besides the performance gain of generating the code at compiling time? Commented Mar 10, 2018 at 4:24
  • 3
    @WhatABeautifulWorld No. You could even do things like returning different types in the different implementations. That wouldn't compile with just an if. However, in C++17, we get if constexpr, which does work for this use case.
    – Justin
    Commented Mar 10, 2018 at 4:35
  • What if instead of std::true_type you took a bool and in instead of passing in std::is_arithmetic<T>{} you passed in std::is_arithmetic::value?
    – Zebrafish
    Commented Dec 23, 2020 at 1:22
  • @Zebrafish Then you'd always get a bool regardless of whether the trait is true or not, which means that you'd only be able to have the one overload. That doesn't allow for things such as different return types or implementations which only compile if true vs. false.
    – Justin
    Commented Dec 23, 2020 at 19:13
8

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.

6
  • Given that we can have non-type template parameters, what are the cases where we can use a type but not an integral value? If we had traits classes with members that are constant integral expressions, instead of members that are true_type or false_type, what would that preclude?
    – Maxpm
    Commented Mar 28, 2019 at 17:30
  • @Maxpm, I am a little confused by your question. Whether you can use a type or a value to instantiate a template depends on how a template is defined. I don't follow your train of thought regarding the second question.
    – R Sahu
    Commented Mar 28, 2019 at 17:35
  • It depends on how the template is defined, yes, but for metaprogramming purposes, we can define templates however we want. Why invent a type-based way of representing integral constants when we have a perfectly good value-based way already built in to the language? For my second question: as you said, true_type and false_type are extensively used in type traits - but how is that an improvement over something like this?
    – Maxpm
    Commented Mar 28, 2019 at 18:30
  • @Maxpm, thanks for clarifying your questions. The answers to those questions are not only long but philosophical too. This is not the right place to answer them.
    – R Sahu
    Commented Mar 28, 2019 at 18:35
  • Okay. “This is not the right place to answer them” meaning this comment thread, or this question, or Stack Overflow in general?
    – Maxpm
    Commented Mar 28, 2019 at 19:31
5

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;
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.