non*_*741 0 c++ templates function implicit-conversion c++20
考虑这个简单的检查是否定义了(全局)函数:
template <typename T>
concept has_f = requires ( const T& t ) { Function( t ); };
// later use in MyClass<T>:
if constexpr ( has_f<T> ) Function( value );
Run Code Online (Sandbox Code Playgroud)
不幸的是,这允许隐式转换。这显然是一个很大的混乱风险。
问题:如何检查 Function( const T& t ) 是否“显式”存在?
就像是
if constexpr ( std::is_same_v<decltype( Function( t ) ), void> )
Run Code Online (Sandbox Code Playgroud)
应该没有隐式转换,但我无法让它工作。
注意:概念方法的重点是摆脱旧的“检测模式”并进行简化。
在解释如何做到这一点之前,我将解释为什么你不应该想要这样做。
您提到了“旧的'检测模式'”,但没有添加任何关于您所指内容的细节。C++ 用户有时会使用相当多的习惯用法,它们可以做一些事情,例如检测函数是否采用特定参数。根据您的计算,其中哪些算作“检测模式”尚不清楚。
然而,这些习语中的绝大多数存在于特定的、单一的目的:查看具有给定参数集的特定函数调用是否有效、合法的 C++ 代码。他们并不真正关心一个函数是否正好需要T;T具体测试就是这些习语中的一些如何产生重要信息。即您是否可以将 a 传递T给所述函数。
寻找特定的函数签名几乎总是达到目的的手段,而不是最终目标。
概念,特别是需要表达的,是目的本身。它允许您直接提问。因为实际上,您并不关心是否Function有一个带T;的参数。你关心是否Function(t)是合法的代码。具体如何发生是一个实现细节。
我能想到的唯一原因是有人可能想要将模板限制在精确签名(而不是参数匹配)上是为了阻止隐式转换。但是您真的不应该尝试破坏这样的基本语言功能。如果某人编写的类型可以隐式转换为另一种类型,则他们有权享受该语言定义的转换带来的好处。也就是说,能够以多种方式使用它,就好像它是其他类型一样。
也就是说,如果Function(t)您的受约束模板代码实际上要执行的操作,那么该模板的用户完全有权提供使该编译器符合 C++ 语言限制的代码。不在你个人认为该语言的哪些特性好坏的范围内。
概念不像基类,你决定每个方法的确切签名,用户必须严格遵守。概念是约束模板定义的模式。概念约束中的表达式是您希望在模板中使用的表达式。如果您计划在受该概念约束的模板中使用它,则只能将表达式放入该概念中。
您不使用函数签名;你调用函数。因此,您限制了可以使用哪些参数调用哪些函数的概念。你说的是“你必须让我这样做”,而不是“提供这个签名”。
话虽如此......你想要的通常是不可能的;)
您可能会采用多种机制来实现它,但没有一种机制能在所有情况下完全满足您的要求。
函数的名称解析为由所有可以调用的函数组成的重载集。当且仅当该签名是重载集中的函数之一时,该名称才能转换为指向特定函数签名的指针。所以理论上,你可以这样做:
template <typename T>
concept has_f = requires () { static_cast<void (*)(T const&)>(&Function); };
Run Code Online (Sandbox Code Playgroud)
然而,因为这个名字Function是不依赖于T(至于C ++而言),它必须是两阶段名称查找第一遍为模板时解决。这意味着任何和所有Function你打算去关心过载必须声明之前 has_f的定义,不只是用适当的实例T。
我认为这足以声明这是非功能性的解决方案。即使它有效,它也只能在 3 种情况下“有效”:
Function已知/需要是一个实际的函数,而不是一个具有operator()重载的全局对象。因此,如果 的提供者T想要提供全局函子而不是常规函数(出于多种原因),则此方法将不起作用,即使Function(t)100% 完全有效、合法,并且不会执行某些可怕的隐式转换理由必须停止。
该表达式Function(t)不应使用 ADL 来查找实际Function要调用的内容。
Function 不是模板函数。
这些可能性中的任何一个都与隐式转换无关。如果你要调用Function(t),那么 ADL 100% 可以找到它,模板参数推导来实例化它,或者用户使用一些全局 lambda 来实现它。
你的第二个选择是依赖重载解析的工作方式。C++ 只允许在运算符重载中进行单个用户定义的转换。因此,您可以创建一种类型,该类型将在函数调用表达式中使用该用户定义的转换来代替T. 这种转换应该是对T自身的转换。
你会像这样使用它:
template<typename T>
class udc_killer
{
public:
//Will never be called.
operator T const&();
};
template <typename T>
concept has_f = requires () { Function(udc_killer<T>{}); };
Run Code Online (Sandbox Code Playgroud)
这当然仍然保留标准转换,因此您无法区分采用floatif Tis的函数int或来自基类的派生类。您也无法检测Function在第一个参数之后是否有任何默认参数。
总的来说,您仍然没有检测到签名,只是检测到了可调用性。因为这就是您开始时应该关心的全部内容。
| 归档时间: |
|
| 查看次数: |
135 次 |
| 最近记录: |