如何使用特征访问编译时const值
how to use traits to access compile-time const value?
我正在研究一个项目,其中某个功能的行为需要在几个值之间切换:
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;
};
然后将詹姆斯变成模板类
template<typename Trait>
class James{
constexpr int a = Trait::a;
public:
James(){
if(a=0){
//do this
}else{
// do that
}
}
};
我不知道我在哪里弄错了,但这不会编译。
我想知道这里有人是否曾经解决过这样的问题。谁能分享一些见解?
正如SkyPjack所提到的那样,只有static
数据成员可以是constexpr
,并且您需要在条件中使用==
而不是=
。
也就是说,由于您想在编译时确定a
,因此在编译时间基于a
分支可能对您可能是有益的。为此,您可以使用sfinae或(从C 17)进行constexpr,如果。
假设以下三个特征...
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"; } };
这样做的是根据
Traits::a
的值有条件地选择James()
的版本之一,使用虚拟参数启用过载;这对于构造函数和破坏者以外的功能更简单,因为enable_if
可以在其返回类型上使用。请注意使用模板参数,而不是直接检查
enable_if
本身中的Trait::a
。由于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是否可以用来创建比Sfinae更自然的代码,其优势是,它仍将在编译时间而不是运行时间进行评估;不幸的是,它尚未得到大多数编译器的支持。[在这种特殊情况下,
James()
的每个版本也将是一台机器指令(使用GCC 7.0编译时),因为不使用虚拟参数来区分过载。]更具体地说,如果条件为
true
,则使用constexpr if, statement-false 被丢弃,并且如果是false
,则将 statement-true 丢弃;实际上,这基本上意味着编译器将整个constexpr视为要执行的分支。例如,在这种情况下,编译器将基于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
,因此我更喜欢这样做,因此我不必检查a
是什么是放弃的。如果愿意,请随时使用它,或者如果需要其他地方需要。
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数据成员),在这种情况下,您将分配给a
0并不断获得else
分支。
您很可能正在寻找类似但略有不同的东西:
if(a == 0){
以下是修复后的代码的示例:
#include<iostream>
struct TraitZero{
static constexpr int a = 0;
};
struct TraitOne{
static constexpr int a = 1;
};
template<typename Trait>
class James{
static constexpr int a = Trait::a;
public:
James(){
if(a==0){
std::cout << "0" << std::endl;
}else{
std::cout << "1" << std::endl;
}
}
};
int main() {
James<TraitZero> j0;
James<TraitOne> j1;
}
为什么不使用-D选项在编译时间传递#Define?例如:在compile Time