我试图理解为什么此代码无法编译:
// test.h
struct Base
{
virtual ~Base{};
virtual void execute() {}
virtual void execute(int) {}
virtual void execute(double) {}
}
template<class T>
struct Test : Base
{
void execute(typename std::enable_if<std::is_void<T>::value, void>::type)
{
// Do A
}
void execute(typename std::enable_if<!std::is_void<T>::value, int>::type t)
{
// Do B
}
};
// main.cpp
Test<void> t;
我收到一个编译器错误:“无类型命名类型”。
即使我使用
修改代码的A版本,也存在相同的错误std::enable_if<std::is_void<T>::value>
目标是创建一个根据参数T创建不同函数成员的类。在这种情况下2,但我也会对此感兴趣。
[编辑] 我在注释中添加了我正在谈论的继承部分。
答案 0 :(得分:4)
注意:此答案对于问题的先前编辑很有价值。最近的编辑彻底改变了这个问题,这个答案已经不足够了。
由于execute
不是模板函数,因此可能不会涉及SFINAE。实际上,无论何时Test<void>
实例化时,execute
的两个版本都是相同的,这会导致错误,这不是模板推导失败。
您需要一个功能模板(让我们调用模板参数U
),以便从SFINAE中受益。并且由于您需要使用Test
(T
)的相同类型模板参数,因此可以提供默认参数U = T
):
解决方案:
template<class T>
struct Test
{
template<class U = T>
std::enable_if_t<std::is_void_v<U>> execute()
{ std::cout << "is_void\n"; }
template<class U = T>
std::enable_if_t<!std::is_void_v<U>> execute()
{ std::cout << "!is_void\n"; }
};
答案 1 :(得分:4)
实例化Test<void>
时,还实例化了所有成员函数的声明。那只是基本的实例化。那给你什么声明?像这样:
void execute(void);
void execute(<ill-formed> t);
如果您期望SFINAE静默消除格式错误的重载,则需要记住S代表“替代”。将模板参数替换为(成员)功能模板的参数。 execute
都不是成员函数模板。它们都是模板专业化的常规成员函数。
您可以通过几种方法对其进行修复。一种方法是制作这两个模板,正确执行SFINAE,并让超载分辨率使您从那里开始。 @YSC已经向您展示了方法。
另一种方法是使用帮助程序模板。这样,您就可以达到最初的目标,即可以随时存在一个成员函数。
template<typename T>
struct TestBase {
void execute(T t) { }
};
template<>
struct TestBase<void> {
void execute() { }
};
template<class T>
struct Test : private TestBase<T> {
using TestBase<T>::execute;
};
您可以选择最适合您的需求。
解决您的修改。我认为第二种方法实际上更适合您的需求。
template<typename T>
struct TestBase : Base {
void execute(T t) override { }
};
template<>
struct TestBase<void> : Base {
void execute() override { }
};
TestBase
是实现您所追求的目标的中间人。
答案 2 :(得分:2)
还有另一种选择,它不如使用CRTP的选择优雅。它包括在替代程序的主体中进行选择,以使其继续执行基本实现,或者提供该函数的新实现。
如果您使用的是c ++ 17,则多亏if constexpr
很简单。在c ++ 11中,替代方法是使用标签分配:
template<class T>
struct Test : Base
{
void execute()
{
void do_execute(std::integral_constant<bool,std::is_void<T>::value>{});
}
void execute(int t)
{
void do_execute(std::integral_constant<bool,!std::is_void<T>::value>{}, t);
}
private:
void do_execute(std::integral_constant<bool,true>){
/*implementation*/
}
void do_execute(std::integral_constant<bool,false>){
Base::execute();//Call directly the base function execute.
//Such call does not involve the devirtualization
//process.
}
void do_execute(std::integral_constant<bool,true>,int t){
/*implementation*/
}
void do_execute(std::integral_constant<bool,false>,int t){
Base::execute(t);//Call directly the base function execute.
//Such call does not involve the devirtualization
//process.
}
};
使用C ++ 17 if constexpr
,它看起来比CRTP解决方案更优雅:
template<class T>
struct Test : Base
{
void execute(){
if constexpr (is_void_v<T>){
Base::execute();
}
else{
/* implementation */
}
}
void execute(int t){
if constexpr (!is_void_v<T>){
Base::execute(t);
}
else{
/* implementation */
}
}
};
答案 3 :(得分:1)
您可以将execute
的不同重载封装在一组相关的帮助器类中,如下所示:
template <class T>
struct TestHelper : Base
{
void execute(int) override {}
};
template <>
struct TestHelper<void> : Base
{
void execute() override {}
};
template <class T>
struct Test : TestHelper<T>
{
// Other Test stuff here
};
如果execute
的实现实际上依赖于应该在它们之间共享的“其他测试内容”,那么您也可以使用CRTP:
template <class T, class Self>
struct TestHelper : Base
{
void execute(int) override
{
Self& self = static_cast<Self&>(*this);
// Now access all members through `self.` instead of directly
}
};
template <class Self>
struct TestHelper<void, self> : Base
{
void execute() override
{
Self& self = static_cast<Self&>(*this);
// Now access all members through `self.` instead of directly
}
};
template <class T>
struct Test : TestHelper<T, Test>
{
// Other Test stuff here
};