Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding the architecture of type traits

I would like to phrase this question the best possible way, based on my current understanding of type traits.

My understanding is that all template classes in type traits inherit from std::integral_constant, which wraps the representation of both the value that the template class has been instantiated with, and its corresponding type. Further, the common template classes in type traits inherit from the helper alias template of std::integral_constant, viz., std::bool_constant, as evinced by their definitions.

For instance, in the case of std::is_arithmetic, the type of value inherited from std::integral_constant is bool, which implies that the template class inherits from std::bool_constant. Further, the template class std::is_arithmetic also inherits the operator bool from its underlying base, which is supposed to convert the object to bool and (possibly return that as the result in value).

So my question is: how does the whole semantics tie in together?

In the case of an instance of std::is_arithmetic<2>, if one were to assume that the base is std::integral_constant<typename T, T v>, T becomes int and and v becomes 2. However, since most of the common template classes in type traits evidently inherit from std::bool_constant, how does the base class instantiation happen in the first place? And what is the underlying logic for (possible) conversion of value to true or false, as returned by the inherited operator bool?

like image 678
Vinod Avatar asked Jul 24 '19 05:07

Vinod


1 Answers

Here is a possible implementation of integral_constant:

template <typename T, T v>
struct integral_constant {
    static constexpr T value = v;

    using value_type = T;
    using type = integral_constant; // the current instantiation

    constexpr   operator T() const noexcept { return v; }
    constexpr T operator()() const noexcept { return v; }
};

So there are three ways to access the value of an integral_constant (or a derived class therefrom):

  • integral_constant<T, v>::value, where the static data member value is used;

  • integral_constant<T, v>{}(), where the operator() is called on an object of type integral_constant<T, v>; and

  • integral_constant<T, v>{} implicitly converted to bool.

bool_constant is just an alias template:

template <bool B>
using bool_constant = integral_constant<bool, B>;

using true_type = bool_constant<true>;
using false_type = bool_constant<false>;

Now let's consider an actual type trait. Take is_same for example:

template <typename T, typename U>
struct is_same :false_type {};
template <typename T>
struct is_same<T, T> :true_type {};

It derives from true_type (namely integral_constant<bool, true>) if the types are the same, or false_type (namely integral_constant<bool, false>) otherwise. So, you can use it in the three ways mentioned:

static_assert(is_same<int, int>::value); // use ::value member
static_assert(is_same<int, int>{}());    // use operator()
static_assert(is_same<int, int>{});      // use operator bool

You can also use ::type to extract the underlying integral_constant base class, and ::value_type to extract the type of the value:

static_assert(is_same<true_type, is_same<int, int>::type>{});
static_assert(is_same<bool, is_same<int, int>::value_type>{});

In C++17, we have another way to access the value: is_same_v<int, int>.

static_assert(is_same_v<int, int>);      // since C++17

This is no magic; is_same_v is simply a variable template that is defined to be the corresponding value:

template <typename T, typename U>
inline constexpr bool is_same_v = is_same<T, U>::value;
like image 199
L. F. Avatar answered Nov 15 '22 05:11

L. F.