compiler c++ c++11 copy-constructor variadic-templates

c++ - compiler - ¿Cómo puedo evitar que un constructor variadic sea preferido al constructor de copia?



c++17 (4)

Tengo una plantilla ''Foo'', que posee una T, y me gustaría tener un constructor variad que envíe sus argumentos al constructor de T:

template<typename T> struct Foo { Foo() : t() {} Foo(const Foo& other) : t(other.t) {} template<typename ...Args> Foo(Args&&... args) : t(std::forward<Args>(args)...) {} T t; };

Sin embargo, esto hace que Foo no sea copiable:

int main(int argc, char* argv[]) { Foo<std::shared_ptr<int>> x(new int(42)); decltype(x) copy_of_x(x); // FAILS TO COMPILE return EXIT_SUCCESS; }

porque, según esta respuesta , la no constancia del argumento hace que el constructor variadic sea una mejor coincidencia. No quiero forzar a mis interlocutores a usar const_cast, por razones obvias.

Una posible solución que encontré fue escribir un "constructor de copia" para Foo que toma un Foo sin constancia y usa el reenvío de constructor:

Foo(Foo& other) : Foo(const_cast<const Foo&>(other)) {}

Cuando se define este constructor, las cosas funcionan de nuevo: ahora se prefiere el ctor de copia Foo de argumento no constante. Sin embargo, esto me parece muy incompleto, ya que esta "cura" parece peor que la enfermedad.

¿Hay otra manera de lograr este efecto, para indicar que el constructor de copia natural debe ser preferido al constructor variadic? Si no, ¿hay consecuencias adversas de definir este constructor de copia argumento no const?


Si no es así, ¿hay consecuencias adversas de definir este constructor de copia de argumento no constante?

Voy a ignorar el "Si no", ya que hay otros enfoques. Pero hay una consecuencia adversa de su enfoque. Lo siguiente sigue usando el constructor de la plantilla

Foo<X> g(); Foo<X> f(g());

Debido a que g() es un rvalue, la plantilla es una mejor coincidencia porque deduce el parámetro a una referencia rvalue.


Deshabilite el constructor cuando el tipo de argumento sea del mismo tipo o se derive de esto:

template<typename ThisType, typename ... Args> struct is_this_or_derived : public std::false_type {}; template<typename ThisType, typename T> struct is_this_or_derived<ThisType, T> : public std::is_base_of<std::decay_t<ThisType>, std::decay_t<T> >::type {}; template<typename ThisType, typename ... Args> using disable_for_this_and_derived = std::enable_if_t<!is_this_or_derived<ThisType, Args ...>::value>;

Usalo como

template<typename ...Args , typename = disable_for_this_and_derived<Foo, Args ...> > //^^^^ //this needs to be adjusted for each class Foo(Args&&... args) : t(std::forward<Args>(args)...) {}


El mejor enfoque es no hacer lo que estás haciendo.

Dicho esto, una solución simple es permitir que el constructor variadic avance hasta un constructor de clase base, con algún primer argumento especial.

Por ejemplo, las siguientes compilaciones con MinGW g ++ 4.7.1:

#include <iostream> // std::wcout, std::endl #include <memory> // std::shared_ptr #include <stdlib.h> // EXIT_SUCCESS #include <tuple> #include <utility> // std::forward void say( char const* const s ) { std::wcout << s << std::endl; } template<typename T> struct Foo; namespace detail { template<typename T> struct Foo_Base { enum Variadic { variadic }; Foo_Base() : t() { say( "default-init" ); } Foo_Base( Foo_Base const& other ) : t( other.t ) { say( "copy-init" ); } template<typename ...Args> Foo_Base( Variadic, Args&&... args ) : t( std::forward<Args>(args)... ) { say( "variadic-init" ); } T t; }; template<typename T> struct Foo_ConstructorDispatch : public Foo_Base<T> { Foo_ConstructorDispatch() : Foo_Base<T>() {} template<typename ...Args> Foo_ConstructorDispatch( std::tuple<Foo<T>&>*, Args&&... args ) : Foo_Base<T>( args... ) {} template<typename ...Args> Foo_ConstructorDispatch( std::tuple<Foo<T> const&>*, Args&&... args ) : Foo_Base<T>( args... ) {} template<typename ...Args> Foo_ConstructorDispatch( void*, Args&&... args) : Foo_Base<T>( Foo_Base<T>::variadic, std::forward<Args>(args)... ) {} }; } // namespace detail template<typename T> struct Foo : public detail::Foo_ConstructorDispatch<T> { template<typename ...Args> Foo( Args&&... args) : detail::Foo_ConstructorDispatch<T>( (std::tuple<Args...>*)0, std::forward<Args>(args)... ) {} }; int main() { Foo<std::shared_ptr<int>> x( new int( 42 ) ); decltype(x) copy_of_x( x ); }


Puedes usar un feo SFINAE con std::enable_if , pero no estoy seguro de que sea mejor que tu solución inicial (de hecho, estoy bastante seguro de que es peor!):

#include <memory> #include <type_traits> // helper that was not included in C++11 template<bool B, typename T = void> using disable_if = std::enable_if<!B, T>; template<typename T> struct Foo { Foo() = default; Foo(const Foo &) = default; template<typename Arg, typename ...Args, typename = typename disable_if< sizeof...(Args) == 0 && std::is_same<typename std::remove_reference<Arg>::type, Foo >::value >::type > Foo(Arg&& arg, Args&&... args) : t(std::forward<Arg>(arg), std::forward<Args>(args)...) {} T t; }; int main(int argc, char* argv[]) { Foo<std::shared_ptr<int>> x(new int(42)); decltype(x) copy_of_x(x); decltype(x) copy_of_temp(Foo<std::shared_ptr<int>>(new int)); return 0; }