2

I'm working on a small library, and one of the things I need to do is apply visitors to some data and return the results.

In some older C++ code, visitors were expected to declare a typedef return_type. For instance, boost::static_visitor does that.

In newer code, all those visitors are deprecated. In C++14 you can usually use decltype(auto), but I'm trying to do it using something like std::common_type so that I can do it in C++11.

I tried simply backporting an example implementation of std::common_type to C++11 and using that to figure out return types.

However I get some unexpected results when using the "possible implementation" on cppreference.com

#include <ostream>
#include <type_traits>

// decay_t backport

template <typename T>
using decay_t = typename std::decay<T>::type;

// common_type backport

template <typename T, typename... Ts>
struct common_type;

template <typename T>
struct common_type<T> {
  using type = decay_t<T>;
};

template <typename T1, typename T2>
struct common_type<T1, T2> {
  using type = decay_t<decltype(true ? std::declval<T1>() : std::declval<T2>())>;
};

// TODO: Is this needed?
/*
template <typename T>
struct common_type<T, T> {
  using type = T;
};
*/

template <typename T1, typename T2, typename T3, typename... Ts>
struct common_type<T1, T2, T3, Ts...> {
  using type = typename common_type<typename common_type<T1, T2>::type, T3, Ts...>::type;
};

template <typename T, typename... Ts>
using common_type_t = typename common_type<T, Ts...>::type;

// static_assert(std::is_same<common_type_t<std::ostream &, std::ostream &>, std::ostream &>::value, "This is what I expected!");
static_assert(std::is_same<common_type_t<std::ostream &, std::ostream &>, std::ostream>::value, "Hmm...");

int main() {}

What "should" the result of std::common_type_t<std::ostream&, std::ostream&> be? Should it not be std::ostream &? If not, then why do both gcc 5.4.0 and clang 3.8.0 think it is std::ostream?

Note: When I use the "real" std::common_type_t in C++14, I still get std::ostream and not std::ostream &.

Is specializing std::common_type so that std::common_type_t<T, T> is always T a valid approach? It seems to work well in my program, but it feels like a hack.

Chris Beck
  • 15,614
  • 4
  • 51
  • 87
  • 2
    Related: http://stackoverflow.com/q/21975812/2069064, but yeah `common_type` doesn't give you references because sad face. – Barry Aug 01 '16 at 17:44
  • Thank you... so I guess I should not use `std::declval` there? Should I use `decltype(true ? std::forward(std::declval()) : std::forward(std::declval()))` ? I guess this is actually pretty tricky... – Chris Beck Aug 01 '16 at 17:47

1 Answers1

2

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.

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • I guess I'm working with this version for now, marking your answer as correct though: `template using mini_decay_t = conditional_t< std::is_lvalue_reference::value, T, decay_t>; ` – Chris Beck Aug 01 '16 at 20:57