Chr*_*eck 24 c++ templates visitor variadic-templates c++11
从C++ 11开始,我们可以创建可以接受任何参数序列的模板函数:
template <typename... Ts>
void func(Ts &&... ts) {
step_one(std::forward<Ts>(ts)...);
step_two(std::forward<Ts>(ts)...);
}
Run Code Online (Sandbox Code Playgroud)
但是,假设在每个参数具有相同类型的情况下调用我的函数真的是有意义的 - 任何数量的参数都可以.
这样做的最佳方法是什么,即在这种情况下是否有一种很好的方法来约束模板以生成一个很好的错误消息,或者理想情况下,func
当参数不匹配时,可以避免参与重载解析?
如果它有帮助我可以使它真正具体:
假设我有一些结构:
struct my_struct {
int foo;
double bar;
std::string baz;
};
Run Code Online (Sandbox Code Playgroud)
现在,我希望能够执行以下操作:打印结构的成员以进行调试,序列化和反序列化结构,按顺序访问结构的成员等.我有一些代码可以帮助解决这个问题:
template <typename V>
void apply_visitor(V && v, my_struct & s) {
std::forward<V>(v)("foo", s.foo);
std::forward<V>(v)("bar", s.bar);
std::forward<V>(v)("baz", s.baz);
}
template <typename V>
void apply_visitor(V && v, const my_struct & s) {
std::forward<V>(v)("foo", s.foo);
std::forward<V>(v)("bar", s.bar);
std::forward<V>(v)("baz", s.baz);
}
template <typename V>
void apply_visitor(V && v, my_struct && s) {
std::forward<V>(v)("foo", std::move(s).foo);
std::forward<V>(v)("bar", std::move(s).bar);
std::forward<V>(v)("baz", std::move(s).baz);
}
Run Code Online (Sandbox Code Playgroud)
(生成这样的代码看起来有点费力,但是前段时间我做了一个小型库以帮助解决这个问题.)
所以,现在我想扩展它,以便它可以同时访问两个实例my_struct
.如果我想实现相等或比较操作,那么使用它.在boost::variant
文档中,他们称之为"二元访问",与"一元访问"形成鲜明对比.
可能没有人愿意做二元访问.但是假设我想做一般的n-ary
访问.然后,我猜这看起来像这样
template <typename V, typename ... Ss>
void apply_visitor(V && v, Ss && ... ss) {
std::forward<V>(v)("foo", (std::forward<Ss>(ss).foo)...);
std::forward<V>(v)("bar", (std::forward<Ss>(ss).bar)...);
std::forward<V>(v)("baz", (std::forward<Ss>(ss).baz)...);
}
Run Code Online (Sandbox Code Playgroud)
但现在,它变得更加松散 - 如果有人传递了一系列甚至根本不是相同结构类型的类型,代码仍然可以编译并做一些完全意外的用户.
我想这样做:
template <typename V, typename ... Ss>
void apply_visitor(V && v, Ss && ... ss) {
auto foo_ptr = &my_struct::foo;
std::forward<V>(v)("foo", (std::forward<Ss>(ss).*foo_ptr)...);
auto bar_ptr = &my_struct::bar;
std::forward<V>(v)("bar", (std::forward<Ss>(ss).*bar_ptr)...);
auto baz_ptr = &my_struct::baz;
std::forward<V>(v)("baz", (std::forward<Ss>(ss).*baz_ptr)...);
}
Run Code Online (Sandbox Code Playgroud)
如果它们使用不匹配的类型,至少会导致编译错误.但是,它也发生得太晚了 - 它在模板类型被解决后发生,并且在重载解析后我想.
我想过使用SFINAE,而不是返回void,使用std::enable_if_t
并检查std::is_same<std::remove_cv_t<std::remove_reference_t<...>>
参数包中每种类型的一些表达式.
但是对于一个,SFINAE表达式相当复杂,对于两个,它也有一个缺点 - 假设某人有一个派生类struct my_other_struct : my_struct { ... }
,并且他们想要将它与访问者机制一起使用,所以有些参数是my_struct
,有些是my_other_struct
.理想情况下,系统将所有引用转换为my_struct
和应用访问者这种方式,并且据我所知我与成员指针上面给的例子foo_ptr
,bar_ptr
,baz_ptr
会做正确的事情存在,但它甚至不是很清楚,我怎么写这样的一个约束与SFINAE - 我想要找到我猜的所有参数的共同基础?
是否有一种很好的方法可以协调这些问题?
Que*_*tin 15
有了std::common_type
,这很简单:
template <class... Args, class = std::common_type_t<Args...>>
void foo(Args &&... args) {
}
Run Code Online (Sandbox Code Playgroud)
不过,这只会保证SFINAE
从一C++17
开始就是友好的.Clang
并且GCC
两者都已经实现了.
这里有一个类型的特征,你可以在使用static_assert
或std::enable_if
在您的休闲.
template <class T, class ... Ts>
struct are_all_same : conjunction<std::is_same<T, Ts>...>{};
template <class Ts...>
struct conjunction : std::true_type{};
template <class T, class ... Ts>
struct conjunction<T, Ts...> :
std::conditional<T::value, conjunction<Ts...>, std::false_type>::type {};
Run Code Online (Sandbox Code Playgroud)
它非常简单地用第一个类型检查每个类型,如果有任何不同则失败.
我认为使用std::common_type
看起来像这样:
template <class ... Args>
typename std::common_type<Args...>::type common_type_check(Args...);
void common_type_check(...);
template <class ... Ts>
struct has_common_type :
std::integral_constant<
bool,
!std::is_same<decltype(common_type_check(std::declval<Ts>()...)), void>::value> {};
Run Code Online (Sandbox Code Playgroud)
那你可以做 static_assert(std::has_common_type<Derived, Base>::value, "")
当然,这种方法并不是万无一失的,因为common_type
在基类方面有一些限制:
struct A {};
struct B : A{};
struct C : A{};
struct D : C{};
struct E : B{};
static_assert(has_common_type<E, D, C, A, B>::value, ""); //Fails
static_assert(has_common_type<A, B, C, D, E>::value, ""); //Passes
Run Code Online (Sandbox Code Playgroud)
这是因为模板首先尝试在D
和之间获取公共类型E
(即,auto a = bool() ? D{}: E{};
无法编译).
你真正想要的是:
template<typename T, T ... args>
void myFunc(T ... args);
Run Code Online (Sandbox Code Playgroud)
但显然上面不是合法的语法.但是,您可以解决这个问题,并且using
可以帮助您.所以这个想法是这样的:
template<typename T, size_t val>
using IdxType = T;
Run Code Online (Sandbox Code Playgroud)
上面没有真正的目的:一个IdxType<T, n>
只是T
任何一个n
.但是,它允许您这样做:
template<typename T, size_t ... Indices>
void myFunc(IdxType<T, Indices> ... args);
Run Code Online (Sandbox Code Playgroud)
这很好,因为这正是您需要获得一组可变参数的可变参数.剩下的唯一问题是你不能做的事情myFunc(obj1, obj2, obj3)
,因为编译器将无法推断出所需的Indices
- 你将不得不做myFunc<1,2,3>(obj1, obj2, obj3)
,这是丑陋的.幸运的是,您可以通过包装辅助函数来解决这个问题,该函数负责使用索引生成make_index_sequence
.
下面是一个完整的示例,它与您的访问者类似(此处为现场演示):
template<typename T, size_t sz>
using IdxType = T;
struct MyType
{};
struct Visitor
{
void operator() (const MyType&)
{
std::cout << "Visited" << std::endl;
}
};
template <typename V>
void apply_visitor(std::index_sequence<>, V && v)
{
}
template <typename V, typename T, size_t FirstIndex, size_t ... Indices>
void apply_visitor(std::index_sequence<FirstIndex, Indices...>, V && v, T && first, IdxType<T, Indices> && ... ss) {
std::forward<V>(v)(std::forward<T>(first));
apply_visitor(std::index_sequence<Indices...>(), std::forward<V>(v), std::forward<T>(ss) ...);
}
template <typename V, typename T, typename ... Rest>
void do_apply_visitor(V && v, T && t, Rest && ... rest )
{
apply_visitor(std::make_index_sequence<sizeof...(Rest)+1>(), v, t, rest ... );
}
int main()
{
Visitor v;
do_apply_visitor(v, MyType{}, MyType{}, MyType{});
return 0;
}
Run Code Online (Sandbox Code Playgroud)