14

(这个问题的灵感来自How can I generate a compiler error to prevent certain VALUE (not type) to go into the function?

比方说,我们有一个单参数foo,语义上定义为

int foo(int arg) {
    int* parg;
    if (arg != 5) {
        parg = &arg;
    }

    return *parg;
}

上面的整个代码用于说明一个简单的想法 - 函数返回它自己的参数,除非参数等于 5,在这种情况下,行为是未定义的。

现在,挑战 - 以这样的方式修改函数,如果它的参数在编译时已知,则应该生成编译器诊断(警告或错误),如果不是,则行为在运行时保持未定义。解决方案可能依赖于编译器,只要它在四大编译器之一中可用。

以下是一些无法解决问题的潜在路线:

  • 使函数成为将其参数作为模板参数的模板 - 这并不能解决问题,因为它使函数不符合运行时参数的条件
  • 制作函数 a constexpr- 这并不能解决问题,因为即使编译器看到未定义的行为,它们也不会在我的测试中产生诊断 - 相反,gcc 会插入ud2指令,这不是我想要的。
4

3 回答 3

4

constexpr常量表达式中使用时出现错误:

constexpr int foo(int arg) {
    int* parg = nullptr;
    if (arg != 5) {
        parg = &arg;
    }
    return *parg;
}

演示

我们无法知道参数值在编译类型中是已知的,但我们可以使用类型表示值std::integral_constant

// alias to shorten name. 
template <int N>
using int_c = std::integral_constant<int, N>;

可能使用UDLoperator "" _cto have 5_c, 42_c.

然后,添加重载:

template <int N>
constexpr auto foo(int_c<N>) {
    return int_c<foo(N)>{};
}

所以:

foo(int_c<42>{}); // OK
foo(int_c<5>{}); // Fail to compile

// and with previous constexpr:
foo(5); // Runtime error, No compile time diagnostic
constexpr auto r = foo(5); // Fail to compile

正如我所说,参数在函数内部不知道是恒定的,并且is_constexpr在标准中似乎不可能允许分派,但是一些编译器为此提供了内置的 ( __builtin_constant_p),所以使用 MACRO,我们可以进行分派:

#define FOO(X) [&](){ \
    if constexpr (__builtin_constant_p(X)) {\
        return foo(int_c<__builtin_constant_p (X) ? X : 0>{});\
    } else {\
        return foo(X); \
    } \
}()

演示

注意:不能foo(int_c<X>{})直接使用,即使在 if constexpr 中,因为仍然有一些语法检查。

于 2018-12-26T19:20:44.863 回答
2

gcc/clang/intel 编译器支持__builtin_constant_p,所以你可以使用类似的东西:

template <int D>
int foo_ub(int arg) {
    static_assert(D != 5, "error");
    int* parg = nullptr;
    if (arg != 5) {
        parg = &arg;
    }

    return *parg;
}

#define foo(e) foo_ub< __builtin_constant_p(e) ? e : 0 >(e)

这些语句产生编译时错误:

  • foo(5)
  • foo(2+3)
  • constexpr int i = 5; foo(i);

而所有其他 - 运行时段错误(如果没有使用,则为 ub nullptr

于 2018-12-26T23:46:33.490 回答
0

它并不完美,它要求我们在两个不同的地方使用参数,但它“有效”

template<int N = 0>
int foo(int arg = 0) {
    static_assert(N != 5, "N cannot be 5!");
    int* parg;
    if (arg != 5) {
        parg = &arg;
    }

    return *parg;
}

我们可以这样称呼它:

foo<5>();   // does not compile
foo(5);     // UB
foo<5>(5);  // does not compile
foo<5>(10); // does not compile
foo<10>(5); // UB
foo();      // fine
foo<10>();  // fine
foo(10);    // fine
于 2018-12-26T15:50:43.300 回答