Reputation: 1853
#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?
Upvotes: 5
Views: 3367
Reputation: 25663
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);
}
Upvotes: 8
Reputation: 66230
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);
}
Upvotes: 6