为什么不能使用 'enable_if' 来禁用这里声明
Why 'enable_if' cannot be used to disable this declaration here
#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;
}
以上代码按预期工作(由我 :))并在 msvc 2015 中的 运行 时间调用 void foo(Args&&... args)
但相同的代码甚至无法在 gcc 7.3 和 clang 6.0.0 中编译,出现错误:
error: no type named 'type' in 'std::enable_if';
'enable_if' cannot be used to disable this declaration
我想了解上面的代码有什么问题,如何解决?
SFINAE 仅适用于推导的模板参数。在您的情况下,您的方法不依赖于方法调用中的任何参数,因此它不在推导的上下文中。在实例化 class 本身时,一切都已经知道了。
在那种情况下,MSVC 完全是错误的。
解决方法:
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);
}
我不知道你为什么需要这样的重载,因为如果参数列表是空的,你只需要为它写一个重载,根本不需要任何 SFINAE 的东西。
如果您的编译器没有过时(仅限 c++14),使用 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);
}
评论后编辑:
为了避免 SFINAE,您还可以使用像这样的专用模板 class:
// 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);
}
正如 Klaus 更好地解释的那样,您的原始代码不起作用,因为 std::enable_if_t
需要检查方法本身的模板并且 class 的模板列表不够。
我提出了 Klaus 解决方案的简化替代方案。
首先,你需要一个模板参数来检查;您可以使用从 class (Args...
).
的模板参数中推导出的默认值
当然你可以使用一个带有 Args...
的 std::tuple
的类型,但是考虑到你只对 Args...
参数的数量感兴趣,我发现它是使用 std::size_t
模板参数更简单,模板参数初始化为 Args...
template <std::size_t N = sizeof...(Args)>
std::enable_if_t<N> foo (Args const & ... args)
{ std::cout << "N args" << std::endl; }
关于零args版本,不需要做模板版本;你可以简单地用零参数编写它
void foo ()
{ std::cout << "zero args" << std::endl; }
如果为零 Args...
,非模板版本优先于模板版本。
下面是一个完整的编译示例
#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);
}
#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;
}
以上代码按预期工作(由我 :))并在 msvc 2015 中的 运行 时间调用 void foo(Args&&... args)
但相同的代码甚至无法在 gcc 7.3 和 clang 6.0.0 中编译,出现错误:
error: no type named 'type' in 'std::enable_if'; 'enable_if' cannot be used to disable this declaration
我想了解上面的代码有什么问题,如何解决?
SFINAE 仅适用于推导的模板参数。在您的情况下,您的方法不依赖于方法调用中的任何参数,因此它不在推导的上下文中。在实例化 class 本身时,一切都已经知道了。
在那种情况下,MSVC 完全是错误的。
解决方法:
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);
}
我不知道你为什么需要这样的重载,因为如果参数列表是空的,你只需要为它写一个重载,根本不需要任何 SFINAE 的东西。
如果您的编译器没有过时(仅限 c++14),使用 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);
}
评论后编辑:
为了避免 SFINAE,您还可以使用像这样的专用模板 class:
// 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);
}
正如 Klaus 更好地解释的那样,您的原始代码不起作用,因为 std::enable_if_t
需要检查方法本身的模板并且 class 的模板列表不够。
我提出了 Klaus 解决方案的简化替代方案。
首先,你需要一个模板参数来检查;您可以使用从 class (Args...
).
当然你可以使用一个带有 Args...
的 std::tuple
的类型,但是考虑到你只对 Args...
参数的数量感兴趣,我发现它是使用 std::size_t
模板参数更简单,模板参数初始化为 Args...
template <std::size_t N = sizeof...(Args)>
std::enable_if_t<N> foo (Args const & ... args)
{ std::cout << "N args" << std::endl; }
关于零args版本,不需要做模板版本;你可以简单地用零参数编写它
void foo ()
{ std::cout << "zero args" << std::endl; }
如果为零 Args...
,非模板版本优先于模板版本。
下面是一个完整的编译示例
#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);
}