C ++参数包,被限制为具有单一类型的实例?

时间:2016-07-22 14:19:54

标签: c++ templates c++11 variadic-templates visitor

从C ++ 11开始,我们可以创建可以接受任何参数序列的模板函数:

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

但是,假设在每个参数具有相同类型的情况下调用我的函数真的是有意义的 - 任何数量的参数都可以。但/ p>

最好的方法是什么,即有一种很好的方法来限制模板在这种情况下制作一个很好的错误信息,或理想情况下,消除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文档中,他们称之为&#34;二元访问&#34;与&#34;一元探视&#34;形成鲜明对比。

可能没有人愿意做二元访问。但是假设我想做一般的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_ptrbar_ptrbaz_ptr在那里做正确的事情,但我甚至不清楚如何用SFINAE写出这样的约束 - 我必须尝试找到我猜的所有参数的共同基础?

是否有一种很好的方法可以协调这些问题?

7 个答案:

答案 0 :(得分:15)

使用std::common_type,这很简单:

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

}

这只是保证SFINAE - 从C++17开始是友好的。 ClangGCC都已经以这种方式实施。

答案 1 :(得分:7)

这是一种类型特征,您可以在闲暇时使用static_assertstd::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 {};

它非常简单地用第一个类型检查每个类型,如果有任何不同则失败。

我认为使用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> {};

然后你可以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

这是因为模板首先尝试获取DE之间的公共类型(即auto a = bool() ? D{}: E{};无法编译)。

答案 2 :(得分:7)

你真正想要的是:

.zip

但显然上面不是合法的语法。但是,您可以解决此问题,使用模板template<typename T, T ... args> void myFunc(T ... args); 来帮助您。所以这个想法是这样的:

using

上述内容没有任何实际意义:template<typename T, size_t val> using IdxType = T; 仅适用于任何IdxType<T, n> T。但是,它可以让你这样做:

n

这很棒,因为这正是您需要获得一组可变参数的可变参数。剩下的唯一问题是,您无法执行template<typename T, size_t ... Indices> void myFunc(IdxType<T, Indices> ... args); 之类的操作,因为编译器无法推断出所需的myFunc(obj1, obj2, obj3) - 您必须执行{{1}这是丑陋的。幸运的是,您可以通过使用Indices包装一个帮助您生成索引的辅助函数来摆脱这种情况。

以下是一个完整示例,与您的访问者类似(现场演示here):

myFunc<1,2,3>(obj1, obj2, obj3)

答案 3 :(得分:3)

这将采用任意In类型,并将其r / lvalue引用移至隐式转换中的Out类型。

template<class Out>
struct forward_as {
  template<class In,
    std::enable_if_t<std::is_convertible<In&&,Out>{}&&!std::is_base_of<Out,In>{},int>* =nullptr
  >
  Out operator()(In&& in)const{ return std::forward<In>(in); }
  Out&& operator()(Out&& in)const{ return std::forward<Out>(in); }
  template<class In,
    std::enable_if_t<std::is_convertible<In&,Out&>{},int>* =nullptr
  >
  Out& operator()(In& in)const{ return in; }
  template<class In,
    std::enable_if_t<std::is_convertible<In const&,Out const&>{},int>* =nullptr
  >
  Out const& operator()(In const& in)const{ return in; }
};

有了这个,这是我们的n-ary apply_visitor

template <typename V, typename ... Ss,
  decltype(std::void_t<
    std::result_of_t<forward_as<my_struct>(Ss)>...
  >(),int())* =nullptr
>
void apply_visitor(V && v, Ss && ... ss) {
  auto convert = forward_as<my_struct>{};

  std::forward<V>(v)("foo", (convert(std::forward<Ss>(ss)).foo)...);
  std::forward<V>(v)("bar", (convert(std::forward<Ss>(ss)).bar)...);
  std::forward<V>(v)("baz", (convert(std::forward<Ss>(ss)).baz)...);
}
如果forward_as<my_struct>无法在Ss上评估,则

无法匹配。

live example

答案 4 :(得分:2)

  

假设在每个参数具有相同类型的情况下调用我的函数真的是有意义的 - 任何数量的参数都可以。但/ p>

在这种情况下,为什么不使用std::initializer_list

template <typename T>
void func(std::initializer_list<T> li) {
    for (auto ele : li) {
        // process ele
        cout << ele << endl;
    }
}

正如评论中提到的@Yakk,您可能希望避免const份副本。在这种情况下,您可以将指针复制到std::initializer_list

// Only accept pointer type
template <typename T>
void func(std::initializer_list<T> li) {
    for (auto ele : li) {
        // process pointers, so dereference first
        cout << *ele << endl;
    }
}

或指定func指针:

// Specialize for pointer
template <typename T>
void func(std::initializer_list<T*> li) {
    for (auto ele : li) {
        // process pointers, so dereference first
        cout << *ele << endl;
    }
}

my_struct a, b, c;
func({a, b, c}); // copies
func({&a, &b, &c}); // no copies, and you can change a, b, c in func

答案 5 :(得分:0)

可能的解决方案是在以下示例中使用类似are_same的编译时函数:

#include <type_traits>

template<typename T, typename... O>
constexpr bool are_same() {
    bool b = true;
    int arr[] = { (b = b && std::is_same<T, O>::value, 0)... };
    return b;
}

int main() {
    static_assert(are_same<int, int, int>(), "!");
    static_assert(not are_same<int, double, int>(), "!");
}

按照以下方式使用它:

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

您将根据要求获得一个很好的编译时错误消息。

答案 6 :(得分:-3)

我认为您可以创建这样的函数并检查函数内部的参数。

template <typename T, typename... Args> bool check_args(T a, Args args)
{
static string type;
if(type == "") type = typeid(a).name;
else if(type != typeid(a).name) return false;
else return check_args(args...);
}
bool check_args() {return true;}