简体   繁体   English

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

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

Explicit template instantiation is very useful when creating libraries.显式模板实例化在创建库时非常有用。 Suppose I have a template with an int paramater:假设我有一个带有 int 参数的模板:

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

To perform explicit template instantiation, the grammar is something like要执行显式模板实例化,语法类似于

template struct S<1>;

However, I can only instantiate one instance using one line in this way.但是,我只能以这种方式使用一行来实例化一个实例。 What I want to do is to define a range of template in an elegant way.我想要做的是以优雅的方式定义一系列模板。 For example, consider the wrong code that can not be compiled:例如,考虑无法编译的错误代码:

#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>;

In this way, when MAX_I is changed, the modification is very simple.这样,修改MAX_I时,修改就很简单了。 Can I achieve this goal?我能实现这个目标吗? If it is possible, is there an easy way to do such thing?如果可能的话,有没有简单的方法来做这样的事情? Thank you!谢谢!

Also, this question can be generalized to a more general setting.此外,这个问题可以推广到更一般的设置。 For example, I can take 1,2,4,8,16,32,64,128,256 or some predefined sequence.例如,我可以采用 1,2,4,8,16,32,64,128,256 或一些预定义的序列。

The reason that I create a template library is not easy to say.我创建模板库的原因不好说。 In short, I will create a CUDA library that runs on GPU (which is compiled by nvcc compiler), and is called by a standard c++ program which is compiled by gcc.简而言之,我将创建一个运行在 GPU(由 nvcc 编译器编译)上的 CUDA 库,并由由 gcc 编译的标准 c++ 程序调用。

Let's start with a slight shift in perspective.让我们从观点的轻微转变开始。 The goal is to instantiate certain instances of a given template.目标是实例化给定模板的某些实例。 Note that I dropped the word "explicit" – while there will be an explicit instantiation, it need not be of the template in question.请注意,我删除了“显式”这个词——虽然会有显式实例化,但它不必是相关模板。 Rather, I would explicitly instantiate a helper template that will implicitly instantiate the desired template.相反,我将显式实例化一个帮助模板,该模板将隐式实例化所需的模板。

The helper template will use a parameter pack to accommodate an arbitrary number of arguments.帮助程序模板将使用参数包来容纳任意数量的 arguments。 This would be reasonably straight-forward if the goal was to simply list numbers.如果目标是简单地列出数字,这将是相当直截了当的。 However, there is also a desire to be able to specify a min and max.然而,也希望能够指定最小值和最大值。 To handle this case, I'll support std::integer_sequence as a template argument, which I'll handle via partial specialization.为了处理这种情况,我将支持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;
};

Here, parameter pack expansion is used to iterate over all desired template arguments.在这里,参数包扩展用于迭代所有所需的模板 arguments。 This plays the role given to the loop in the "wrong code" of the question.这在问题的“错误代码”中扮演了赋予循环的角色。 Of course, we still have to get the indices for a given minimum and maximum.当然,我们仍然需要获取给定最小值和最大值的索引。

For convenience, I would provide another layer of helper templates, an "interface" layer, if you will.为方便起见,如果您愿意,我会提供另一层帮助模板,即“界面”层。 The first interface helper allows specifying just the template, the minimum, and the maximum.第一个接口助手允许仅指定模板、最小值和最大值。 Instantiating this helper will cause the desired templates to be instantiated.实例化这个助手将导致所需的模板被实例化。

// 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;
};

The other interface helper allows simply listing the desired arguments.另一个接口助手允许简单地列出所需的 arguments。 To some extent, the purpose of this helper is to hide the complexities introduced by supporting a range of arguments.在某种程度上,这个助手的目的是隐藏通过支持一系列 arguments 引入的复杂性。 (If it were not for that support, the parameters to this template could have been the parameters to force_instantiation_impl .) (如果没有这种支持,这个模板的参数可能是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;
};

And that's it.就是这样。 It might look like a lot of code because of the comments, but it's actually reasonably short.由于注释的原因,它可能看起来像很多代码,但实际上它相当短。 There are three struct template definitions, each with a single member.有三个结构模板定义,每个都有一个成员。 To put this to use, explicitly instantiate one of the interface helpers.要使用它,请显式实例化其中一个接口助手。

// 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>;

Equivalently, the arguments could be explicitly listed.等效地,可以明确列出 arguments。

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

Admittedly, I did shift the question a bit.诚然,我确实改变了这个问题。 If you really need explicit instantiations directly of your template, then this approach will not work.如果您确实需要直接对模板进行显式实例化,那么这种方法将不起作用。 If that is the case, you might have to rely on the pre-processor, which can be a nightmare to get right.如果是这种情况,您可能不得不依赖预处理器,这可能是一场噩梦。 Fortunately, Boost.Preprocessor has already taken care of the most painful of the details.幸运的是, Boost.Preprocessor已经处理了最痛苦的细节。 I'd take advantage of that.我会利用这一点。

With Boost.Preprocessor it could be done literally in two lines.使用 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;
}

All is compiling, linking and running correctly.一切都在正确编译、链接和运行。 It looks like if you want to become a full-fledged metaprogrammer, you should master preprocessor techniques as well.看起来如果你想成为一个成熟的元程序员,你也应该掌握预处理器技术。

this question can be generalized to a more general setting.这个问题可以推广到更一般的环境。 For example, i can take 1,2,4,8,16,32,64,128,256 or some predefined sequence.例如,我可以采用 1,2,4,8,16,32,64,128,256 或一些预定义的序列。

This can also be easily:这也很容易:

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>();   
}

Working demo .工作演示


What I want to do is to define a range of template in an elegant way.我想要做的是以优雅的方式定义一系列模板。

Here is another way(more straighforward) of doing this using templates in both C++11 as well as C++20.这是使用 C++11 和 C++20 中的模板执行此操作的另一种方法(更直接)。

C++20 Version C++20版本

Here we make use of requires .这里我们使用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>();   
}

Working demo c++20工作演示 c++20


It is trivial to make this work with C++11. See Working demo C++11 .使用 C++11 进行这项工作很简单。请参阅工作演示 C++11 This C++11 version uses SFINAE这个C++11版本使用SFINAE

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

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