简体   繁体   English

检查(全局)function 是否存在,但不允许隐式转换

[英]Check existence of (global) function but disallow implicit conversion

Consider this simple check for whether a (global) function is defined:考虑这个简单的检查是否定义了(全局)function:

template <typename T>
concept has_f = requires ( const T& t ) { Function( t ); };
// later use in MyClass<T>:
if constexpr ( has_f<T> ) Function( value );

unfortunately this allows for implicit conversions .不幸的是,这允许隐式转换 This is obviously a big risk for mess-ups.这显然是一个很大的混乱风险。

Question: How to check if Function( const T& t ) 'explicitly' exists?问题:如何检查 Function( const T& t ) 是否“明确”存在?

Something like就像是

if constexpr ( std::is_same_v<decltype( Function( t ) ), void> )

should be free of implict conversions, but I can't get it working.应该没有隐式转换,但我无法让它工作。

Note: The point of the concept approach was to get rid of old 'detection patterns' and simplify.注意:概念方法的重点是摆脱旧的“检测模式”并简化。

Before explaining how to do this, I will explain why you shouldn't want to do any of this.在解释如何做到这一点之前,我将解释为什么你不应该这样做。

You mentioned "old 'detection patterns'" without adding any specifics as to what you are referring to.您提到了“旧的'检测模式'”,但没有添加任何关于您所指内容的细节。 There are a fair number of idioms C++ users sometimes employ that can do something like detecting if a function takes a particular parameter.用户有时会使用许多惯用语 C++ 来执行诸如检测 function 是否采用特定参数之类的操作。 Which ones of these count as "detection patterns" by your reckoning is not known.您认为其中哪些算作“检测模式”尚不清楚。

However, the vast majority of these idioms exist to serve a specific, singular purpose: to see if a particular function call with a given set of arguments is valid, legal C++ code.然而,绝大多数这些成语的存在是为了服务于一个特定的、单一的目的:查看一个特定的 function 调用是否具有给定的 arguments 集是有效的、合法的 ZF6F87C9FDCF8B3C3F07F93F1EE8712CZ 代码。 They don't really care if a function exactly takes T ;他们并不真正关心 function 是否完全占用T testing for T specifically is just how a few of those idioms work to produce the important information.专门测试T只是其中一些习语如何产生重要信息。 Namely whether you can pass a T to said function.即是否可以将T传递给所述 function。

Looking for a specific function signature was almost always a means to an end, not the final goal.寻找特定的 function 签名几乎总是达到目的的手段,而不是最终目标。

Concepts, particularly requires expressions, is the end itself .概念,特别是需要表达的,是目的本身 It allows you to ask the question directly.它允许您直接提出问题。 Because really, you don't care if Function has a parameter that takes a T ;因为真的,你不在乎Function是否有一个带T的参数; you care whether Function(t) is legitimate code or not.你关心Function(t)是否是合法代码。 Exactly how that happens is an implementation detail.具体如何发生是一个实现细节。

The only reason I can think of that someone might want to constrain a template on an exact signature (rather than an argument match) is to defeat implicit conversion.我能想到有人可能想要将模板限制在精确签名(而不是参数匹配)上的唯一原因是为了打败隐式转换。 But you really shouldn't try to break basic language features like that.但是你真的不应该尝试破坏这样的基本语言特性。 If someone writes a type that is implicitly convertible to another, they have the right to the benefits of that conversion, as defined by the language.如果有人编写的类型可以隐式转换为另一种类型,则他们有权享受这种转换带来的好处,正如语言所定义的那样。 Namely, the ability to use it in many ways as if it were that other type.也就是说,能够以多种方式使用它,就好像它是其他类型一样。

That is, if Function(t) is what your constrained template code is actually going to do, then the user of that template has every right to provide code that makes that compiler within the limits of the C++ language.也就是说,如果Function(t)是您的受约束模板代码实际上要执行的操作,那么该模板的用户有权提供使该编译器处于 C++ 语言限制范围内的代码。 Not within the limits of your personal ideas of what features are good or bad in that language.不在您对该语言中哪些功能好坏的个人想法的范围内。

Concepts are not like base classes, where you decide the exact signature for each method and the user must strictly abide by that.概念不像基类,你决定每个方法的确切签名,用户必须严格遵守。 Concepts are patterns that constrain template definitions.概念是约束模板定义的模式。 Expressions in concept constraints are expressions that you expect to use in your template .概念约束中的表达式是您希望在模板中使用的表达式。 You only put an expression in a concept if you plan on using it in your templates constrained by that concept.如果您计划在受该概念约束的模板中使用表达式,则只能将表达式放入概念中。

You don't use a function signature;您不使用 function 签名; you call functions .调用函数 So you constrain a concept on what functions can be called with which arguments.因此,您限制了可以使用哪个 arguments 调用哪些函数的概念。 You're saying "you must let me do this", not "provide this signature".您是在说“您必须让我这样做”,而不是“提供此签名”。


That having been said... what you want is not generally possible;)话虽如此......你想要的通常是不可能的;)

There are several mechanisms that you might employ to achieve it, but none of them do exactly what you want in all cases.您可以使用多种机制来实现它,但它们都不能在所有情况下都完全符合您的要求。

The name of a function resolves to an overload set consisting of all of the functions that could be called. function 的名称解析为由所有可以调用的函数组成的重载集。 This name can be converted into a pointer to a specific function signature if and only if that signature is one of the functions in the overload set.当且仅当该签名是重载集中的函数之一时,此名称才能转换为指向特定 function 签名的指针。 So in theory, you might do this:所以理论上,你可以这样做:

template <typename T>
concept has_f = requires () { static_cast<void (*)(T const&)>(&Function); };

However, because the name Function is not dependent on T (as far as C++ is concerned), it must be resolved during the first pass of two-phase name lookup for templates.但是,由于名称Function依赖于T (就 C++ 而言),它必须在模板的两阶段名称查找的第一遍中解析。 That means any and all Function overloads you intend to care about have to be declared before has_f is defined , not merely instantiated with an appropriate T .这意味着您打算关心的任何和所有Function重载都必须在定义has_f之前声明,而不仅仅是用适当的T实例化。

I think this is sufficient to declare that this is non-functional as a solution.我认为这足以声明这是一种非功能性的解决方案。 Even if it worked though, it would only "work" given 3 circumstances:即使它有效,它也只会在 3 种情况下“有效”:

  1. Function is known/required to be an actual function, rather than a global object with an operator() overload. Function已知/必须是实际的 function,而不是具有operator()重载的全局 object。 So if a provider of T wants to provide a global functor instead of a regular function (for any number of reasons) this method will not work, even though Function(t) is 100% perfectly valid, legitimate, and does none of those terrible implicit conversions that for some reason must be stopped.因此,如果T的提供者想要提供一个全局仿函数而不是常规的 function(出于多种原因),则此方法将不起作用,即使Function(t) 100% 完全有效、合法,并且没有那些可怕的由于某种原因必须停止的隐式转换。

  2. The expression Function(t) is not expected to use ADL to find the actual Function to call.表达式Function(t)预计不会使用 ADL 来查找要调用的实际Function

  3. Function is not a template function. Function不是模板 function。

And not one of these possibilities has anything to do with implicit conversions.这些可能性中没有一种与隐式转换有关。 If you're going to call Function(t) , then it's 100% OK for ADL to find it, template argument deduction to instantiate it, or for the user to fulfill this with some global lambda.如果你要调用Function(t) ,那么 ADL 100% 可以找到它,模板参数推导来实例化它,或者用户用一些全局 lambda 来实现它。

Your second-best bet is to rely on how overload resolution works.您的第二个最佳选择是依靠重载解决方案的工作原理。 C++ only permits a single user-defined conversion in operator overloading. C++ 仅允许在运算符重载中进行单个用户定义的转换。 As such, you can create a type which will consume that one user-defined conversion in the function call expression in lieu of T .因此,您可以创建一种类型,该类型将在 function 调用表达式中使用该用户定义的转换来代替T And that conversion should be a conversion to T itself.这种转换应该是对T本身的转换。

You would use it like this:你会像这样使用它:

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

This of course still leaves the standard conversions, so you can't differentiate between a function taking a float if T is int , or derived classes from bases.当然,这仍然保留标准转换,因此您无法区分 function 如果Tint则采用float或从基类派生类。 You also can't detect if Function has any default parameters after the first one.您也无法检测Function在第一个参数之后是否有任何默认参数。

Overall, you're still not detecting the signature, merely call-ability.总的来说,你仍然没有检测到签名,只是调用能力。 Because that's all you should care about to begin with.因为这就是你开始时应该关心的一切。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM