should std::common_type use std::decay?
should std::common_type use std::decay?
Yes, see Library Working Group Defect #2141.
Short version (long version, see link above):
declval<A>()
returns aA&&
common_type
is specified viadeclval
, n3337:template <class T, class U>
struct common_type<T, U> {
typedef decltype(true ? declval<T>() : declval<U>()) type;
};common_type<int, int>::type
therefore yieldsint&&
, which is unexpectedproposed resolution is to add
decay
template <class T, class U>
struct common_type<T, U> {
typedef decay_t < decltype(true ? declval<T>() : declval<U>()) > type;
};common_type<int, int>::type
now yieldsint
Specializations of std::common_type - misunderstanding, bug, both, or neither?
This appears to be a bug in your libstdc++. Current versions of libc++ doesn't have the bug, as evidenced by godbolt.
3.3.1 does mandate that it decay the arguments and recurse; libc++ doesn't do this.
Why is `std::common_type_tstd::ostream &, std::ostream &` equal to `std::ostream` and not `std::ostream &`?
See this question for related discussion on the history around common_type
and why it doesn't actually yield a reference.
Is specializing
std::common_type
so thatstd::common_type_t<T, T>
is always T a valid approach?
I assume you mean specializing your implementation of common_type
(since you can't specialize the other one). And no, that isn't sufficient. You probably want common_type<Base&, Derived&>
to be Base&
, but that instantiation won't go through your specialization.
What you really want is to not use decay
. The reason the decay
is in there is to drop the surprise rvalue reference that declval
provides in some cases. But you want to maintain the lvalue reference - so just add your own type trait:
template <class T>
using common_decay_t = std::conditional_t<
std::is_lvalue_reference<T>::value,
T,
std::remove_reference_t<T>>;
And use that instead of the normal decay
.
What is std::decay and when it should be used?
<joke>It's obviously used to decay radioactive std::atomic
types into non-radioactive ones.</joke>
N2609 is the paper that proposed std::decay
. The paper explains:
Simply put,
decay<T>::type
is the identity type-transformation except
if T is an array type or a reference to a function type. In those
cases thedecay<T>::type
yields a pointer or a pointer to a function,
respectively.
The motivating example is C++03 std::make_pair
:
template <class T1, class T2>
inline pair<T1,T2> make_pair(T1 x, T2 y)
{
return pair<T1,T2>(x, y);
}
which accepted its parameters by value to make string literals work:
std::pair<std::string, int> p = make_pair("foo", 0);
If it accepted its parameters by reference, then T1
will be deduced as an array type, and then constructing a pair<T1, T2>
will be ill-formed.
But obviously this leads to significant inefficiencies. Hence the need for decay
, to apply the set of transformations that occurs when pass-by-value occurs, allowing you to get the efficiency of taking the parameters by reference, but still get the type transformations needed for your code to work with string literals, array types, function types and the like:
template <class T1, class T2>
inline pair< typename decay<T1>::type, typename decay<T2>::type >
make_pair(T1&& x, T2&& y)
{
return pair< typename decay<T1>::type,
typename decay<T2>::type >(std::forward<T1>(x),
std::forward<T2>(y));
}
Note: this is not the actual C++11 make_pair
implementation - the C++11 make_pair
also unwraps std::reference_wrapper
s.
Is there a c++ trait to find the most restricted type between two types in C++?
I am afraid that you need to roll your own. You can warp your types in a std::tuple, then pass it to std::common_type
, e.g.
#include <tuple>
#include <type_traits>
template <class T1, class T2>
struct common {
using type = typename std::tuple_element<0, typename std::common_type<std::tuple<T1>, std::tuple<T2>>::type>::type;
};
template <class T>
struct common<const T, T> {
using type = const T;
};
template <class T>
struct common<T, const T> {
using type = const T;
};
template <class T>
struct common<const T, const T> {
using type = const T;
};
int main()
{
static_assert(std::is_same<common<int, int>::type, int>::value, "");
static_assert(std::is_same<common<const int, int>::type, const int>::value, "");
static_assert(std::is_same<common<int, int &>::type, int>::value, "");
static_assert(std::is_same<common<int &, int &>::type, int &>::value, "");
static_assert(std::is_same<common<int &, int const &>::type, int const &>::value, "");
return 0;
}
But you have to create special cases for const
.
std::common_type with references to type_info
First, common_type_t<T1, T2>
is (roughly) std::decay_t<decltype(true? std::declval<T1>() : std::declval<T2>())>
. It decays the type - strip away referenceness, remove top-level cv-qualification, and does the array-to-pointer and function-to-pointer conversion.
So, common_type<const type_info&, const type_info&>::type
is type_info
. While func1
's declaration appears to work, you'll have serious problems writing its definition.
common_type_t<T1, T2, T3>
is common_type_t<common_type_t<T1, T2>, T3>
, so common_type<const type_info&, const type_info&, const type_info&>::type
is common_type<type_info, const type_info&>::type
.
That results in a mixed-value-category ternary expression, which by the rules in [expr.cond] will try to make a temporary type_info
out of the chosen operand - which doesn't work because type_info
's copy constructor is deleted.
In SFINAE-friendly implementations, that results in common_type<const type_info&, const type_info&, const type_info&>
having no member type
. If you use a non-SFINAE-friendly implementation, you'll get a hard error instead.
Related Topics
How to Run the Preprocessor on Local Headers Only
Building Boost with Visual Studio 2013 (Express)
Is Using Unsigned Integer Overflow Good Practice
Initializing a Ublas Vector from a C Array
How to Save Hicon to an .Ico File
Qt 5 and Qprocess Redirect Stdout with Signal/Slot Readyread
What am I Allowed to Do with a Static, Constexpr, In-Class Initialized Data Member
How to Handle a Transitive Dependency Conflict Using Git Submodules and Cmake
Why Is the Order of Evaluation for Function Parameters Unspecified in C++
How Could Comma Separated Initialization Such as in Eigen Be Possibly Implemented in C++
Qdialog Exec() and Getting Result Value