元宵三五,夜雪初霁,蕙风透寒。

乘暇再更一个 C++26 的小特性,动机简单,看一下小例子:

using Point3D = std::tuple<float, float, float>;

int main() {
    auto [x, y, z] = Point3D(1, 2, 3);

    return x;
}

yz 皆未使用,可仍旧要起一个名称,实显多余。新特性允许使用 _ 作为占位符,忽略不关心的值。是以可以这样写:

using Point3D = std::tuple<float, float, float>;

int main() {
    auto [x, _, _] = Point3D(1, 2, 3);

    return x;
}

除了 Structured bindingRAII 也是一个常用的场景。

int main() {
    std::mutex mtx;
    std::lock_guard _(mtx);
    std::ifstream _;
    std::unique_ptr<int> _;
}

允许重定义,所以全部写在一起也不会存在问题。但是,重定义之后无法使用,Name Lookup 会出现歧义。

void g() {
    int _;
    _ = 0; // OK
    int _; // OK, name independent declaration
    _ = 0; // error: two non-function declarations in the lookup set
}

void h () {
    int _; // #1
    _ ++;  // OK
    static int _; // error: conflicts with #1 because static variables are not
                  // name-independent
}

int main() {
    int _{}; // equivalent to [[maybe_unused]] int _;
    float _{};
    double _{};
    char _{};

    return _; // ambiguous
}

这个占位符本身的目的就是避免为不准备使用的变量命名,所以这不是问题。此外,使用 _ 还会隐式 [[maybe_unused]],因此编译器不会给出任何警告。

需要注意,允许使用这种占位符命名的变量,包含 automatic storage duration、非静态成员变量、Structured bindinglambda capture。其他情况并不满足,比如 NTTPrequires clause 参数名和函数参数。

一个例子:

namespace a {
    auto _ = f(); // Ok, declare a variable "_"
    auto _ = f(); // error: "_" is already defined in this namespace scope
}
void f() {
    auto _ = 42; // Ok, declare a variable "_"
    auto _ = 0; // Ok, re-declare a variable "_"
    {
        auto _ = 1; // Ok, shaddowing
        assert( _ == 1 ); // Ok
    }
    assert( _ == 42 ); // ill-formed: Use of a redeclared placeholder variables
}

int main() {

}

这里也出现了 Names shadow,和重载决议里面提到的意义相同,所以它能够访问。

这个小特性并不稀奇,其他语言中早已有之。比如 Python:

for _ in range(5):
    print("Hello")

_, second, _, _, fifth = [1, 2, 3, 4, 5]
print(second)  # 2
print(fifth)   # 5
print(_)       # 4

不过 Python 在输出重定义的 _ 时,会匹配最后一个里面的值。原本 C++ 也有一种做法是匹配第一个里面的值,最后采取了目前的行为。

C++ 的行为和 Rust 比较相似:

fn main() {
    let _ = 42;
    println!("{}", _); // error!

    let (x, _, z) = (1, 2, 3);
    println!("x = {}, z = {}", x, z);

    for _ in 0..5 {
        println!("Hello");
    }
}

C#, Ruby, Swift, Scala … 也支持相似的特性。

Leave a Reply

Your email address will not be published. Required fields are marked *

You can use the Markdown in the comment form.