I have a code that should implement SFINAE to resolve between two different methods, which however have a different template signature: one has an additional non-type template parameter. As suggested here, for example, one should better implement the SFINAE in the template parameters.
The compiler however issues an "ambiguous call" error.
This (simplified) example illustrates the issue
#include <iostream>
#include <type_traits>
template <bool X>
class A {
template <unsigned int I>
void print_true_impl();
void print_false_impl();
public:
template <unsigned int I, bool activate = X, class = std::enable_if_t<activate>>
void print() { print_true_impl<I>(); }
template <bool activate = !X, class = std::enable_if_t<activate>>
void print() { print_false_impl(); }
};
template <bool X>
template <unsigned int I>
void A<X>::print_true_impl()
{
std::cout << "true " << I << std::endl;
}
template <bool X>
void A<X>::print_false_impl()
{
std::cout << "false" << std::endl;
}
int main()
{
A<true> atrue;
A<false> afalse;
afalse.print();
atrue.print<1>();
return 0;
}
Check it live on Coliru.
The compile error appears on
atrue.print<1>();
The reason for ambiguity lies in the fact that the template parameter <1>
is implicitly converted to bool, hence the compiler can pick up the overload
template <bool activate = !X, class = std::enable_if_t<activate>>
void print() { print_false_impl(); }
setting activate = true
, and ignoring the default activate = !X
(=false
when called from atrue
).
Ideally one would like to avoid the implicit conversions in the template parameters, such that bool activate
is always set to the default value.
So, one way to solve the ambiguity would be to prepend the templates with a class to which an unsigned int cannot converted to. For example
template <unsigned int I, class = A, bool activate = X, class = std::enable_if_t<activate>>
void print() { print_true_impl<I>(); }
template <class = A, bool activate = !X, class = std::enable_if_t<activate>>
void print() { print_false_impl(); }
This works, however it seems inelegant to artificially add a useless template parameter.
Another alternative would be to pack the choice of overload inside a single method using if-constexpr
, like:
#include <iostream>
#include <type_traits>
template <auto...>
inline constexpr bool dependent_false_v = false;
template <bool X>
class A {
template <unsigned int I>
void print_true_impl();
void print_false_impl();
public:
template <unsigned int... I>
void print() {
if constexpr (sizeof...(I) == 0) {
print_false_impl();
}
else if constexpr (sizeof...(I) == 1) {
print_true_impl<I...>();
}
else {
static_assert(dependent_false_v<I...>, "Cannot call print()!");
}
}
};
Is there a better solution, within c++17 (no concepts), and using SFINAE and not if-constexpr?