zbh2047
zbh2047

Reputation: 453

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:

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. 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.

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.

Upvotes: 7

Views: 1690

Answers (3)

user12002570
user12002570

Reputation: 1

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.

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++20 Version

Here we make use of 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


It is trivial to make this work with C++11. See Working demo C++11. This C++11 version uses SFINAE

Upvotes: 1

kasos5000
kasos5000

Reputation: 41

With Boost.Preprocessor it could be done literally in two lines.

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

Upvotes: 4

JaMiT
JaMiT

Reputation: 16873

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. 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.

// 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. 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. To some extent, the purpose of this helper is to hide the complexities introduced by supporting a range of arguments. (If it were not for that support, the parameters to this template could have been the parameters to 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.

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. I'd take advantage of that.

Upvotes: 6

Related Questions