【问题标题】:Passing variadic parameters in an already template-variadic function在已经模板可变的函数中传递可变参数
【发布时间】:2018-10-22 17:49:52
【问题描述】:

标题不好,但我想不出更好的。随意更改它。

这是我目前正在处理的一个模板多维数组类。我正在尽我所能优化它:

#include <array>

template <typename T, std::size_t... Dimensions>
class multidimensional_array
{
    public:

        using value_type = T;
        using size_type = std::size_t;

    private:

        template<typename = void>
        static constexpr size_type multiply(void)
        {
            return 1u;
        }

        template<std::size_t First, std::size_t... Other>
        static constexpr size_type multiply(void)
        {
            return First * multidimensional_array::multiply<Other...>();
        }

    public:

        using container_type = std::array<value_type, multidimensional_array::multiply<Dimensions...>()>;
        using reference = value_type &;
        using const_reference = value_type const&;
        using iterator = typename container_type::iterator;

    private:

        container_type m_data_array;

        template<typename = void>
        static constexpr size_type linearise(void)
        {
            return 0u;
        }

        template<std::size_t First, std::size_t... Other>
        static constexpr size_type linearise(std::size_t index, std::size_t indexes...)
        {
            return multidimensional_array::multiply<Other...>()*index + multidimensional_array::linearise<Other...>(indexes);
        }

    public:

        // Constructor
        explicit multidimensional_array(const_reference value = value_type {})
        {
            multidimensional_array::fill(value);
        }

        // Accessors
        reference operator()(std::size_t indexes...)
        {
            return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
        }

        const_reference operator()(std::size_t indexes...) const
        {
            return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
        }

        // Iterators
        iterator begin()
        {
            return m_data_array.begin();
        }

        iterator end()
        {
            return m_data_array.end();
        }

        // Other
        void fill(const_reference value)
        {
            m_data_array.fill(value);
        }
};

我的主要功能是

int main(void)
{
    multidimensional_array<int, 2u, 3u, 4u, 5u, 6u> foo;
    int k = 0;

    for (auto& s : foo)
        s = k++;

    //std::cout << foo(0u, 0u, 0u, 1u, 0u) << std::endl;
    return 0;
}

上述代码编译器没有警告/错误。不过,一旦我取消注释 std::cout 部分,我就会明白:

g++-7 -std=c++17 -o foo.o -c foo.cpp -Wall -Wextra -pedantic
foo.cpp: In instantiation of ‘multidimensional_array<T, Dimensions>::value_type& multidimensional_array<T, Dimensions>::operator()(std::size_t, ...) [with T = int; long unsigned int ...Dimensions = {2, 3, 4, 5, 6}; multidimensional_array<T, Dimensions>::reference = int&; multidimensional_array<T, Dimensions>::value_type = int; std::size_t = long unsigned int]’:
foo.cpp:99:37:   required from here
foo.cpp:60:72: error: no matching function for call to ‘multidimensional_array<int, 2, 3, 4, 5, 6>::linearise<2, 3, 4, 5, 6>(std::size_t&)’
    return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
foo.cpp:38:30: note: candidate: template<class> static constexpr multidimensional_array<T, Dimensions>::size_type multidimensional_array<T, Dimensions>::linearise() [with <template-parameter-2-1> = <template-parameter-1-1>; T = int; long unsigned int ...Dimensions = {2, 3, 4, 5, 6}]
   static constexpr size_type linearise(void)
                              ^~~~~~~~~
foo.cpp:38:30: note:   template argument deduction/substitution failed:
foo.cpp:60:72: error: wrong number of template arguments (5, should be at least 0)
    return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
foo.cpp:44:30: note: candidate: template<long unsigned int First, long unsigned int ...Other> static constexpr multidimensional_array<T, Dimensions>::size_type multidimensional_array<T, Dimensions>::linearise(std::size_t, std::size_t, ...) [with long unsigned int First = First; long unsigned int ...Other = {Other ...}; T = int; long unsigned int ...Dimensions = {2, 3, 4, 5, 6}]
   static constexpr size_type linearise(std::size_t index, std::size_t indexes...)
                              ^~~~~~~~~
foo.cpp:44:30: note:   template argument deduction/substitution failed:
foo.cpp:60:72: note:   candidate expects 2 arguments, 1 provided
    return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
Makefile:17: recipe for target 'foo.o' failed
make: *** [foo.o] Error 1

我现在知道为什么了。我的问题是,我怎样才能修复linearise 以便它可以通过indexes 而无需通过va_list 等?不幸的是linearise已经是一个模板,可变参数函数,所以我不能在这方面使用可变参数模板恶作剧。

【问题讨论】:

  • 不错的问题,但这不是minimal reproducible example - 这不是最小的。
  • 好的,possible duplicate
  • @user202729 对我来说似乎是一个合理的问题。你的行为看起来像是过分热心的 SO 节制的更糟糕的问题。
  • @ArthurTacca 我怀疑operator() 及其它的 const 变体swap/fill "utility" 是否有用。此外,我讨厌滚动阅读代码。
  • @Pippin 您也不需要fill 函数或构造函数。将其设为零初始化不会影响功能。

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


【解决方案1】:

preceding question一样,问题在于以下签名

template<std::size_t First, std::size_t... Other>
static constexpr size_type linearise(std::size_t index,
                                     std::size_t indexes...)

reference operator()(std::size_t indexes...)

const_reference operator()(std::size_t indexes...) const

不是你的意思(indexesstd::size_t 的可变参数列表)但完全等同于

template<std::size_t First, std::size_t... Other>
static constexpr size_type linearise(std::size_t index,
                                     std::size_t indexes,
                                     ...)

reference operator()(std::size_t indexes, ...)

const_reference operator()(std::size_t indexes, ...) const

其中indexes 是一个std::size_t,后跟一个C 风格的可选参数序列。

一个简单的解决方案(您标记了 C++17,但从 C++11 开始可用)是基于使用可变参数模板。

举例如下

template <std::size_t First, std::size_t ... Other, typename ... Ts>
static constexpr size_type linearise (std::size_t index,
                                      Ts ... indexes)
 { return multidimensional_array::multiply<Other...>() * index
        + multidimensional_array::linearise<Other...>(indexes...); }

  // Accessors
  template <typename ... Ts>
  reference operator() (Ts ... indexes)
   { return m_data_array[
        multidimensional_array::linearise<Dimensions...>(indexes...)]; }

  template <typename ... Ts>
  const_reference operator() (Ts ... indexes) const
   { return m_data_array[
        multidimensional_array::linearise<Dimensions...>(indexes...)]; }

以下是你的代码,修改后可编译

#include <array>
#include <iostream>

template <typename T, std::size_t ... Dimensions>
class multidimensional_array
 {
   public:
      using value_type = T;
      using size_type  = std::size_t;

   private:
      template <typename = void>
      static constexpr size_type multiply ()
       { return 1u; }

      template <std::size_t First, std::size_t ... Other>
      static constexpr size_type multiply(void)
       { return First * multidimensional_array::multiply<Other...>(); }

   public:
      using container_type  = std::array<value_type,
               multidimensional_array::multiply<Dimensions...>()>;
      using reference       = value_type &;
      using const_reference = value_type const &;
      using iterator        = typename container_type::iterator;

   private:
      container_type m_data_array;

      template <typename = void>
      static constexpr size_type linearise ()
       { return 0u; }

      template <std::size_t First, std::size_t ... Other, typename ... Ts>
      static constexpr size_type linearise (std::size_t index,
                                            Ts ... indexes)
       { return multidimensional_array::multiply<Other...>() * index
              + multidimensional_array::linearise<Other...>(indexes...); }

   public:
      // Constructor
      explicit multidimensional_array (const_reference value = value_type{})
       { multidimensional_array::fill(value); }

      // Accessors
      template <typename ... Ts>
      reference operator() (Ts ... indexes)
       { return m_data_array[
            multidimensional_array::linearise<Dimensions...>(indexes...)]; }

      template <typename ... Ts>
      const_reference operator() (Ts ... indexes) const
       { return m_data_array[
            multidimensional_array::linearise<Dimensions...>(indexes...)]; }

      // Iterators
      iterator begin ()
       { return m_data_array.begin(); }

      iterator end ()
       { return m_data_array.end(); }

      // Other
      void fill (const_reference value)
       { m_data_array.fill(value); }
 };

int main ()
 {
   multidimensional_array<int, 2u, 3u, 4u, 5u, 6u> foo;

   int k{ 0 };

   for ( auto & s : foo )
      s = k++;

   std::cout << foo(0u, 0u, 0u, 1u, 0u) << std::endl;
 }

奖励建议。

您标记了 C++17,因此您可以使用“折叠”。

所以你可以替换几个 multiply() 模板函数

  template <typename = void>
  static constexpr size_type multiply ()
   { return 1u; }

  template <std::size_t First, std::size_t ... Other>
  static constexpr size_type multiply ()
   { return First * multidimensional_array::multiply<Other...>(); }

只有一个折叠的

  template <std::size_t ... Sizes>
  static constexpr size_type multiply ()
   { return ( 1U * ... * Sizes ); } 

【讨论】:

  • 我的代码现在可以完美编译并且可以工作了,谢谢。我不知道可以有超过 1 个可变参数模板。多么重要的事情要记住。
  • @Pippin - 是的:在某些情况下,您可以拥有多个可变参数模板列表;但是……很复杂。无论如何...添加了一个奖励建议。
【解决方案2】:

我的方法与this answer 中的方法类似,只是我没有使用std::tuple 来存储类型 列表,而是定义了自己的类型size_t_pack 来存储(编译-时间)size_t 的列表。

using std::size_t;

template<size_t... values>
struct size_t_pack{};

template<size_t first_value,size_t... rest_values>
struct size_t_pack<first_value,rest_values...>{
    static constexpr size_t first=first_value;
    using rest=size_t_pack<rest_values...>;
    static constexpr size_t product=first*rest::product;
};

template<>struct size_t_pack<>{
    static constexpr size_t product=1;
};

定义成员:firstrest(如果不为空)和product(因为据我所知,无法使用模板参数的模板来专门化函数,另一种选择是if constexpr 并使类型支持检查empty)

这样,定义linearize 函数就很容易了:

template<class dimensions,class... SizeTs>
static constexpr size_type linearise(std::size_t index, SizeTs... indices)
{
    using restDimensions=typename dimensions::rest;
    return restDimensions::product *index + 
    multidimensional_array::linearise<restDimensions>(indices...);
}

据我所知,使用std::tuple 来存储类型列表 (SizeTs) 也是可能的,尽管仍然需要 struct 部分特化。

【讨论】:

    【解决方案3】:

    您需要通过将operator()函数作为模板来使索引成为参数包,并在使用时通过将...放在后面来扩展参数包:

        template <class... DimensionType>
        const_reference operator()(DimensionType... indexes) const
        {
            return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes...)];
        }
    

    见:parameter pack expansion

    由于linearize() 中的类似问题,代码仍然无法编译,但这会让你走上正轨。

    【讨论】:

      猜你喜欢
      • 2012-12-29
      • 2014-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-15
      • 2011-03-16
      • 1970-01-01
      • 2013-02-18
      相关资源
      最近更新 更多