Использование шаблонов псевдонимов для sfinae: позволяет ли это язык?

Я только что открыл следующую технику. Он очень близок к одному из предложенных синтаксисов концепций, отлично работает на 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 в псевдониме типа" и ничего не получил. Есть ли название для этой техники и действительно ли язык позволяет это?


Полный пример:

#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
}

Ответ 1

[...] действительно ли язык позволяет это?

Не могу сказать ничего об имени, но мне кажется, что это да.

Соответствующая формулировка - [temp.alias]/2:

Когда идентификатор шаблона ссылается на специализацию шаблона псевдонима, он эквивалентен связанному типу, полученному путем подстановки его шаблонных аргументов для параметров шаблона в идентификаторе типа шаблона псевдонимов.

и правило sfinae, [temp.deduct]/8:

Только недопустимые типы и выражения в непосредственном контексте типа функции, его типы параметров шаблона и его явный спецификатор могут приводить к сбою дедукции.

Принимая аргумент типа require_rvalue<T> он ведет себя так, как будто мы подставляем этот псевдоним, который либо дает нам T&& либо сбой замены, и что сбой замены, возможно, находится в непосредственном контексте подстановки, и поэтому "sfinae-friendly "в отличие от жесткой ошибки. Обратите внимание, что хотя аргумент типа по умолчанию не используется, в результате CWG 1558 (правило void_t) мы получили добавление [temp.alias]/3:

Однако, если идентификатор шаблона зависит, последующая замена аргумента шаблона по-прежнему применяется к идентификатору шаблона.

Это гарантирует, что мы все равно заменим аргумент типа по умолчанию, чтобы вызвать требуемый сбой замены.

Вторая недосказанная часть вопроса заключается в том, может ли это фактически вести себя в качестве справочника пересылки. Правило есть в [temp.deduct.call]/3:

Ссылка на пересылку представляет собой ссылку rvalue на параметр cv-unqualified template, который не представляет шаблонный шаблон шаблона класса (во время вывода аргумента шаблона шаблона ([over.match.class.deduct])). Если P является ссылкой для пересылки, а аргумент - значением l, то вместо значения A для типа используется тип "lvalue reference to A".

Является шаблоном псевдонима с одним параметром шаблона, связанный с ним тип которого является ссылкой rvalue на его параметр cv-unqualified template, рассматриваемый как ссылка пересылки? Ну, [temp.alias]/2 говорит, что require_rvalue<T> эквивалентно T&&, а T&& - правильная вещь. Так возможно... да.

И все компиляторы рассматривают это как таковое, что, безусловно, является хорошей проверкой.


Хотя, отмечает наличие РГСА 1844 и отсутствие фактического определения для непосредственного контекста, и пример там, который также опирается на провал подстановки из дефолта аргумента - что эмиссионная государства имеет дивергенцию реализации.

Ответ 2

Он работает и разрешен, потому что он передает широко используемые функции C++, разрешенные стандартом:

  1. 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)
    { /* ... */ }
    

    мы не можем выводить по фактическому параметру, например void foo(typename std::enable_if<std::is_rvalue_reference<T&&>::value, T>::type&&) (CWG # 549), но можно обойти это ограничение с помощью шаблона aliases (это трюк, который я представил в моем вопросе)

  2. SFINAE в объявлении параметра шаблона ([temp.deduct]/7):

    template <typename T, typename std::enable_if<std::is_rvalue_reference<T&&>::value>::type* = nullptr>
    void foo(T&& v)
    { /* ... */ }
    
  3. Шаблоны псевдонимов в функциональных параметрах ([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)
      { /* ... */ }
    
  4. Шаблоны псевдонимов могут иметь параметры по умолчанию ([temp.param]/12, [temp.param]/15, [temp.param]/18)

  5. Шаблонные параметры шаблонов псевдонимов, параметризованные выводимыми типами, все еще могут быть выведены ([temp.deduct.type]/17):

Я принял @Barry ответ и поставил это (с концентрированной информацией и обо всех аспектах, которые использует трюк), потому что многие люди (включая меня) боятся стандартного вуду-языка C++ о материалах дефолта шаблонов.