DangoPython
DangoPython

Reputation: 23

C++17 Template argument deduction failed

I wanted to simulate timer which would call a function (callback) periodically for that i wrote following snippet (naive though) wherein, Argument deduction is failing at line Timer/<int, int, float>/.. in main function. I am compiling this code with c++17 std. How I can fix this? or those arguments necessary? Any help in this regard is appreciated.

#include <functional>
#include <chrono>

using namespace std::chrono_literals;

template<typename ...Args>
class Timer
{
public:
  Timer(std::function<void(Args...)> func, Args&&... args, std::chrono::milliseconds step)
    : m_callback{ func },
      m_args{ std::forward<Args>(args)... },
      m_time{ step },
      m_stop{ false },
  { }

private:
  std::function<void(Args...)> m_callback;
  std::thread m_executer;
  std::tuple<Args...> m_args;
  std::chrono::milliseconds m_time;
  bool m_stop;
};

int main()
{
  // Create a timer
  auto timer = Timer/*<int, int, float>*/([](int a, int b, float c) { }, 15, 17, 12.0f, 500ms);

  return 0;
}

Upvotes: 2

Views: 385

Answers (2)

cigien
cigien

Reputation: 60208

There are 2 issues with template argument deduction here. The std::function parameter can't be deduced from the lambda argument, and deduction only works for parameter packs when they are trailing.

To fix this, you can change the order of the arguments to the constructor, like this:

Timer(std::function<void(Args...)> func, std::chrono::milliseconds step, Args&&... args)
 // ...                                                              //  ^^^^^^^^^ trailing 

and add a deduction guide

template<typename Func, typename ...Args>
Timer(Func, std::chrono::milliseconds, Args&&...) -> Timer<Args...>;

Here's a demo.

Note the warning, your member initializer list is not in the same order as the member declarations.

Upvotes: 1

Chris Uzdavinis
Chris Uzdavinis

Reputation: 6131

I stripped out everything that wasn't germane to your question (you should do that to make it easier to get help), and here's what I got to work. First, I moved the milliseconds before the variadic arguments. It can confuse the compiler where parameter packs end, so you usually want ... to be the last thing in an argument list.

Second, CTAD (Class template arg deduction) is not working for you due to the need to convert your lambda to a std::function before it can match.

If main() passes an actual std::function into your constructor as it expects, then it works:

auto timer = Timer(
    std::function<void(int, int, float)>([](int a, int b, float c) { }),
    500ms, 15, 17, 12.0f);

But that is arguably worse looking than what you originally tried.

The better fix, which is a bit obscure, is to help CTAD figure out what you're doing with a c++17 deduction guide. This is a pattern match saying, "if they try to create this class using this syntax, it really means it's this type."

Here's a guide I found to work for you:

template <typename T, typename... Args> 
Timer(T&&, std::chrono::milliseconds, Args...) -> Timer<Args...>;

What this guide does is says: they can pass anything T&& as the first parameter and it's just ignored, and thats followed by chrono milliseconds, then some more types in the Args pack. Given that, the acutal type of Timer (right of the arrow) is just the Timer<Args...> whatever they are. It then proceeds, and your function just needs to be convertible to the std::function.

Now it matches your use with the lambda, instantiates Timer with the given Args, and from that knows it can convert your lambda to the std::function.

Here's a working example, minus all the stuff that was unrelated:

#include <functional>
#include <chrono>

using namespace std::chrono_literals;

template<typename ...Args>
struct Timer {
  Timer(std::function<void(Args...)> func,  std::chrono::milliseconds step, Args&&... args )
  { }
};

template <typename T, typename... Args>
Timer(T&&, std::chrono::milliseconds, Args...) -> Timer<Args...>;

int main() {
    auto timer = Timer([](int a, int b, float c) { }, 500ms, 15, 17, 12.0f);
}

Compiler Explorer: https://godbolt.org/z/Wa3oK3

Upvotes: 1

Related Questions