如何使用traits来访问编译时的const值?

时间:2017-03-03 17:19:04

标签: c++ templates

我正在开发一个项目,其中某个函数的行为需要在几个值之间切换:

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
            }
        }
    };

我不知道哪里弄错了,但这不能编译。

我想知道这里是否有人反击过这样的问题。任何人都可以分享一些见解吗?

3 个答案:

答案 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