2011-10-23 22 views
17

Escribo una biblioteca de red y uso la semántica de movimientos en gran medida para manejar la propiedad de los descriptores de archivos. Uno de mi clase desea recibir las envolturas de descriptor de archivo de otros tipos y tomar posesión, por lo que es algo así como¿Cómo hacer que el parámetro de referencia de rvalue de la plantilla ÚNICAMENTE se vincule con la referencia rvalue?

struct OwnershipReceiver 
{ 
    template <typename T> 
    void receive_ownership(T&& t) 
    { 
    // taking file descriptor of t, and clear t 
    } 
}; 

Tiene que tratar varios tipos relacionados de manera receive_ownership tiene que ser una plantilla, y para estar seguro, me gustaría ÚNICAMENTE se une a las referencias rvalue, de modo que el usuario tiene que indicar explícitamente std :: move al pasar un lvalue.

receive_ownership(std::move(some_lvalue));

Pero el problema es: C++ deducción plantilla permite una lvalue a ser aprobada en sin esfuerzo adicional. Y en realidad me pegué un tiro en el pie una vez al pasar accidentalmente un lvalor para recibir la propiedad y usar ese lvalue (despejado) más tarde.

Así que aquí está la pregunta: ¿cómo hacer que una plantilla ÚNICAMENTE se vincule a la referencia rvalue?

Respuesta

20

Puede restringir T a no ser una referencia de valor izquierdo, y así evitar lvalues ​​se una a ella:

#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 añadir algún tipo de restricción a T tal manera que sólo se acepta envolturas del descriptor de archivo.

+0

Usted puede usar 'std: : is_rvalue_reference' en su lugar – David

+0

Gracias Howard, eso funciona bien. Y Dave, pensé exactamente lo mismo que al principio, luego encontré std :: is_rvalue_reference no va a funcionar: no se une a un valor real "r" ni a std :: move() ed lvalue . –

+4

@Ralph: ¿Intentó 'is_rvalue_reference :: value'? (Observe el '&&') – fredoverflow

3

Agradeceré a Howard nuevamente por la respuesta oportuna y útil, mi problema fue resuelto.

Y durante el curso, me entero de algo que la gente parece confundirse con bastante frecuencia: usando SFINAE está bien, pero no puedo utilizar

std::is_rvalue_reference<T>::value 

la única forma en que funciona como yo quiero es

!std::is_lvalue_reference<T>::value 

La razón es: Necesito que mi función reciba "un valor r", no "una referencia de valor". Una función SFINAE ed con std :: is_rvalue_reference :: value no recibirá "a rvalue", pero solo recibirá "una referencia rvalue". (renuncie a una peculiaridad, ¿eh?)

2

Para las referencias lvalue, se deduce que T es una referencia lvalue, y para las referencias rvalue, T se deduce que no es una referencia.

lo tanto, si la función se une a una referencia de valor de lado derecho, lo que se ve al final por el compilador para un tipo determinado T es:

std::is_rvalue_reference<T>::value

y no

std::is_rvalue_reference<T&&>::value

3

Una forma simple es proporcionar un miembro eliminado que acepta una referencia lvalue:

template<typename T> void receive_ownership(T&) = delete; 

Esto siempre será una mejor coincidencia para un argumento lvalue.


Si usted tiene una función que toma varios argumentos, todos los cuales necesitan ser rvalues, necesitaremos varias funciones eliminados. En esta situación, podemos preferir usar SFINAE para ocultar la función de cualquier argumento lvalue.

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, usted es capaz de definir una nuevo concepto propio, que puede ser útil si quiere 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, tendrá que pasar -fconcepts para el compilador, ya que es una extensión de C++ en lugar de una parte central de la misma.

simplemente para la corrección, aquí está mi prueba sencilla:

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

¿No puedo' plantilla void receive_ownership (T & t) = delete'? – Xeverous

+0

Sí, eso funciona y es más simple - He editado en consecuencia. Gracias por la sugerencia. –

+0

Pero entonces, ¿no debería ser 'const T &' o ambos? – Xeverous

0

Por desgracia, parece que probar is_rvalue_reference<TF> (donde TF es el tipo remitido perfectamente) no funciona bien si en realidad se está tratando de hacer sobrecargas que distinguen entre const T& y T&& (por ejemplo, usando enable_if en ambos, uno con is_rvalue_reference_v<TF> y el otro con !is_rvalue_reference_V<TF>).

Una solución (aunque hacky) es decaer el reenviado T, luego coloque las sobrecargas en un contenedor consciente de estos tipos. Generada this example:

Hup, estaba mal, simplemente se olvidó de mirar a la respuesta de Toby (is_rvalue_reference<TF&&>) - aunque es confuso que puede hacer std::forward<TF>(...), pero supongo que es por eso decltype(arg) también funciona.

anywho, esto es lo que he usado para la depuración: (1) el uso de struct sobrecargas, (2) el uso de la comprobación incorrecta para is_rvalue_reference, y (3) la comprobació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; 
} 
Cuestiones relacionadas