我正在开发一个项目,其中某个函数的行为需要在几个值之间切换:
class James{
public:
James(){
if(a==0){
//do this
}else{
// do that
}
}
};
目前,' a'在运行时从配置文件中读取。但是,在实践中,' a'可以在编译时确定,而不是运行时。我正在考虑有一个特质课
struct TraitZero{
constexpr int a = 0;
};
struct TraitOne{
constexpr int a = 1;
};
然后将James变成模板类
template<typename Trait>
class James{
constexpr int a = Trait::a;
public:
James(){
if(a=0){
//do this
}else{
// do that
}
}
};
我不知道哪里弄错了,但这不能编译。
我想知道这里是否有人反击过这样的问题。任何人都可以分享一些见解吗?
答案 0 :(得分:1)
a
数据成员必须声明为constexpr
和 static
,以便您尝试使用它们:
struct TraitZero{
static constexpr int a = 0;
};
struct TraitOne{
static constexpr int a = 1;
};
撇开它现在形成不良的事实,否则你将无法以Traits::a
的形式访问它。
这同样适用于班级James
:
template<typename Trait>
class James{
static constexpr int a = Trait::a;
//...
};
另请注意,以下可能不是您想要的:
if(a=0){
即使您被允许修改a
(并且您不是因为它是静态的constexpr数据成员),在这种情况下,您将为0分配a
并不断获得{{1}分支。
很可能你正在寻找类似但略有不同的东西:
else
下面是一个基于您的代码修复后的示例:
if(a == 0){
答案 1 :(得分:1)
作为has already been mentioned by skypjack,只有static
个数据成员可以是constexpr
,您需要在条件中使用==
而不是=
。
也就是说,由于您希望在编译时确定a
,因此在编译时根据a
进行分支可能会对您有所帮助。为此,您可以使用SFINAE或(从C ++ 17开始)constexpr if。
假设有以下三个特征......
struct TraitZero{
static constexpr int a = 0;
};
struct TraitOne{
static constexpr int a = 1;
};
template<size_t N>
struct TraitN {
static constexpr int a = N;
};
我们可以这样做......
SFINAE:
template<typename Trait>
class James {
// Unnecessary, we can access Trait::a directly.
//static constexpr int a = Trait::a;
public:
template<bool AZero = Trait::a == 0>
James(std::enable_if_t<AZero, unsigned> = 0) {
std::cout << "Trait::a is 0.\n";
}
template<bool AOne = Trait::a == 1>
James(std::enable_if_t<AOne, int> = 0) {
std::cout << "Trait::a is 1.\n";
}
template<bool ANeither = (Trait::a != 0) && (Trait::a != 1)>
James(std::enable_if_t<ANeither, long> = 0) {
std::cout << "Trait::a is neither 0 nor 1.\n";
}
};
这样做有条件地根据James()
的值选择Traits::a
的一个版本,使用虚拟参数来启用重载;这对于构造函数和析构函数以外的函数来说更简单,因为enable_if
可以用在它们的返回类型上。
请注意使用模板参数,而不是直接检查Trait::a
本身的enable_if
。由于SFINAE只能在函数的直接上下文中使用类型和表达式执行,因此这些用于“拖入”,可以这么说;我喜欢在这样做时执行逻辑,因为它最大限度地减少了enable_if
的侵入性。
constexpr if:
template<typename Trait>
class James {
// Unnecessary, we can access Trait::a directly.
//static constexpr int a = Trait::a;
public:
James() {
if constexpr (Trait::a == 0) {
std::cout << "Trait::a is 0.\n";
} else if constexpr (Trait::a == 1) {
std::cout << "Trait::a is 1.\n";
} else {
std::cout << "Trait::a is neither 0 nor 1.\n";
}
}
};
从这里可以看出,constexpr if可以用来创建比SFINAE更清晰,更自然的代码,其优点是它仍然可以在编译时而不是运行时进行评估;遗憾的是,大多数编译器尚未支持它。 [在这种特殊情况下,James()
的每个版本也将是一个较短的机器指令(当使用GCC 7.0编译时),因为没有使用伪参数来区分重载。]
更具体地说,使用constexpr if,如果条件为true
,则丢弃 statement-false ,如果{{1},则忽略 statement-true }};实际上,这基本上意味着编译器将整个constexpr if语句视为将要执行的分支。在这种情况下,例如,编译器将根据false
的值生成以下三个函数之一。
Trait::a
在任何一种情况下,使用以下代码......
// If Trait::a == 0:
James() {
std::cout << "Trait::a is 0.\n";
}
// If Trait::a == 1:
James() {
std::cout << "Trait::a is 1.\n";
}
// If Trait::a == anything else:
James() {
std::cout << "Trait::a is neither 0 nor 1.\n";
}
生成以下输出:
int main() { James<TraitZero> j0; James<TraitOne> j1; James<TraitN<2>> j2; }
每个类型的构造函数将被专门编码以输出相应的行,并且三个构造函数中没有一个实际上包含任何分支。
请注意,我仅仅根据个人喜好将成员Trait::a is 0.
Trait::a is 1.
Trait::a is neither 0 nor 1.
标记为不必要;因为我可以直接访问a
,所以我更愿意这样做,所以我不必检查Trait::a
是什么,如果我永远不会。如果您愿意,可以随意使用,或者在其他地方使用它。
答案 2 :(得分:-1)
为什么不在编译时使用-D选项传递#define? 例如:Including a #define in all .c source files at compile time