Lambda表达式是合法的默认(非类型模板)参数吗?

2022-05-16 00:00:00 lambda language-lawyer templates c++ c++20

以下所有标准参考引用N4861 (March 2020 post-Prague working draft/C++20 DIS)。


背景

在Q&;AAre captureless lambdas structural types?中明确指出,某些lambda表达式具有关联的闭包类型,这些闭包类型是(文本和)结构类型,因此特定的此类闭包类型可以用作非类型模板参数;本质上是将结构类型lambdas作为非类型模板参数传递。

template<auto v>
constexpr auto identity_v = v;

constexpr auto l1 = [](){};
constexpr auto l2 = identity_v<l1>;

现在,根据[expr.prim.lambda.closure]/1,每个lambda表达式的类型是唯一的

[...]唯一的、未命名的非并集类类型,称为闭包类型[...]

另一方面,[basic.def.odr]/1[摘录,强调我的]状态

任何翻译单元不得包含任何变量、函数、类类型、枚举类型、模板、参数的默认实参(对于给定作用域中的函数)或默认模板实参的一个以上定义。

可能意味着默认模板参数被视为需要尊重ODR的定义。

问题

.这引出了我的问题:

  • lambda表达式是合法的默认(非类型模板)参数吗?如果是,这是否意味着使用此类默认参数的每个实例化都实例化唯一的专门化?

(如果接近非法,也请突出显示:例如,如果超出单个实例化会导致违反ODR)。


为什么?

如果这实际上是合法的,则以lambda作为默认参数每次调用变量模板都会导致唯一专门化的实例化:

template<auto l = [](){}>
               // ^^^^^^ - lambda-expression as default argument
constexpr auto default_lambda = l;

static_assert(!std::is_same_v<
    decltype(default_lambda<>),
    decltype(default_lambda<>)>);

GCC(DEMO)和浪(DEMO)都接受上述程序

如果编译器接受这个示例是正确的,这意味着允许另一种机制来捕获和检索元编程状态,根据CWG open issue 2118,这项技术长期以来被认为是

.晦涩难懂,应使其格式不正确。


解决方案

lambda表达式是合法的默认(非类型模板)参数吗?如果是,这是否意味着使用此类默认参数的每个实例化都实例化唯一的专门化?

是,表示给定您的模板化变量:

template<auto l = [](){}>
constexpr auto default_lambda = l;

每次调用default_lambda都将通过生成新的唯一闭包类型来生成新的模板实例化,从而提供捕获元编程状态的新方法。

因此,使用default_lambda的每个表达式都必须重新求值。如果编译器的状态自上次实例化以来发生了更改,并且如果我们在依赖表达式中使用了它(例如:检查是否定义了类型),则表达式的结果可能会更改。 例如:

struct X; // not defined

template <typename T, auto = default_lambda<>>
consteval bool is_defined() {
    if constexpr (requires { T{}; }) {
        return true;
    } else {
        return false;
    }
}

static_assert(is_defined<X>() == false);

struct X {};
static_assert(is_defined<X>() == true);

相关文章