0

I have a class with a function template.

class Foo {
 public:
  void Foo_fn1();

  template <typename Closure>
  void Foo_util (Closure&& closure) {
    Foo_fn1();
    std::forward<Closure>(closure)();
  }

};

The above is part of a .h file that has been included at least 10k times in the code base. My question is the following:

Can such usage of templates in such a way slow down compilation? Would it be better to write it in a util file with the following signature:

  template <typename Closure>
  void Foo_util (Foo foo, Closure&& closure) {
    foo.Foo_fn1();
    std::forward<Closure>(closure)();
  }

Why or Why not? Any documentation explaining the above would be helpful.

Aman Deep Gautam
  • 8,091
  • 21
  • 74
  • 130
  • 1
    It can't show down the compilation by noticeable time. – 273K Jul 18 '23 at 01:17
  • What is the purpose of forwarding a result of `closure()`, does it produce own type? – 273K Jul 18 '23 at 01:18
  • 2
    `std::forward(closure())` is clearly wrong. It should be `std::forward(closure)()`, or better `std::invoke(std::forward(closure))`. – user17732522 Jul 18 '23 at 01:41
  • "_Would it be better to write it in a util file with the following signature:_": What difference do you see here? The util file would also need to be a header file included everywhere that `Foo_util` is used and in the end you do the same number of instantiations. – user17732522 Jul 18 '23 at 01:42
  • @user17732522 Foo_util is not needed in 10k places. Maybe just a few 10s of places. – Aman Deep Gautam Jul 18 '23 at 04:54
  • @user17732522, yes you are correct. I was typing manually so made the mistake. – Aman Deep Gautam Jul 18 '23 at 04:55
  • @user17732522 `std::invoke(std::forward(closure))` why is this better? can you please explain? – Aman Deep Gautam Jul 18 '23 at 04:56
  • @273K It cannot, my purpose of asking is more towards understanding if it would impact or not. – Aman Deep Gautam Jul 18 '23 at 04:57
  • @AmanDeepGautam [What is std::invoke in c++?](https://stackoverflow.com/questions/43680182/what-is-stdinvoke-in-c) – user17732522 Jul 18 '23 at 06:37
  • @AmanDeepGautam The cost of only _including_ the template definition in a header file will not be significant. _Instantiating_ the template can be significant, but you only instantiate it in a translation unit that actually uses the function. However, it can be advantageous to separate a seldom-used template in its own header file, so that there are less interdependencies and so that build avoidance in your build system can avoid rebuilding everything when you change the definition of this function. – user17732522 Jul 18 '23 at 06:39
  • To separate the definition of the template into a separate header file, you don't need to make it a free function template btw. you can still declare it as member of the class and then define it in the header file as member function template. You then have the rebuild cost only if you change the declaration of the template, which probably shouldn't happen as often. Also, the free function template is not equivalent, because it takes the first argument by-value instead of by-reference. – user17732522 Jul 18 '23 at 06:46
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/254560/discussion-between-aman-deep-gautam-and-user17732522). – Aman Deep Gautam Jul 19 '23 at 09:23

1 Answers1

0

In general including stuff in fewer spots causes faster compilation.

However, template compilation slowdown mostly comes from it being used in multiple spots. Each time it is used, the template has to be instantiated, which can take much longer than it being parsed originally.

Moving the definition cannot change how many times it is instantiated, unless you also change other code.

If you want to know if build times will be faster with work, one way is to actually test it. Change:

template <typename Closure>
void Foo_util (Closure&& closure) {
  Foo_fn1();
  std::forward<Closure>(closure)();
}

to

void Foo_util (std::function<void()> closure);

and then in Foo.cpp file:

void Foo::Foo_util(std::function<void()> closure) {
  Foo_fn1();
  closure();
}

almost any refactoring changes you make to your code will result in less speedup than the above.

(Note that a few lambdas won't convert to std function. If that is the case, here is a simple function view:

template<class Sig>
struct function_view;
template<class R, class...Args>
struct function_view<R(Args...)> {
  template<class T,
    std::enable_if_t<!std::is_same_v<std::decay_t<T>,function_view>, bool> = true
  >
  function_view( T&& t ):
    pdata{std::addressof(t)},
    paction(+[](state pt, Args&&...args)->R {
      return ((T&&)(*static_cast<std::decay_t<T> const*>(pt.pdata)))(std::forward<Args>(args)...);
    } )
  {}
  function_view( R(*pf)(Args...) ):
    pdata{pf},
    paction(+[](state pf, Args&&...args)->R {
      return ((R(*)(Args...))pf.pfunc)(std::forward<Args>(args)...);
    })
  {}
  R operator()(Args...args)const {
    return paction(pdata, std::forward<Args>(args)...);
  }
private:
  union state {
    const void* pdata;
    R(*pfunc)(Args...);
    state():pdata(nullptr){};
    state(const void*p):pdata(p){}
    state(R(*f)(Args...)):pfunc(f){}
  };
  state pdata;
  R(*paction)(state, Args&&...) = nullptr;
};

used like this:

void Foo_util (function_view<void()> closure);

and then in Foo.cpp file:

void Foo::Foo_util(function_view<void()> closure) {
  Foo_fn1();
  closure();
}
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524