Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Boost MPL have integral constants?

Since you can take integral values as template parameters and perform arithmetic on them, what's the motivation behind boost::mpl::int_<> and other integral constants? Does this motivation still apply in C++11?

like image 323
Brent Avatar asked Jan 17 '13 22:01

Brent


2 Answers

You can take integral values as template parameters, but you cannot take both types and non-type template parameters with a single template. Long story short, treating non-type template parameters as types allows for them to be used with a myriad of things within MPL.

For instance, consider a metafunction find that works with types and looks for an equal type within a sequence. If you wished to use it with non-type template parameters you would need to reimplement new algorithms 'overloads', a find_c for which you have to manually specify the type of the integral value. Now imagine you want it to work with mixed integral types as the rest of the language does, or that you want to mix types and non-types, you get an explosion of 'overloads' that also happen to be harder to use as you have to specify the type of each non-type parameter everywhere.

This motivation does still apply in C++11.

This motivation will still apply to C++y and any other version, unless we have some new rule that allows conversion from non-type template parameters to type template parameters. For instance, whenever you use 5 and the template requests a type instantiate it with std::integral_constant< int, 5 > instead.

like image 163
K-ballo Avatar answered Sep 22 '22 16:09

K-ballo


tldr; Encoding a value as a type allows it to be used in far more places than a simple value. You can overload on types, you can't overload on values.

K-Ballo's answer is great.

There's something else I think is relevant though. The integral constant types aren't only useful as template parameters, they can be useful as function arguments and function return types (using the C++11 types in my examples, but the same argument applies to the Boost ones that predate them):

template<typename R, typename... Args>
  std::integral_constant<std::size_t, sizeof...(Args)> 
  arity(R (*)(Args...))
  { return {}; }

This function takes a function pointer and returns a type telling you the number of arguments the function takes. Before we had constexpr functions there was no way to call a function in a constant expression, so to ask questions like "how many arguments does this function type take?" you'd need to return a type, and extract the integer value from it.

Even with constexpr in the language (which means the function above could just return sizeof...(Args); and that integer value would be usable at compile time) there are still good uses for integral constant types, e.g. tag dispatching:

template<typename T>
  void frobnicate(T&& t)
  {
    frob_impl(std::forward<T>(t), std::is_copy_constructible<T>{});
  }

This frob_impl function can be overloaded based on the integer_constant<bool, b> type passed as its second argument:

template<typename T>
  void frob_impl(T&& t, std::true_type)
  {
    // do something
  }

template<typename T>
  void frob_impl(T&& t, std::false_type)
  {
    // do something else
  }

You could try doing something similar by making the boolean a template parameter:

frob_impl<std::is_copy_constructible<T>::value>(std::forward<T>(t));

but it's not possible to partially specialize a function template, so you couldn't make frob_impl<true, T> and frob_impl<false, T> do different things. Overloading on the type of the boolean constant allows you to easily do different things based on the value of the "is copy constructible" trait, and that is still very useful in C++11.

Another place where the constants are useful is for implementing traits using SFINAE. In C++03 the conventional approach was to have overloaded functions that return two types with different sizes (e.g an int and a struct containing two ints) and test the "value" with sizeof. In C++11 the functions can return true_type and false_type which is far more expressive, e.g. a trait that tests "does this type have a member called foo?" can make the function indicating a positive result return true_type and make the function indicating a negative result return false_type, what could be more clear than that?

As a standard library implementor I make very frequent use of true_type and false_type, because a lot of compile-time "questions" have true/false answers, but when I want to test something that can have more than two different results I will use other specializations of integral_constant.

like image 35
Jonathan Wakely Avatar answered Sep 18 '22 16:09

Jonathan Wakely