31

I started looking at std::common_type and am not exactly sure about its purpose and its functionality. A few things still strike me as odd:

  • Order of arguments is important: common_type<Foo, Bar, Baz> might be different from common_type<Baz, Foo, Bar>. Either might compile, the other might not. While this is clear from the way common_type is defined, it feels strange and unintuitive. Is this for lack of a universal solution or intended?
  • Instantiation can result in a compiler error instead of something I can handle. How to check if common_type will actually compile? is_convertible is not enough as common_type might be specialized?
  • There is still no way to figure out the common type in a situation like this:

    struct Baz;
    struct Bar { int m; };
    struct Foo { int m; }; 
    struct Baz { Baz(const Bar&); Baz(const Foo&); };
    

    The recommended solution would be to specialize common_type which is tedious. Is there a better solution?

For reference see §20.9.7.6 Table 57 in N3242.

Paul Rooney
  • 20,879
  • 9
  • 40
  • 61
pmr
  • 58,701
  • 10
  • 113
  • 156

2 Answers2

40

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.

Anthony Williams
  • 66,628
  • 14
  • 133
  • 155
6

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.

Amir Kirsh
  • 12,564
  • 41
  • 74