【问题标题】:Summing up the elements in a std::integer_sequence总结 std::integer_sequence 中的元素
【发布时间】:2019-03-19 17:37:23
【问题描述】:

出于教育目的,我尝试创建一个std::integer_sequence 并将其元素总结为一个参数包。我希望这很简单,并在下面编写了代码。

第 1 步:创建一组 add() 操作以正确处理同质的、基于整数的参数包。使用add<0,1,2,3>()-call 进行测试并正常工作。

第 2 步:在这一步中,我想传递一个 std::integer_sequence 作为模板参数,对序列中的整数求和。我失败了,看注释掉的代码。

如何将std::integer_sequence 作为模板参数传递?

一个错误消息是没有函数模板的实例与调用匹配。为什么?我希望整数序列等价于int 参数包。

另一条错误消息说Args 模板参数是非常量的。怎么可能?我预计这是一个后续错误,但我在实验中经常看到这种错误组合。

我正在使用 Microsoft VC++ 2017 编译器。

#include <iostream>
#include <utility>

template <typename T>
T add(T first) {
    return first;
}

template <typename T, typename ... Args>
T add(T car, Args... cdr) {
    return car + add(cdr...);
}

template <int ... Args>
int total() {
    return add(Args...);
}


int main(int argc, char** argv)
{
    using std::cout;
    using std::endl;

    int s1 = 
        total<0, 1, 2, 3>();
    std::cout << "s1 = " << s1 << std::endl;

    // The following does not compile:
#if 0
    int s2 = 
        total<std::make_integer_sequence<int, 4>>();
    std::cout << "s2 = " << s2 << std::endl;

    static_assert(s1 == s2, "no match");
#endif

    // This should work:
    //
    {
        cout << typeid(std::make_integer_sequence<int, 4>()).name() << endl;;
        cout << std::make_integer_sequence<int, 4>().size << endl;;
    }

    return 0;
}

...后来,经过很多有用的cmets。我学到了很多东西,现在有以下工作代码:

#include <iostream>
#include <utility>

template <typename T>
constexpr T add(T first) {
    return first;
}

template <typename T, typename ... Args>
constexpr T add(T car, Args... cdr) {
    return car + add(cdr...);
}

template <int ... Args>
constexpr int total() {
    return add(Args...);
}

template<int... Args>
constexpr int total(std::integer_sequence<int, Args...>) {
    return total<Args...>();
}

int main(int argc, char** argv)
{
    using std::cout;
    using std::endl;

    constexpr int s1 =
        total<0, 1, 2, 3>();
    std::cout << "s1 = " << s1 << std::endl;

    // The following now compiles. Oh, happy day!
#if 1
    constexpr int s2 =
        total(std::make_integer_sequence<int, 4> {});

    std::cout << "s2 = " << s2 << std::endl;

    static_assert(s1 == s2, "no match");
#endif

    // This should work:
    //
    {
        cout << typeid(std::make_integer_sequence<int, 4>()).name() << endl;;
        cout << std::make_integer_sequence<int, 4>().size << endl;;
    }

    return 0;
}

【问题讨论】:

  • 复制并粘贴错误消息。请,只需粘贴文本。写一个描述错误的句子比逐字粘贴对您来说更多,对我们帮助更少。
  • 错误信息是本地化的,所以我添加了一个大部分读者都能理解的翻译。

标签: c++ visual-studio-2017 variadic-templates template-meta-programming


【解决方案1】:

使用fold expressions 会更容易做到这一点:

template<class T, T... Args>
constexpr T total(std::integer_sequence<T, Args...> = {}) {
    return (Args + ...);
}

int main() {
    int s2 = 
        total(std::make_integer_sequence<int, 4>{});
    // Or calling directly
    int s1 = total<int, 0, 1, 2, 3>();
}

注意默认的std::integer_sequence 参数,所以如果你传递一个整数序列,它将推导出Args 参数。

如果您希望能够在模板中使用std::integer_sequence 类型调用它,只需创建一个辅助函数即可:

template<class IntegerSequence>
constexpr auto total_of(IntegerSequence argument = {}) {
    return total(argument);  // Will deduce `T` and `Args`
}

total_of<std::make_integer_sequence<int, 4>>();

【讨论】:

    【解决方案2】:

    我希望一个整数序列等价于一个 int 参数包。

    不是:std::integer_sequence 是一个类型;所以

    total<std::make_integer_sequence<int, 4>>();
    

    可以匹配接收类型的模板total()函数

    template <typename T>
    int total ()
     { return ???; }
    

    问题是从T 类型中你不能轻易地提取整数序列。

    如何将 std::integer_sequence 作为模板参数传递?

    解决方案(嗯……一个可能的解决方案)是偏特化。

    很遗憾,函数不能部分特化。

    但如果totalstruct(或class),一切都不同了。

    你可以写total如下

    template <typename>
    struct total;
    
    template <typename T, T ... Is>
    struct total<std::integer_sequence<T, Is...>>
       : public std::integral_constant<T, (Is + ...)>
     { };
    

    所以你可以通过以下方式使用它

    constexpr auto s2 { total<std::make_integer_sequence<int, 4>>::value };
    

    题外话:声明constexpr要在static_assert()测试中使用的变量。

    以下是一个完整的编译C++17示例(total结构重命名total2以避免与total()函数冲突)

    #include <utility>
    
    template <typename>
    struct total2;
    
    template <typename T, T ... Is>
    struct total2<std::integer_sequence<T, Is...>>
       : public std::integral_constant<T, (Is + ...)>
     { };
    
    template <typename T>
    constexpr T add (T first)
     { return first;}
    
    template <typename T, typename ... Args>
    constexpr T add (T car, Args ... cdr)
     { return car + add(cdr...); }
    
    template <int ... Args>
    constexpr int total ()
     { return add(Args...); }
    
    int main ()
     {
       constexpr auto s1 { total<0, 1, 2, 3>() };
       constexpr auto s2 { total2<std::make_integer_sequence<int, 4>>::value };
    
       static_assert(s1 == s2);
     }
    

    【讨论】:

    • 我没有通过微软编译器得到这个,虽然我把它切换到了 C++17。
    【解决方案3】:

    这样做的一种方法是为total 添加一个重载并以不同的方式调用它:

    template<int... Args>
    int total(std::integer_sequence<int, Args...> ) {
        return add(Args...);
    }
    

    比打电话

    nt s2 = 
        total(std::make_integer_sequence<int, 4>>{});
    

    这将解决呼叫问题。为了在 static_assert 中使用函数的结果,您还需要确保它(以及存储它的结果的变量)被标记为 constexpr

    【讨论】:

      【解决方案4】:

      我希望一个整数序列等价于一个 int 参数包。

      但是你可以看到它不是通过写出以下两个扩展:

      template <int ... Args> int total();
      

      第一种情况是

      total<0, 1, 2, 3>();
      // => Args... is (0, 1, 2, 3)
      

      这是一个有效的整数包,第二个是

      total<std::make_integer_sequence<int, 4>>();
      // => Args... must be std::integer_sequence<int, 0, 1, 2, 3>
      

      这显然不像整数包。链接到std::integer_sequence 的文档以供参考。

      如果你想把那个整数序列转回传递给make_integer_sequence的原始参数包,你需要使用模板参数推导重载SergeyA的回答显示:

      template<int... Args>
      int total(std::integer_sequence<int, Args...> ) {
          return add(Args...);
      }
      

      (函数参数是一个伪参数,只是为了推导Args...)。

      不过,如果可以使用 fold 表达式,它还是比旧式递归要好得多。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-06-10
        • 2013-08-17
        • 1970-01-01
        • 1970-01-01
        • 2017-11-22
        • 2015-02-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多