wvn*_*wvn 8 c++ c++-concepts c++20
requires在这里,我详细介绍了概念中使用的子句的一个怪癖的 MWE 。我想要的是一个概念,指示某个函数类型是否可以通过一系列参数调用。我知道这是由 提供的std::invocable,但我在这里所提供的内容将说明这一点。
考虑以下概念:
template <typename func_t, typename... args_t>
concept callable = requires(const func_t& f, const args_t&... args) {f(args...);};
Run Code Online (Sandbox Code Playgroud)
这是相当简单的:如果我有一个func_t,我可以用 来调用它args_t...吗?根据我的理解,只要使用提供的参数调用函数是有效的操作(包括 conversions ) ,这个概念就应该评估为 true 。例如,如果我有一个 lambda:
auto func = [](const double& i) -> void {};
Run Code Online (Sandbox Code Playgroud)
那么以下两个概念的计算结果为true:
callable<decltype(func), int> //true
callable<decltype(func), double> //true
Run Code Online (Sandbox Code Playgroud)
这似乎是因为存在从int到 的转换double。这很好,因为这是我在项目中想要的行为,让我发现了这个问题。
现在,我想使用稍微复杂一点的类型来调用我的 lambda,如下所示:
auto func = [](const type1_t<space1>& t1) -> int {return 1;};
Run Code Online (Sandbox Code Playgroud)
考虑以下类型:
enum space {space1,space2};
template <const space sp> struct type2_t{};
template <const space sp> struct type1_t
{
type1_t(){}
template <const space sp_r>
type1_t(const type2_t<sp_r>& t2){}
};
Run Code Online (Sandbox Code Playgroud)
这里我们可以转换type2_t为,type1_t而不管模板参数如何,因为type1_t. 在这些条件下,以下概念的计算结果为true:
callable<decltype(func), type1_t<space1>> //true
callable<decltype(func), type2_t<space1>> //true
callable<decltype(func), type2_t<space2>> //true
Run Code Online (Sandbox Code Playgroud)
假设我不想在具有不同space参数的类型之间进行任何转换。有几种方法可以做到这一点,但我会选择在构造函数requires上使用子句type1_t:
template <const space sp_r>
requires (sp_r == sp)
type1_t(const type2_t<sp_r>& t2)
{
//all other code remains unchanged.
}
Run Code Online (Sandbox Code Playgroud)
经过这次机会,我得到以下评价:
callable<decltype(func), type1_t<space1>> //true
callable<decltype(func), type2_t<space1>> //true
callable<decltype(func), type2_t<space2>> //false
Run Code Online (Sandbox Code Playgroud)
这是我期望的行为,因为概念类中的代码requires不再编译。
现在,假设我删除了requires构造函数中的子句type1_t,并且构造函数现在调用一个名为 的成员函数dummy_func:
template <const space sp> struct type1_t
{
type1_t(){}
template <const space sp_r>
void dummy_func(const type2_t<sp_r>& t2){}
template <const space sp_r>
type1_t(const type2_t<sp_r>& t2)
{
dummy_func(t2);
}
};
Run Code Online (Sandbox Code Playgroud)
构造函数几乎保持不变,因此所有概念都true再次计算为:
callable<decltype(func), type1_t<space1>> //true
callable<decltype(func), type2_t<space1>> //true
callable<decltype(func), type2_t<space2>> //true
Run Code Online (Sandbox Code Playgroud)
requires当我们引入一个子句时,就会出现奇怪的行为dummy_func:
template <const space sp_r>
requires (sp_r == sp)
void dummy_func(const type2_t<sp_r>& t2){}
Run Code Online (Sandbox Code Playgroud)
通过该条款,我期望进行以下概念评估:
callable<decltype(func), type1_t<space1>> //true
callable<decltype(func), type2_t<space1>> //true
callable<decltype(func), type2_t<space2>> //false
Run Code Online (Sandbox Code Playgroud)
然而,当我使用 new 子句进行编译时,我实际上得到:
callable<decltype(func), type1_t<space1>> //true
callable<decltype(func), type2_t<space1>> //true
callable<decltype(func), type2_t<space2>> //true
Run Code Online (Sandbox Code Playgroud)
这对我来说很奇怪,因为以下内容将编译:
auto func = [](const type1_t<space1>& t1) -> int {return 1;};
func(type1_t<space1>());
Run Code Online (Sandbox Code Playgroud)
但这不会编译:
func(type2_t<space2>());
Run Code Online (Sandbox Code Playgroud)
callable<decltype(func), type2_t<space2>>对我来说,这与评估 to 的概念是矛盾的true,因为我直接使用子句中的代码主体requires。
这种矛盾的根源是什么?requires为什么编译器没有完全检查概念子句中代码的有效性?
附录
两项免责声明:
我知道我应该使用std::invocable. 以上仅供说明之用。请注意,当我使用 时也会出现同样的问题std::invocable。
我可以通过对 的构造函数施加约束来解决该问题type1_t,但这在我的项目中是不可取的。
有关显示该问题的完整代码,请参阅以下内容:
#include <iostream>
#include <concepts>
enum space
{
space1,
space2
};
template <typename func_t, typename... args_t>
concept callable = requires(const func_t& f, const args_t&... args) {f(args...);};
template <const space sp> struct type2_t{};
template <const space sp> struct type1_t
{
type1_t(){}
template <const space sp_r>
requires (sp_r == sp)
void dummy_func(const type2_t<sp_r>& t2){}
template <const space sp_r>
type1_t(const type2_t<sp_r>& t2)
{
dummy_func(t2);
}
};
int main(int argc, char** argv)
{
auto func = [](const type1_t<space1>& t1) -> int {return 1;};
std::cout << callable<decltype(func), type1_t<space1>> << std::endl; //true
std::cout << callable<decltype(func), type2_t<space1>> << std::endl; //true
std::cout << callable<decltype(func), type2_t<space2>> << std::endl; //true, should be false!!
}
Run Code Online (Sandbox Code Playgroud)
请注意,我使用带有-std=c++20标志的 g++ 11.3。
Nic*_*las 11
对函数的约束约束了函数签名;它们与函数体无关。约束并不关心主体是否无法编译;他们只关心应用于该函数签名的约束的有效性。因此,如果您希望对函数进行约束,则必须将这些约束放入该函数的签名中。
是的,这确实会递归地向上传播调用图。这只是你必须处理的事情。concept这就是为什么将任意表达式约束捆绑到命名中很有用的原因之一。