c++ - ¿Cómo hacer que el parámetro de referencia de rvalor de plantilla SOLO se enlace a la referencia de rvalue?
rvalue-reference (5)
Le agradeceré a Howard nuevamente por la respuesta oportuna y útil, mi problema se resolvió.
Y durante el curso, aprendo algo que la gente parece confundirse bastante a menudo: usar SFINAE está bien, pero no puedo usar
std::is_rvalue_reference<T>::value
La única forma en que funciona como quiero es.
!std::is_lvalue_reference<T>::value
La razón es: necesito que mi función reciba "un valor de rvalor", no "una referencia de valor de rvalor". Una función SFINAE ed con std :: is_rvalue_reference :: value no recibirá "un rvalue", sino que solo recibirá "una rvalue reference". (Salir de una peculiaridad, ¿eh?)
Estoy escribiendo una biblioteca de red y uso la semántica de movimiento en gran medida para manejar la propiedad de los descriptores de archivos. Uno de mi clase desea recibir envoltorios de descriptores de archivos de otros tipos y tomar posesión, por lo que es algo así como
struct OwnershipReceiver
{
template <typename T>
void receive_ownership(T&& t)
{
// taking file descriptor of t, and clear t
}
};
Tiene que tratar múltiples tipos no relacionados, por lo que receive_ownership debe ser una plantilla, y para estar seguro, deseo que SOLO se vincule a las referencias de valor, de modo que el usuario tenga que indicar explícitamente std :: move al pasar un valor l.
receive_ownership(std::move(some_lvalue));
Pero el problema es que: la deducción de la plantilla de C ++ permite que se pase un lvalor sin esfuerzo adicional. Y en realidad me disparé en el pie una vez al pasar accidentalmente un lvalue a receive_ownership y usar ese lvalue (borrado) más tarde.
Entonces, aquí está la pregunta: ¿cómo hacer que una plantilla SOLO se una a la referencia de valor?
Para las referencias de valores, se deduce que T es una referencia de valores, y para las referencias de valores, se deduce que T es una no referencia.
Entonces, si la función se enlaza a una referencia rvalue, lo que el compilador ve en el final de un determinado tipo T es:
std::is_rvalue_reference<T>::value
y no
std::is_rvalue_reference<T&&>::value
Puede restringir T
para que no sea una referencia de valor l, y así evitar que los valores l se vinculen a él:
#include <type_traits>
struct OwnershipReceiver
{
template <typename T,
class = typename std::enable_if
<
!std::is_lvalue_reference<T>::value
>::type
>
void receive_ownership(T&& t)
{
// taking file descriptor of t, and clear t
}
};
También podría ser una buena idea agregar algún tipo de restricción a T
para que solo acepte envoltorios de descriptor de archivos.
Una forma sencilla es proporcionar un miembro eliminado que acepte una referencia de valor l:
template<typename T> void receive_ownership(T&) = delete;
Esto siempre será una mejor coincidencia para un argumento lvalue.
Si tiene una función que toma varios argumentos, todos los cuales deben ser rvalues, necesitaremos varias funciones eliminadas. En esta situación, podemos preferir usar SFINAE para ocultar la función de cualquier argumento de valor l.
Una forma de hacer esto podría ser con C ++ 17 y los Conceptos TS:
#include <type_traits>
template<typename T>
void receive_ownership(T&& t)
requires !std::is_lvalue_reference<T>::value
{
// taking file descriptor of t, and clear t
}
o
#include <type_traits>
void receive_ownership(auto&& t)
requires std::is_rvalue_reference<decltype(t)>::value
{
// taking file descriptor of t, and clear t
}
Yendo un poco más lejos, puede definir un nuevo concepto propio, que puede ser útil si desea reutilizarlo, o simplemente para mayor claridad:
#include <type_traits>
template<typename T>
concept bool rvalue = std::is_rvalue_reference<T&&>::value;
void receive_ownership(rvalue&& t)
{
// taking file descriptor of t, and clear t
}
Nota: con GCC 6.1, deberás pasar -fconcepts
al compilador, ya que es una extensión de C ++ en lugar de una parte central del mismo.
Sólo para completar, aquí está mi simple prueba:
#include <utility>
int main()
{
int a = 0;
receive_ownership(a); // error
receive_ownership(std::move(a)); // okay
const int b = 0;
receive_ownership(b); // error
receive_ownership(std::move(b)); // allowed - but unwise
}
Desafortunadamente, parece que probar is_rvalue_reference<TF>
(donde TF
es el tipo perfectamente reenviado) no funciona bien si en realidad está intentando hacer sobrecargas que distinguen entre const T&
y T&&
(por ejemplo, usar enable_if
en ambos, uno con is_rvalue_reference_v<TF>
y el otro con !is_rvalue_reference_V<TF>
).
Una solución (aunque sea hacky) es descomponer la T
reenviada, luego colocar las sobrecargas en un contenedor consciente de estos tipos. Generé este ejemplo :
Hup, me equivoqué, solo olvidé mirar la respuesta de Toby ( is_rvalue_reference<TF&&>
) - aunque es confuso que puedas hacer std::forward<TF>(...)
, pero creo que por eso decltype(arg)
tambien funciona
De todos modos, esto es lo que usé para la depuración: (1) usando sobrecargas de struct
, (2) usando la verificación incorrecta para is_rvalue_reference
, y (3) la verificación correcta:
/*
Output:
const T& (struct)
const T& (sfinae)
const T& (sfinae bad)
---
const T& (struct)
const T& (sfinae)
const T& (sfinae bad)
---
T&& (struct)
T&& (sfinae)
const T& (sfinae bad)
---
T&& (struct)
T&& (sfinae)
const T& (sfinae bad)
---
*/
#include <iostream>
#include <type_traits>
using namespace std;
struct Value {};
template <typename T>
struct greedy_struct {
static void run(const T&) {
cout << "const T& (struct)" << endl;
}
static void run(T&&) {
cout << "T&& (struct)" << endl;
}
};
// Per Toby''s answer.
template <typename T>
void greedy_sfinae(const T&) {
cout << "const T& (sfinae)" << endl;
}
template <
typename T,
typename = std::enable_if_t<std::is_rvalue_reference<T&&>::value>>
void greedy_sfinae(T&&) {
cout << "T&& (sfinae)" << endl;
}
// Bad.
template <typename T>
void greedy_sfinae_bad(const T&) {
cout << "const T& (sfinae bad)" << endl;
}
template <
typename T,
typename = std::enable_if_t<std::is_rvalue_reference<T>::value>>
void greedy_sfinae_bad(T&&) {
cout << "T&& (sfinae bad)" << endl;
}
template <typename TF>
void greedy(TF&& value) {
using T = std::decay_t<TF>;
greedy_struct<T>::run(std::forward<TF>(value));
greedy_sfinae(std::forward<TF>(value));
greedy_sfinae_bad(std::forward<TF>(value));
cout << "---" << endl;
}
int main() {
Value x;
const Value y;
greedy(x);
greedy(y);
greedy(Value{});
greedy(std::move(x));
return 0;
}