#c #c 11 #templates #decltype #declval
#c #c 11 #шаблоны #decltype #declval
Вопрос:
Приведенный ниже код взят из libstdc -v3 std::type_traits
, который является реализацией std::declval
:
template<typename _Tp, typename _Up = _Tpamp;amp;> // 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
- Почему реализация в libstdc -v3 выглядит такой сложной?
- Что делает шаблон 1 в первом фрагменте?
- Зачем
__declval
нужен параметр (int
/long
)? - Почему template 1 (
int
) и template 2 (long
) имеют разные типы параметров? - Есть ли какие-либо проблемы с моей простой реализацией?
Ответ №1:
std::declval
на самом деле:
template<class T>
typename std::add_rvalue_reference<T>::type declval() noexcept;
Где std::add_rvalue_reference<T>
обычно Tamp;amp;
, за исключением случаев, когда это недопустимо (например, if T = void
или T = int() const
), где это просто T
. Основное отличие состоит в том, что функции не могут возвращать массивы, но могут возвращать ссылки на массивы, такие как U(amp;amp;)[]
или U(amp;amp;)[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>
is decltype(__declval<_Tp>(0))
. При поиске обнаруживаются __declval
два шаблона функций.
Первый имеет возвращаемый тип _Up = Tamp;amp;
. Второй просто имеет возвращаемый тип T
.
Первый принимает параметр int
, а второй long
. Он передается 0
, что является an int
, поэтому первая функция лучше подходит, выбирается и Tamp;amp;
возвращается.
За исключением случаев, когда Tamp;amp;
недопустимый тип (например, T = void
), тогда, когда аргумент шаблона _Up
заменяется на выведенный Tamp;amp;
, происходит сбой замены. Так что это больше не кандидат на функцию. Это означает, что остается только второй, и 0
преобразуется в long (а возвращаемый тип — just T
).
В случаях, когда T
и Tamp;amp;
не может быть возвращен из функции (например, 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=committdiff;h=ec26ff5a012428ed864b679c7c171e2e7d917f76
Они оба были ранее std::add_rvalue_reference<T>::type
Комментарии:
1. Но… почему второй параметр шаблона с аргументом по умолчанию, а не
_Tpamp;amp;
?2. @JohannesSchaub-litb, похоже, не является какой-либо конкретной причиной. libstdc реализует его с
_Tpamp;amp;
возвращаемым типом. Остальные признаки типа в libc используют неназванныйbool = SFINAE_stuff
ortypename = SFINAE_stuff
, так что, может быть, для согласованности? В любом случае это не имеет значения
Ответ №2:
Это делается для перехвата типов, в которых ссылки не могут быть сформированы. В частности, void
.
Обычно int
выбирается перегрузка. Если _Tp
есть void
, int
перегрузка завершится ошибкой _Up = voidamp;amp;
, а затем long
выбирается перегрузка.
Ваша реализация не добавляет ссылки, что приводит к сбою с массивами и функциями.
test::declval<void()>() // fails