c++ c++11 language-lawyer sfinae

c++ - Usando plantillas de alias para sfinae: ¿el lenguaje lo permite?



c++11 language-lawyer (2)

[...] ¿el lenguaje realmente lo permite?

No puedo decir nada sobre el nombre, pero esto me parece un sí.

La redacción relevante es [temp.alias]/2 :

Cuando una plantilla-id se refiere a la especialización de una plantilla de alias, es equivalente al tipo asociado obtenido mediante la sustitución de sus argumentos de plantilla por los parámetros de plantilla en la ID de tipo de la plantilla de alias.

y la regla sfinae, [temp.deduct]/8 :

Solo los tipos y expresiones no válidos en el contexto inmediato del tipo de función, sus tipos de parámetros de plantilla y su especificador explícito pueden dar como resultado un error de deducción.

Tomar un argumento del tipo require_rvalue<T> se comporta como si sustituyéramos ese alias, lo que nos da un T&& o un fallo de sustitución, y ese fallo de sustitución está probablemente en el contexto inmediato de la sustitución y por lo tanto es "amigable". "en lugar de ser un error duro. Tenga en cuenta que aunque el argumento de tipo predeterminado no se usa, como resultado de CWG 1558 (la regla void_t ), obtuvimos la adición de [temp.alias]/3 :

Sin embargo, si la plantilla-id es dependiente, la sustitución de argumentos de la plantilla subsiguiente se aplica a la plantilla-id .

Esto garantiza que aún sustituimos en el argumento de tipo predeterminado para desencadenar la falla de sustitución requerida.

La segunda parte no contestada de la pregunta es si esto realmente puede comportarse como una referencia de reenvío. La regla que hay en [temp.deduct.call]/3 :

Una referencia de reenvío es una referencia de rvalor a un parámetro de plantilla no calificada para cv que no representa un parámetro de plantilla de una plantilla de clase (durante la deducción del argumento de la plantilla de clase ([over.match.class.deduct])). Si P es una referencia de reenvío y el argumento es un lvalor, se usa el tipo "lvalue reference to A" en lugar de A para la deducción de tipo.

¿Una plantilla de alias con un parámetro de plantilla cuyo tipo asociado es una referencia rvalue a su parámetro de plantilla no calificada cv se considera una referencia de reenvío? Bueno, [temp.alias] / 2 dice que require_rvalue<T> es equivalente a T&& , y T&& es lo correcto. Así que podría decirse que ... sí.

Y todos los compiladores lo tratan como tal, lo que sin duda es una buena validación.

Aunque, tenga en cuenta la existencia de CWG 1844 y la falta de una definición real para el contexto inmediato, y el ejemplo allí, que también se basa en un fallo de sustitución de un argumento predeterminado, que el tema establece tiene divergencia en la implementación.

Acabo de descubrir la siguiente técnica. Se ve muy cerca de uno de los conceptos de sintaxis, funciona perfectamente en Clang, GCC y 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);

Traté de encontrarlo con solicitudes de búsqueda como "sfinae in type alias" y no obtuve nada. ¿Hay un nombre para esta técnica y el lenguaje realmente lo permite?

El ejemplo completo:

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


Funciona y se permite porque se transmite a las funciones de C ++ ampliamente utilizadas permitidas por el estándar:

  1. SFINAE en los parámetros de función ( [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) { /* ... */ }

    no podemos deducir el parámetro real como void foo(typename std::enable_if<std::is_rvalue_reference<T&&>::value, T>::type&&) ( CWG#549 ), pero es posible solucionar esta limitación con la plantilla alias (es el truco que he presentado en mi pregunta)

  2. SFINAE en la declaración de parámetros de plantilla ( [temp.deduct]/7 ):

    template <typename T, typename std::enable_if<std::is_rvalue_reference<T&&>::value>::type* = nullptr> void foo(T&& v) { /* ... */ }

  3. Plantillas de alias en parámetros de función ( [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. Las plantillas de alias pueden tener parámetros predeterminados ( [temp.param]/12 , [temp.param]/15 , [temp.param]/18 )

  5. Los parámetros de plantilla de las plantillas de alias parametrizadas con tipos deducibles aún se pueden deducir ( [temp.deduct.type]/17 ):

He aceptado la respuesta de @ Barry y la puse (con información concentrada y sobre todos los aspectos que utiliza el truco) porque muchas personas (incluido yo) tienen miedo del lenguaje vudú estándar de C ++ acerca de las cosas de deducción de plantillas.