Понимание std:: вперед
Почему компилятор не может вывести параметр шаблона для std::forward
?
Я имею в виду:
#include <memory>
#include <iostream>
struct X{};
struct A{
A( const X& ) { std::cout << "cpy ctorn"; }
A( X&& ) { std::cout << "move ctorn"; }
};
X foo() { return {}; }
template<typename T,typename Arg>
T* factory( Arg&& a )
{
return new T(std::forward(a));
// ----------^^^^^^^^^^^^^^^ error: can't deduce template parameter
}
int main()
{
factory<A>(foo());
}
Я знаю, что это выбор дизайна (из-за std::remove_reference
в определении std::forward
), чтобы пользователь не забыл указать тип. Чего я не могу понять, так это: почему то, как это реализовано, работает, чтобы предотвратить дедукцию? Почему компилятор не просто выводит параметр шаблона forward
как Arg
.
3 ответа:
std::forward
объявляется примерно так:template< class T > T&& forward( typename std::remove_reference<T>::type& t );
typename std::remove_reference<T>::type
являетсянедедуцированным контекстом . Компилятор не может знать, какойT
должен быть выведен, потому что он не понимает семантической связи между типом членаtype
и заданнымT
. Он должен был бы искать во всех типах, чтобы найти совпадение и быть в состоянии как-то устранить неоднозначность столкновений. Это неразумно, поэтому стандарт не позволяет этого.
Причина, по которой вы должны указать тип для
forward
, заключается в том, что происходит сa
внутри функции:template<typename T,typename Arg> T* factory( Arg&& a ) { // 'a' is always an lvalue here
С
a
составляет всегда, в значение lvalue, у нас недостаточно информации вa
себя, чтобы иметь возможность определить, если он был принят в качестве lvalue-выражение или значение rvalue. Что информация доступна только через ТипArg
, который будет либоX
, либоX&
. Без этой дополнительной информации о типе невозможно узнать, нужно ли пересылатьa
сейчас или нет. как lvalue или rvalue... именно поэтому вам нужно предоставить его:return new T(std::forward<Arg>(a)); }
Из стандарта C++11:
14.8.2.5 выведение аргументов шаблона из типа
Недедуцированные контексты:
- вложенное-имя-описатель типа, который был определен, используя квалифицированный-идентификатор
- выражение спецификатора decltype.
- аргумент шаблона не-типа или массив, связанный в котором a подвыражение ссылается на параметр шаблона.
- параметр шаблона, используемый в параметре типа a функция параметр, который имеет аргумент по умолчанию, используемый в вызове для какого аргумента делается дедукция.
И т. д...
std::forward
объявляется так:
template<typename _Tp> constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept
Согласно первому предложению выше:
typename std::remove_reference<_Tp>::type
- не выводимый контекст.