2

I want to define two kinds of member functions run according to one template paramter of class. Here is my code:

template <int VAL, typename D = std::chrono::seconds, bool IS_DAILY = false>
class TimerJob {
public:
  template <typename C, typename F, typename... Args, typename = std::enable_if_t<IS_DAILY>>
  void run(F C::*f, C* c, Args&&... args) {
    td_ = std::make_unique<std::thread>(
        [](F C::*f, C* c, Args... args){
            do {
              c.f(args...);
              std::this_thread::sleep_for(D{VAL});
            } while (true);
        });
  }

  template <typename C, typename F, typename... Args, typename = std::enable_if_t<!IS_DAILY>>
  void run(F C::*f, C* c, Args&&... args) {
    td_ = std::make_unique<std::thread>(
        [](F C::*f, C* c, Args... args){
            do {
              c.f(args...);
              // just an example to make a difference with the other run()
              // the real case is much complicated
              std::this_thread::sleep_for(D{VAL + 60});
            } while (true);
        });
  }
private:
  std::unique_ptr<std::thread> td_;
};

As you see, I'm trying to overload the function run with different IS_DAILY.

But I got an error:

<source>:27:8: error: 'template<int VAL, class D, bool IS_DAILY> template<class C, class F, class ... Args, class> void TimerJob<VAL, D, IS_DAILY>::run(F C::*, C*, Args&& ...)' cannot be overloaded with 'template<int VAL, class D, bool IS_DAILY> template<class C, class F, class ... Args, class> void TimerJob<VAL, D, IS_DAILY>::run(F C::*, C*, Args&& ...)'
   27 |   void run(F C::*f, C* c, Args&&... args) {
      |        ^~~
<source>:15:8: note: previous declaration 'template<int VAL, class D, bool IS_DAILY> template<class C, class F, class ... Args, class> void TimerJob<VAL, D, IS_DAILY>::run(F C::*, C*, Args&& ...)'
   15 |   void run(F C::*f, C* c, Args&&... args) {

I'm confused now... Isn't std::enable_if used like this?

BTW, I'm working with C++14.

Yves
  • 11,597
  • 17
  • 83
  • 180
  • Either do two partial specializations of `TimerJob` based on `IS_DAILY` value, or implement one `run` using `if constexpr`, or with C++20 you can use `requires IS_DAILY` and `requires (!IS_DAILY)` – Patrick Roberts Jul 01 '23 at 02:49
  • FYI, the `std::enable_if_t` overloading only works if the condition is dependent on the template parameters of the method template, not the class template. – Patrick Roberts Jul 01 '23 at 02:50
  • @PatrickRoberts But `template >` wont work either. `IS_DAILY_FUNC` is the template paramter of the method, right? – Yves Jul 01 '23 at 03:00
  • 1
    @Yves you need to combine what Patrick said with alfC's imperfect answer. – ALX23z Jul 01 '23 at 03:21
  • Yes, apparently if you use method template indirection + NTTPs, it works: https://godbolt.org/z/he8f18va8 I was going to suggest defining a CRTP mixin using partial specializations which completely sidesteps the need for `std::enable_if`, but it is a little more complex: https://godbolt.org/z/3qTar3EKo – Patrick Roberts Jul 01 '23 at 06:08

2 Answers2

3

Because the (template) signatures are exactly the same.

A common workaround is to use a non-type parameter to solve this. And in any case, you always need to make the contents of enable_if dependent on at least one of the current parameter.

Try with this:

  template <typename C, typename F, typename... Args, std::enable_if_t<IS_DAILY and sizeof(C*)>* =nullptr> // typename = std::enable_if_t<IS_DAILY>>

...
  }

  template <typename C, typename F, typename... Args, std::enable_if_t<!IS_DAILY and sizeof(C*)>* =nullptr> // typename = std::enable_if_t<!IS_DAILY>>
  void run(F C::*f, C* c, Args&&... args) {
...
  }

I think this is how it works.

Code comparison: https://godbolt.org/z/b5v13MaWf (uncomment the code at the end of the two lines to see the original problem)

NOTE: In C++14, lacking if constexpr, you should use tag dispatch for this, not std::enable_if. Like this: https://godbolt.org/z/KhW76Wbsz

alfC
  • 14,261
  • 4
  • 67
  • 118
  • 1
    Hmm, but `TimerJob<30, std::chrono::seconds, false> job;` would generate another error: `error: no type named 'type' in 'struct std::enable_if' 2610 | using enable_if_t = typename enable_if<_Cond, _Tp>::type;` – Yves Jul 01 '23 at 03:18
  • Really? https://godbolt.org/z/WMe71GnbK – alfC Jul 01 '23 at 05:22
  • @alfC Really. https://godbolt.org/z/q5dh1K5W3 – Patrick Roberts Jul 01 '23 at 05:24
  • I see what you mean, in that case, you need to do an extra step: https://godbolt.org/z/b5v13MaWf – alfC Jul 01 '23 at 05:40
  • Wow, I'm kind of confused by the trick `sizeof(C*)`, could you please explain a little bit? – Yves Jul 01 '23 at 06:10
  • 1
    The argument of `enable_if` always has to be an expression that depends on the previous template parameters. That is the only way to make it work correctly (otherwise it generates hard errors, not SFINAE). To make the expression dependent on the current template parameters I have to add an spurious dependency that doesn't affect the result, since `sizeof(anything)` is always true I can add `and true`. I make it a pointer to ensure this further by recommendation of Raymond Chen, since in the future we might have classes with zero-size, but never pointers. I still recommend using tag dispatching – alfC Jul 01 '23 at 07:21
  • I'm sceptical of using `* = 0` at the end. Compilers or clang-tidy might give you a warning about initializing pointers to zero instead of using `nullptr`. I'd prefer `..., int> = 0`. Or even better: just do SFINAE in the trailing return type. – Jan Schultke Jul 01 '23 at 08:07
  • yes, and yes. I used `nullptr` in the original version, since the return of the function is void, it is ideal to put the enable_if as return. Still, I don't recommend this technique for this particular case. – alfC Jul 01 '23 at 10:14
  • @JanSchultke yes, `std::enable_if<..., int> =0` is shorter. But `std::enable_if<...>* ={}` is even more. ;) https://godbolt.org/z/Gq9d5KjGE – alfC Jul 01 '23 at 10:19
-2

I tried following using your code as base version:

/*
    Compilation using MINGW at Windows:
    /usr/bin/g++.exe -g -c -Wall 76593060.cpp -std=c++17
    $ /usr/bin/ls -ltr 76593060.o
    -rw----r--+ 1 murugesan openssl 19360 Jul  1 09:02 76593060.o
*/
#include <thread>   // ‘std::thread’ is defined in header ‘<thread>’;
#include <memory>   // ‘std::unique_ptr’ is defined in header ‘<memory>’
#include <type_traits>  // std::enable_if_t
#include <chrono>   // std::chrono::seconds
#include <iostream>
using namespace std;    // Remove all std::
template <int VAL, typename D = chrono::seconds, bool IS_DAILY = false>
class TimerJob
{
    public:
    template <typename C, typename F, typename... Args, typename = enable_if_t<IS_DAILY>>
    void run(F C::*f, C* c, Args&&... args)
    {
        td_ = make_unique<thread>(
        [](F C::*f, C* c, Args... args){
        do
        {
            c.f(args...);
            this_thread::sleep_for(D{VAL});
        } while (true);
        });
    }

    // REQUIREMENT VIEWED AT ERROR DURING COMPILATION:
    // template<class C, class F, class ... Args, class>
    // HOWEVER OLD CODE WAS NOT HAVING FOURTH ARGUMENT:
    template <typename C, typename F, typename... Args, typename = enable_if_t<!IS_DAILY>>
    // void run(F C::*f, C* c, Args&&... args)
    void run(F C::*f, C* c, Args&&... args, bool DAILY_VALIDATION)
    {
        td_ = make_unique<thread>(
        [](F C::*f, C* c, Args... args){
        do
        {
            c.f(args...);
            // just an example to make a difference with the other run()
            // the real case is much complicated
            this_thread::sleep_for(D{VAL + 60});
        } while (true);
        });
    }
    private:
    unique_ptr<thread> td_;
};