C ++多组可变参数函数参数

时间:2017-09-12 12:43:02

标签: c++ templates c++17 variadic-templates template-meta-programming

我试图在计算密集型应用程序中优化非常低级别和广泛使用的功能。 我们说我有以下类型:

template<typename T, int N> 
class Elem {...};

我想编写一个可以调用的函数,例如:

template<typename T, int N>
void func(const Elem<T, N> & ... /*N elements*/, Elem<T, N> & ... /* N elements*/)

我正在寻找一种方法,我可以确保编译器能够删除函数签名引入的任何临时文件。

元素通常是从矢量/数组的不同位置获取的元素。例如。 :

Elem<float, 3> inputs[10];
Elem<float, 3> outputs[10];
...
func(input[4], input[2], input[9], output[6], output[8], output[1]);

答案可能是初始化列表,但我担心它可能会有一些开销......

注意:上面的间接索引都是编译时计算的函数,并且在短范围内。

修改

事实上,我喜欢的是:

template<typename... T, int N>
void func(const Elem<T, N>&... inputs, const Elem<T, N>&... outputs)
{
  static_assert(sizeof...(inputs) == N, "invalid number of arguments");
  static_assert(sizeof...(outputs) == N, "invalid number of arguments");
  static_assert(std::is_same<std::integral_constant<int N>...>::value, "invalid arguments");
}

但我无法在VS2017上编译此代码。 答案可以是C ++ 17。

3 个答案:

答案 0 :(得分:3)

我会将每个集合作为引用元组传递,您可以使用std::tie进行引用。

在大多数情况下,根本没有开销,因为编译器会看到所有元组结构。

示例:

#include <tuple>
#include <type_traits>


template<class T, std::size_t N>
struct Elem {
    T value() const { return val; }

    T val;
};

Elem<float, 3> input[10];
Elem<float, 3> output[10];


namespace detail {
    template<typename T, typename F, std::size_t... Is>
    constexpr auto tuple_foreach(T&& tup, F& f, std::index_sequence<Is...>) {
        using expand = int[];
        void(expand{0,
                (f(std::get<Is>(std::forward<T>(tup))), 0)...
        })
        ;
    }
}

template<typename T, typename F, std::size_t TupSize = std::tuple_size_v<std::decay_t<T>>>
constexpr auto tuple_foreach(T&& tup, F f) {
    return detail::tuple_foreach(
        std::forward<T>(tup), f,
        std::make_index_sequence<TupSize>{}
    );
}

template<class Set1, class Set2>
auto func(Set1 set1, Set2 set2)
{
    constexpr auto N1 = std::tuple_size<Set1>::value;
    constexpr auto N2 = std::tuple_size<Set2>::value;
    static_assert(N1 == N2, "");

    // now do things with std::get<0 ... N-1>(set1) and
    // std::get<0 ... N-1>(set2);

    using result_type = std::decay_t<decltype(std::get<0>(set1).value())>;

    // let's compute the sum of the inputs
    result_type result = 0;
    tuple_foreach(set1, 
                        [&](auto&& elem)
                        {
                            result += elem.value();
                        });
    tuple_foreach(set2, 
                        [&](auto&& elem)
                        {
                            result += elem.value();
                        });

    return result;
}

void emit(float);

int main()
{
    auto x = func(std::tie(input[4], input[2], input[9]), 
                  std::tie(output[6], output[8], output[1]));
    emit(x);
}

使用编译器设置[{1}}:

发出的程序集
-O2

不能比那更有效率。

答案 1 :(得分:2)

  

我想编写一个可以调用的函数,例如:

template<typename T, int N>
void func(const Elem<T, N> & ... /*N elements*/, 
          Elem<T, N> & ... /* N elements*/)

据我所知,你问的内容并不容易用现有语言表达。

我能想象的最好的是编写func()函数,如下所示

template <typename ... Es>
typename std::enable_if<checkElems<Es...>::value>::type
   func (Es & ... es)
 {
   using type = typename checkElems<Es ...>::type;   // former T

   constexpr std::size_t num { sizeof...(Es) >> 1 }; // former N

   // ...
 }

func()收到参数列表(Es & ... es)且仅当类型的相对列表(Es ...)满足实现的要求列表时才启用SFINAE功能自定义类型特征checkElems(请参阅以下示例)。

以下checkElems检查:

  • 至少有一个参数类型
  • 第一个参数的格式为Elem<T, N> const
  • 参数类型的数量正好是2 * N
  • 第一个N类型彼此相等
  • 以下N类型彼此相同
  • 添加const以下N类型,它们等于第一个N
  • 未添加const,以下N类型与第一个N不同

func()内,您可以使用type(参见示例),即T中的Elem<T, N>类型和num,这是N中的Elem<T, N>值。

你可以做这个检查,但我不知道这是不是一个好主意。

可编辑的例子

#include <tuple>
#include <type_traits>

template<typename T, std::size_t N> 
struct Elem {};

template <typename>
struct extrElem;

template <typename T, std::size_t N>
struct extrElem<Elem<T, N> const>
 {
   using type = T;

   static constexpr std::size_t num { N };
 };

template <std::size_t, std::size_t, typename ...>
struct extrTypes;

template <std::size_t Skip, std::size_t Num, typename ... Es, typename T0,
          typename ... Ts>
struct extrTypes<Skip, Num, std::tuple<Es...>, T0, Ts...>
 { using type = typename extrTypes<
      Skip-1U, Num, std::tuple<Es...>, Ts...>::type; };

template <std::size_t Num, typename ... Es, typename T0, typename ... Ts>
struct extrTypes<0U, Num, std::tuple<Es...>, T0, Ts...>
 { using type = typename extrTypes<
      0U, Num-1U, std::tuple<Es..., T0>, Ts...>::type; };

template <typename ... Es, typename T0, typename ... Ts>
struct extrTypes<0U, 0U, std::tuple<Es...>, T0, Ts...>
 { using type = std::tuple<Es...>; };

template <typename ... Es>
struct extrTypes<0U, 0U, std::tuple<Es...>>
 { using type = std::tuple<Es...>; };

template <typename>
struct sameContTypes : public std::false_type
 { };

template <template <typename ...> class C, typename T0, typename ... Ts>
struct sameContTypes<C<T0, Ts...>>
   : public std::is_same<C<T0, Ts...>, C<Ts..., T0>>
 { };

template <typename E0, typename ... Es>
struct checkElems
 {
   static constexpr std::size_t num { extrElem<E0>::num };

   using type = typename extrElem<E0>::type;
   using lt1 = typename extrTypes<0U, num, std::tuple<>, E0, Es...>::type;
   using lt2 = typename extrTypes<num, num, std::tuple<>, E0, Es...>::type;

   static constexpr bool value {
         ( (num << 1) == 1U + sizeof...(Es) )
      && sameContTypes<lt1>::value
      && sameContTypes<lt2>::value
      && (true == std::is_same<E0,
             typename std::tuple_element<0U, lt2>::type const>::value)
      && (false == std::is_same<E0,
             typename std::tuple_element<0U, lt2>::type>::value) };
 };

template <typename ... Es>
typename std::enable_if<checkElems<Es...>::value>::type
   func (Es & ... es)
 {
   using type = typename checkElems<Es ...>::type;   // former T

   constexpr std::size_t num { sizeof...(Es) >> 1 }; // former N

   // ...
 }

int main()
 {
   Elem<int, 3>        ei3;
   Elem<int, 4>        ei4;
   Elem<int, 3> const  eci3;
   Elem<int, 4> const  eci4;

   func(eci3, eci3, eci3, ei3, ei3, ei3);    // compile
   //func(eci3, eci3, eci3, ei3, eci3, ei3); // compilation error
   //func(eci3, eci3, eci3, ei3, ei3, ei4); // compilation error
   //func(eci3, eci3, eci4, ei3, ei3, ei3); // compilation error
   //func(eci4, eci4, eci4, ei4, ei4, ei4); // compilation error
   //func(eci4, eci4, eci4, eci4, ei4, ei4, ei4); // compilation error
   func(eci4, eci4, eci4, eci4, ei4, ei4, ei4, ei4); // compile
 }

答案 2 :(得分:0)

不确定,我是否得到了问题,但你可以做到

template<typename... T, int... N>
void func(const Elem<T, N>&... elems)

如果您希望您的函数采用任意数量的Elem<>。如果您需要将它们限制为单个T(例如Elem<int,>并且Elem<float,>可能无法一起传递),请使用

template<typename T, int... N>
void func(const Elem<T, N>&... elems)

如果您需要限制参数的数量,只需使用

static_assert(sizeof...(elems) <= 6, "...");

在函数体内。如果所有元素的N必须相等且参数的数量应为2 * N,请使用:

template<typename... T, int N>
void func(const Elem<T, N>&... elems) 
{
    static_assert(sizeof...(elems) == 2*N, "invalid number of arguments");
}