2012-02-15 23 views
9

Este problema se basa en el código que funciona para mí en GCC-4.6 pero no para otro usuario con CLang-3.0, ambos en modo C++ 0x.Conflicto entre el constructor de copia y el constructor de reenvío

template <typename T> 
struct MyBase 
{ 
//protected: 
    T m; 

    template <typename Args...> 
    MyBase(Args&& ...x) : m(std::forward<Args>(x)...) {} 
}; 

Un objeto de MyBase puede tomar cualquier lista de argumentos del constructor, siempre y cuando T admite que la firma de la construcción. El problema tiene que ver con las funciones de miembros especiales.

  1. IIUC, la plantilla del constructor cancela el constructor predeterminado definido automáticamente. Sin embargo, dado que la plantilla puede aceptar cero argumentos, actuará como un constructor predeterminado explícitamente definido (siempre que T sea predeterminado-construible).
  2. IIUC, determinación de una política de copia de construcción de clase ignora las plantillas de constructor. Esto significa que en este caso MyBase ganará un constructor de copias definido automáticamente (siempre que se pueda copiar T) que tendrá el canal T copy-construction.
  3. También aplique el paso anterior para la construcción de movimientos.

Así que si paso un MyBase<T> const & como el único argumento del constructor, que el constructor es llamada, la expedición o la implícita una copia?

typedef std::vector<Int> int_vector; 
typedef MyBase<int_vector> VB_type; 

int_vector a{ 1, 3, 5 }; 
VB_type  b{ a }; 
VB_type  c{ b }; // which constructor gets called 

El problema de mi usuario fue utilizar esto como una clase base. El compilador se quejó de que su clase no podía sintetizar un constructor de copia definido automáticamente, porque no podía encontrar una coincidencia con la plantilla de constructor de la clase base. ¿No debería llamar al MyBase copia-constructor automático para su propio copia-constructor automático? ¿Es CLang un error por tener un conflicto?

+0

Interesante pregunta. Recomiendo hacer una sscce (ver http://sscce.org/) que funcione en GCC y fallar en CLang para ayudarnos a entender mejor el problema y recrearlo nosotros mismos. Además, envíenos el mensaje de error exacto de CLang. –

+2

En realidad, una versión de gcc relativamente cerca de la cabeza (a partir de 20120202) tampoco acepta este código. Parece que el constructor de reenvío está siendo recogido incluso para la copia. No estoy muy seguro de por qué esto. No está relacionado con la "sintaxis de inicialización uniforme": incluso cuando se usa paréntesis para la última declaración, no se compila. –

+0

Mi código es algo que escribí hace una década para Boost y que se actualizó para C++ 11 la semana pasada. No sé cómo trabajar con todo el sistema Trac de Boost, pero puede consultar [mis cambios] (https://svn.boost.org/trac/boost/changeset/76982) y el [estado actual] (http : //svn.boost.org/svn/boost/trunk/boost/utility/base_from_member.hpp) del archivo (en la revisión 77031 al momento de escribir este documento). – CTMacUser

Respuesta

9

Estoy en el bar con Richard Corden y entre nosotros llegamos a la conclusión de que el problema no tiene nada que ver con valores variados o r. La construcción de copia generada implícitamente en este caso toma un MyBase const& como argumento. El constructor con plantilla dedujo el tipo de argumento como MyBase&. Esta es una coincidencia mejor que se llama aunque no es un constructor de copia.

El código de ejemplo he utilizado para la prueba es la siguiente:

#include <utility> 
#include <vector>i 

template <typename T> 
struct MyBase 
{ 
    template <typename... S> MyBase(S&&... args): 
     m(std::forward<S>(args)...) 
    { 
    } 
    T m; 
}; 

struct Derived: MyBase<std::vector<int> > 
{ 
}; 

int main() 
{ 
    std::vector<int>    vec(3, 1); 
    MyBase<std::vector<int> > const fv1{ vec }; 
    MyBase<std::vector<int> >  fv2{ fv1 }; 
    MyBase<std::vector<int> >  fv3{ fv2 }; // ERROR! 

    Derived d0; 
    Derived d1(d0); 
} 

que necesitaba para eliminar el uso de listas de inicializador porque este no es compatible con sonido metálico, todavía. Este ejemplo compila a excepción de la inicialización de fv3 que falla: el constructor de copia sintetizado para MyBase<T> toma un MyBase<T> const& y así pasa fv2 llama al constructor variadic reenviando el objeto a la clase base.

Pude haber entendido mal la pregunta, pero basado en d0 y d1 parece que se sintetizan tanto un constructor predeterminado como un constructor de copia. Sin embargo, esto es con versiones bastante recientes de gcc y clang. Es decir, no explica por qué no se sintetiza ningún constructor de copia porque hay uno sintetizado.

Para enfatizar que este problema no tiene nada que ver con listas de argumentos varicos o valores r: el siguiente código muestra el problema que se llama el constructor con plantilla aunque parece que se llama un constructor de copia y los constructores de copia nunca son plantillas.Esto es en realidad un comportamiento un tanto sorprendente, que estaba sin duda consciente de:

#include <iostream> 
struct MyBase 
{ 
    MyBase() {} 
    template <typename T> MyBase(T&) { std::cout << "template\n"; } 
}; 

int main() 
{ 
    MyBase f0; 
    MyBase f1(const_cast<MyBase const&>(f0)); 
    MyBase f2(f0); 
} 

Como resultado, la adición de un constructor variadic como en la cuestión de una clase que no tiene ningún otro constructor trabajo cambia los constructores de copia comportamiento! Personalmente, creo que esto es bastante desafortunado. Esto significa efectivamente que la clase MyBase necesita ser aumentada con copiar y mover constructores así:

MyBase(MyBase const&) = default; 
    MyBase(MyBase&) = default; 
    MyBase(MyBase&&) = default; 

Desafortunadamente, esto no parece funcionar con gcc: se queja de los constructores de copia en default (cesación de pagos que reclama el el constructor de copia que toma una referencia no const no se puede definir en la definición de clase). Clang acepta este código sin ninguna queja. Usando una definición del constructor de copia de tomar una referencia no const funciona tanto con gcc y sonido metálico:

template <typename T> MyBase<T>::MyBase(MyBase<T>&) = default; 
+0

@MooingDuck: No pude reproducir un problema con el uso de una base correspondiente clase (ver mi actualización a la respuesta). –

+0

Sí, he usado diferentes nombres originalmente y no actualicé todos los lugares. Espero haber arreglado todo ahora. –

1

que he tenido personalmente el problema con instantáneas del CCG desde hace bastante tiempo ahora. He tenido problemas para descubrir qué estaba pasando (y si estaba permitido) pero llegué a una conclusión similar a la de Dietmar Kühl: los constructores de copia/movimiento todavía están aquí, pero no siempre son preferidos a través de la mecánica de la sobrecarga. resolución.

He estado usando esto para conseguir alrededor del problema desde hace algún tiempo:

// I don't use std::decay on purpose but it shouldn't matter 
template<typename T, typename U> 
using is_related = std::is_same< 
    typename std::remove_cv<typename std::remove_reference<T>::type>::type 
    , typename std::remove_cv<typename std::remove_reference<U>::type>::type 
>; 

template<typename... T> 
struct enable_if_unrelated: std::enable_if<true> {}; 

template<typename T, typename U, typename... Us> 
struct enable_if_unrelated 
: std::enable_if<!is_related<T, U>::value> {}; 

la utiliza con un constructor como la suya se vería así:

template< 
    typename... Args 
    , typename = typename enable_if_unrelated<MyBase, Args...>::type 
> 
MyBase(Args&&... args); 

Algunas explicaciones están en orden . is_related es un rasgo binario run off the mill que comprueba que dos tipos son idénticos independientemente de los especificadores de nivel superior (const, volatile, &, &&). La idea es que los constructores que estarán protegidos por este rasgo están 'convirtiendo' constructores y no están diseñados para tratar con parámetros del tipo de clase en sí, sino solo si ese parámetro está en la primera posición. Una construcción con parámetros, p. (std::allocator_arg_t, MyBase) estaría bien.

Ahora solía tener enable_if_unrelated como una metafunción binaria, también, pero como es muy conveniente tener constructores variadic de reenvío perfecto para trabajar en el caso nullary también lo rediseñé para aceptar cualquier cantidad de argumentos (aunque podría diseñarse para aceptar al menos un argumento, el tipo de clase del constructor que estamos protegiendo). Esto significa que, en nuestro caso, si se llama al constructor sin argumentos, no tiene SFINAE desactivado. De lo contrario, necesitaría agregar una declaración MyBase() = default;.

Finalmente, si el constructor está reenviando a una base, otra alternativa es heredar el constructor de esa base (es decir, using Base::Base;). Este no es el caso en tu ejemplo.

0

Subí la respuesta de Dietmar porque estoy totalmente de acuerdo con él. Pero quiero compartir una "solución" que estaba usando algún tiempo antes para evitar estos problemas:

he añadido intencionalmente a un parámetro ficticio al constructor variadic:

enum fwd_t {fwd}; 

template<class T> 
class wrapper 
{ 
    T m; 
public: 
    template<class...Args> 
    wrapper(fwd_t, Args&&...args) 
    : m(std::forward<Args>(args)...) 
    {} 
}; 

::: 

int main() 
{ 
    wrapper<std::string> w (fwd,"hello world"); 
} 

Sobre todo porque el constructor aceptaría nada sin este parámetro ficticio, parece apropiado hacer que el código de usuario elija explícitamente el constructor correcto al (tipo de) "nombrarlo".

Es posible que no sea posible en su caso. Pero a veces puedes salirte con la tuya.

Cuestiones relacionadas