Понимание оптимизированной реализации declval

Глядя на исходный код libstdc++, я нашел следующую declval реализацию:

template<typename _Tp, typename _Up = _Tp&&>
_Up __declval(int);  // (1)

template<typename _Tp>
_Tp __declval(long); // (2)

template<typename _Tp>
auto declval() noexcept -> decltype(__declval<_Tp>(0));

Эта реализация была предложена Эриком Ниблером в качестве оптимизации времени компиляции: он объясняет, что перегрузка разрешение быстрее, чем создание экземпляра шаблона.

Однако я не могу понять, как это работает. Конкретно:

  1. В (1), почему использование _Up лучше, чем просто возврат _Tp&& ?
  2. Кажется, что перегрузка (2) никогда не используется. Зачем это нужно?

Как все это мешает инстанцированию шаблонов, в отличие от самой наивной реализации:

template<typename T>
T&& declval() noexcept;

person Igor R.    schedule 19.05.2019    source источник
comment
1. llvm.org/ viewvc/llvm-project/libcxx/trunk/include/   -  person Language Lawyer    schedule 20.05.2019
comment
@Language Lawyer отлично, поэтому реализация libc++ не использует этот второй параметр. Еще интересно, какая мотивация стояла за этим.   -  person Igor R.    schedule 20.05.2019
comment
Я подозреваю, что ответ будет таким же, как и ответ на вопрос о том, почему использовался завершающий тип возврата: Это просто стилистика. Но лучше спросить у Эрика, чтобы быть уверенным на 100%.   -  person Language Lawyer    schedule 21.05.2019


Ответы (1)


Наивная реализация не совсем корректна. В соответствии со Стандартом declval определяется как ([declval]):

template <class T> add_rvalue_reference_t<T> declval() noexcept;

а для add_rvalue_reference<T> Стандарт гласит ([meta.trans.ref]):

Если T называет ссылочный тип, то член typedef type называет T&&; в противном случае type называет T.

Примером нессылаемого типа является void. В этом случае будет использоваться вторая перегрузка благодаря SFINAE.

Что касается первого вопроса, то особых причин не вижу. _Tp&& должно работать нормально.

person Evg    schedule 19.05.2019