Я только что открыл для себя следующую технику. Он очень похож на синтаксис одной из предложенных концепций, отлично работает на Clang, GCC и MSVC.
template <typename T, typename = typename std::enable_if<std::is_rvalue_reference<T&&>::value>::type>
using require_rvalue = T&&;
template <typename T>
void foo(require_rvalue<T> val);
Я попытался найти его с помощью поисковых запросов типа «sfinae in type alias» и ничего не получил. Есть ли название для этой техники и позволяет ли это язык?
Полный пример:
#include <type_traits>
template <typename T, typename = typename std::enable_if<std::is_rvalue_reference<T&&>::value>::type>
using require_rvalue = T&&;
template <typename T>
void foo(require_rvalue<T>)
{
}
int main()
{
int i = 0;
const int ic = 0;
foo(i); // fail to compile, as desired
foo(ic); // fail to compile, as desired
foo(std::move(i)); // ok
foo(123); // ok
}





[...] does the language actually allows it?
Ничего не могу сказать о названии, но мне кажется, что это да.
Соответствующая формулировка - [temp.alias] / 2:
When a template-id refers to the specialization of an alias template, it is equivalent to the associated type obtained by substitution of its template-arguments for the template-parameters in the type-id of the alias template.
и правило sfinae, [temp.deduct] / 8:
Only invalid types and expressions in the immediate context of the function type, its template parameter types, and its explicit-specifier can result in a deduction failure.
Принятие аргумента типа require_rvalue<T> действительно ведет себя так, как если бы мы заменяли этот псевдоним, что либо дает нам T&&, либо ошибку замены - и эта ошибка замены, возможно, находится в непосредственном контексте † замены и поэтому является "дружественным к sfinae" в отличие от это серьезная ошибка. Обратите внимание, что даже несмотря на то, что аргумент типа по умолчанию не используется, в результате CWG 1558 (правило void_t) мы получили добавление [temp.alias] / 3:
However, if the template-id is dependent, subsequent template argument substitution still applies to the template-id.
Это гарантирует, что мы по-прежнему подставляем аргумент типа по умолчанию, чтобы вызвать требуемый сбой замены.
Вторая недосказанная часть вопроса заключается в том, действительно ли это может выступать в качестве ссылки пересылки. Правило есть в [temp.deduct.call] / 3:
A forwarding reference is an rvalue reference to a cv-unqualified template parameter that does not represent a template parameter of a class template (during class template argument deduction ([over.match.class.deduct])). If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction.
Считается ли шаблон псевдонима с одним параметром шаблона, связанный тип которого является ссылкой rvalue на его параметр шаблона cv-unqualified, ссылкой для пересылки? Что ж, [temp.alias] / 2 говорит, что require_rvalue<T> эквивалентен T&&, а T&& - правильная вещь. Так что, возможно ... да.
И все компиляторы рассматривают это как таковое, что, безусловно, является хорошей проверкой.
Интересно то, что подстановка псевдонима должна произойти до вывода типа, чтобы понять, что параметр является ссылкой пересылки, и сделать возможным вывод T. И подстановка псевдонима должна произойти после определения типа, чтобы определить, действителен ли аргумент шаблона по умолчанию. Но я почти уверен, что это именно значение [temp.alias] / 4 «Однако, если идентификатор шаблона является зависимым, последующая подстановка аргументов шаблона по-прежнему применяется к идентификатору шаблона».
Да, я заметил свой комментарий, и ваше правка разошлась на несколько секунд.
Да, это часть стандарта, определяемого энергичным маханием руками.
Он работает и разрешен, потому что он использует широко используемые функции C++, разрешенные стандартом:
SFINAE в параметрах функции ([temp.over] / 1, [temp.deduct] / 6, [temp.deduct] / 8):
template <typename T>
void foo(T&& v, typename std::enable_if<std::is_rvalue_reference<T&&>::value>::type* = nullptr)
{ /* ... */ }
we cannot deduce on the actual parameter like void foo(typename std::enable_if<std::is_rvalue_reference<T&&>::value, T>::type&&) (CWG#549), but it is possible to workaround this limitation with template aliases (it is the trick I have presented in my question)
SFINAE в объявлении параметра шаблона ([temp.deduct] / 7):
template <typename T, typename std::enable_if<std::is_rvalue_reference<T&&>::value>::type* = nullptr>
void foo(T&& v)
{ /* ... */ }
Шаблоны псевдонимов в параметрах функции ([temp.alias] / 2):
template<class T> struct Alloc { /* ... */ };
template<class T> using Vec = vector<T, Alloc<T>>;
template<class T>
void process(Vec<T>& v)
{ /* ... */ }
Шаблоны псевдонимов могут иметь параметры по умолчанию ([temp.param] / 12, [temp.param] / 15, [temp.param] / 18)
Параметры шаблонов псевдонимов шаблонов, параметризованных выводимыми типами, все еще могут быть выведены ([temp.deduct.type] / 17):
Я принял ответ @Barry и поставил его (с концентрированной информацией и обо всех аспектах, которые использует трюк), потому что многие люди (включая меня) боятся стандартного языка вуду C++ о материалах вывода шаблонов.
Хороший вопрос! Я тоже его никогда не видел. Хотя выглядит очень круто.