What is the purpose of std::common_type?
std::common_type
was introduced for use with std::duration
--- if you add a std::duration<int>
and a std::duration<short>
then the result should be std::duration<int>
. Rather than specifying an endless stream of allowed pairings, the decision was made to delegate to a separate template which found the result using the core language rules applicable to the ?:
arithmetic-if operator.
People then saw that this template might be generally useful, and it was added as std::common_type
, and extended to handle an arbitrary number of types. In the C++0x library it is only used for pairs of types though.
You should be able to use the new SFINAE rules to detect whether or not some instantiation of std::common_type
is valid. I haven't tried though. In most cases if there isn't a "common type" then there isn't anything meaningful you can do anyway, so a compile error is reasonable.
std::common_type
is not magic --- it follows the rules of ?:
. If true?a:b
will compile, std::common_type<decltype(a),decltype(b)>::type
will give you the type of the result.
Here are a few use cases of std::common_type
:
1. sum of variadic pack
Here is a version for variadic sum that needs common_type
:
template<typename... T>
constexpr auto sum(T&&... values) {
std::common_type_t<T...> sum {}; // <= here we need std::common_type
// see code in the link above for the machinery of the below code
static_for<sizeof...(T)>([&](auto index) {
sum += get<index>(values...);
});
return sum;
}
Above example is using machinery from this and this SO posts.
A note: you can achieve the same with the following code without the need for common_type
:
template<typename T>
auto sum(T&& t) {
return t;
}
template<typename T, typename... Ts>
auto sum(T&& t, Ts&&... ts) {
return t + sum(std::forward<Ts>(ts)...);
}
2. requiring variadic pack to have a common type
Code below is based on this SO post.
template <typename AlwaysVoid, typename... Ts>
struct has_common_type_impl : std::false_type {};
template <typename... Ts>
struct has_common_type_impl<std::void_t<std::common_type_t<Ts...>>, Ts...>
: std::true_type {};
template <typename... Ts>
concept has_common_type =
sizeof...(Ts) < 2 ||
has_common_type_impl<void, Ts...>::value;
template<typename... Ts> requires has_common_type<Ts...>
void foo(Ts&&... ts) {}
3. make_array from variadic pack
There was a pending proposal for the function make_array. For a discussion if there still a need for make_array see this SO post.
A simple implementation of make_array
would look like this:
template<typename... T>
constexpr auto make_array(T&&... values) requires has_common_type<T...> {
using TYPE = std::common_type_t<std::decay_t<T>...>;
return std::array<TYPE, sizeof...(T)>{static_cast<TYPE>(values)...};
}
with the following usage examples:
constexpr auto arr1 = make_array(1, 2, 3);
constexpr auto arr2 = make_array(1, 2.5, 3);
using namespace std::string_literals;
auto arr3 = make_array("hello"s, "world");
Note that the proposal for make_array
had an option to provide the actual requested type, but in case it is not provided then the common_type
is to be used.