Consider the following code:
#include <boost/range.hpp>
#include <boost/iterator/counting_iterator.hpp>
typedef boost::iterator_range<boost::counting_iterator<int>> int_range;
template <typename T>
class Ref {
T* p_;
public:
Ref(T* p) : p_(p) { }
/* possibly other implicit conversion constructors,
but no unconstrained template constructors that don't
use the explicit keyword... */
operator T*() const { return p_; }
operator const T*() const { return p_; }
};
struct Bar { };
class Foo {
public:
Foo(int a, char b) { /* ... */ }
Foo(int a, const Ref<Bar>& b) { /* ... */ }
Foo(int a, const int_range& r) { /* ... */ }
};
int main() {
Bar b;
Foo f(5, &b);
return 0;
}
This code doesn't compile because the use of the Foo
constructor is ambiguous, since boost::iterator_range
apparently has a templated constructor that takes a single argument and is not declared as explicit
. Assuming that changing the structure of Ref
is not an option, how can I fix this problem? I came up with the following possible solution, but it's ugly and not easily maintainable, especially if there are more than a few constructors of Foo
:
template<typename range_like>
Foo(
int a,
const range_like& r,
typename std::enable_if<
not std::is_convertible<range_like, Ref<Bar>>::value
and std::is_convertible<range_like, int_range>::value,
bool
>::type unused = false
) { /* ... */ }
or similarly
template<typename range_like>
Foo(
int a,
const range_like& r,
typename std::enable_if<
std::is_same<typename std::decay<range_like>::type, int_range>::value,
bool
>::type unused = false
) { /* ... */ }
which has the disadvantage that all other implicit type conversion for int_range
is disabled, and thus relies on unspecified features of boost
(and my instincts tell me it's probably a bad idea anyway). Is there a better way to do this? (C++14 "concepts-lite" aside, which is really what this problem wants I think).
I think this program is a minimal example of your problem:
#include <iostream>
struct T {};
struct A {
A(T) {}
};
struct B {
B(T) {}
};
struct C {
C(A const&) { std::cout << "C(A)\n"; }
C(B const&) { std::cout << "C(B)\n"; }
};
int main() {
C c{T{}};
}
You have two types A
and B
both implicitly convertible from another type T
, and another type C
implicitly convertible from A
and B
, but for which implicit conversion from T
is ambiguous. You desire to disambiguate the situation so that C
is implicitly convertible from T
using the sequence of conversions T => A => C
, but you must do so without changing the definitions of A
and B
.
The obvious solution - already suggested in the comments - is to introduce a third converting constructor for C
: C(T value) : C(A(value)) {}
. You have rejected this solution as not general enough, but without clarifying what the "general" problem is.
I conjecture that the more general problem you want solved is to make C
unambiguously implicitly convertible from any type U
that is implicitly convertible to A
using the sequence of conversions U => A => C
. This is achievable by introducing an additional template constructor to C
(Live code demo at Coliru):
template <typename U, typename=typename std::enable_if<
!std::is_base_of<A,typename std::decay<U>::type>::value &&
std::is_convertible<U&&, A>::value>::type>
C(U&& u) : C(A{std::forward<U>(u)}) {}
The template constructor is a direct match for C(U)
, and so is unambiguously preferred over the C(A)
and C(B)
constructors that would require a conversion. It is constrained to accept only types U
such that
U
is convertible to A
(for obvious reasons)U
is not A
or a reference to A
or a type derived from A
, to avoid ambiguity with the C(const A&)
constructor and infinite recursion in the case that U
is e.g. A&
or A&&
.Notably this solution does not require changing the definitions of T
, A
, B
, C(A const&)
or C(B const&)
, so it is nicely self-contained.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With