Should Std::Common_Type Use Std::Decay

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 a A&&

  • common_type is specified via declval, 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 yields int&&, which is unexpected

  • proposed 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 yields int

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 that std::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 the decay<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_wrappers.

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



Leave a reply



Submit