Setup
I asked a question yesterday about template method overloading and resolving issues using type traits. I received some excellent answers, and they led me to a solution. And that solution led me to more reading.
I landed on a page at Fluent CPP -- https://www.fluentcpp.com/2018/05/18/make-sfinae-pretty-2-hidden-beauty-sfinae/ that was interesting, and then I listened to the Stephen Dewhurst talk that Mr. Boccara references. It was all fascinating.
I'm now trying to understand a little more. In the answers yesterday, I was given this solution:
template< class Function, class... Args,
std::enable_if_t<std::is_invocable_v<Function, Args...>, std::nullptr_t> = nullptr>
explicit MyClass( const std::string & theName, Function&& f, Args&&... args )
: name(theName)
{
runner(f, args...);
}
Alternative Answer
After reading the CPP Fluent post and watching the talk, I came to this final solution:
template< class Function, class... Args>
using IsInvocable = std::enable_if_t < std::is_invocable_v<Function, Args...> >;
template< class Function, class... Args, typename = IsInvocable<Function, Args...> >
explicit ThreadHandle( const std::string & name, Function && f, Args &&... args ) {
startWithName(name, f, args...);
}
The first bit just moves some of the syntax into a common include file, but overall, this is simpler. I think this is clean and requires little explanation, even for someone unfamiliar with using type traits.
The Question
What I'm wondering is this. All three answers I received used a more complex form of enable_if_t
like this:
std::enable_if_t<std::is_invocable_v<Function, Args...>, std::nullptr_t> = nullptr>
And I'm not sure why they would do that if I can do this instead:
std::enable_if_t< std::is_invocable_v < Function, Args... > >
Are there implications? Or is this simply a matter of the more complex one is C++11, and now C++ 14 and 17 allows a simpler form? Perhaps the people responding were simply helping me out by showing me the complete form.
To add to my confusion, one of the answers did this:
std::enable_if_t<!std::is_convertible_v<Function, std::string>, bool> = true>
And another one did this:
std::enable_if_t<std::is_invocable_v<Function, Args...>, int> = 0>
I don't really understand these implications, either.
Any help getting over the hurdle would be great. I imagine there will be cases I'll want the more complicated versions, so understanding it better would be good.
std::enable_if_t< std::is_invocable_v < Function, Args... > >
isvoid
when the condition istrue
. That's fine forusing IsInvocable = void;
but doesn't work for template parameters. In that case you instead make itvoid*
or some other simple type and assign a default value so the user doesn't have to provide anything. I'm not sure this is the specific reason that applies here, but it is the first that comes to mind. – Lancers