Почему реализация declval в libstdC++ - v3 выглядит такой сложной?

Код ниже взят из libstdC++-v3 std::type_traits, который является реализацией std::declval:

  template<typename _Tp, typename _Up = _Tp&&> // template 1
    _Up
    __declval(int);
  template<typename _Tp> // template 2
    _Tp
    __declval(long);
  template<typename _Tp> // template 3
    auto declval() noexcept -> decltype(__declval<_Tp>(0));

Но я думаю, что смогу реализовать declval так просто:

template <typename T> T declval();

Вот мой тестовый код:

#include <iostream>
using namespace std;

struct C {
    C() = delete;
    int foo() { return 0; }
};

namespace test {
template <typename T> T declval();
};// namespace test

int main() {
    decltype(test::declval<C>().foo()) n = 1;
    cout << n << endl;
}

Команды сборки и запуска:

g++ -std=c++11 ./test.cpp
./a.out
  1. Почему реализация в libstdC++-v3 выглядит такой сложной?
  2. Что делает шаблон 1 в первом фрагменте?
  3. Почему __declval нужен параметр (int/long)?
  4. Почему шаблон 1 (int) и шаблон 2 (long) имеют разные типы параметров?
  5. Есть ли проблемы с моей простой реализацией?

2 ответа

Решение

std::declval на самом деле:

template<class T>
typename std::add_rvalue_reference<T>::type declval() noexcept;

где std::add_rvalue_reference<T> обычно T&&, за исключением случаев, когда это недопустимо (например, если T = void или же T = int() const), где это просто T. Основное отличие состоит в том, что функции не могут возвращать массивы, но могут возвращать ссылки на массивы, например U(&&)[] или же U(&&)[N].

Проблема с явным использованием std::add_rvalue_referenceв том, что он создает экземпляр шаблона. И это само по себе создает около 10 шаблонов на глубине создания ~4 в реализации libstdC++. В общем коде std::declvalможно много использовать, и, согласно https://llvm.org/bugs/show_bug.cgi?id=27798, время компиляции увеличивается на>4%, если не использовать std::add_rvalue_reference. (Реализация libC++ создает меньше шаблонов, но все равно оказывает влияние)

Это исправляется вставкой символа "add_rvalue_reference"прямо в declval. Это делается с помощью SFINAE.


Тип возврата для declval<T> является decltype(__declval<_Tp>(0)). Глядя вверх __declval, найдено два шаблона функций.

Первый имеет тип возврата _Up = T&&. У второго просто есть возвращаемый тип T.

Первый принимает параметр int, а второй long. Это проходит 0, что является int, поэтому первая функция лучше подходит и выбрана, и T&& возвращается.

Кроме случаев, когда T&& не является допустимым типом (например, T = void), тогда, когда аргумент шаблона _Up заменяется выведенным T&&, произошла ошибка замены. Таким образом, он больше не является кандидатом на эту функцию. Это означает, что остался только второй, а 0 преобразуется в длинное (а возвращаемый тип просто T).

В случаях, когда T и T&& не может быть возвращено из функции (например, T = int() const), ни одна из функций не может быть выбрана, и std::declval<T> функция имеет ошибку замены и не является жизнеспособным кандидатом.


Вот коммит libC++, представляющий оптимизацию: https://github.com/llvm/llvm-project/commit/ae7619a8a358667ea6ade5050512d0a27c03f432

И вот коммит libstdC++: https://gcc.gnu.org/git/?p=gcc.git;a=commitdiff;h=ec26ff5a012428ed864b679c7c171e2e7d917f76

Они оба были раньше std::add_rvalue_reference<T>::type

Это нужно для того, чтобы поймать типы, на которые невозможно сформировать ссылки. В частности, void.

Обычно intвыбрана перегрузка. Если _Tp является void, то int перегрузка не удастся _Up = void&&, а затем long выбрана перегрузка.

Ваша реализация не добавляет ссылок, что не работает с массивами и функциями.

test::declval<void()>() // fails
Другие вопросы по тегам