На самом деле, обработка исключений имеет особые правила в отношении lvalues и rvalues. Объект временного исключения является lvalue, см. 15.1 / 3 текущего проекта:
Выражение throw инициализирует временный объект, называемый объектом исключения, тип которого определяется путем удаления любых cv-квалификаторов верхнего уровня из статического типа операнда throw и корректировки типа из «массива T». »Или« функция, возвращающая T »в« указатель на T »или« указатель на функцию, возвращающую T », соответственно. Временным является lvalue и используется для инициализации переменной, названной в соответствующем обработчике (15.3). Если тип объекта исключения будет неполным типом или указателем на неполный тип, отличный от (возможно, cv-квалифицированный) void, программа является некорректной. За исключением этих ограничений и ограничений на сопоставление типов, упомянутых в 15.3, операнд throw обрабатывается в точности как аргумент функции в вызове (5.2.2) или операнд оператора return.
И отлов по ссылке rvalue тоже незаконен, см. 15.3 / 1:
Объявление-исключение в обработчике описывает тип (ы) исключений, которые могут привести к вводу этого обработчика. Объявление-исключение не должно обозначать неполный тип или ссылочный тип rvalue . Объявление исключения не должно обозначать указатель или ссылку на неполный тип, кроме void *, const void *, volatile void * или const volatile void *.
Кроме того, вы, похоже, не понимаете идеальную пересылку. Ваш прямой вызов не лучше, чем ход. Идея идеальной пересылки состоит в том, чтобы закодировать категорию значения аргумента как часть типа и дать понять выводу аргумента шаблона. Но ваш обработчик исключений не является и не может быть шаблоном функции.
По сути, совершенная пересылка зависит от вывода аргументов шаблона и ссылок на значения:
void inner(const int&); // #1 takes only lvalues or const rvalues
void inner(int&&); // #2 takes non-const rvalues only
template<class T>
void outer(T && x) {
inner(forward<T>(x));
}
int main() {
int k = 23;
outer(k); // outer<T=int&> --> forward<int&> --> #1
outer(k+2); // outer<T=int> --> forward<int> --> #2
}
В зависимости от категории значения аргумента, вычитание аргумента шаблона выводит T как ссылку lvalue или тип нормального значения. Из-за свертывания ссылок T && также является ссылкой lvalue в первом случае или ссылкой rvalue во втором случае. Если вы видите, что T && и T - это параметр шаблона, который можно вывести, это, по сути, «поймать все». std :: forward восстанавливает исходную категорию значений (закодированную в T), поэтому мы можем идеально передать аргумент перегруженным внутренним функциям и выбрать правильную. Но это работает только потому, что external является шаблоном и потому что существуют специальные правила для определения T относительно его категории значений. Если вы используете ссылки на rvalue без вывода шаблонов / аргументов шаблона (как в # 2), функция будет принимать только значения rvalue.