#include <type_traits>
#include <concepts>
#include <utility>
#include <string>
#include <sstream>
#include <tuple>
template <typename F_, typename... Ts_>
struct cvt_adaptor {
F_ f;
std::tuple<Ts_...> args;
template <typename T_>
requires(requires (Ts_... ts) {
{ std::move(f)(std::type_identity<T_>{}, ts...) } -> std::convertible_to<T_>;
})
constexpr operator T_ () && noexcept(noexcept(std::move(f)(std::type_identity<T_>{}, std::declval<Ts_>()...))) {
return [&]<std::size_t... is>(std::index_sequence<is...>){
return std::move(f)(std::type_identity<T_>{}, std::get<is>(std::move(args))...);
}(std::index_sequence_for<Ts_...>{});
}
};
template <typename... Fs_>
struct overload : Fs_... {
using Fs_::operator()...;
};
template <typename T_, typename... Xs_>
concept one_of = (... || std::is_same_v<T_, std::type_identity<Xs_>>);
// --- ^^^ --- write onece, in your library somewhere
// --- vvv ---- your ambiguous function
constexpr auto ambiguous(std::string s) {
return cvt_adaptor{overload{
[](one_of<std::string> auto return_type, auto&& s) {
return std::move(s);
},
[](one_of<int, float, double> auto return_type, auto&& s) {
std::stringstream ss(std::move(s));
typename decltype(return_type)::type x;
ss >> x;
return x;
}
}, std::tuple{std::move(s)}};
}
int main() {
float a = ambiguous("5.2");
int i = ambiguous("5");
// traits work too
static_assert(!std::is_convertible_v<std::size_t, decltype(ambiguous("3"))>);
}