C ++参数包,是否必须具有单一类型的实例? [英] C++ parameter pack, constrained to have instances of a single type?

查看:109
本文介绍了C ++参数包,是否必须具有单一类型的实例?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

自C ++ 11起,我们可以制作可以接受任何参数序列的模板函数:

Since C++11 we can make template functions which can accept any sequence of arguments:

template <typename... Ts>
void func(Ts &&... ts) {
   step_one(std::forward<Ts>(ts)...);
   step_two(std::forward<Ts>(ts)...);
}

但是,假设只有在这种情况下调用我的函数才有意义其中每个参数都具有相同的类型-可以有任意数量的参数。

However, suppose that it really only makes sense to call my function in the case where each argument has the same type -- any number of arguments would be okay though.

做到这一点的最佳方法是什么,即是否有一种很好的方法来限制模板,以在这种情况下发出很好的错误消息,或者理想情况下,在参数不匹配时消除 func 参与重载解析?

What's the best way to do that, i.e. is there a good way to constrain the templates to make a nice error message in that case, or ideally, eliminate func from participating in overload resolution when the arguments don't match?

如果有帮助,我可以说得很具体:

I can make it really concrete if it helps:

假设我有一些结构:

struct my_struct {
  int foo;
  double bar;
  std::string baz;
};

现在,我希望能够做类似的事情,打印结构的成员以进行调试,序列化和反序列化该结构,依次访问该结构的成员等。我有一些代码可以帮助您:

Now, I want to be able to do things like, print the members of the struct for debugging purposes, serialize and deserialize the struct, visit the members of the struct in sequence, etc. I have some code to help with that:

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 href = https://github.com/cbeck88/visit_struct rel = noreferrer>一个小型图书馆前一段时间对此进行了帮助。)

(It looks a bit laborious to generate code like this, but I made a small library some time ago to help with that.)

因此,现在我想扩展它,以便它可以同时访问两个 my_struct 实例。它的用途是,如果我想实现相等或比较操作。在 boost :: variant 文档中,他们称这种情况为二进制访问,而不是一元访问。

So, now I would like to extend it so that it can visit two instances of my_struct at the same time. The use of that is, what if I want to implement equality or comparison operations. In boost::variant documentation they call that "binary visitation" as contrasted with "unary visitation".

可能,没有人愿意做比二进制访问更多的事情。但是假设我想做一般的 n-ary 访问。然后,我猜是这样的

Probably, no one will want to do more than binary visitation. But suppose I want to do like, general n-ary visitation. Then, it looks like this I guess

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)...);
}

但是现在,它变得更加松散了-如果有人通过一系列甚至根本不是同一个结构类型的类型,代码仍可能会编译并执行用户完全无法预料的操作。

But now, it's getting a little more squirrelly -- if someone passes a series of types that aren't even the same structure type at all, the code may still compile and do something totally unexpected by the user.

我考虑过这样做:

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)...);
}

如果将它们与不匹配的类型一起使用,至少会导致编译错误。但是,它也发生得太晚了–它是在解析了模板类型之后,并且在重载解析之后发生的。

That at least will cause a compile error if they use it with mismatching types. But, it's also happening too late -- it's happening after the template types are resolved, and after overload resolution I guess.

我考虑过使用SFINAE,例如,而不是使用 std :: enable_if_t 返回void并检查某些表达式 std :: is_same< std :: remove_cv_t< std :: remove_reference_t< ...>

I thought about using SFINAE, like, instead of returning void, using std::enable_if_t and checking some expression std::is_same<std::remove_cv_t<std::remove_reference_t<...>> for each type in the parameter pack.

但是对于一个,SFINAE表达式非常复杂,而对于两个,它也很复杂。有一个缺点-假设某人有一个派生类 struct my_other_struct:my_struct {...} ,并且他们想将其与访客机制一起使用,因此一些参数是 my_struct ,有些是 my_other_struct 。理想情况下,系统会将所有引用转换为 my_struct 并以这种方式应用访问者,然后使用上面给出的成员指针 foo_ptr , bar_ptr baz_ptr 在那里会做正确的事情,但我什至不清楚用SFINAE编写这样的约束-我将不得不尝试寻找我猜到的所有参数的公共基础?

But for one, that SFINAE expression is pretty complicated, and for two, it also has a drawback -- suppose someone has a derived class struct my_other_struct : my_struct { ... }, and they want to use it with the visitor mechanism, so some of the parameters are my_struct and some are my_other_struct. Ideally the system would convert all the references to my_struct and apply the visitor that way, and afaik the example I gave above with the member pointers foo_ptr, bar_ptr, baz_ptr would do the right thing there, but it's not even clear to me how to write a constraint like that with SFINAE -- I would have to try to find a common base of all the parameters I guess?

是否有一个很好的方法来协调那些参数

Is there a good way to reconcile those concerns in general?

推荐答案

使用 std :: common_type ,这是一般问题吗?简单:

With std::common_type, this is straightforward:

template <class... Args, class = std::common_type_t<Args...>>
void foo(Args &&... args) {

}

保证成为 SFINAE C ++ 17 起。 Clang GCC 都已经以这种方式实现了。

This will only be guaranteed to be SFINAE-friendly from C++17 onwards, though. Clang and GCC both implement it that way already.

这篇关于C ++参数包,是否必须具有单一类型的实例?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆