是否可以根据初始化是否为constexpr自动定义变量static或non-static?

时间:2018-11-17 14:01:38

标签: c++ c++17

假设我有一个类型:

struct Foo {
    int a, b, c, d, e;
};

现在,我想拥有一个宏(或任何其他解决方案),该宏可以以某种方式定义Foo对象,如果该对象可以是constexpr(因为它已初始化) (int s),然后将其定义为static constexpr Foo。如果不能为constexpr,则它将定义为const Foo(我将在函数作用域中使用此宏)。

所以,我想要一个宏(或一些等效的解决方案):

#define DEF(a, b, c, d, e) ... // magic here

如果我使用编译时常量来调用它:

DEF(1, 2, 3, 4, 5);

然后我想将其扩展为:

static constexpr Foo foo{1, 2, 3, 4, 5};

但是,如果任何参数不是编译时常量(因此不能为constexpr):

int b = 2;
DEF(1, b, 3, 4, 5); // second parameter is not a compile-time constant

那我想拥有:

const Foo foo{1, b, 3, 4, 5};

我想要这样的原因是编译器not allowed可以从堆栈中优化foo,所以我必须手动进行此优化。

(请注意,我在很多地方都使用Foo,这就是为什么我想拥有一个自动解决方案的原因。目前,我需要确定foo应该是static还是不是逐个案例,这很乏味。)

1 个答案:

答案 0 :(得分:5)

我知道您在评论中提到__builtin_constant_p不适合您,因为您想要一个可移植的解决方案,但是如果其他人偶然发现了这个问题,则可以肯定地使用它来解决此问题:

通过结合decltype(auto),自动lambda捕获和临时生命周期延长,我们可以执行以下操作:

struct Foo {
    int a;
    int b;
    int c;
};

void worker(const Foo*);

#define DEF(A, B, C)                            \
  [&]() -> decltype(auto) {                     \
        constexpr bool test =                   \
          __builtin_constant_p(A) &&            \
          __builtin_constant_p(B) &&            \
          __builtin_constant_p(C);              \
                                                \
        if constexpr(test) {                    \
            static const Foo res {A, B, C};     \
            return (res);                       \
        }                                       \
        else {                                  \
            return Foo{A, B, C};                \
        }                                       \
    }()                                         \
    //end of macro

void foo(int v) {
    const Foo& x = DEF(1, 2, 3);
    //const Foo& x = DEF(1, v, 3);

    worker(&x);
}

在两种情况下都会生成正确的代码。请参见godbolt

如果有人可以提出一些狡猾的SFINAE sheananigans来代替__builtin_constant_p,而在这种情况下,这种东西可以随身携带,那么您就可以做生意。

说明:此处的真正关键是临时的生存期延长。原因是让宏吐出static关键字将是一个巨大的麻烦,所以让我们不必理会它!

const Foo&完全能够指向static const,并且只要将常规const作为临时对象构建,生命周期扩展将(针对每种意图和目的)促进编译期间将引用转换为常规变量。另外,请记住,引用没有自己的地址,因此链接问题中解释的问题不适用于它们。

然后使用decltype(auto),我们可以创建一个函数,该函数可以返回临时Fooconst Foo&,该函数将填充该const引用。

最后,将其包装在lambda中(与辅助函数/模板相对),使我们可以轻松地区分文字和命名变量,并允许编译器确定使用常量表达式初始化静态变量。这很重要,因为一堆线程安全样板将在丝毫模棱两可的情况下被粘贴。