如何转发元组类型以专门化其他模板?
how to forward the types of tuple to specialize other template?
目前我正在研究一种动态容器结构,它表示一个 pod 值或具有相同容器类型的指针向量。容器有一个接口 optional<T> expect_value<T>()
。对于 pod 类型,实现很简单。对于非 pod 值,我会调用 expect_value<tuple<args...>>()
,args
也将是元组。但是在实现这个功能的时候遇到了一个麻烦:如何把a.expect_value<tuple<args...>>()
重定向到a.expect_value_tuple<args...>>()
。例如,对 a.expect_value<tuple<int,int>()
的调用将 return a.expect_value_tuple<int, int>()
的结果。因为参数是空的,所以我不能使用解压参数的类型推导。然后整个项目就不能再进步了。有任何想法吗?以下是我的意图的最小示例。
#include <tuple>
#include <vector>
#include <optional>
#include <functional>
using namespace std;
template<typename T>
struct is_tuple_impl : std::false_type {};
template<typename... Ts>
struct is_tuple_impl<std::tuple<Ts...>> : std::true_type {};
template<typename T>
struct is_tuple : is_tuple_impl<std::decay_t<T>> {};
class my_container;
template<typename... args, size_t... arg_idx>
optional<tuple<args>...> get_tuple_value_from_vector(const vector<my_container*>& v_list, std::index_sequence<arg_idx...>)
{
auto temp_result = make_tuple((*v_list[arg_idx]).expect_value<arg>()...);
if(!(get<arg_idx>(temp_result) &&...))
{
return nullopt;
}
return make_tuple(get<arg_idx>(temp_result).value()...);
}
class my_container
{
public:
int value_type; // 1 for v_int 2 for v_list 0 empty
union
{
int v_int;
};
vector<my_container*> v_list;
template<typename T>
optional<T> expect_simple_value();
template<typename... args>
optional<tuple<args...>> expect_tuple_value();
template<typename T>
optional<T> expect_value();
};
template <typename T>
optional<T> my_container::expect_simple_value()
{
return nullopt;
}
template <>
optional<int> my_container::expect_simple_value()
{
if(value_type == 1)
{
return v_int;
}
return nullopt;
}
template<typename... args>
optional<tuple<args...>> my_container::expect_tuple_value()
{
if(v_list.size() == 0)
{
return nullopt;
}
for(const auto i: v_list)
{
if(!i)
{
return nullopt;
}
}
auto the_tuple_size = sizeof...(args);
if(v_list.size() != the_tuple_size)
{
return nullopt;
}
return get_tuple_value_from_vector<args...>(v_list, index_sequence_for<args...>{});
}
template <typename T>
optional<T> my_container::expect_value()
{
if(is_tuple<T>::value)
{
return expect_tuple_value<T>();
}
else
{
return expect_simple_value<T>();
}
}
int main()
{
my_container test_value;
test_value.value_type = 1;
test_value.v_int = 1;
auto result = test_value.expect_value<tuple<int, int>>();
if(result)
{
return 0;
}
else
{
return 1;
}
}
问题的核心是行 return expect_tuple_value<T>();
当逻辑到那里时,T 应该是 tuple<args...>
,但我想要的是 return return expect_tuple_value<args...>()
.
相关行之前的 if
应该是 constexpr if
.
如果不使用 class 助手,类型的解包很烦人。我可以用一些花哨的 c++14 lambda 动作来做到这一点。
template<class T>
struct tag_t{using type=T;};
template<class Tag>
using type=typename Tag::type;
template<class Tuple>
struct unpack_tuple;
template<class...Ts>
struct unpack_tuple<std::tuple<Ts...>> {
template<class F>
decltype(auto) operator()(F&& f)const {
return std::forward<F>(f)( tag_t<Ts>{}... );
}
};
#define TYPE_FROM(...) \
type< std::decay_t<decltype(__VA_ARGS__)> >
然后我们得到
if constexpr(is_tuple<T>::value)
{
return unpack_tuple<T>{}([&](auto...tags){
return expect_tuple_value<TYPE_FROM(tags)...>();
});
}
else
{
return expect_simple_value<T>();
}
完成。
通过函数模板的部分排序使用模板参数推导和重载决议怎么样:
class my_container
{
public:
template<class T> optional<T> expect_value_simple();
template<class...Args> optional<tuple<Args...>> expect_value_tuple();
private:
template<class T> struct deduce_type{};
template<typename T>
auto expect_value_dispatching(deduce_type<T>){
return expect_value_simple<T>();
}
template<typename...Args>
auto expect_value_dispatching(deduce_type<tuple<Args...>>){
return expect_value_tuple<Args...>();
}
public:
template<typename T>
auto expect_value(){
return expect_value_dispatching(deduce_type<T>{});
}
};
(Demo)
这里的核心问题是您需要至少进行一次参数推导才能从类型返回到其可变参数。为此,您必须将这种可变模板类型的一些实例传递给函数 - 但它不必是原始类型。
Yakk 的解决方案通过传递标签类型实例(每个元组类型一个)的可变参数 lambda 来实现这一点。这里的好处是每次都可以使用 lambda 而不是显式的中间函数。
Oliv 的解决方案使用单态类型,我们可以实例化并传递给函数进行类型推导。它更简洁,但每个用例都需要这样的中间函数。
这是一个(或多或少理论上的)版本,结合了两者,使用模板化可变参数 lambdas(C++20,他们显然甚至没有 clang support 截至目前):
template<class... Args>
struct MonostateTuple
{};
template<class... Args>
auto tupleToMonostate(std::tuple<Args...>)
{
return MonostateTuple<Args...>{};
}
template<class T, class F>
auto unpack_tuple(F&& f)
{
using MT = decltype(tupleToMonostate(std::declval<T>()));
return std::forward<F>(f)(MT{});
}
/// User code
template<class Tuple>
auto foo()
{
return unpack_tuple<Tuple>([&] <typename... Args> (MonostateTuple<Args...>) {
return expect_tuple_value<Args...>();
});
}
在 lambda 签名中有点难看(更不用说再次缺乏编译器支持)但理论上结合了两者的优点。
目前我正在研究一种动态容器结构,它表示一个 pod 值或具有相同容器类型的指针向量。容器有一个接口 optional<T> expect_value<T>()
。对于 pod 类型,实现很简单。对于非 pod 值,我会调用 expect_value<tuple<args...>>()
,args
也将是元组。但是在实现这个功能的时候遇到了一个麻烦:如何把a.expect_value<tuple<args...>>()
重定向到a.expect_value_tuple<args...>>()
。例如,对 a.expect_value<tuple<int,int>()
的调用将 return a.expect_value_tuple<int, int>()
的结果。因为参数是空的,所以我不能使用解压参数的类型推导。然后整个项目就不能再进步了。有任何想法吗?以下是我的意图的最小示例。
#include <tuple>
#include <vector>
#include <optional>
#include <functional>
using namespace std;
template<typename T>
struct is_tuple_impl : std::false_type {};
template<typename... Ts>
struct is_tuple_impl<std::tuple<Ts...>> : std::true_type {};
template<typename T>
struct is_tuple : is_tuple_impl<std::decay_t<T>> {};
class my_container;
template<typename... args, size_t... arg_idx>
optional<tuple<args>...> get_tuple_value_from_vector(const vector<my_container*>& v_list, std::index_sequence<arg_idx...>)
{
auto temp_result = make_tuple((*v_list[arg_idx]).expect_value<arg>()...);
if(!(get<arg_idx>(temp_result) &&...))
{
return nullopt;
}
return make_tuple(get<arg_idx>(temp_result).value()...);
}
class my_container
{
public:
int value_type; // 1 for v_int 2 for v_list 0 empty
union
{
int v_int;
};
vector<my_container*> v_list;
template<typename T>
optional<T> expect_simple_value();
template<typename... args>
optional<tuple<args...>> expect_tuple_value();
template<typename T>
optional<T> expect_value();
};
template <typename T>
optional<T> my_container::expect_simple_value()
{
return nullopt;
}
template <>
optional<int> my_container::expect_simple_value()
{
if(value_type == 1)
{
return v_int;
}
return nullopt;
}
template<typename... args>
optional<tuple<args...>> my_container::expect_tuple_value()
{
if(v_list.size() == 0)
{
return nullopt;
}
for(const auto i: v_list)
{
if(!i)
{
return nullopt;
}
}
auto the_tuple_size = sizeof...(args);
if(v_list.size() != the_tuple_size)
{
return nullopt;
}
return get_tuple_value_from_vector<args...>(v_list, index_sequence_for<args...>{});
}
template <typename T>
optional<T> my_container::expect_value()
{
if(is_tuple<T>::value)
{
return expect_tuple_value<T>();
}
else
{
return expect_simple_value<T>();
}
}
int main()
{
my_container test_value;
test_value.value_type = 1;
test_value.v_int = 1;
auto result = test_value.expect_value<tuple<int, int>>();
if(result)
{
return 0;
}
else
{
return 1;
}
}
问题的核心是行 return expect_tuple_value<T>();
当逻辑到那里时,T 应该是 tuple<args...>
,但我想要的是 return return expect_tuple_value<args...>()
.
相关行之前的 if
应该是 constexpr if
.
如果不使用 class 助手,类型的解包很烦人。我可以用一些花哨的 c++14 lambda 动作来做到这一点。
template<class T>
struct tag_t{using type=T;};
template<class Tag>
using type=typename Tag::type;
template<class Tuple>
struct unpack_tuple;
template<class...Ts>
struct unpack_tuple<std::tuple<Ts...>> {
template<class F>
decltype(auto) operator()(F&& f)const {
return std::forward<F>(f)( tag_t<Ts>{}... );
}
};
#define TYPE_FROM(...) \
type< std::decay_t<decltype(__VA_ARGS__)> >
然后我们得到
if constexpr(is_tuple<T>::value)
{
return unpack_tuple<T>{}([&](auto...tags){
return expect_tuple_value<TYPE_FROM(tags)...>();
});
}
else
{
return expect_simple_value<T>();
}
完成。
通过函数模板的部分排序使用模板参数推导和重载决议怎么样:
class my_container
{
public:
template<class T> optional<T> expect_value_simple();
template<class...Args> optional<tuple<Args...>> expect_value_tuple();
private:
template<class T> struct deduce_type{};
template<typename T>
auto expect_value_dispatching(deduce_type<T>){
return expect_value_simple<T>();
}
template<typename...Args>
auto expect_value_dispatching(deduce_type<tuple<Args...>>){
return expect_value_tuple<Args...>();
}
public:
template<typename T>
auto expect_value(){
return expect_value_dispatching(deduce_type<T>{});
}
};
(Demo)
这里的核心问题是您需要至少进行一次参数推导才能从类型返回到其可变参数。为此,您必须将这种可变模板类型的一些实例传递给函数 - 但它不必是原始类型。
Yakk 的解决方案通过传递标签类型实例(每个元组类型一个)的可变参数 lambda 来实现这一点。这里的好处是每次都可以使用 lambda 而不是显式的中间函数。
Oliv 的解决方案使用单态类型,我们可以实例化并传递给函数进行类型推导。它更简洁,但每个用例都需要这样的中间函数。
这是一个(或多或少理论上的)版本,结合了两者,使用模板化可变参数 lambdas(C++20,他们显然甚至没有 clang support 截至目前):
template<class... Args>
struct MonostateTuple
{};
template<class... Args>
auto tupleToMonostate(std::tuple<Args...>)
{
return MonostateTuple<Args...>{};
}
template<class T, class F>
auto unpack_tuple(F&& f)
{
using MT = decltype(tupleToMonostate(std::declval<T>()));
return std::forward<F>(f)(MT{});
}
/// User code
template<class Tuple>
auto foo()
{
return unpack_tuple<Tuple>([&] <typename... Args> (MonostateTuple<Args...>) {
return expect_tuple_value<Args...>();
});
}
在 lambda 签名中有点难看(更不用说再次缺乏编译器支持)但理论上结合了两者的优点。