Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

declval<_Xp(&)()>()() - what does this mean in the below context?

This is from: https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/include/std/type_traits

  template<typename _Xp, typename _Yp>
    using __cond_res
      = decltype(false ? declval<_Xp(&)()>()() : declval<_Yp(&)()>()());
...
  template<typename _Tp1, typename _Tp2>
    struct __common_reference_impl<_Tp1, _Tp2, 3,
                   void_t<__cond_res<_Tp1, _Tp2>>>
    { using type = __cond_res<_Tp1, _Tp2>; };

I'm trying to figure out what _Xp(&)() is - is it a function call signature? i.e. a constructor? Doesn't really make sense. It seems there is a anonymous variable name there, i.e.:

_Xp(&anon)()

I still can't wrap my head around it somehow and I've been coding C++ for the last 34 years.

Any explanation is appreciated. Thanks.

like image 588
David Bien Avatar asked Sep 10 '25 16:09

David Bien


1 Answers

tl;dr; We need a way to produce an expression with type and value category T, not type and value category T&&, so we can't just use std::declval<T>() and instead need to do something else.


The point of this:

  template<typename _Xp, typename _Yp>
    using __cond_res
      = decltype(false ? declval<_Xp(&)()>()() : declval<_Yp(&)()>()());

is to give you the type of false ? x : y where x is an expression of type and value category _Xp and y is an expression of type and value category _Yp.

The conditional operator (usually called the ternary operator), ?:, is an extremely complicated language feature. It's one of the places in the language where there is actually a differentiation between prvalues and xvalues.

The naive way to implement this would be:

  template<typename _Xp, typename _Yp>
    using __cond_res
      = decltype(false ? declval<_Xp>() : declval<_Yp>());

Because, well, isn't that what declval<T>() is for, to give you a T? But actually, there's a flaw here, because declval isn't specified as:

template <typename T>
auto declval() -> T;

It's specified as (add_rvalue_reference_t<T> rather than T&& to correctly handle void):

template <typename T>
auto declval() -> std::add_rvalue_reference_t<T>;

As a result, __cond_res<int, int> and __cond_res<int&&, int&&> would be indistinguishable, even though the first needs to be int while the latter needs to be int&&.


So, we need a way to actually produce an arbitrary expression of type T. One way would to just actually:

template <typename T>
auto better_declval() -> T;

template<typename _Xp, typename _Yp>
  using __cond_res
    = decltype(false ? better_declval<_Xp>() : better_declval<_Yp>());

This works.

An alternative is to produce an instance of a function that gives you T and then invoke it. That's what declval<_Xp(&)()>()() does - gives you a reference to a nullary function that returns a _Xp, and then invokes it, giving you an _Xp (of the correct value category).

In this case, this seems like unnecessary complexity compared to the better_declval approach, but it turns out that this pattern is useful in other contexts as well. Like concepts:

template <typename T>
concept something = requires (T(&obj)()){
    f(obj());
};

Here, I have a concept that checks to see if I can call f with an expression of type T, including differentiating between prvalues and xvalues correctly. The above is the most convenient way I know of to achieve that goal. Which is, admittedly, unfortunate.

You could also do:

template <typename T>
concept something = requires {
    f(better_declval<T>());
};

It just depends on your perspective I guess, and how many times you need to use obj.

Once you've seen this T(&)() pattern used in the concept context, it's a familiar pattern, so it makes sense to just use it consistently.

like image 190
Barry Avatar answered Sep 13 '25 07:09

Barry