Prevent implicit conversion of constructor arguments to external library type
boost, c++, c++11, constructor, implicit-conversion
Solution
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.
Problem
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).