I tried to create a class deriving from boost::multiprecision::mpz_int
and to have it inherit the base class constructors:
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.