Constructor inheritance failure with boost::multiprecision::mpz_int
Asked Answered
E

1

13

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?

Except answered 23/7, 2014 at 13:49 Comment(8)
clang compiles it just fine - possible gcc bug?Matrona
"a class deriving from boost::multiprecision::mpz_int" Probably not such a great idea. Just sayin'.Coper
As long as you avoid the usual pitfalls of things that look polymorphic but really aren't, more power to you. Can you guarantee your users are able to do the same? Wouldn't it be much much more simple and reliable to provide any added functionality via non-member functions?Coper
@n.m. That wasn't for users actually, but merely an implementation detail of a private project. I was trying to do some concept-based polymorphism to store this class (and others) in some dedicated container; that container was meant to be usable with other types. I encountered some problems with ADL and free functions, so I decided to try custom classes and member functions instead. I didn't remember exactly, but in the end, I tried that and got that error.Except
Then private inheritance (or containment) and an explicit mpz_int& Integer::get() would be my choice.Coper
First, ditch the using namespace ⋯ line and instead declare just the items you need or qualify names when used. The using directive (not to be confused with a using declaration) likes to cause problems with ambiguities and other surprises.Jab
The inheriting constructors is a new feature. Many times I've had to bring down constructors by hand, and this is a welcome addition. So, it's conceivable that gcc doesn't understand it correctly, or is buggy when mixed with other new features such as variadic argument packs.Jab
Well, that will somehow sound like a rant, but I know that using namespace is generally not to be used in production code, that one should be aware of the pitfalls of non-virtual inheritance and I also know the difference between using directives and using declarations. But come on, this is example code; I just tried to simplify the code so that the question's code would be simple, free of production code boilerplate.Except
I
9

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.

Isomorph answered 4/9, 2014 at 4:2 Comment(4)
Excellent write up. (I think the problem would be presented more strongly if started with the non-template version)Olethea
@Olethea Hmm, I started with the SFINAE/template case since that's the usual scenario for having two constructors yielding the same signature after removal of default argument. The non-template case is a bit contrived and far less likely to show up in real code, since the ambiguity makes it impossible to actually use the default argument.Isomorph
suggest changing the thread title to describe the core issue so that anyone else having the problem can find it (although I can't think of something appropriate right now!)Ulric
Wow, that was a great answer, thank you very much! Concerning the title, I think that there are not that many questions with "constructor inheritance failure" is them. Even if it explicitly states boost::multiprecision::mpz_int, people will probably have a look to see whether their case is similar :)Except

© 2022 - 2024 — McMap. All rights reserved.