﻿# An article for those who, like me, do not understand the purpose of std::common\_type

This article investigates why the standard library needs a way to deduce a common type, how it is implemented and how it works\.

![0892_common_type/image1.png](https://import.viva64.com/docx/blog/0892_common_type/image1.png)

To begin with, I'd like to thank my teammate Phillip\. He helped me figure out some things in the C\+\+ standard that I found ambiguous\. He also helped me refine my code examples\.

## Ways std::common\_type was useful for us

It all started when the PVS\-Studio team set out to sift through and majorly enhance the C\+\+ analyzer's core\. Currently, one of the big tasks is to implement a new type system\. Right now, our type system consists of strings encoded in a specific way\. We want to replace this system with a hierarchical one\. I won't go into too much detail about the new type system\. Loosely put, we are trying to turn this:

![0892_common_type/image2.png](https://import.viva64.com/docx/blog/0892_common_type/image2.png)

into this:

![0892_common_type/image3.png](https://import.viva64.com/docx/blog/0892_common_type/image3.png)

If you want to know more about it, check out the [talk](https://www.youtube.com/watch?app=desktop&v=qwnJWPuLNwo) my teammate Yuri gave at the [itCppCon21](https://italiancpp.github.io/) conference\. There he discussed our old and new type systems in great detail \- and showed funny pictures\. By now, I think, he's assembled enough material for two or three new talks\. So, we can all start looking forward to them :\)

The new type system uses analogs of [_type\_traits_](https://en.cppreference.com/w/cpp/header/type_traits)\. These custom traits, same as their predecessors, help modify types and get the necessary information on types\.

Just recently I wrote an [_std::common\_type_](https://timsong-cpp.github.io/cppwp/n4868/meta.trans.other) implementation for our type system\. The original std::common\_type trait is often used in metaprogramming to process an arbitrary number of types passed \- and deduce a common type for them\. We found our custom trait helpful when we needed to deduce a resulting type \- for example, when we come across an arithmetic operation in a binary expression:

```cpp
if (operationInfo->m_type == OperatorType::Arithmetic)
{
  auto leftType  = TypeTraits::ExtractMemberType
                   (result->GetLeftOperand().GetType());
  auto rightType = TypeTraits::ExtractMemberType
                   (result->GetRightOperand().GetType());
  auto resType = Types::Traits::CommonType(leftType, rightType);
  ....
}
```

Before, this operation required much more code\. Now the code looks elegant\.

## Why C\+\+ developers need std::common\_type at all

Suppose we want to write a naive implementation of a function template in order to calculate two vectors' dot product\. These vectors can be instantiated with various types passed to them\. The dot product type must be deduced automatically\. In C\+\+14 and later, one of the ways to implement such a function template is as follows:

```cpp
#include <vector>

template <typename T, typename U>
auto dot_product(const std::vector<T> &a, const std::vector<U> &b)
{
  // some bounds checks

  ??? result {};

  auto a_it = a.begin();
  auto b_it = b.begin();
  
  while (a_it != a.end())
  {
    result += static_cast<???>(*a_it++) * static_cast<???>(*b_it++);
  }

  return result;
}
```

The scenario assumes that the function receives vectors of the same size\. Otherwise, calculating the dot product is impossible and will produce an array out\-of\-bounds error\.

So, the function does exactly what we intended it to do\. The compiler deduces for us the resulting type from the _return statement_\. Only one problem remains \- we somehow need to deduce the common type for the _result_ variable\.

However, before writing any code, let's study one very interesting language construct \- the ternary operator\. Maybe it can help us with this task\.

## Conditional operator

Since the standard describes the ternary operator in [great detail](https://timsong-cpp.github.io/cppwp/n4868/expr.cond), covering the operator's every aspect here seems excessive\. So, I'll focus on the most common cases that involve type deduction\.

To help you understand the scenarios and results better, I'll use the following to help me present them:

* A variadic template of the _tp_ class, with no definition: enables you to use the compilation error to find out the resulting type\.
* Clang AST: shows the program's abstract syntax tree\.

Alright, let's get our hands dirty and look at some scenarios\.

### Case 1

If the second and third operands are both of type _void_, then the result is also of type _void_\. This is possible if both expressions contain, for example, _throw_, or calls to functions that return _void_, or explicit conversion to the _void_ type\. Below is some code that demonstrates this, with [messages the compiler prints](https://godbolt.org/z/jTeT8EG1K):

```cpp
void foo();
void bar();

int foobar();
float barfoo();

template <typename ...>
struct tp;              // type printer

void examples(bool flag)
{
  tp<decltype(flag ? foo() : bar()),                     // void
     decltype(flag ? (void) foobar() : (void) barfoo()), // void
     decltype(flag ? throw 0 : throw 3.14)> _;           // void
}
```

If the second or third operand is a _throw_ expression, then the resulting type is deduced from the other operand\. In this case, the other operand must be of some type other than _void_\. The code below demonstrates this scenario, with [messages the compiler prints](https://godbolt.org/z/n9GsrGf8K):

```cpp
char arr[16];

template <typename ...>
struct tp;              // type printer

void examples(bool flag)
{
  tp<decltype(flag ? nullptr : throw "abs"), // nullptr_t
     decltype(flag ? 3.14 : throw 3.14),     // double
     decltype(flag ? arr : throw 3.14)> _;   // char (&)[16]
}
```

### Case 2

If operand two and three are of different types and one of them is of a class type, the compiler chooses an overload that produces operands of the same types\. For example, the compiler may choose a [converting constructor](https://timsong-cpp.github.io/cppwp/n4868/class.conv) or an [implicit conversion operator](https://timsong-cpp.github.io/cppwp/n4868/class.conv)\. This is shown in the code below, with [printed compiler messages](https://godbolt.org/z/5ozqafszT):

```cpp
template <typename ...>
struct tp;              // type printer

struct IntWrapper
{
  IntWrapper(int) 
  {
    // ....
  }
};

void examples(bool flag)
{
  tp<decltype(flag ? IntWrapper {42} : 42)> _;
}
```

If you take a look at the AST that Clang built for this code, you can notice the following:

```cpp
....

-FunctionDecl <line:9:1, line:12:1> line:9:6 foo 'IntWrapper (bool)'
 |-ParmVarDecl <col:10, col:15> col:15 used b 'bool'
 `-CompoundStmt <line:10:1, line:12:1>
   `-ReturnStmt <line:11:3, col:34>
     `-ConditionalOperator <col:10, col:34> 'IntWrapper'
       |-ImplicitCastExpr <col:10> 'bool' <LValueToRValue>
       | `-DeclRefExpr <col:10> 'bool' lvalue ParmVar 0x558edcfc99d8 'b' 'bool'
       |-CXXTemporaryObjectExpr <col:14, col:30> 'IntWrapper' 'void (int)' list
       | `-IntegerLiteral <col:27> 'int' 42
       `-ImplicitCastExpr <col:34> 'IntWrapper' <ConstructorConversion>   // <=
         `-CXXConstructExpr <col:34> 'IntWrapper' 'void (int)'
          `-IntegerLiteral <col:34> 'int' 42                              // <=
```

Here Clang implicitly calls a converting constructor for the third operand, and consequently, both operands become of the same type \- _IntWrapper_\.

### Case 3

This scenario involves the second and third operands with [standard conversions](https://timsong-cpp.github.io/cppwp/n4868/conv) applied: [lvalue\-to\-rvalue](https://timsong-cpp.github.io/cppwp/n4868/conv.lval), [array\-to\-pointer](https://timsong-cpp.github.io/cppwp/n4868/conv.array), or [function\-to\-pointer](https://timsong-cpp.github.io/cppwp/n4868/conv.func)\. After the conversions are executed, several situations are possible\.

If the second and third operands are of the same type, the resulting type will be the same\. The codebelow demonstrates this, with [messages the compiler prints](https://godbolt.org/z/4z38sPccT):

```cpp
template <typename ...>
struct tp;              // type printer

struct MyClass
{
  // ....
};

void examples(bool flag)
{
  tp<decltype(flag ? MyClass {} : MyClass {})> _;
}
```

The second and third operands can also have an arithmetic type or an enumeration type\. For arithmetic and enumeration types, the [usual arithmetic conversions](https://timsong-cpp.github.io/cppwp/n4868/expr.arith.conv) form the common type\. This common type is the resulting type\. The code below demonstrates this, with [printed compiler messages](https://godbolt.org/z/zMKK39rKT):

```cpp
template <typename ...>
struct tp;              // type printer

void examples(bool flag)
{
  char ch                = 1;
  short sh               = 2;
  double d               = 3;
  float f                = 4;
  unsigned long long ull = 5;
  long double ld         = 6;
  
  tp<decltype(flag ? ch :  sh),
     decltype(flag ? f :  d),
     decltype(flag ? ull : ld) > _;
}
```

Note that one or both operands can be of type pointer or of type pointer\-to\-member\. In this case a [composite pointer type](https://timsong-cpp.github.io/cppwp/n4868/expr.type) is formed and it becomes the resulting type\. The following rules are used to form it: [pointer conversions](https://timsong-cpp.github.io/cppwp/n4868/conv.ptr)/[pointer\-to\-member conversions](https://timsong-cpp.github.io/cppwp/n4868/conv.mem), [function pointer conversions](https://timsong-cpp.github.io/cppwp/n4868/conv.fctptr) and [qualification conversions](https://timsong-cpp.github.io/cppwp/n4868/conv.qual)\. This is what it looks like, with [printed compiler messages](https://godbolt.org/z/3e35YcTcf):

```cpp
template <typename ...>
struct tp;              // type printer

struct MyBaseClass
{
  // ....
};

struct MyClass : MyBaseClass
{
  // ....
};

void examples(bool flag)
{
  auto a = new MyClass();
  auto b = new MyBaseClass();
  tp<decltype(flag ? a : b)> _;
}
```

Also, both operands can be of type [_std::nullptr\_t_](https://timsong-cpp.github.io/cppwp/n4868/lex.nullptr)\. Or one operand can be of type _std::nullptr\_t_, and the other one is _nullptr_\. Then the resulting type is _std::nullptr\_t_\. This is what the code looks like, with [printed compiler messages:](https://godbolt.org/z/qfsKrqGce)

```cpp
#include <cstddef>

template <typename ...>
struct tp;              // type printer

void examples(bool flag)
{ 
  tp<decltype(flag ? std::nullptr_t {} : nullptr )> _;
}
```

Now we can see that deducing a common type is very easy \- and in most cases the ternary operator can help\. Well, enough theory\. Let's use the principles described above and write some code that deduces a common type\!

P\.S\. In order to write a custom _std::common\_type_ trait implementation for our new type system \(_TypeTraits::CommonType_\), we needed to use all common type deduction rules described above, and some that we haven't mentioned\.

## Writing a custom common\_type

Let's get back to our function that calculates a dot product of vectors\. Starting with C\+\+11, we can use the [_decltype_](https://en.cppreference.com/w/cpp/language/decltype) specifier that takes an expression and returns this expression's type\. We've already used this specifier earlier \- when we worked with _type\_printer_\. From the previous paragraph we know, that if decltype receives a ternary operator call with objects of two types, the compiler deduces the common type\.

Let's try it:

```cpp
#include <vector>

template <typename T, typename U>
auto dot_product(const std::vector<T> &a, const std::vector<U> &b)
{
  // ....
  decltype(true ? std::declval<T>() : std::declval<U>()) result {};
  // ....
}
```

Let's take a closer look at what this code does:

1. The _decltype_ specifier deduces the type of the expression in the parentheses\.
1. Inside _decltype_, we use the ternary operator\. Any expression can be the first operand, for example, _true_\.
1. Then the passed template types are substituted for the second and third operands\. There is only one problem \- the ternary operator operates expressions\. So let's create objects through [_std::declval_](https://en.cppreference.com/w/cpp/utility/declval)\.

_std::declval<T\>_ is a function template with **no implementation**\. This template returns an _rvalue_\-link to type _T_\. When _T\=void_, the expression returns the _void_ type\. This template is often used in compile\-time context \(_decltype_, _sizeof_, _requires_, \.\.\.\.\) and allows working with an object of the passed type and avoid the constructor call\. This is especially useful if the _T_ type does not have a default public constructor or if this constructor has been removed\.

Note that as a type, you may get references\. In this case [_std::decay_](https://en.cppreference.com/w/cpp/types/decay) comes in handy\. It removes CV\-qualifiers and references\. It adds pointers for functions \(_function\-to\-pointer conversion_\) and converts arrays to pointers \(_array\-to\-pointer conversion_\):

```cpp
#include <vector>

template <typename T, typename U>
auto dot_product(const std::vector<T> &a, const std::vector<U> &b)
{
  // ....
  std::decay_t<
        decltype( true ? std::declval<typename std::decay<T>::type>()
                       : std::declval<typename std::decay<U>::type>()
        )
  > result {};
  // ....
}
```

Agree \- most people wouldn't want to write this in their code\. Let's try to refactor the code a little\. To do this, we'll need to write a couple of helper class templates for convenience\. First, let's try to write a class that deduces a common type for two types passed:

```cpp
template <class T, class U>
struct common_type
{
  using type = std::decay_t<
      decltype( true ? std::declval< std::decay_t<T> >()
                     : std::declval< std::decay_t<U> >() ) >;
};
```

Now we can use this _common\_type_ in our code:

```cpp
#include <vector>

template <typename T, typename U>
auto dot_product(const std::vector<T> &a, const std::vector<U> &b)
{
  // ....
  common_type<T, U>::type result {};
  // ....
}
```

Excellent, we got rid of all this scary bunch of code and made the code easy to read\. Now it's time to teach _common\_type_ to work with any number of types passed \- from zero to n\. Let's slightly change our basic class template and its specializations:

```cpp
#include <type_traits>

template <typename ...>
struct common_type; // (1)

template <typename ...Ts>
using common_type_t = typename common_type<Ts...>::type;

template <>
struct common_type<> // (2)
{
};

template <class T>
struct common_type<T> // (3)
{
  using type = std::decay_t<T>;
};

template <class T, class U>
struct common_type<T, U>         // (4)
{
  using type = std::decay_t<
      decltype( true ? std::declval< std::decay_t<T> >()
                     : std::declval< std::decay_t<U> >() ) >;
};

template <class T, class U, class... V>
struct common_type<T, U, V...>         // (5)
{
  using type =  typename common_type
               <typename common_type<T,U>::type, V...>::type;
};
```

It's worth mentioning that _common\_type_ is implemented in the standard library in a similar way\. Now let's examine the code above and see what happens there: 

1. The primary variadic class template is declared\.
1. For an empty list of template arguments, we declare an explicit template specialization that does not contain anything\.
1. For one template argument, we declare a partial template specialization that contains this type after the [_std::decay_](https://en.cppreference.com/w/cpp/types/decay) trait is performed\. This trait removes CV\-qualifiers, links, decays functions into pointers \(_function\-to\-pointer conversion_\), and converts arrays to pointers \(_array\-to\-pointer conversion_\)\.
1. For two template arguments, we declare a partial specialization that infers the resulting type based on the type inference rules of the conditional operator, applying the _std::decay_ trait to the passed arguments beforehand\. 
1. For three or more template arguments, we declare a partial specialization that first retrieves the common type for the first two arguments\. It uses the specialization for 2 types to do this\. Then it instantiates itself recursively, passing the common type for the first pair of types and the rest of the template parameter pack as template arguments\. Overall, _common\_type<a, b, c, d\>_ is equivalent to _common\_type<common\_type<common\_type<a, b\>, c\>, d\>_\. See an example on [C\+\+ Insights](https://cppinsights.io/lnk?code=I2luY2x1ZGUgPHR5cGVfdHJhaXRzPgoKdGVtcGxhdGUgPHR5cGVuYW1lIC4uLj4Kc3RydWN0IGNvbW1vbl90eXBlOyAvLyAoMSkKCnRlbXBsYXRlIDx0eXBlbmFtZSAuLi5Ucz4KdXNpbmcgY29tbW9uX3R5cGVfdCA9IHR5cGVuYW1lIGNvbW1vbl90eXBlPFRzLi4uPjo6dHlwZTsKCnRlbXBsYXRlIDw+CnN0cnVjdCBjb21tb25fdHlwZTw+IC8vICgyKQp7Cn07Cgp0ZW1wbGF0ZSA8Y2xhc3MgVD4Kc3RydWN0IGNvbW1vbl90eXBlPFQ+IC8vICgzKQp7CiAgdXNpbmcgdHlwZSA9IHN0ZDo6ZGVjYXlfdDxUPjsKfTsKCnRlbXBsYXRlIDxjbGFzcyBULCBjbGFzcyBVPgpzdHJ1Y3QgY29tbW9uX3R5cGU8VCwgVT4gICAgICAgICAvLyAoNCkKewogIHVzaW5nIHR5cGUgPSBzdGQ6OmRlY2F5X3Q8CiAgICAgIGRlY2x0eXBlKCB0cnVlID8gc3RkOjpkZWNsdmFsPCBzdGQ6OmRlY2F5X3Q8VD4gPigpCiAgICAgICAgICAgICAgICAgICAgIDogc3RkOjpkZWNsdmFsPCBzdGQ6OmRlY2F5X3Q8VT4gPigpICkgPjsKfTsKCnRlbXBsYXRlIDxjbGFzcyBULCBjbGFzcyBVLCBjbGFzcy4uLiBWPgpzdHJ1Y3QgY29tbW9uX3R5cGU8VCwgVSwgVi4uLj4gICAgICAgICAvLyAoNSkKewogIHVzaW5nIHR5cGUgPSB0eXBlbmFtZSBjb21tb25fdHlwZTx0eXBlbmFtZSBjb21tb25fdHlwZTxULFU+Ojp0eXBlLCBWLi4uPjo6dHlwZTsKfTsKCnRlbXBsYXRlCnN0cnVjdCBjb21tb25fdHlwZTxjaGFyLCBzaG9ydCwgaW50Pjs=&insightsOptions=cpp2a&std=cpp2a&rev=1.0)\.

As I said above, the complete information about the ternary operator's type inference is available in the standard\. I used the latest up\-to\-date working draft\. You can find this information in chapter [7\.6\.16](https://eel.is/c++draft/expr.cond)\. The drafts themselves are available, for example, [here\.](http://www.open-std.org/jtc1/sc22/wg21/docs/standards) You can also use documentation from [cppreference](https://en.cppreference.com/w/)\.

## Conclusion

In this article I've reviewed how _std::common\_type_ works\. For a better understanding, we read the standard and wrote the trait's implementation \- we even discussed the ternary operator's logic\. I hope you find this article useful\. Thank you for reading\!