这可能会做得更好,但这里是根据您在 cmets 中的要求的尝试。
需要 C++17,适用于 Clang,但在 GCC 上提供 Internal Compiler Error。
不过,它确实需要使构造函数对 SFINAE 友好,否则无法检查它是否可以调用:
所以用
return [](auto... args) -> decltype(U(args)...) { return U(args...); };
而不是
return [](auto... args) { return U(args...); };
给定参数tup 和index,此函数的行为如下:
它返回一个 lambda,当使用参数列表调用该 lambda 时,将返回 std::variant,其中包含可能由 std::get<i>(tup)(/*arguments*/) 形式的调用产生的所有类型。其中哪一个被实际调用并存储在返回的变体中是在运行时通过index 参数决定的。如果index 引用的元组元素不能像std::get<index>(tup)(/*arguments*/) 那样调用,则在运行时抛出异常。
中间的 lambda 可以存储并在以后调用。但请注意,它保存了对 tup 参数的引用,因此如果您不立即调用并丢弃它,您需要确保该参数比 lambda 更有效。
#include <tuple>
#include <type_traits>
#include <variant>
#include <utility>
#include <stdexcept>
template<auto V> struct constant_t {
static constexpr auto value = V;
using value_type = decltype(value);
constexpr operator value_type() const {
return V;
}
};
template<auto V>
inline constexpr auto constant = constant_t<V>{};
template<auto V1, auto V2>
constexpr auto operator+(constant_t<V1>, constant_t<V2>) {
return constant<V1+V2>;
}
template<typename T>
struct wrap_t {
using type = T;
constexpr auto operator+() const {
return static_cast<wrap_t*>(nullptr);
}
};
template<typename T>
inline constexpr auto wrap = wrap_t<T>{};
template<auto A>
using unwrap = typename std::remove_pointer_t<decltype(A)>::type;
template <typename Tup>
auto magic_get(Tup&& tup, std::size_t index) {
return [&tup, index](auto&&... args) {
// Get the input tuple size
constexpr auto size = std::tuple_size_v<std::remove_const_t<std::remove_reference_t<Tup>>>;
// Lambda: check if element i of tuple is invocable with given args
constexpr auto is_valid = [](auto i) {
return std::is_invocable_v<decltype(std::get<i>(tup)), decltype(args)...>;
};
// Lambda: get the wrapped return type of the invocable element i of tuple with given args
constexpr auto result_type = [](auto i) {
return wrap<std::invoke_result_t<decltype(std::get<i>(tup)), decltype(args)...>>;
};
// Recursive lambda call: get a tuple of wrapped return type using `result_type` lambda
constexpr auto valid_tuple = [=]() {
constexpr auto lambda = [=](auto&& self, auto i) {
if constexpr (i == size)
return std::make_tuple();
else if constexpr (is_valid(i))
return std::tuple_cat(std::make_tuple(result_type(i)), self(self, i + constant<1>));
else
return self(self, i + constant<1>);
};
return lambda(lambda, constant<std::size_t{0}>);
}();
// Lambda: get the underlying return types as wrapped variant
constexpr auto var_type =
std::apply([](auto... args) { return wrap<std::variant<unwrap<+args>...>>; }, valid_tuple);
/**
* Recursive lambda: get a variant of all underlying return type of matched functions, which
* contains the return value of calling function with given index and args.
*
* @param self The lambda itself
* @param tup A tuple of functions
* @param index The index to choose from matched (via args) functions
* @param i The running index to reach `index`
* @param j The in_place_index for constructing in variant
* @param args The variadic args for callling the function
* @return A variant of all underlying return types of matched functions
*/
constexpr auto lambda = [=](auto&& self, auto&& tup, std::size_t index, auto i, auto j,
auto&&... args) -> unwrap<+var_type> {
if constexpr (i == size)
throw std::invalid_argument("index too large");
else if (i == index) {
if constexpr (is_valid(i)) {
return unwrap<+var_type>{std::in_place_index<j>,
std::get<i>(tup)(decltype(args)(args)...)};
} else {
throw std::invalid_argument("invalid index");
}
} else {
return self(self, decltype(tup)(tup), index, i + constant<1>, j + constant<is_valid(i)>,
decltype(args)(args)...);
}
};
return lambda(lambda, std::forward<Tup>(tup), index, constant<std::size_t{0}>,
constant<std::size_t{0}>, decltype(args)(args)...);
};
}
在 C++20 中,您可以通过
来简化它
使用std::remove_cvref_t<Tup> 代替std::remove_const_t<std::remove_reference_t<Tup>>
-
将unwrap的定义改为:
template<auto A>
using unwrap = typename decltype(A)::type;
并将其用作unwrap<...> 而不是unwrap<+...>,这也允许从wrap_t 中删除operator+。
wrap/unwrap的目的:
wrap_t 旨在将类型转换为我可以传递给函数并从它们返回的值,而无需创建原始类型的对象(这可能会导致各种问题)。它实际上只是一个以类型为模板的空结构和一个类型别名type,它返回类型。
我把wrap写成一个全局内联变量,这样我就可以写wrap<int>而不是wrap<int>{},因为我认为额外的大括号很烦人。
unwrap<...> 不是真的需要的。 typename decltype(...)::type 也是如此,它只是返回 wrap 实例所代表的类型。
但我还是想要一些更简单的编写方式,但如果没有 C++20,这实际上是不可能的。在 C++20 中,我可以直接将 wrap 对象作为模板参数传递,但这在 C++17 中不起作用。
所以在 C++17 中,我将对象“衰减”为一个指针,该指针可以是一个非类型模板参数,并带有一个重载的 operator+,模仿了常见的 lambda-to-function-pointer 技巧的语法使用一元 + 运算符(但我可以使用任何其他一元运算符)。
实际的指针值无所谓,我只需要类型,但是模板参数必须是常量表达式,所以我让它为空指针。后一个要求是为什么我不使用内置的地址运算符& 而不是重载的+。