Ivan Smirnov - 4 months ago 22

C++ Question

I have multiple overloads of the same function. For some set of arguments several overloads fit equally, and I naturally get a "ambiguous overload" error. I want to define priorities to these functions someway such that in case of ambiguity the one with the smallest priority is selected.

I tried to make a series of template helper classes, "tags",

`P<0>`

`P<1>`

`P<N>`

`P<N+1>`

`f(P<2>)`

`f(P<4>)`

`f(P<0>)`

It doesn't work in practice. The "clean" example with classic

"int/long" and "long/int" functions still produces an ambiguity. I tried to add several tag parameters to the function to increase the "weight" of a tag, but it does'n help.

Can this approach be tuned somehow?

`constexpr static int MAX = 20;`

template<int N, class Enable = void>

class P {};

template<int N>

class P<N, typename std::enable_if<N < MAX, void>::type> : public P<N+1> {};

void f(int, long, P<2>) {}

void f(long, int, P<5>) {}

int main() {

f(1, 2, P<0>());

}

Answer

First of all, you can simplify your tag type construction like:

```
template <int N> struct P : P<N+1> { };
template <> struct P<MAX> { };
```

Next, the escalator of tag types only works if they are **the sole** tiebreaker for your overloads. That is, every other argument is equivalent - except the tag argument. The reason your calls are still ambiguous is that the conversion sequences you end up with are:

```
f(int, long, P<2>); // #1: Exact, Integral Conversion, P<0> -> ... -> P<2>
f(long, int, P<5>); // #2: Integral Conversion, Exact, P<0> -> ... -> P<2> -> ... -> P<5>
```

One overload is only better than another overload if, for **each** argument, the conversion sequence is at least as good as the other overload's argument's conversion sequence. That's not true here: `#1`

is better in the first argument and worse in the second argument than `#2`

. The addition of another argument doesn't change that flaw.

Where the tag types are helpful is if you are using SFINAE with non-disjoint conditions:

```
template <class T, std::enable_if_t<cond_a<T>::value>* = nullptr>
void f(T&&, P<0> ) { ... }
template <class T, std::enable_if_t<cond_b<T>::value>* = nullptr>
void f(T&&, P<1> ) { ... }
template <class T, std::enable_if_t<cond_c<T>::value>* = nullptr>
void f(T&&, P<2> ) { ... }
f(whatever, P<0>{});
```

`cond_a`

, `cond_b`

, and `cond_c`

need not be disjoint, and the first argument in every case is the same. So the final tiebreaker, for those overloads that aren't removed by SFINAE, is the tag.