namespace cpp c++ c++11 boost language-lawyer inheriting-constructors

cpp - using c++ 11



Error de herencia de constructor con boost:: multiprecision:: mpz_int (1)

Esto parece ser causado por los parámetros predeterminados de los constructores de mpz_int ( mpz_int es un typedef para una instanciación particular de boost::multiprecision::number ), que se utilizan para SFINAE (por ejemplo, dado un constructor de template <class V> tomando un const V & , seleccione un constructor si V satisface el criterio X y otro constructor si V satisface el criterio Y).

Una pequeña reproducción es:

#include <type_traits> struct foo { template<class T> foo(T , typename std::enable_if<std::is_integral<T>::value>::type * = nullptr) { } template<class T> foo(T , typename std::enable_if<std::is_floating_point<T>::value>::type * = nullptr) { } }; struct bar : foo { using foo::foo; }; int main() { }

Esto compila en clang pero no en g ++ , produciendo el mismo error. (Vale la pena señalar que aunque clang compila el código de reprografía anterior, en realidad no funciona si intenta utilizar el constructor heredado con un único argumento, que es casi igual de malo. Puede hacer que funcione en clang, sin embargo, por suministrando explícitamente el segundo parámetro .)

Incluso podemos omitir la plantilla para los constructores de foo simplemente usando en su lugar:

struct foo { foo(double, int = 0) { } foo(double, double = 0) { } };

y aún obtener el mismo resultado - error en g ++, OK en clang.

Ahora, la pregunta es si este constructo debería ser aceptado de acuerdo con el estándar. Lamentablemente, no hay una respuesta clara. §12.9 [class.inhctor] / p1 dice que

Una declaración-uso (7.3.3) que nombra un constructor declara implícitamente un conjunto de constructores heredadores . El conjunto candidato de constructores heredados de la clase X nombrada en la declaración de uso consiste en constructores reales y constructores nocionales que resultan de la transformación de los parámetros predeterminados de la siguiente manera:

  • todos los constructores sin plantilla de X , y
  • para cada constructor no plantilla de X que tiene al menos un parámetro con un argumento predeterminado, el conjunto de constructores que resulta de omitir cualquier especificación de parámetro elipsis y omitir sucesivamente parámetros con un argumento predeterminado desde el final del parámetro-lista-tipo , y
  • todas las plantillas de constructor de X , y
  • para cada plantilla de constructor de X que tiene al menos un parámetro con un argumento predeterminado, el conjunto de plantillas de constructor que resulta de omitir cualquier especificación de parámetro elipse y omitir sucesivamente los parámetros con un argumento predeterminado desde el final de la lista de tipos de parámetros .

El problema es que el estándar en realidad no especifica qué sucede si este procedimiento sucesivamente-omisión-parámetros-con-argumentos-default resulta en dos constructores con la misma firma. (Tenga en cuenta que con los dos constructores de plantillas de foo anteriores, omitir el parámetro con el argumento predeterminado le da a la template<class T> foo(T); firma template<class T> foo(T); ) Mientras que el párrafo 7 tiene una nota que dice

Si dos declaraciones-uso declaran heredar constructores con las mismas firmas, el programa está mal formado (9.2, 13.1), porque un constructor declarado implícitamente introducido por la primera declaración-uso no es un constructor declarado por el usuario y por lo tanto no impide otra declaración de un constructor con la misma firma por una declaración de uso posterior.

aquí solo tenemos una declaración de uso , por lo que la nota no se aplica, y, si bien las declaraciones duplicadas están prohibidas, es discutible que la referencia a un conjunto en el párrafo 1 signifique que las firmas duplicadas simplemente se tratarán como una sola, entonces que una sola declaración de uso no introducirá una declaración duplicada.

Este problema es, de hecho, el tema de dos informes de defectos contra el estándar: CWG 1645 y CWG 1941 , y no está claro cómo se resolverán esos informes de defectos. Una posibilidad, señalada en la nota de 2013 en el CWG número 1645, es hacer que dichos constructores heredados (que provienen de múltiples constructores base) sean eliminados, de modo que causen un error solo cuando se usen. Un enfoque alternativo sugerido en el CWG issue 1941 es hacer que los constructores heredadores se comporten como otras funciones de clase base introducidas en la clase derivada.

Intenté crear una clase derivada de boost::multiprecision::mpz_int y hacer que heredara los constructores de la clase base:

#include <boost/multiprecision/gmp.hpp> using namespace boost::multiprecision; struct Integer: mpz_int { using mpz_int::mpz_int; };

g ++ 4.9.0 me da el siguiente error :

main.cpp:8:20: error: ''template<class tag, class Arg1, class Arg2, class Arg3, class Arg4> Integer::Integer(const boost::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4>&)'' inherited from ''boost::multiprecision::number<boost::multiprecision::backends::gmp_int>'' using mpz_int::mpz_int; ^ main.cpp:8:20: error: conflicts with version inherited from ''boost::multiprecision::number<boost::multiprecision::backends::gmp_int>'' main.cpp:8:20: error: ''template<class Other, boost::multiprecision::expression_template_option ET> Integer::Integer(const boost::multiprecision::number<Backend, ExpressionTemplates>&)'' inherited from ''boost::multiprecision::number<boost::multiprecision::backends::gmp_int>'' main.cpp:8:20: error: conflicts with version inherited from ''boost::multiprecision::number<boost::multiprecision::backends::gmp_int>'' main.cpp:8:20: error: ''template<class Other, boost::multiprecision::expression_template_option ET> Integer::Integer(const boost::multiprecision::number<Backend, ExpressionTemplates>&)'' inherited from ''boost::multiprecision::number<boost::multiprecision::backends::gmp_int>'' main.cpp:8:20: error: conflicts with version inherited from ''boost::multiprecision::number<boost::multiprecision::backends::gmp_int>'' main.cpp:8:20: error: ''template<class V> Integer::Integer(const V&)'' inherited from ''boost::multiprecision::number<boost::multiprecision::backends::gmp_int>'' main.cpp:8:20: error: conflicts with version inherited from ''boost::multiprecision::number<boost::multiprecision::backends::gmp_int>'' main.cpp:8:20: error: ''template<class V> constexpr Integer::Integer(const V&)'' inherited from ''boost::multiprecision::number<boost::multiprecision::backends::gmp_int>'' main.cpp:8:20: error: conflicts with version inherited from ''boost::multiprecision::number<boost::multiprecision::backends::gmp_int>'' main.cpp:8:20: error: ''template<class V> Integer::Integer(const V&)'' inherited from ''boost::multiprecision::number<boost::multiprecision::backends::gmp_int>'' main.cpp:8:20: error: conflicts with version inherited from ''boost::multiprecision::number<boost::multiprecision::backends::gmp_int>''

La verdad es que no tengo idea de por qué sucede esto. La siguiente solución logra lo que quiero hacer:

struct Integer: mpz_int { template<typename... Args> Integer(Args&&... args): mpz_int(std::forward<Args>(args)...) {} };

¿Alguien puede explicar por qué el primer ejemplo produce un error? Pensé que heredar los constructores de la clase base y los valores de reenvío a ellos era más o menos lo mismo. Supongo que estaba equivocado, pero todavía estoy interesado en saber la diferencia.

EDITAR: Dejaré las cosas claras. No me importa en absoluto si hay mejores métodos para lograr esto (hay toneladas). Lo único que pregunté es por qué la herencia del constructor falló en este caso. ¿Se debe a un error del compilador o a alguna regla oscura en algún lugar del estándar?