【发布时间】:2016-11-26 11:54:53
【问题描述】:
从 C++11 开始,我们可以制作可以接受任何参数序列的模板函数:
template <typename... Ts>
void func(Ts &&... ts) {
step_one(std::forward<Ts>(ts)...);
step_two(std::forward<Ts>(ts)...);
}
但是,假设只有在每个参数具有相同类型的情况下调用我的函数才有意义——不过,任何数量的参数都可以。
最好的方法是什么,即有没有一种好的方法来约束模板以在这种情况下发出很好的错误消息,或者理想情况下,当参数不匹配时,消除func 参与重载决议?
如果有帮助,我可以把它具体化:
假设我有一些结构:
struct my_struct {
int foo;
double bar;
std::string baz;
};
现在,我希望能够做一些事情,例如打印结构的成员以进行调试、序列化和反序列化结构、按顺序访问结构的成员等。我有一些代码可以帮助解决这个问题:
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);
}
(生成这样的代码看起来有点费力,但我前段时间创建了a small library 来帮助解决这个问题。)
所以,现在我想扩展它,使它可以同时访问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)...);
}
但是现在,它变得有点松鼠了——如果有人传递了一系列甚至根本不是相同结构类型的类型,代码仍然可以编译并执行用户完全意想不到的事情。
我想过这样做:
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)...);
}
如果他们将它与不匹配的类型一起使用,至少会导致编译错误。但是,它也发生得太晚了——它发生在模板类型被解析之后,我猜是在重载解析之后。
我考虑过使用 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 编写这样的约束——我必须尝试找到我猜想的所有参数的共同基础?
有没有一种好方法可以调和这些顾虑?
【问题讨论】:
-
std::common_type? -
您希望它们都属于特定的已知类型,还是只是相同的未知类型?
-
@cpplearner:哦,那很好,我不知道这个
-
@Quentin:我想如果它会有所作为,我想看看一个例子。我想知道一种做更一般的方法的好方法——相同的、未知的类型
-
多次
forward<V>(v)没有意义。
标签: c++ templates c++11 variadic-templates visitor-pattern