简体   繁体   English

C ++函数具有无限参数但具有相同(固定)类型

[英]C++ function with unlimited parameters but with the same (fixed) type

I want to have a function with unlimited number of parameters but I want also make sure that those are all pointers of the same type. 我想要一个具有无限数量参数的函数,但我也想确保那些都是相同类型的指针。 Something like this: 像这样的东西:

void myFunc(float value, MyClass* ....)
{
    // take all pointers of type MyClass and call function `f` like this: a->(value);
    // store pointer in a vector like: vector.push_back(a);
}

Can I achieve this in C++? 我可以用C ++实现吗?

void myFunc(float value, std::initializer_list<MyClass*> il){
  for(auto* p:il)
    p->f(value);
}

no heap/free store allocation will occur. 不会发生堆/免费商店分配。

Use is myFunc(3.14, {ptr1,ptr2,ptr3}); 使用是myFunc(3.14, {ptr1,ptr2,ptr3});

If you really hate {} you can forward to the above with an unrestricted template. 如果你真的讨厌{}你可以使用不受限制的模板转发到上面。 At the point of forwarding there will be type checking. 在转发点,将进行类型检查。 SFINAE can be used to type check earlier. SFINAE可用于更早地进行类型检查。

template<class...MyClasses>
void myFunc2( float value, MyClasses*... ps) {
  myFunc( value, {ps...} );
}

(possibly with name changes) (可能名称更改)

Alternatively, full SFINAE based solutions can be done, with direct calling of p->f , bit that is like using a bazooka to deal with litter. 或者,可以完成基于SFINAE的完整解决方案,直接调用p->f ,就像使用火箭筒来处理垃圾一样。 Sure, the litter will be gone, but it was still a bad idea. 当然,垃圾将会消失,但这仍然是一个坏主意。

initializer lists are designed for efficient bundling of parameters of identical type. 初始化列表旨在有效地捆绑相同类型的参数。

Now, a good question to ask about your MyClass* request is ... why do you care? 现在,询问您的MyClass*请求的一个好问题是......为什么要关心? If the passed in parameters are compatible with ->f(3.14f) , why not just call ->f(3.14f) ? 如果传入的参数与->f(3.14f)兼容,为什么不调用->f(3.14f) This is why practical problems are better problems to ask about, rather than abstract ones: the best solution to a problem varies with practical issues. 这就是为什么实际问题是更好的问题而不是抽象的问题:问题的最佳解决方案因实际问题而异。

The bazooka solution looks like the follows. 火箭筒解决方案如下所示。

First, a small template metaprogramming library: 首先,一个小模板元编程库:

// Better name for the type:
template<bool b>
using bool_t = std::integral_constant<bool, b>;
// bundle of types:
template<class...>struct types{using type=types;};

// takes a test, and a types<?...> of things to test against it:
template<template<class...>class Z, class args>
struct test_lists:std::true_type{};
template<template<class...>class Z, class...Ts, class...Us>
struct test_lists<Z,types<types<Ts...>,Us...>>:bool_t<
  Z<Ts...>{} && test_lists<Z, types<Us...>>{}
>{};

// takes 0 or more types<?...> and concatenates them:
template<class...types>
struct concat_types;
template<class...types>
using concat_types_t=typename concat_types<types...>::type;
template<>
struct concat_types<>:types<>{};
template<class...Ts>
struct concat_types<types<Ts...>>:types<Ts...>{};
template<class...T0s,class...T1s, class...more>
struct concat_types<types<T0s...>,types<T1s...>,more...>:
  concat_types_t< types<T0s...,T1s...>, more... >
{};

// takes a template Z and and arg, and produces a template
// equal to Z<Arg, ?...>:
template<template<class...>class Z, class Arg>
struct bind_1st {
    template<class...Ts>
    using apply=Z<Arg,Ts...>;
};

// takes a template Z and a types<?...> and produces
// types< Z<?>... >:
template<template<class...>class Z, class types>
struct map;
template<template<class...>class Z, class types>
using map_t=typename map<Z,types>::type;
template<template<class...>class Z, class...Ts>
struct map<Z,types<Ts...>>:types<Z<Ts>...>{};

// builds a cross product of zero or more types<?...>:
template<class...types0>
struct cross_types;
template<class...types>
using cross_types_t=typename cross_types<types...>::type;

// valid degenerate cases:
template<class...Ts>
struct cross_types<types<>,Ts...>:types<>{};
template<>
struct cross_types<>:types<types<>>{};

// meat of cross_types:
template<class T0, class...T0s, class...Us>
struct cross_types<types<T0,T0s...>, Us...>:
  concat_types_t<
    map_t< bind_1st< concat_types_t, types<T0> >::template apply, cross_types_t<Us...> >,
    cross_types_t< types<T0s...>, Us... >
  >
{};

// takes a test Z, and a sequence of types<?...> args
// tests the cross product of the contents of the args:
template<template<class...>class Z, class...Args>
struct test_cross : test_lists<Z, cross_types_t<Args...>> {};

everything above this point is generic metaprogramming code. 这一点之上的一切都是通用的元编程代码。 You can do the next part more directly, but the generic metaprogramming code above can be used in other similar problems, and it does make the later stuff "clearer". 你可以更直接地完成下一部分,但上面的通用元编程代码可以用于其他类似的问题,它确实使后面的东西“更清晰”。

// a toy MyClass type to test against:
struct MyClass {
    void f(float x){
        std::cout << x << '\n';
    }
};

// Simple SFINAE test that the types passed in are exactly
// pointers to MyClass:
template<class...Ts>
std::enable_if_t<
  test_cross<std::is_same, types<MyClass>, types<Ts...>>{}
>
myFunc( float x, Ts*... p ) {
  using discard=int[];
  (void)discard{0,((
    p->f(x)
  ),void(),0)...};
}

note that std::is_base_of might be a better choice than is_same . 请注意, std::is_base_of可能是比is_same更好的选择。

The core is here: 核心在这里:

  test_cross<std::is_same, types<MyClass>, types<Ts...>>{}

this evaluates std::is_same<A,B> for every pair of <MyClass, Ts> . 这会为每对<MyClass, Ts>计算std::is_same<A,B> <MyClass, Ts>

A far easier way to do it would be a template that took a bunch of bool... and did an && on them, together with std::is_same<MyClass, Ts>{}... . 一个更容易实现的方法是使用一堆bool...并在它们上面执行&& ,与std::is_same<MyClass, Ts>{}... But I like writing metaprogramming libraries, and doing n-way cross product tests with brevity is a more interesting problem. 但我喜欢编写元编程库,并且简洁地进行n路交叉产品测试是一个更有趣的问题。

live example 实例

Cross product based off of this stack overflow answer in python 基于python的堆栈溢出答案的交叉产品

You could do the following, then you can stick to your existing syntax.. 您可以执行以下操作,然后您可以坚持使用现有语法..

#include <iostream>
#include <type_traits>

using namespace std;

struct V
{
    int a;
    int b;
};

// Little test function to ensure that it's the type we're expecting..
template <typename T>
int test(T*)
{
    static_assert(is_same<T, V>::value, "Must only be V");
    return 0;
}

template <typename ...T>
void foo(int a, T*... args)
{
    // Use expansion to test all the parameters, this will generate
    // an error if the wrong type is passed in
    auto v = { (test(args), 0)... };
    (void) v; // result discarded..
}

int main()
{
    V a, b, c, d, e;
    int f;
    foo(10, &a, &b, &c, &d, &e, &f); // this will fail
    foo(10, &a, &b, &c, &d, &e); // this will compile
}

Basically use the a parameter pack with a static_assert to force the type to be the same... 基本上使用带有static_assert的参数包来强制类型相同...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM