ISO/ IEC JTC1/SC22/WG21 p0015r0

Document number: P0015
Date: 2015-08-13
Project: Programming Language C++, Library Evolution Working Group
Reply-to: David Stone <david at doublewise dot net>

A specialization-friendly std::common_type


1. Introduction / Motivation

The default behavior for std::common_type<T, U> is, generally speaking, to try to convert type T to type U and to try to convert type U to type T. If one of these is an unambiguous best match, that is selected as the result type.

std::common_type is a customization point for the standard library for types in which this is not the correct behavior. Users are allowed to provide a specialization if at least one of the types is a user-defined type. This is done by std::chrono::duration and std::chrono::time_point. For the std::chrono types, the correct result type is a third type to which both types can be converted.

The result type has std::decay applied at the very end of the computation for the one- and two-argument versions. However, this means that to pick up user-defined specializations, the caller of std::common_type already has to call std::decay on the types that they pass in. The alternative is for the user-defined specializations to include all specialization combinations for const and volatile qualifiers, as well as lvalue, rvalue, and no reference qualifiers. This adds to up to 144 specializations to ensure that common_type<MyType, MyOtherType> always gives the correct result.

This proposal extends N3843: A SFINAE-Friendly std::common_type, to also be specialization-friendly to ensure users do not have to do this extra work.


2. Discussion

Following the lead of N3843, we will primarily be looking at example code that does what we need. Compared with N3843, the following has changed: common_type has a new definition and ct no longer needs to call decay_t.

	template<typename T, typename U>
	using ct2 = decltype(declval<bool>() ? declval<T>() : declval<U>());

	template<typename T>
	using void_t = conditional_t<true,void,T>;

	template<typename, typename...>
	struct ct { };

	template<typename T>
	struct ct<void, T> { using type = decay_t<T>; };

	template<typename T, typename U, typename... V>
	struct ct<void_t<ct2<T,U>>, T, U, V...> : ct<void, ct2<T,U>, V...> {};

	template<typename... T>
	struct common_type : conditional_t<
		(is_same<decay_t<T>, T>::value and ...),
		ct<void, T...>,
		common_type<decay_t<T>...>
	> {};


2.1. Recursive common_type

The new definition of common_type says that if the decayed version of the arguments are the same as the non-decayed version, the implementation is as specified in N3843. Otherwise, we call common_type again with the arguments all decayed. This prevents infinite recursion and makes sure to pick up user-defined specializations. The conditional uses fold expressions, as outlined in N4295, but can be trivially implemented with a variadic function similar to std::all.


2.2. Removing decay_t from the implementation of ct

We only ever call ct with decayed arguments, so it no longer needs to call decay in the single argument version. This maintains the resolution to LWG defect 2141.


3. Proposed wording


3.1. Update a note

Change the entry in row common_type, column "Comments" of Table 57 - Other transformations. It contains a note that currently reads

[Note: Such specializations are needed only when explicit conversions are
desired between two consecutive template arguments. — end note]

Change it to read instead

[Note: Such specializations are needed only when explicit conversions are
desired between two consecutive template arguments or the result type of two
consecutive template arguments is a third distinct type that both arguments can
convert to. — end note]


3.2. Specification

Replace paragraph 3 of subclause [meta.trans.other] (20.10.7.6) with the following mostly-code specification. (This wording follows the presentation style of this trait’s C++11 and C++14 versions.)

3 For the common_type trait, the member type shall be either defined or not present according to
the following specification in terms of the exposition-only templates ct2, void_t, and ct:

template<typename T, typename U>
using ct2 = decltype(declval<bool>() ? declval<T>() : declval<U>());

template<typename T>
using void_t = conditional_t<true,void,T>;

template<typename, typename...>
struct ct { };

template<typename T>
struct ct<void, T> { using type = decay_t<T>; };

template<typename T, typename U, typename... V>
struct ct<void_t<ct2<T,U>>, T, U, V...> : ct<void, ct2<T,U>, V...> {};

template<typename... T>
struct common_type : conditional_t<
	(is_same<decay_t<T>, T>::value and ...),
	ct<void, T...>,
	common_type<decay_t<T>...>
> {};


3.3 Feature testing macro

For the purposes of SG10, we recommend the macro name __cpp_lib_common_type_simple_specializations.

4. Bibliography

[N3694] Clark Nelson: "Feature-testing recommendations for C++" http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3694.htm
[N3843] Walter E. Brown: "A SFINAE-Friendly std::common_type" http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3843.pdf
[N4295] Andrew Sutton, Richard Smith: "Folding expressions" https://isocpp.org/files/papers/n4295.html
[LWG defect 2141] Doug Gregor: "common_type trait produces reference types" http://www.open-std.org/JTC1/SC22/WG21/docs/lwg-defects.html#2141