如何使用特征访问编译时const值

how to use traits to access compile-time const value?

本文关键字:const 编译 访问 何使用 特征      更新时间:2023-10-16

我正在研究一个项目,其中某个功能的行为需要在几个值之间切换:

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

中包括所有.c源文件中的#define