元组的运行时索引
Run-time indexing of tuple
假设我有一个变量constructors
,它是用可变参数泛型lambda表示的构造函数元组。
// types for constructors
using type_tuple = std::tuple<ClassA, ClassB, ClassC>;
// Get a tuple of constructors(variadic generic lambda) of types in type_tuple
auto constructors = execute_all_t<type_tuple>(get_construct());
// For definitions of execute_all_t and get_construct, see link at the bottom.
我可以使用以下命令实例化一个对象:
// Create an object using the constructors, where 0 is index of ClassA in the tuple.
ClassA a = std::get<0>(constructors)(/*arguments for any constructor of ClassA*/);
是否可以在运行时使用如下所示的magic_get
为类型编制索引?
auto obj = magic_get(constructors, 0)(/*arguments for any constructor of ClassA*/);
// Maybe obj can be a std::variant<ClassA, ClassB, ClassC>, which contains object of ClassA?
编辑:理想情况下,obj
应该是ClassA
的实例。如果不可能,我可以接受obj
std::variant<ClassA, ClassB, ClassC>
.
请查看最小可重现示例:在线试用!
类似的问题:C++11 在运行时不使用 switch 索引元组的方法 .
你可能会让你的运行时得到返回std::variant
,如下所示:
template <typename ... Ts, std::size_t ... Is>
std::variant<Ts...> get_impl(std::size_t index,
std::index_sequence<Is...>,
const std::tuple<Ts...>& t)
{
using getter_type = std::variant<Ts...> (*)(const std::tuple<Ts...>&);
getter_type funcs[] = {+[](const std::tuple<Ts...>& tuple)
-> std::variant<Ts...>
{ return std::get<Is>(tuple); } ...};
return funcs[index](t);
}
template <typename ... Ts>
std::variant<Ts...> get(std::size_t index, const std::tuple<Ts...>& t)
{
return get_impl(index, std::index_sequence_for<Ts...>(), t);
}
然后,您可能会std::visit
变体来执行您想要的操作。
演示
或者对于您的"工厂"示例:
int argA1 = /*..*/;
std::string argA2 = /*..*/;
int argB1 = /*..*/;
// ...
auto obj = std::visit(overloaded{
[&](const A&) -> std::variant<A, B, C> { return A(argA1, argA2); },
[&](const B&) -> std::variant<A, B, C> { return B(argB1); },
[&](const C&) -> std::variant<A, B, C> { return C(); },
}, get(i, t))
这可能做得更好,但这里是根据您在评论中的要求进行的尝试。
需要 C++17,适用于 Clang,但在 GCC 上给出内部编译器错误。
不过,它确实需要使构造函数 SFINAE 友好,否则无法检查是否可以调用它:
所以使用
return [](auto... args) -> decltype(U(args)...) { return U(args...); };
而不是
return [](auto... args) { return U(args...); };
给定参数tup
和index
的此函数的行为如下:
它返回一个 lambda,当使用参数列表调用该 lambda 时,将返回可能由std::get<i>(tup)(/*arguments*/)
形式的调用导致的所有类型的std::variant
。其中哪一个实际调用并存储在返回的变体中,在运行时通过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 到函数指针技巧的语法+
(但我可以使用任何其他一元运算符(。
实际的指针值无关紧要,我只需要类型,但模板参数必须是常量表达式,所以我让它成为空指针。后一个要求是为什么我不使用内置的运算符 address-of 运算符&
而不是重载的+
。
- CMake-按正确顺序将项目与C运行时对象文件链接
- 我在c++代码中生成了一个运行时#3异常
- 为什么在运行时没有向我们提供有关分段错误的更多信息?
- 删除指向指针的指针是运行时错误吗
- 如何用参数值调用函数(仅在运行时已知)
- 为什么即使使用-cudart-static进行编译,库用户仍然需要链接到cuda运行时
- 是否可以在编译时初始化数组,以便在运行时不会花费时间?
- c++中的指针和运行时错误
- 元组的运行时索引
- 运行时错误:基0x000000000000溢出到0xffffffffffffffff的指针索引表达式 (basic_st
- 使用variadic模板和运行时索引构造iterator_range
- 在运行时C++11 个索引模板参数包,以便访问第 N 种类型
- 使用查找表选择具有运行时索引的可变参数类型
- 在运行时按索引访问 std::tuple 元素的最佳方法
- C++11 在运行时不使用 switch 为元组编制索引的方法
- 在运行时获取元组中索引的类型
- 当索引超出向量类的范围时,没有编译错误或运行时错误
- QAbstractItemModel::endInsertRows:无效索引运行时错误
- 在c++中,当元组的元素索引在运行时已知时,是否有可能获得该元素的类型?
- 如何获取相对于运行时索引的类型