程序员的知识教程库

网站首页 > 教程分享 正文

C++20尝鲜:常量表达式变化(c++常量表示方法)

henian88 2024-10-18 06:00:46 教程分享 10 ℃ 0 评论


Language Feature

Proposal

Less eager instantiation of constexpr functions

P0859R0

Allowing Virtual Function Calls in Constant Expressions

P1064R0

Immediate functions (consteval)

P1073R3

P1937R2

std::is_constant_evaluated

P0595R2

Relaxations of constexpr restrictions

P1002R1

P1327R1

P1330R0

P1331R2

P1668R1

P0784R7

consteval 立即函数

consteval 说明符声明函数或函数模板为立即函数,即该函数的每次潜在求值的调用必须产生编译时常量表达式。它不可应用于析构函数、分配函数或解分配函数。consteval 说明符蕴含 inline 。同一声明说明符序列中允许出现至多一个 constexpr 、 consteval 及 constinit 说明符。

#include <iostream>

consteval int GetInt(int x){
    return x;
}

constexpr int f(){
    int x1 = GetInt(1);
    //constexpr auto x2 = GetInt(x1); // 错误, x1 不是常量表达式
    return x1;
}

int main()
{
    auto v = f();
    std::cout << "f:" << v << std::endl;
    return 0;
}
https://wandbox.org/nojs/gcc-head
https://wandbox.org/nojs/clang-head


允许constexpr 虚函数

虚函数现在可以是constexpr。Constexpr函数可以覆盖非Constexpr函数,反之亦然。

#include <iostream>

struct Base{
    virtual int Get() const = 0;    // non-constexpr
};

struct Derived1 : Base{

    constexpr int Get() const override {
        return 1;
    }
};

struct Derived2 : Base{

    constexpr int Get() const override {
        return 2;
    }
};

constexpr auto GetSum(){
    const Derived1 d1;
    const Derived2 d2;
    const Base* pb1 = &d1;
    const Base* pb2 = &d2;
    return pb1->Get() + pb2->Get();
};

int main()
{
    static_assert(GetSum());   // 编译时赋值
    std::cout << "GetSum:" << GetSum() << std::endl;
    return 0;
}

constexpr 中使用 try-catch 块

现在允许在constexpr函数中使用try-catch块,但是throw不可以,因此,catch块被简单地忽略。这可能很有用,例如,与constexpr new结合使用,我们可以有一个在运行/编译时工作的函数。

#include <iostream>

constexpr int f(int x){
    try{
        if( x > 0){
            auto p = new int;
            *p = 1;
            x += *p;
            delete p;
        }else{
            throw x;
        }
    }
    catch(...){     // 忽略,编译时,运行时保留
        // ...
        int y = 1;
        return x - y;
    }
    return x;
}

int main()
{
    static_assert(f(1) == 2);   // 编译时赋值
    std::cout << "f:" << f(0) << std::endl; //运行时
    return 0;
}

constexpr 中使用 dynamic_cast和多态类型typeid

在constexpr中允许动态类型转换和多态类型typeid 。可惜的是,std::type_info还没有constexpr成员,所以没啥用。

#include <iostream>
#include <typeinfo>
#include <string>

struct Base1{
    constexpr virtual int get() const = 0;
};

struct Derived1 : Base1{
    constexpr int get() const override {
        return 1;
    }
};

struct Base2{
    constexpr virtual int get() const = 0;
};

struct Derived2 : Base2{
    constexpr int get() const override {
        return 2;
    }
};

template<typename Base, typename Derived>
constexpr auto downcasted_get(){
    const Derived d;
    const Base& upcasted = d;
    const auto& downcasted = dynamic_cast<const Derived&>(upcasted);
    
    return downcasted.get();
}
template<typename Base, typename Derived>
constexpr auto print_type(){
	Derived d1;
	const Base *pb = &d1;
	return  typeid(*pb).name(); // name() 不是constexpr
}

int main()
{
    static_assert(downcasted_get<Base1, Derived1>() == 1);
    static_assert(downcasted_get<Base2, Derived2>() == 2);
    //static_assert(downcasted_get<Base2, Derived1>() == 1);  //编译时错误,不能将Deriv1强制转换为Base2
    //static_assert(print_type<Base1, Derived1>());  
    
    return 0;
}

在constexpr内更改联合体的有效成员

#include <string>

union Foo {
  int i;
  float f;
};

constexpr int f() {
  Foo foo{};
  foo.i = 3;    // i 是有效成员
  foo.f = 1.2f; // 从C++20起, f 变成有效成员
  // return foo.i;  // 错误, 读取无效union成员
  return foo.f;
}

int main()
{
    static_assert(f() == 1);
    return 0;
}

constexpr 中使用allocator

允许调用std::allocator<T>::allocate()和new-expression,内存可以在编译时分配,但也必须在编译时释放。

#include <iostream>
#include <string>
#include <array>

constexpr auto get_str()
{
    std::string s1{"hello "};
    std::string s2{"world"};
    std::string s3 = s1 + s2;
    return s3;
}

constexpr auto get_array()
{
    constexpr auto N = get_str().size();
    std::array<char, N> arr{};
    std::copy_n(get_str().data(), N, std::begin(arr));
    return arr;
}

int main()
{
  static_assert(!get_str().empty());

  // 错误,因为其持有编译时内存分配
  // constexpr auto str = get_str();

  // 正确, 字符串存储在 std::array<char>
  constexpr auto arr = get_array();
  std::copy(arr.cbegin(), arr.cend(), std::ostream_iterator<char>(std::cout, ""));

  return 0;
}

注意:此例子仅支持MSVC STL 19.29,在撰写本文时没有其他编译器。我在godbolt上测试了11.1版本的GCC编译器,简单的constexpr std::string在这个版本中仍然会产生错误。

在constexpr函数中进行简单的默认初始化

在C++17中,除其他要求外,constexpr构造函数必须初始化所有非静态数据成员。该规则在C++20中被删除了。但是,因为在constexpr上下文中不允许使用未定义行为,所以你不能从这些未初始化的成员中读取,只能写入它们。

#include <iostream>
#include <string>


struct NonTrivial{
    bool b = false;
};

struct Trivial{
    bool b;
};

template <typename T>
constexpr T f1(const T& other) {
    T t;        // default initialization
    t = other;
    return t;
}

template <typename T>
constexpr auto f2(const T& other) {
    T t;
    return t.b;
}

int main(){
    constexpr auto a = f1(Trivial{});   // 在C++17中错误,,在 C++20中正确
    constexpr auto b = f1(NonTrivial{});// OK

    //constexpr auto c = f2(Trivial{}); // 错误, 未初始化Trivial::b被使用
    constexpr auto d = f2(NonTrivial{}); // OK
}

constexpr函数中使用未计算的asm声明

asm声明现在可以出现在constexpr函数中,以防它在编译时没有被计算。这样就可以在一个函数中同时有编译和运行时(现在有asm)的代码。

#include <iostream>
#include <string>

constexpr int add(int a, int b){
    if (std::is_constant_evaluated()){
        return a + b;
    }
    else{
        asm("asm magic here");
        return 0;
    }
}
int main(){
    static_assert(add(1,2) == 1+2);
}

std::is_constant_evaluated()

通过std::is_constant_evaluated(),您可以检查当前调用是否发生在常量求值的上下文中。

#include <iostream>
#include <assert.h>
  
constexpr int GetNumber(){
    if(std::is_constant_evaluated()){   // should not be `if constexpr`
        return 1;
    }
    return 2;
}

constexpr int GetNumber(int x){
    if(std::is_constant_evaluated()){   // should not be `if constexpr`
        return x;
    }
    return x+1;
}

int main(){
    constexpr auto v1 = GetNumber();
    const auto v2 = GetNumber();

    // initialization of a non-const variable, not constant-evaluated
    auto v3 = GetNumber();

    assert(v1 == 1);
    assert(v2 == 1);
    assert(v3 == 2);

    constexpr auto v4 = GetNumber(1);
    int x = 1;

    // x is not a constant-expression, not constant-evaluated
    const auto v5 = GetNumber(x);

    assert(v4 == 1);
    assert(v5 == 2);    
}

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表