3

Hi I am looking at one of std::optional implementations here at here and I have found this code snippet confuses me:

// workaround: std utility functions aren't constexpr yet
template <class T> inline constexpr T&& constexpr_forward(typename 
std::remove_reference<T>::type& t) noexcept
{
    return static_cast<T&&>(t);
}

So I don't understand this in the following way:

  1. What does typename do here? Just to declare the following part is a type?
  2. Why do we need std::remove_reference here? Didn't we add the reference back in the type& part?
  3. What does "std utility functions are not constexpr yet" mean? How does this function make them constexpr? The body of it is just a static_cast.
  4. This function is used in a number of constructors and it looks like this: template <class... Args> constexpr storage_t( Args&&... args ) : value_(constexpr_forward<Args>(args)...) {}, so what does it do here to args?

Thanks a lot.

Bob Fang
  • 6,963
  • 10
  • 39
  • 72

2 Answers2

5
template <class T> inline constexpr T&& constexpr_forward(typename 
std::remove_reference<T>::type& t) noexcept
{
    return static_cast<T&&>(t);
}
  1. What does typename do here? Just to declare the following part is a type?

std::remove_reference<T>::type is a dependent type which depends on the template parameter T, hence we need typename to tell the compiler we are trying to use a dependent-name,

  1. Why do we need std::remove_reference here? Didn't we add the reference back in the type& part?

If you check the example usage of this utility function as in here

....
template <class... Args> explicit constexpr constexpr_optional_base(in_place_t, Args&&... args)
      : init_(true), storage_(constexpr_forward<Args>(args)...) {}
...

You can see, a variadic fowarding reference type is used as the explicit template argument to constexpr_foward<Args>(args).... This will preserve the value category of the type. When any of the argument is a reference, it will be as if we called that utility function with constexpr_forward<Arg01&>(arg01). And the instatiation of that template will be

inline constexpr Arg01&&& constexpr_forward(Arg01& t) noexcept
{
    return static_cast<Arg01&&&>(t);
}

and by reference collapsing rule, we have

inline constexpr Arg01& constexpr_forward(Arg01& t) noexcept
{
    return static_cast<Arg01&>(t);
}

Actually, the remove reference should be superfluous there (read about reference collapsing);

  1. What does "std utility functions are not constexpr yet" mean? How does this function make them constexpr? The body of it is just a static_cast.

It does nothing more than forwarding a non-constexpr function in constexpr functions and constructor.

  1. This function is used in a number of constructors and it looks like this: template <class... Args> constexpr storage_t( Args&&... args ) : value_(constexpr_forward<Args>(args)...) {}, so what does it do here to args?

Basically as explained above..

WhiZTiM
  • 21,207
  • 4
  • 43
  • 68
2

You may have to go back a step and do some more reading, this code is reasonably advanced and your questions indicate that you haven't read up on some of this stuff. I would recommend Modern Effective C++, by Scott Meyers. As to your specific questions:

  1. Yes, it is necessary to use typename, because we're dealing with a member of a template class, which is a dependent type. Think of it as assuring the compiler that this thing is really a type, at a point before the compiler can verify it (because the template hasn't been instantiated, only defined, so it doesn't know what T is).
  2. Unlike most template functions, the idea with std::forward is to explicitly specify the template parameter to make it work correctly. This form disables type deduction which is desirable in this context (rare). Notice that removing a reference and adding it back isn't the same as doing nothing, as if it's not a reference to start with, it still ends up as one. That's the goal; to always take an lvalue reference.
  3. It simply means they are not marked constexpr. A function can be used in constant expressions, if it is marked constexpr. Not all functions can be marked constexpr, there are restrictions. The standard library function std::forward can be constexpr, but simply isn't. This function is just a workaround for that.
  4. This is basically equivalent to std::forward, so it would be good to google about that and read about it, as it's not a trivial thing to grok (and the book I mentioned talks about it). Basically the idea is that once you have a function accepting a set of arguments, suppose it wants to forward those arguments elsewhere. The thing is that all symbol names (in this case, args) are considered lvalues (even though they are rvalue references... as I said, it's confusing, type vs value category). However, if the user passed in any temporaries then we want it to be forwarded correctly (i.e. as a temporary) to the next function. std::forward ensures that things passed in as rvalues, get cast back into rvalue references and can be used correctly by the inner function.

I hope that helps. Again, you will need to spend some time reading to gain a deeper understanding of value categories, and rvalue references to fully digest it.

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72