一个概念的 lambdification 是改进还是不好的做法?
Is lambdification of a concept an improvement or bad practice?
看来可以把lambda放在概念里面,然后在里面写代码。让我们以此为例。我更喜欢此类概念的标准概念,请记住,这仅用于此示例的目的 - godbolt
template<class T>
concept labdified_concept =
requires {
[](){
T t, tt; // default constructible
T ttt{t}; // copy constructible
tt = t; //copy assignable
tt = std::move(t); // move assignable
};
};
而不是:
template<class T>
concept normal_concept =
std::default_initializable<T> && std::movable<T> && std::copy_constructible<T>;
lambdification 是一种改进还是不良做法?从可读性的角度来看也是如此。
忽略此机制中明显的可读性缺陷,它实际上不起作用。考虑以下因素:
template<labdified_concept T>
void foo(T t) {}
template<typename T>
void foo(T t) {}
概念规则告诉我们,如果给定的T
不满足labdified_concept
,那么应该实例化另一个foo
。但是,如果我们向这样的模板提供 SS
则不会发生这种情况。相反,我们得到一个硬错误,因为 labdified_concept<SS>
无法实例化。
requires
表达式中的内容有特殊处理,允许将某些类型的错误视为不符合要求。但是这种处理不适用于 lambda 的主体。在那里,ill-formed 代码是 ill-formed,因此在尝试实例化它时会出现编译错误。
而且即使它确实起作用了,它仍然不起作用。概念具有包含概念的复杂规则,这允许不同的概念被认为比其他概念更高度专业化。这允许 ,它可以调用更受约束的概念。例如,只需要 default_initializable
的概念比需要 default_initializable
和 moveable
的概念更通用。因此,如果一个类型满足两者,则后者将被采用,因为它更受约束。
但这只适用于 concept
s 的特殊规则。在 lambda 中隐藏需求将不允许此工作。
这应该是无效的。允许 lambda 进入未计算上下文的目的并不是突然允许 SFINAE on statements。
我们在 [temp.deduct]/9 中确实有一些措辞明确了这一点:
A lambda-expression appearing in a function type or a template parameter is not considered part of the immediate context for the purposes of template argument deduction. [Note: The intent is to avoid requiring implementations to deal with substitution failure involving arbitrary statements. [Example:
template <class T>
auto f(T) -> decltype([]() { T::invalid; } ());
void f(...);
f(0); // error: invalid expression not part of the immediate context
template <class T, std::size_t = sizeof([]() { T::invalid; })>
void g(T);
void g(...);
g(0); // error: invalid expression not part of the immediate context
template <class T>
auto h(T) -> decltype([x = T::invalid]() { });
void h(...);
h(0); // error: invalid expression not part of the immediate context
template <class T>
auto i(T) -> decltype([]() -> typename T::invalid { });
void i(...);
i(0); // error: invalid expression not part of the immediate context
template <class T>
auto j(T t) -> decltype([](auto x) -> decltype(x.invalid) { } (t)); // #1
void j(...); // #2
j(0); // deduction fails on #1, calls #2
— end example] — end note]
我们只是没有满足要求的东西。 gcc 的行为确实符合您的预期:
template <typename T> concept C = requires { []{ T t; }; };
struct X { X(int); };
static_assert(!C<X>); // ill-formed
因为 lambda 的主体在直接上下文之外,所以这不是替换失败,而是硬错误。
看来可以把lambda放在概念里面,然后在里面写代码。让我们以此为例。我更喜欢此类概念的标准概念,请记住,这仅用于此示例的目的 - godbolt
template<class T>
concept labdified_concept =
requires {
[](){
T t, tt; // default constructible
T ttt{t}; // copy constructible
tt = t; //copy assignable
tt = std::move(t); // move assignable
};
};
而不是:
template<class T>
concept normal_concept =
std::default_initializable<T> && std::movable<T> && std::copy_constructible<T>;
lambdification 是一种改进还是不良做法?从可读性的角度来看也是如此。
忽略此机制中明显的可读性缺陷,它实际上不起作用。考虑以下因素:
template<labdified_concept T>
void foo(T t) {}
template<typename T>
void foo(T t) {}
概念规则告诉我们,如果给定的T
不满足labdified_concept
,那么应该实例化另一个foo
。但是,如果我们向这样的模板提供 SS
则不会发生这种情况。相反,我们得到一个硬错误,因为 labdified_concept<SS>
无法实例化。
requires
表达式中的内容有特殊处理,允许将某些类型的错误视为不符合要求。但是这种处理不适用于 lambda 的主体。在那里,ill-formed 代码是 ill-formed,因此在尝试实例化它时会出现编译错误。
而且即使它确实起作用了,它仍然不起作用。概念具有包含概念的复杂规则,这允许不同的概念被认为比其他概念更高度专业化。这允许 default_initializable
的概念比需要 default_initializable
和 moveable
的概念更通用。因此,如果一个类型满足两者,则后者将被采用,因为它更受约束。
但这只适用于 concept
s 的特殊规则。在 lambda 中隐藏需求将不允许此工作。
这应该是无效的。允许 lambda 进入未计算上下文的目的并不是突然允许 SFINAE on statements。
我们在 [temp.deduct]/9 中确实有一些措辞明确了这一点:
A lambda-expression appearing in a function type or a template parameter is not considered part of the immediate context for the purposes of template argument deduction. [Note: The intent is to avoid requiring implementations to deal with substitution failure involving arbitrary statements. [Example:
template <class T> auto f(T) -> decltype([]() { T::invalid; } ()); void f(...); f(0); // error: invalid expression not part of the immediate context template <class T, std::size_t = sizeof([]() { T::invalid; })> void g(T); void g(...); g(0); // error: invalid expression not part of the immediate context template <class T> auto h(T) -> decltype([x = T::invalid]() { }); void h(...); h(0); // error: invalid expression not part of the immediate context template <class T> auto i(T) -> decltype([]() -> typename T::invalid { }); void i(...); i(0); // error: invalid expression not part of the immediate context template <class T> auto j(T t) -> decltype([](auto x) -> decltype(x.invalid) { } (t)); // #1 void j(...); // #2 j(0); // deduction fails on #1, calls #2
— end example] — end note]
我们只是没有满足要求的东西。 gcc 的行为确实符合您的预期:
template <typename T> concept C = requires { []{ T t; }; };
struct X { X(int); };
static_assert(!C<X>); // ill-formed
因为 lambda 的主体在直接上下文之外,所以这不是替换失败,而是硬错误。