【发布时间】:2019-02-28 00:14:45
【问题描述】:
我在 GCC 编译 enable_ifs 时遇到问题,该问题应用于模板化类方法的返回值。使用 Clang,我可以在 enum 模板参数上使用 enable_if 中的表达式,而 GCC 拒绝编译此代码。
这里是问题描述、初始代码以及试图满足我和编译器的后续修改(不幸的是,不是同时)。
我有一个非模板类Logic,其中包含一个模板类方法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 应用于返回值。
变体 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++ 标准(或常识)?
- 我是否有一种简单的方法可以使 Variant 2 类型的解决方案工作而无需像 Variant 3 中那样使用虚拟包装器?
(如果重要,GCC 7.4.0 和 Apple LLVM 版本 10.0.0:clang-1000.11.45.5)
【问题讨论】:
-
@NikitaKniazev 不完全确定在给定的上下文中会导致什么。
-
我仍然不相信这是“更简单的调用”。无论如何,你可以只用一个包装器
isStrat:godbolt.org/z/fXNTWP 编辑变体 3:它通过 gcc,但在 MSVC 上仍然失败,正如@NikitaKniazev 指出的那样 -
您将“性能关键”作为此更改的原因。您是否进行过测试以查看性能是否有所提升? (如果编译器已经在幕后做了与此等效的操作,那么这可能是无益的大量工作。)
-
但是变体 1 中没有额外的运行时
if条件...只有编译时if条件。 -
@AntonMenshov 我会尝试将代码分解为几个(新)函数,但从变体 1 的
computeThings()中现有的if-else框架调用这些函数。理论上,该模板的每个实例化都将简化为一个函数调用,编译器可以内联。单独的函数,没有额外的模板魔法,以及相同的开销。 (但不是关于为什么变体 2 有时有效/无效的答案。)
标签: c++ templates gcc c++17 enable-if