`enable_if` 与 `enum` 模板专业化问题
`enable_if` with `enum` template specialization problem
我在 GCC 编译 enable_if
s 时遇到问题应用于模板化 class 方法的 return 值。使用 Clang,我可以在 enum
模板参数上使用 enable_if
中的表达式,而 GCC 拒绝编译此代码。
这是问题描述、初始代码及其后续修改,试图让我和编译器满意(不幸的是,不是同时)。
我有一个非模板化的 class Logic
,它包含一个模板化的 class 方法 computeThings()
,它有一个 enum Strategy
作为 其模板参数之一。 computeThings()
中的逻辑取决于编译时 Strategy
,因此 if constexpr
是一种合理的实现方式。
变体 1
#include <iostream>
class Logic {
public:
enum Strategy { strat_A, strat_B };
// class A and class B are dummy in this example, provided to show that there are several template
// parameters, and strategy selection effectively results in
// partial (not full) templated method specification
template <class A, class B, Strategy strategy>
int computeThings();
};
template <class A, class B, Logic::Strategy strategy>
int Logic::computeThings() {
if constexpr(strategy==strat_A)
return 0;
else
return 1;
}
int main() {
Logic mylogic;
std::cout<<mylogic.computeThings<int,int,Logic::strat_A>()<<std::endl; //outputs 0
std::cout<<mylogic.computeThings<int,int,Logic::strat_B>()<<std::endl; //outputs 1
return 0;
}
变体 1 工作正常并且可以在 clang 和 gcc 中编译。但是,我想摆脱 if constexpr
并根据选择的 Strategy
将 computeThings()
拆分为两个专门的方法。原因:该函数性能要求高,代码量大
因此,我想出了变体 2,它使用 enable_if
应用于 return 值。
变体 2
#include <iostream>
class Logic {
public:
enum Strategy { strat_A, strat_B };
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<strategy==Logic::strat_A,int>
computeThings();
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<strategy==Logic::strat_B,int>
computeThings();
};
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<strategy==Logic::strat_A,int>
Logic::computeThings() {
return 0;
}
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<strategy==Logic::strat_B,int>
Logic::computeThings() {
return 1;
}
int main() {
Logic mylogic;
std::cout<<mylogic.computeThings<int,int,Logic::strat_A>()<<std::endl; //outputs 0
std::cout<<mylogic.computeThings<int,int,Logic::strat_B>()<<std::endl; //outputs 1
return 0;
}
我对变体 2 非常满意(尽管也希望得到反馈)。这段代码使用 AppleClang(通常可能还有 Clang)可以很好地编译并产生正确的结果。但是,它无法使用 GCC 进行编译,并出现以下错误(+ 相同但对于其他方法):
error: prototype for 'std::enable_if_t<(strategy == Logic:: strat_A),int> Logic::computeThings()' does not match any in class 'Logic' Logic::computeThings()
candidates are: template<class A, class B, Logic::Strategy strategy> std::enable_if_t<(strategy == strat_B), int> Logic::computeThings() computeThings();
candidates are: template<class A, class B, Logic::Strategy strategy> std::enable_if_t<(strategy == strat_A), int> Logic::computeThings() computeThings();
所以,显然,使用简单的 strategy==Logic::strat_A
与 GCC 冲突。因此,我想出了一个同时满足 clang 和 gcc 的解决方案,它将 strategy==Logic::strat_A
包装成 struct
:
变体 3
#include <iostream>
class Logic {
public:
enum Strategy { strat_A, strat_B };
template <Logic::Strategy strategy> struct isStratA {
static const bool value = strategy==Logic::strat_A;
};
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<Logic::isStratA<strategy>::value,int>
computeThings();
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<!Logic::isStratA<strategy>::value,int>
computeThings();
};
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<Logic::isStratA<strategy>::value,int>
Logic::computeThings() {
return 0;
}
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<!Logic::isStratA<strategy>::value,int>
Logic::computeThings() {
return 1;
}
int main() {
Logic mylogic;
std::cout<<mylogic.computeThings<int,int,Logic::strat_A>()<<std::endl; //outputs 0
std::cout<<mylogic.computeThings<int,int,Logic::strat_B>()<<std::endl; //outputs 1
return 0;
}
对于变体 3,Clang 和 GCC 都很满意。但是,我不是,因为出于未知原因我必须创建很多虚拟包装器(在这里,我只有一个,但从技术上讲,我应该同时拥有 isStratA<>
和 isStratB<>
)。
问题:
- 我的变体 2 是否违反了任何 C++ 标准(或常识)?
- 我是否有一种简单的方法可以使变体 2 型解决方案工作而无需像变体 3 那样使用虚拟包装器?
(如果重要,GCC 7.4.0 和 Apple LLVM 版本 10.0.0:clang-1000.11.45.5)
正如@bogdan 在评论中所说,这很可能是一个编译器错误。实际上我注意到如果你在函数模板的外联定义中使用尾随 return 类型它会起作用:
template <class A, class B, Logic::Strategy strategy>
auto Logic::computeThings() ->
std::enable_if_t<strategy==Logic::strat_A,int> {
return 0;
}
template <class A, class B, Logic::Strategy strategy>
auto Logic::computeThings() ->
std::enable_if_t<strategy==Logic::strat_B,int> {
return 1;
}
我更喜欢将 enable_if
放在具有默认参数的非类型模板参数的类型中:
template <class A, class B, Logic::Strategy strategy,
std::enable_if_t<strategy==Logic::strat_A,int> = 0>
int Logic::computeThings() {
return 0;
}
template <class A, class B, Logic::Strategy strategy,
std::enable_if_t<strategy==Logic::strat_B,int> = 0>
int Logic::computeThings() {
return 1;
}
但是 SFINAE 的功能对于如此简单的东西来说太复杂了。有更简单的方法来做你想做的事情。以使用标签调度为例:
#include <iostream>
#include <type_traits>
class Logic {
public:
enum Strategy { strat_A, strat_B };
template <class A, class B>
int computeThings(std::integral_constant<Strategy, strat_A>);
template <class A, class B>
int computeThings(std::integral_constant<Strategy, strat_B>);
};
template <class A, class B>
int Logic::computeThings(std::integral_constant<Strategy, strat_A>) {
return 0;
}
template <class A, class B>
int Logic::computeThings(std::integral_constant<Strategy, strat_B>) {
return 1;
}
int main() {
Logic mylogic;
std::cout<<mylogic.computeThings<int,int>(
std::integral_constant<Logic::Strategy, Logic::strat_A>{}
)<<std::endl; //outputs 0
std::cout<<mylogic.computeThings<int,int>(
std::integral_constant<Logic::Strategy, Logic::strat_B>{}
)<<std::endl; //outputs 1
return 0;
}
这可以通过去掉枚举并直接定义一些标签类型来进一步简化:
class Logic {
public:
class strat_A {};
class strat_B {};
template <class A, class B>
int computeThings(strat_A);
template <class A, class B>
int computeThings(strat_B);
};
template <class A, class B>
int Logic::computeThings(strat_A) { return 0; }
template <class A, class B>
int Logic::computeThings(strat_B) { return 1; }
int main() {
Logic mylogic;
std::cout<<mylogic.computeThings<int,int>(Logic::strat_A{})<<std::endl; //outputs 0
std::cout<<mylogic.computeThings<int,int>(Logic::strat_B{})<<std::endl; //outputs 1
return 0;
}
策略模式的一种更惯用和结构化的方法是将不同策略的行为从 computeThings
函数中提升到策略 类 本身:
class Logic {
public:
struct strat_A {
template <class A, class B>
static int computeThings(Logic* self);
};
struct strat_B {
template <class A, class B>
static int computeThings(Logic* self);
};
template <class A, class B, class Strategy>
int computeThings() {
return Strategy::template computeThings<A, B>(this);
}
};
template <class A, class B>
int Logic::strat_A::computeThings(Logic* self) {
return 0;
}
template <class A, class B>
int Logic::strat_B::computeThings(Logic* self) {
return 1;
}
int main() {
Logic mylogic;
std::cout<<mylogic.computeThings<int,int,Logic::strat_A>()<<std::endl; //outputs 0
std::cout<<mylogic.computeThings<int,int,Logic::strat_B>()<<std::endl; //outputs 1
return 0;
}
此示例中不需要 Logic* self
指针,但如果策略需要访问 Logic
实例,则需要。
我在 GCC 编译 enable_if
s 时遇到问题应用于模板化 class 方法的 return 值。使用 Clang,我可以在 enum
模板参数上使用 enable_if
中的表达式,而 GCC 拒绝编译此代码。
这是问题描述、初始代码及其后续修改,试图让我和编译器满意(不幸的是,不是同时)。
我有一个非模板化的 class Logic
,它包含一个模板化的 class 方法 computeThings()
,它有一个 enum Strategy
作为 其模板参数之一。 computeThings()
中的逻辑取决于编译时 Strategy
,因此 if constexpr
是一种合理的实现方式。
变体 1
#include <iostream>
class Logic {
public:
enum Strategy { strat_A, strat_B };
// class A and class B are dummy in this example, provided to show that there are several template
// parameters, and strategy selection effectively results in
// partial (not full) templated method specification
template <class A, class B, Strategy strategy>
int computeThings();
};
template <class A, class B, Logic::Strategy strategy>
int Logic::computeThings() {
if constexpr(strategy==strat_A)
return 0;
else
return 1;
}
int main() {
Logic mylogic;
std::cout<<mylogic.computeThings<int,int,Logic::strat_A>()<<std::endl; //outputs 0
std::cout<<mylogic.computeThings<int,int,Logic::strat_B>()<<std::endl; //outputs 1
return 0;
}
变体 1 工作正常并且可以在 clang 和 gcc 中编译。但是,我想摆脱 if constexpr
并根据选择的 Strategy
将 computeThings()
拆分为两个专门的方法。原因:该函数性能要求高,代码量大
因此,我想出了变体 2,它使用 enable_if
应用于 return 值。
变体 2
#include <iostream>
class Logic {
public:
enum Strategy { strat_A, strat_B };
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<strategy==Logic::strat_A,int>
computeThings();
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<strategy==Logic::strat_B,int>
computeThings();
};
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<strategy==Logic::strat_A,int>
Logic::computeThings() {
return 0;
}
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<strategy==Logic::strat_B,int>
Logic::computeThings() {
return 1;
}
int main() {
Logic mylogic;
std::cout<<mylogic.computeThings<int,int,Logic::strat_A>()<<std::endl; //outputs 0
std::cout<<mylogic.computeThings<int,int,Logic::strat_B>()<<std::endl; //outputs 1
return 0;
}
我对变体 2 非常满意(尽管也希望得到反馈)。这段代码使用 AppleClang(通常可能还有 Clang)可以很好地编译并产生正确的结果。但是,它无法使用 GCC 进行编译,并出现以下错误(+ 相同但对于其他方法):
error: prototype for 'std::enable_if_t<(strategy == Logic:: strat_A),int> Logic::computeThings()' does not match any in class 'Logic' Logic::computeThings()
candidates are: template<class A, class B, Logic::Strategy strategy> std::enable_if_t<(strategy == strat_B), int> Logic::computeThings() computeThings();
candidates are: template<class A, class B, Logic::Strategy strategy> std::enable_if_t<(strategy == strat_A), int> Logic::computeThings() computeThings();
所以,显然,使用简单的 strategy==Logic::strat_A
与 GCC 冲突。因此,我想出了一个同时满足 clang 和 gcc 的解决方案,它将 strategy==Logic::strat_A
包装成 struct
:
变体 3
#include <iostream>
class Logic {
public:
enum Strategy { strat_A, strat_B };
template <Logic::Strategy strategy> struct isStratA {
static const bool value = strategy==Logic::strat_A;
};
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<Logic::isStratA<strategy>::value,int>
computeThings();
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<!Logic::isStratA<strategy>::value,int>
computeThings();
};
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<Logic::isStratA<strategy>::value,int>
Logic::computeThings() {
return 0;
}
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<!Logic::isStratA<strategy>::value,int>
Logic::computeThings() {
return 1;
}
int main() {
Logic mylogic;
std::cout<<mylogic.computeThings<int,int,Logic::strat_A>()<<std::endl; //outputs 0
std::cout<<mylogic.computeThings<int,int,Logic::strat_B>()<<std::endl; //outputs 1
return 0;
}
对于变体 3,Clang 和 GCC 都很满意。但是,我不是,因为出于未知原因我必须创建很多虚拟包装器(在这里,我只有一个,但从技术上讲,我应该同时拥有 isStratA<>
和 isStratB<>
)。
问题:
- 我的变体 2 是否违反了任何 C++ 标准(或常识)?
- 我是否有一种简单的方法可以使变体 2 型解决方案工作而无需像变体 3 那样使用虚拟包装器?
(如果重要,GCC 7.4.0 和 Apple LLVM 版本 10.0.0:clang-1000.11.45.5)
正如@bogdan 在评论中所说,这很可能是一个编译器错误。实际上我注意到如果你在函数模板的外联定义中使用尾随 return 类型它会起作用:
template <class A, class B, Logic::Strategy strategy>
auto Logic::computeThings() ->
std::enable_if_t<strategy==Logic::strat_A,int> {
return 0;
}
template <class A, class B, Logic::Strategy strategy>
auto Logic::computeThings() ->
std::enable_if_t<strategy==Logic::strat_B,int> {
return 1;
}
我更喜欢将 enable_if
放在具有默认参数的非类型模板参数的类型中:
template <class A, class B, Logic::Strategy strategy,
std::enable_if_t<strategy==Logic::strat_A,int> = 0>
int Logic::computeThings() {
return 0;
}
template <class A, class B, Logic::Strategy strategy,
std::enable_if_t<strategy==Logic::strat_B,int> = 0>
int Logic::computeThings() {
return 1;
}
但是 SFINAE 的功能对于如此简单的东西来说太复杂了。有更简单的方法来做你想做的事情。以使用标签调度为例:
#include <iostream>
#include <type_traits>
class Logic {
public:
enum Strategy { strat_A, strat_B };
template <class A, class B>
int computeThings(std::integral_constant<Strategy, strat_A>);
template <class A, class B>
int computeThings(std::integral_constant<Strategy, strat_B>);
};
template <class A, class B>
int Logic::computeThings(std::integral_constant<Strategy, strat_A>) {
return 0;
}
template <class A, class B>
int Logic::computeThings(std::integral_constant<Strategy, strat_B>) {
return 1;
}
int main() {
Logic mylogic;
std::cout<<mylogic.computeThings<int,int>(
std::integral_constant<Logic::Strategy, Logic::strat_A>{}
)<<std::endl; //outputs 0
std::cout<<mylogic.computeThings<int,int>(
std::integral_constant<Logic::Strategy, Logic::strat_B>{}
)<<std::endl; //outputs 1
return 0;
}
这可以通过去掉枚举并直接定义一些标签类型来进一步简化:
class Logic {
public:
class strat_A {};
class strat_B {};
template <class A, class B>
int computeThings(strat_A);
template <class A, class B>
int computeThings(strat_B);
};
template <class A, class B>
int Logic::computeThings(strat_A) { return 0; }
template <class A, class B>
int Logic::computeThings(strat_B) { return 1; }
int main() {
Logic mylogic;
std::cout<<mylogic.computeThings<int,int>(Logic::strat_A{})<<std::endl; //outputs 0
std::cout<<mylogic.computeThings<int,int>(Logic::strat_B{})<<std::endl; //outputs 1
return 0;
}
策略模式的一种更惯用和结构化的方法是将不同策略的行为从 computeThings
函数中提升到策略 类 本身:
class Logic {
public:
struct strat_A {
template <class A, class B>
static int computeThings(Logic* self);
};
struct strat_B {
template <class A, class B>
static int computeThings(Logic* self);
};
template <class A, class B, class Strategy>
int computeThings() {
return Strategy::template computeThings<A, B>(this);
}
};
template <class A, class B>
int Logic::strat_A::computeThings(Logic* self) {
return 0;
}
template <class A, class B>
int Logic::strat_B::computeThings(Logic* self) {
return 1;
}
int main() {
Logic mylogic;
std::cout<<mylogic.computeThings<int,int,Logic::strat_A>()<<std::endl; //outputs 0
std::cout<<mylogic.computeThings<int,int,Logic::strat_B>()<<std::endl; //outputs 1
return 0;
}
此示例中不需要 Logic* self
指针,但如果策略需要访问 Logic
实例,则需要。