【问题标题】:Is a lambda expression a legal default (non-type template) argument?lambda 表达式是合法的默认(非类型模板)参数吗?
【发布时间】:2021-02-04 09:39:18
【问题描述】:

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


背景

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

template<auto v>
constexpr auto identity_v = v;

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

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

[...] 一个unique,未命名的非联合类类型,称为闭包类型 [...]

另一方面,[basic.def.odr]/1 [extract, emphasis mine] 声明

任何翻译单元不得包含超过任何变量、函数、类类型、枚举类型、模板、参数的默认参数(对于给定范围内的函数)的定义,或 默认模板参数。

可能意味着默认模板参数被认为是需要尊重 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) 和 Clang (DEMO) 都接受上述程序

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

... 晦涩难懂,应该是格式错误的。

【问题讨论】:

  • [temp.decls]/2 是关于默认的 function 参数(例如void foo(int = 0);)。它不适用于默认模板参数。确切地说,该标准在适当的时候使用“默认模板参数”,它有意避免草率使用该术语。
  • @StoryTeller-UnslanderMonica 谢谢,我更新为引用[basic.def.odr]/1,它描述了(但是在较弱的意义上/间接地)默认模板参数是定义。
  • 我相信this motion 解决了这个问题,但我几乎不理解那个标准措辞。
  • 我认为因为没有捕获的 lambda 现在是像没有成员的结构这样的微不足道的类型,所以 lambda 可以像模板参数一样传递。我不知道可能违反 ODR。这些可能会导致模板的多个实例化,而预期只有一个。
  • hm,模板只能在 lambda 表达式只能无捕获的范围内声明,因此不会发生任何“神秘”事件?

标签: c++ templates lambda language-lawyer c++20


【解决方案1】:

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);

【讨论】:

  • 虽然 consteval 示例很有趣,但这只是重复了问题中已经观察到的内容:编译器似乎将每个实例化视为唯一的特化。我正在寻找的,强调我的问题的语言-律师标签,是提供明确答案的规范性参考。对于这些类型的异常主题,编译器实际实现的内容可能并非标准所说的那样,观察到的行为可能只是巧合,甚至可能是明显表现良好的非格式 NDR。
  • 你引用的部分似乎表明这种行为是正确的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多