#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);
}