【问题标题】:How to have a const variable in a for loop for the generation of template classes?如何在 for 循环中使用 const 变量来生成模板类?
【发布时间】:2020-03-30 17:10:23
【问题描述】:

我有一个类似的代码

template <size_t N>
class A
{
    template <size_t N>
    someFunctions() {};
};

现在我想创建该类的实例并在 for 循环中调用其中的函数以获取一组许多值,例如

// in main()

int main()
{
    for (int i = 1; i <= 100; i++)
    {
        const int N = i;  // dont know how to do this
        A<N> a;
        a.functionCalls();
    }
}

如何做到这一点? 希望有一种方法可以做到这一点。

【问题讨论】:

  • 要用作模板参数 N 需要是 constexpr 如果它是循环变量则不是这种情况
  • 不能,A 真的需要做模板吗?
  • 是的,由于某些原因,A 类需要成为模板,它是某种东西的模型,所以它必须是模板类

标签: c++ for-loop templates compile-time-constant template-classes


【解决方案1】:

这将需要一个称为 template for 的东西,这是 expansion statements 的预期形式,它看起来像一个 for 循环,但实际上是一个函数中的模板块,被多次实例化。

当然,有一个解决方法。我们可以滥用泛型 lambda 来声明某种本地模板块并自己实例化它:

template <typename T, T... S, typename F>
constexpr void for_sequence(std::integer_sequence<T, S...>, F f) {
    (static_cast<void>(f(std::integral_constant<T, S>{})), ...);
}

这个函数接受一个整数序列,实例化 lambda F 的次数与序列的长度一样多。

它是这样使用的:

for_sequence(std::make_index_sequence<100>(), [](auto N) { /* N is from 0 to 99 */
  A<N + 1> a; /* N + 1 is from 1 to 100 */
  a.functionCalls();
});

这里,N 可以作为模板参数发送,因为它是一个具有 constexpr 转换运算符到整数类型的对象。更准确地说,它是一个std::integral_constant,价值不断增加。

Live example

【讨论】:

  • 呃。当我看到这样有趣的模板时,我只知道我稍后将不得不在没有调用堆栈的情况下对其进行调试,并且不得不猜测发生了什么...... :)
  • static_cast&lt;void&gt; 的目的是什么?
  • @Ayxan 避免当 lambda f 返回一个重载逗号运算符的类型时出现问题
  • @MichaelDorgan 这就是为什么我们需要template for。像这样滥用语言结构总是更痛苦
  • @GuillaumeRacicot 或者我们需要比元编程模板更好的抽象。
【解决方案2】:

N 需要是编译时常量,这与普通的for 循环是不可能的。

但是,有很多解决方法。例如,受SO post 的启发,您可以执行以下操作。 (See a Live demo)

template<size_t N>
class A
{
public:
    // make the member function public so that you can call with its instance
    void someFunctions()
    {
        std::cout << N << "\n";
    };
};

template<int N> struct AGenerator
{
    static void generate()
    {
        AGenerator<N - 1>::generate();
        A<N> a;
        a.someFunctions();
    }
};

template<> struct AGenerator<1>
{
    static void generate()
    {
        A<1> a;
        a.someFunctions();
    }
};

int main()
{
    // call the static member for constructing 100 A objects
    AGenerator<100>::generate();
}

1 打印到100


中,可以使用if constexpr 将上述内容简化为单个模板AGenerator 类(即可以避免专门化)。 (See a Live demo)

template<std::size_t N>
struct AGenerator final
{
    static constexpr void generate() noexcept
    {
        if constexpr (N == 1)
        {
            A<N> a;
            a.someFunctions();
            // .. do something more with `a`
        }
        else
        {
            AGenerator<N - 1>::generate();
            A<N> a;
            a.someFunctions();
            // .. do something more with `a`
        }
    }
};

输出

1
2
3
4
5
6
7
8
9
10

如果提供迭代范围,您可以使用以下内容。(See a Live demo)

template<std::size_t MAX, std::size_t MIN = 1> // `MIN` is set to 1 by default
struct AGenerator final
{
    static constexpr void generate() noexcept
    {
        if constexpr (MIN == 1)
        {
            A<MIN> a;
            a.someFunctions();
            // .. do something more with `a`
            AGenerator<MAX, MIN + 1>::generate();
        }
        else if constexpr (MIN != 1 && MIN <= MAX)
        {
            A<MIN> a;
            a.someFunctions();
            // .. do something more with `a`
            AGenerator<MAX, MIN + 1>::generate();
        }
    }
};

int main()
{
    // provide the `MAX` count of looping. `MIN` is set to 1 by default
    AGenerator<10>::generate();
}

输出与上述版本相同。

【讨论】:

    【解决方案3】:

    从 C++20 开始,您可以使用模板 lambda,因此您可以尝试以下方法

    []<int ... Is>(std::integer_sequence<int, Is...>)
     { (A<Is+1>{}.functionCall(), ...); }
       (std::make_integer_sequence<int, 100>{});
    

    以下是一个完整的编译示例,打印从 0 到 99 的所有数字

    #include <utility>
    #include <iostream>
    
    int main()
     {
      []<int ... Is>(std::integer_sequence<int, Is...>)
       { (std::cout << Is << std::endl, ...); }
         (std::make_integer_sequence<int, 100>{});
     }
    

    【讨论】:

      【解决方案4】:

      您可以这样做的一种方法是使用类似这样的模板元编程:

      #include <iostream>
      
      template <std::size_t N>
      struct A {
        void foo() { std::cout << N << '\n'; }
      };
      
      template <std::size_t from, std::size_t to>
      struct call_foo {
        void operator()() {
          if constexpr (from != to) {
            A<from + 1>{}.foo();
            call_foo<from + 1, to>{}();
          }
        }
      };
      
      int main() { call_foo<0, 100>{}(); }
      

      【讨论】:

        【解决方案5】:

        只是为了完整性 - 如果函数的唯一用途是从循环中调用,那么真的需要模板化类或函数吗?

        如果是这样并且您不想手写,请查看 boost.hana。

        【讨论】:

          猜你喜欢
          • 2017-08-16
          • 2019-09-27
          • 2020-01-23
          • 2011-11-23
          • 2011-11-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多