I'm trying to overload a templated function but they turn out to be ambiguous. The function is used to convert a value into a user-defined type (which can be whatever.) The user provides a conversion function and, in some cases, default values or a check function. In order to detect Callables I rely upon this answer and its definition of is_callable
.
(Simplified) Example below:
#include <iostream>
#include <type_traits>
#include <utility>
#include <functional>
template<class F, class...Args>
struct is_callable {
template<class U> static auto test(U* p) -> decltype((*p)(std::declval<Args>()...), void(), std::true_type());
template<class U> static auto test(...) -> decltype(std::false_type());
static constexpr bool value = decltype(test<F>(0))::value;
};
class Proto {
template <class U>
using ConvertedParamType = typename std::decay<
typename std::result_of<typename std::decay<U>::type&(const std::string&)>::type>::type;
public:
/* --- (A) Ambiguous: ------- */
template <class F, class T = ConvertedParamType<F>,
class C, typename = typename std::enable_if<
is_callable<F, const std::string&>::value && is_callable<C, T&>::value>::type>
T getParamFunc(const std::string& value, F pconv_functor, C pcheck_functor) const
{
std::cout << "** Converting (A)!\n";
T retval = pconv_functor(value);
pcheck_functor(retval);
return retval;
}
/* --- (B) Ambiguous: ------- */
template <class F, class T = ConvertedParamType<F>,
typename = typename std::enable_if<
is_callable<F, const std::string&>::value &&
(not is_callable<T, T&>::value)
>::type >
T getParamFunc(const std::string& value, F pconv_functor, T dflt_val) const
{
std::cout << "** Converting (B)!\n";
T retval = pconv_functor(value);
return (retval > dflt_val ? retval : dflt_val);
}
};
I (partially) understand why the compiler finds (A) and (B) ambiguous, but I can't reason whether this owes to the definition of is_callable
or something else.
Would there be a way to fix this? Or a better way to address this design(*)? My sincere apologies if this is either silly or impossible, I'm still learning how to properly implement metaprogramming techniques.
Test code:
int func1(const std::string& s) {
std::cout << "*** Converting with func1!\n";
return std::stoi(s);
}
int main(void)
{
auto f = [](const std::string& s) -> int {
std::cout << "*** Converting with lambda f!\n";
return std::stoi(s);
};
auto c = [](int& i) {
i *= -1;
};
Proto p;
/* OK: */
std::cout << "Converted value: " << p.getParamFunc("123", func1) << "\n";
std::cout << "Converted value: " << p.getParamFunc("123", f) << "\n";
/* Error, ambiguous (both with f or func1): */
std::cout << "Converted value: " << p.getParamFunc("123", f, 456) << "\n";
std::cout << "Converted value: " << p.getParamFunc("123", f, c) << "\n";
return 0;
}
(* NB: I need the user to provide its own conversion function and I want to overload getParamFunc()
. My design actually has a lot of additional overloads, including ones in which boundaries are given or others in which a different check function is expected. Both the default value and the return type T
should be deducible in all cases, but I think this is already sorted out.)