c++c++11boostlanguage-lawyerinheriting-constructors

Constructor inheritance failure with boost::multiprecision::mpz_int


I tried to create a class deriving from boost::multiprecision::mpz_int and to have it inherit the base class constructors:

#include <boost/multiprecision/gmp.hpp>

using namespace boost::multiprecision;

struct Integer:
    mpz_int
{
    using mpz_int::mpz_int;
};

g++ 4.9.0 gives me the following 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>'

The truth is that I have no idea why this is happening. The following workaround achieves what I want to do:

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

Can anybody explain why the first example produces an error? I thought that inheriting the base class constructors and forwarding values to them did roughly the same thing. I guess I was wrong, but I'm still interested in knowing the difference.

EDIT: I will make things clear. I don't care at all whether there are better methods to achieve this (there are tons). The only thing I asked is why constructor inheritance failed in this case. Is it due to a compiler bug or to some obscure rule somewhere in the standard?


Solution

  • This appears to be caused by the default parameters of mpz_int's constructors (mpz_int is a typedef for a particular instantiation of boost::multiprecision::number), which are used for SFINAE (for instance, given a template <class V> constructor taking a const V &, select one constructor if V satisfies criteria X and another constructor if V satisfies criteria Y).

    A small repro is:

    #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() { }
    

    This compiles in clang but not g++, producing the same error. (It's worth noting that while clang compiles the repro code above, it doesn't actually work if you try to use the inherited constructor with a single argument, which is almost equally as bad. You can make it work in clang, however, by explicitly supplying the second parameter.)

    We can even skip the templateness for foo's constructors by simply using instead:

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

    and still get the same result - error in g++, OK in clang.

    Now, the question is whether this construct should in fact be accepted according to the standard. Unfortunately, there is no clear answer. §12.9 [class.inhctor]/p1 says that

    A using-declaration (7.3.3) that names a constructor implicitly declares a set of inheriting constructors. The candidate set of inherited constructors from the class X named in the using-declaration consists of actual constructors and notional constructors that result from the transformation of defaulted parameters as follows:

    • all non-template constructors of X, and
    • for each non-template constructor of X that has at least one parameter with a default argument, the set of constructors that results from omitting any ellipsis parameter specification and successively omitting parameters with a default argument from the end of the parameter-type-list, and
    • all constructor templates of X, and
    • for each constructor template of X that has at least one parameter with a default argument, the set of constructor templates that results from omitting any ellipsis parameter specification and successively omitting parameters with a default argument from the end of the parameter-type-list.

    The problem is that the standard doesn't actually specify what happens if this successively-omitting-parameters-with-default-arguments procedure result in two constructors with the same signature. (Note that with both template constructors of foo above, omitting the parameter with default argument gives the signature template<class T> foo(T);.) While paragraph 7 has a note that says

    If two using-declarations declare inheriting constructors with the same signatures, the program is ill-formed (9.2, 13.1), because an implicitly-declared constructor introduced by the first using-declaration is not a user-declared constructor and thus does not preclude another declaration of a constructor with the same signature by a subsequent using-declaration.

    here we have only one using-declaration, so the note doesn't apply, and, while duplicate declarations are indeed prohibited, it is arguable that the reference to a set in paragraph 1 means that duplicate signatures will simply be treated as one, so that a single using-declaration will not introduce a duplicate declaration.

    This issue is in fact the subject of two defect reports against the standard: CWG 1645 and CWG 1941, and it is unclear how those defect reports will be resolved. One possibility, noted in the 2013 note in CWG issue 1645, is to make such inherited constructors (that came from multiple base constructors) deleted, so that they cause an error only when used. An alternative approach suggested in CWG issue 1941 is to make inheriting constructors behave like other base class functions introduced into the derived class.