#include<string>
#include<type_traits>
template<typename... Args>
class C {
public:
void foo(Args&&... args) {
}
template<typename = std::enable_if_t<(0 < sizeof...(Args))>>
void foo(const Args&... args) {
}
};
int main() {
C<> c;
c.foo();
return 0;
}
Above code works as expacted (by me :)) and calls void foo(Args&&... args)
at run-time in msvc 2015 but same code fails to even compile in both gcc 7.3 and clang 6.0.0 with error:
error: no type named 'type' in 'std::enable_if'; 'enable_if' cannot be used to disable this declaration
I want to understand what is wrong with the above code and how can it be fixed?
SFINAE only works for deduced template arguments. In your case your method did not depend on any parameter from the method call so it is not in a deduced context. Everything is already known while instantiate the class itself.
MSVC is simply wrong in that case.
Workaround:
template<typename... Args>
class C
{
public:
template< typename U = std::tuple<Args...>>
std::enable_if_t< (std::tuple_size<U>::value > 0 ) > foo(const Args&...)
{
std::cout << "Args > 0 type " << std::endl;
}
template< typename U = std::tuple<Args...>>
std::enable_if_t< (std::tuple_size<U>::value == 0)> foo(const Args&...)
{
std::cout << "Args 0 type " << std::endl;
}
};
int main()
{
C<>{}.foo();
C<int>{}.foo(1);
}
I don't know why you need such a overload, because if the parameter list is empty, you simply should write an overload for such without any SFINAE stuff at all.
if your compiler is not outdated ( c++14 only ) it is much easier with using constexpr if
:
template <typename... Args>
struct C
{
void foo (const Args&... args)
{
if constexpr ( sizeof...(args) == 0)
{
std::cout << "0" << std::endl;
}
else
{
std::cout << ">0" << std::endl;
}
}
};
int main ()
{
C<> c0;
C<int> c1;
c0.foo();
c1.foo(42);
}
EDIT after comment:
To avoid SFINAE you can also use specialized template classes like this:
// provide common stuff here
template <typename ... ARGS>
class CAll { protected: void DoSomeThing(){ std::cout << "Do some thing" << std::endl; } };
template<typename ... ARGS>
class C;
// special for no args
template<>
class C<>: public CAll<>
{
public:
void foo()
{
std::cout << "none" << std::endl;
this->DoSomeThing();
}
};
//special for at minimum one arg
template<typename FIRST, typename ... REST>
class C<FIRST, REST...>: public CAll<FIRST, REST...>
{
public:
void foo( FIRST&, REST&... )
{
std::cout << "lvalue" << std::endl;
this->DoSomeThing();
}
void foo( FIRST&&, REST&&... )
{
std::cout << "rvalue" << std::endl;
this->DoSomeThing();
}
};
int main()
{
int a;
C<>{}.foo();
C<int>{}.foo(1);
C<int>{}.foo(a);
}
As better explained by Klaus, your original code doesn't works because std::enable_if_t
need to check a template of the method itself and isn't enough the template list of the class.
I propose a simplified alternative of the Klaus's solution.
First of all, you need a template parameter to check; you can use one with a default value deduced from the template paramenter of the class (Args...
).
Surely you can use a type taking a std::tuple
of the Args...
but, taking in count that you're only interested in the number of Args...
parameters, I find it's simpler to use a std::size_t
template parameter initialized with the number of Args...
template <std::size_t N = sizeof...(Args)>
std::enable_if_t<N> foo (Args const & ... args)
{ std::cout << "N args" << std::endl; }
Regarding the zero args version, there is no need to make it a template version; you can simply write it with zero parameters
void foo ()
{ std::cout << "zero args" << std::endl; }
In case of zero Args...
, the not-template version take the precedence over the template one.
The following is a full compiling example
#include <iostream>
#include <type_traits>
template <typename... Args>
struct C
{
void foo ()
{ std::cout << "zero args" << std::endl; }
template <std::size_t N = sizeof...(Args)>
std::enable_if_t<N> foo(const Args&... args)
{ std::cout << "N args" << std::endl; }
};
int main ()
{
C<> c0;
C<int> c1;
c0.foo();
c1.foo(42);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With