Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++: Instantiate many templates in library

Tags:

c++

c++14

I have a library with a class templated on a size:

foo.hpp:

template <size_t N>
struct Foo {
   void bar();
};

foo.cpp:

template <size_t N>
void Foo<N>::bar() {
...
};

I want the compiled version to be instatiated for a specific list of sizes which would be defined in the cpp file. In pseudocode, something like this

foo.cpp:

template <size_t N>
void Foo<N>::bar() {
...
};

for (const auto & size: {1,2,7,9})
    template struct Foo<size>;

Right now, I am manually doing this (instatiating for each type), but this is error prone since I have several files that should be defined for the same size.

I am aware that I could move everything to the header file, and use some enable_if's to choose which sizes are valid. However, I want to hide the implementation details. Hence the current approach.

EDIT:

FWIW, I was hoping to avoid macros. I thought that something like this would work if you want a range of variables (e.g. 1,2,3,4...):

template<template<size_t> class E, size_t I = 1, std::enable_if_t<I >= 5> *unused = nullptr>
bool instatiator() {
  return true;
}

template<template<size_t> class E, size_t I = 1, std::enable_if_t<I < 5> *unused = nullptr>
bool instatiator() {
  E<I> e;
  return instatiator<E, I + 1>();
}

bool unused = instatiator<Encoder>();

but I can't seem to get that working.

like image 332
bremen_matt Avatar asked Jun 27 '19 08:06

bremen_matt


Video Answer


3 Answers

You could use an X macro approach for this:

valid_sizes.hpp

// Note: no include guards!!

PROCESS_SIZE(1)
PROCESS_SIZE(2)
PROCESS_SIZE(7)
PROCESS_SIZE(9)

#undef PROCESS_SIZE

foo.cpp

// ...

#define PROCESS_SIZE(n) template struct Foo<n>;
#include "valid_sizes.hpp"

bar.cpp

// ...

#define PROCESS_SIZE(n) some_other_use_of<n>;
#include "valid_sizes.hpp"

Alternatively, instead of a manual list, you could use Boost.Preprocessor:

process_sizes.hpp

#pragma once

#define PROCESS_SIZES(macro, data) BOOST_PP_SEQ_FOR_EACH(macro, data, (1)(2)(7)(9))

some header file

#define INSTANTIATE_CLASS(r, Name, size) template class Name<size>;

foo.cpp

#include "process_sizes.hpp"

// ...

PROCESS_SIZES(INSTANTIATE_CLASS, Foo)

bar.cpp

#include "process_sizes.hpp"

// ...

PROCESS_SIZES(INSTANTIATE_CLASS, Bar)

#define OTHER_SIZE_BASED_WORK(r, data, size) other_use_of<size>;
PROCESS_SIZES(OTHER_SIZE_BASED_WORK, whatever)

Note that explicit instantiation definitions must occur at namespace scope, so it's not possible put them inside a function like your attempted instantiator.

And also note that an implicit instantiation (such as would be caused by referring to the class in a context which requires it to be defined) is not "visible" outside the translation unit, so it cannot be used as a substitute for explicit instantiation.

In other words, having the same list of sizes do different things requires duplicating the list for each use, using macros, or using custom code generation outside of the C++ compilation pipeline. I consider macros the simplest and most maintainable of these.

like image 150
Angew is no longer proud of SO Avatar answered Oct 19 '22 19:10

Angew is no longer proud of SO


In addition to @Angew answer: xmacros can be in a different manner, allowing include guards and also avoiding having #include in the middle of the code:

valid_sizes.hpp

#pragma once

#define SIZES_TABLE(OP)\
OP(1) \
OP(2) \
OP(12)

foo.cpp

#include "valid_sizes.hpp"
// ...
#define INSTANTIATE(Size) template struct Foo<Size>;
SIZES_TABLE(INSTANTIATE)
#undef INSTANTIATE
like image 41
amlucas Avatar answered Oct 19 '22 20:10

amlucas


You can recursively instantiate the Foo class with a helper class. Unfortunately you need to provide a call to each method of Foo in the constructor (the methods will not be called though), in order to provide the definitions to the linker:

// foo.cpp: 

template <size_t S>
struct recursive_helper {
  Foo<S> foo;
  recursive_helper<S-1> r_helper;

  recursive_helper() {
    foo.bar();
    foo.baz();
  }
};

template <>
struct recursive_helper<0> {
  Foo<0> foo;
  recursive_helper() {
    foo.bar();
    foo.baz();
  };
};

And then you instantiate the helper class: template struct recursive_helper<6>;

like image 1
Mike van Dyke Avatar answered Oct 19 '22 19:10

Mike van Dyke