Search code examples
c++c++20type-traits

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.


Solution

  • 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.