繁体   English   中英

C++ 中一系列模板参数的显式模板实例化

[英]Explicit template instantiation for a range of template parameters in C++

显式模板实例化在创建库时非常有用。 假设我有一个带有 int 参数的模板:

template <int i> struct S { ... };

要执行显式模板实例化,语法类似于

template struct S<1>;

但是,我只能以这种方式使用一行来实例化一个实例。 我想要做的是以优雅的方式定义一系列模板。 例如,考虑无法编译的错误代码:

#define MIN_I 1
#define MAX_I 16
for (int i = MIN_I; i <= MAX_I; i++) // i should be a constant
    template struct S<i>;

这样,修改MAX_I时,修改就很简单了。 我能实现这个目标吗? 如果可能的话,有没有简单的方法来做这样的事情? 谢谢!

此外,这个问题可以推广到更一般的设置。 例如,我可以采用 1,2,4,8,16,32,64,128,256 或一些预定义的序列。

我创建模板库的原因不好说。 简而言之,我将创建一个运行在 GPU(由 nvcc 编译器编译)上的 CUDA 库,并由由 gcc 编译的标准 c++ 程序调用。

让我们从观点的轻微转变开始。 目标是实例化给定模板的某些实例。 请注意,我删除了“显式”这个词——虽然会有显式实例化,但它不必是相关模板。 相反,我将显式实例化一个帮助模板,该模板将隐式实例化所需的模板。

帮助程序模板将使用参数包来容纳任意数量的 arguments。 如果目标是简单地列出数字,这将是相当直截了当的。 然而,也希望能够指定最小值和最大值。 为了处理这种情况,我将支持std::integer_sequence作为模板参数,我将通过部分特化来处理它。

// Start with a template that will not be defined; we will define only a
// specialization of this.
// The `C` parameter allows this to be applied to more templates than just `S`.
// The other parameters will make more sense for the specialization.
template<template<int> typename C, int ADD, class T> struct force_instantiation_impl;

// Partially specializing the template allows access to the numbers comprising the
// `integer_sequence` parameter.
// The ADD parameter will be added to each number in the sequence (will be helpful later).
template<template<int> typename C, int ADD, int... Ints>
struct force_instantiation_impl<C, ADD, std::integer_sequence<int, Ints...>> {
    // Implicitly instantiate the desired templates.
    std::tuple<C<ADD + Ints>...> unused;
};

在这里,参数包扩展用于迭代所有所需的模板 arguments。 这在问题的“错误代码”中扮演了赋予循环的角色。 当然,我们仍然需要获取给定最小值和最大值的索引。

为方便起见,如果您愿意,我会提供另一层帮助模板,即“界面”层。 第一个接口助手允许仅指定模板、最小值和最大值。 实例化这个助手将导致所需的模板被实例化。

// Instantiates C<I> for I ranging from MIN to MAX.
// MAX must be at least as large as MIN.
template<template<int> typename C, int MIN, int MAX>
struct force_instantiation_range {
    // Check the assumption.
    static_assert(MIN <= MAX);
    // Make an integer sequence from 0 to (MAX-MIN) for the template argument.
    // When MIN is added to each element, the sequence will go from MIN to MAX.
    force_instantiation_impl<C, MIN, std::make_integer_sequence<int, MAX-MIN+1>> unused;
};

另一个接口助手允许简单地列出所需的 arguments。 在某种程度上,这个助手的目的是隐藏通过支持一系列 arguments 引入的复杂性。 (如果没有这种支持,这个模板的参数可能是force_instantiation_impl的参数。)

// Instantiates C<I> for the given I's.
template<template<int> typename C, int... Ints>
struct force_instantiation_list {
    force_instantiation_impl<C, 0, std::integer_sequence<int, Ints...>> unused;
};

就是这样。 由于注释的原因,它可能看起来像很多代码,但实际上它相当短。 有三个结构模板定义,每个都有一个成员。 要使用它,请显式实例化其中一个接口助手。

// Forgive me, I'm changing the pre-processor directives to type-safe constants.
constexpr int MIN_I = 1;
constexpr int MAX_I = 16;
template struct force_instantiation_range<S, MIN_I, MAX_I>;

等效地,可以明确列出 arguments。

template struct force_instantiation_list<S, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16>;

诚然,我确实改变了这个问题。 如果您确实需要直接对模板进行显式实例化,那么这种方法将不起作用。 如果是这种情况,您可能不得不依赖预处理器,这可能是一场噩梦。 幸运的是, Boost.Preprocessor已经处理了最痛苦的细节。 我会利用这一点。

使用 Boost.Preprocessor 可以分两行完成。

//temp.h
#pragma once

//Let's declare a template with a member function
template<int N>
struct MyStruct {
    void About() const;
};

//temp.cpp
#include "temp.h"
#include <iostream>
#include <boost/preprocessor/repetition/repeat_from_to.hpp>

//Let's define the template member function outside the header. Normally it would not link
template<int N>
void MyStruct<N>::About() const {
    std::cout << N << std::endl;
}

//All the trick is in the next two lines
#define INSTANT(z, n, Struct) template struct Struct<n>;

BOOST_PP_REPEAT_FROM_TO(1, 16, INSTANT, MyStruct);
//main.cpp
#include "temp.h"

int main() {
    MyStruct<2>().About();
    MyStruct<5>().About();
    MyStruct<12>().About();
    return 0;
}

一切都在正确编译、链接和运行。 看起来如果你想成为一个成熟的元程序员,你也应该掌握预处理器技术。

这个问题可以推广到更一般的环境。 例如,我可以采用 1,2,4,8,16,32,64,128,256 或一些预定义的序列。

这也很容易:

template <int i> struct S {  };

//version that will do the real work
template<template<int>typename Functor, int J, int... I>  void f() 
{
    int j = (Functor<J>{},1); 
    int i = (Functor<I>{},...,1);    
}

int main()
{
    f<S, 1, 3, 5, 76, 4, 5>();   
}

工作演示


我想要做的是以优雅的方式定义一系列模板。

这是使用 C++11 和 C++20 中的模板执行此操作的另一种方法(更直接)。

C++20版本

这里我们使用requires

template <int i> struct S {  };

//for ending recursion
template<std::size_t min, std::size_t max, template<int>typename >  void f() requires(min==max+1){}

//version that will do the real work
template<std::size_t min, std::size_t max, template<int>typename Functor>  void f() requires(min!=max+1)
{
    
    int i = (Functor<min>{},1);
    f<min+1, max, Functor>();    
}

int main()
{
    f<1,16, S>();   
}

工作演示 c++20


使用 C++11 进行这项工作很简单。请参阅工作演示 C++11 这个C++11版本使用SFINAE

暂无
暂无

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

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