4

Disclaimer: I know that using user defined implicit conversions is often discouraged. However, in our project, we need these conversions for various template classes to work well with each other.

I need to define a precedence of user defined conversions, e.g.:

struct X{}
struct Y{}

struct Z{
    operator X(){...}
    operator Y(){...}
}

void foo(X x){...}
void foo(Y y){...}

// somewhere in some template client code
...{
    Z z = ...;
    ...
    foo(z); // WILL NOT COMPILE
}

This will not compile, as the conversion from Z to either X or Y is ambiguous. Is there a way to resolve this ambiguity. I.e, can I somehow tell the compiler: if there is a function overloaded for X and Y, then prefer to cast Z to X instead of failing to compile.

I know that there is no simple way to specify this. But maybe some template magic and a wrapping template struct that I am not aware of might do the trick. I am allowed to change the client code. I cannot use an explicit cast, as the client code is a templated code that is not aware about the type Z and the available overloads of foo.

Niall
  • 30,036
  • 10
  • 99
  • 142
gexicide
  • 38,535
  • 21
  • 92
  • 152
  • I can by rewriting `foo`? – Yakk - Adam Nevraumont Sep 26 '14 at 12:25
  • 1. Can you modify the templates also? 2. Can you show us an example of a template that doesn't do what you want? – Ben Sep 26 '14 at 12:28
  • What is the differentiating factor when deciding which flavor of `foo` you want to call? – John Dibling Sep 26 '14 at 13:17
  • @JohnDibling: I always want to prefer `foo(X)` over `foo(Y)` if such `foo`s exist (which is not always the case). If only a `foo(Y)` exists, I want the conversion to `Y`. If only a `foo(X)` exists, then the conversion to `X`, of course. – gexicide Sep 26 '14 at 18:23
  • Sounds like a case for SFINAE – John Dibling Sep 26 '14 at 19:29
  • Is the only thing you can do is modify `Z`? If so you are out of luck. If I can do stuff at other points I can solve it. But need clarification from gexicide. – Yakk - Adam Nevraumont Sep 27 '14 at 14:07
  • @Yakk: No, I can modify everything. However, the code that uses `foo(z)` may *not* hardcode the cast (e.g., not simply `static_cast` to `X`), because the code should be independent from the types to which `Z` can be cast. I.e., the code that calls `foo(z)` may neither mention `X` nor `Y`. – gexicide Sep 29 '14 at 08:08
  • So we can fix `foo` to understand to prefer `X` over `Y`, or I can set up a complex system to take a `foo` function proxy and an argument and ask the argument which it prefers. First is way easier. – Yakk - Adam Nevraumont Sep 29 '14 at 12:02
  • @Yakk: Yes, fixing `foo` is allowed. – gexicide Sep 29 '14 at 12:04

3 Answers3

1

You can do something like "prioritized call", with a pattern like this:

struct P2 {};
struct P1: P2 {};

template<class A> 
void foo(A x, P1, typename std::common_type<X,A>::type* =nullptr) 
{ foo(static_cast<X>(x)); }

template<class A> 
void foo(A y, P2, typename std::common_type<Y,A>::type* =nullptr) 
{ foo(static_cast<Y>(y)); }

template<class A> void foo(A a) { foo(a,P1()); }

being P2 a base for P1 and the call made with P1, if common_type can compile, the first version goes. If it cannot compile, the first version is like not existent (SFINAE) and the second goes. If it cannot compile as well ... if A is just X or just Y the respective original foo is called, otherwise this cannot compile being types incompatible.

Note that you can even generalize the "priority" as

template<size_t N> struct P: P<N+1> {}
template<> struct P<10> {}

declare SFINAE functions taking P<1>, P<2> etc. up to P<10>, and place the root call with P<0>()

Emilio Garavaglia
  • 20,229
  • 2
  • 46
  • 63
1

Here is a little system that smart casts a variable to to a sequence of types Ts... such that the first element in the list Ts... that the variable implicitly converts to is the one chosen:

namespace details {
  template<class...>struct types{using type=types;};
  template<class U, class Types, class=void>
  struct smart_cast_t:std::false_type {
    using type=U;
    template<class A>
    U operator()(A&& a)const{return std::forward<A>(a);}
  };
  template<class U, class T0, class...Ts>
  struct smart_cast_t<
    U, types<T0, Ts...>,
    typename std::enable_if<std::is_convertible<U, T0>::value>::type
  >:std::true_type
  {
    using type=T0;
    template<class A>
    T0 operator()(A&& a)const{return std::forward<A>(a);}
  };
  template<class U, class T0, class...Ts>
  struct smart_cast_t<
    U, types<T0, Ts...>,
    typename std::enable_if<!std::is_convertible<U, T0>::value>::type
  >:smart_cast_t< U, types<Ts...> >
  {};
}

template<class... Ts, class U>
auto smart_cast( U&& u )
-> decltype(details::smart_cast_t< U, details::types<Ts...> >{}( std::forward<U>(u) ))
{
  return details::smart_cast_t< U, details::types<Ts...> >{}( std::forward<U>(u) );
}

Now, the idea is that we can now modify foo as follows:

void foo_impl(X);
void foo_impl(Y);

template<class A>
void foo(A&& a) {
  foo_impl( smart_cast<X, Y>(std::forward<A>(a)) );
}

and foo casts the A to X if possible, and if not to Y.

We could write up an entire system whereby we pass in a description of the overloads of foo in a package like types< types<X,Y> > and an overload set of foo to some magic code, which spits out a dispatcher, but that would be over engineering.

live example

This uses C++11 features. With C++14 we get to drop some verbage in smart_cast.

The design is pretty simple. We create a types bundle to work with bundles of types (just boilerplate).

Then our details::smart_cast_t has a fallback base specialization that just converts our U to U. If we can convert U to the first type, we do so. Otherwise, we recurse on our parent type, possibly terminating in the base specialization.

I hide that in details, so our public function is simple -- it is smart_cast< type1, type2, type3, etc >( expression ). We don't have to pass U the type of the expression, as we deduce it, then pass it into details for the work to be done.

At run time this reduces to a single implicit cast: all the above is what is done at compile time.

About the only downside is that this can result in warnings on some compilers, as we use implicit conversion. Add a static_cast<T0> to the first non-base specialization of smart_cast_t to avoid this.

I inherited smart_cast_t from true_type and false_type needlessly. This means that the value of smart_cast_t<U, types<Ts...>>::value will tell you if U converted to any of Ts..., or was just left alone as a U.

The caller is responsible for rvalue vs lvalue categories. If the cast fails, it will return a U not a U&& if passed an rvalue. The fallback -- to U -- I think will generate better error messages, but if you prefer that smart_cast<int, double>(std::string("hello")) fail to compile instead of returning a std::string, simply remove the operator() from the base specialization of smart_cast_t.

Speaking of which, I also don't work right for smart_cast<int, double>(""). Might need some typename std::decay<U>::types or something in there.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

Yes, cast it explicitly:

foo(static_cast<Y>(z));
Paul Evans
  • 27,315
  • 3
  • 37
  • 54
  • This is not possible. The client code is a template code which does not know about 1) which overloads are available for `foo` and 2) which casts are available for `z`. It is all determined by the template parameters. I have clearified my question. – gexicide Sep 26 '14 at 11:50