检查(全局)函数的存在但不允许隐式转换 [英] Check existence of (global) function but disallow implicit conversion

查看:68
本文介绍了检查(全局)函数的存在但不允许隐式转换的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下简单检查,是否定义了(全局)函数:

 模板< typename T>概念has_f = require(const T& t){Function(t);};//稍后在MyClass< T>中使用:如果constexpr(has_f< T>)Function(value); 

很遗憾,此允许隐式转换.这显然是造成混乱的巨大风险.

问题:如何检查Function(const T& t)'explicitly'是否存在?

类似

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

应该没有隐含的转换,但我无法使其正常工作.

注意:概念方法的重点是摆脱旧的检测模式"并简化.

解决方案

在解释如何执行此操作之前,我将解释为什么您想要执行任何此操作.

您提到了旧的检测模式""而不添加有关您所指的内容的任何细节.C ++用户有时会使用很多习语,它们可以做类似检测函数是否带有特定参数的操作.其中哪些算作检测模式"?根据您的估算是未知的.

但是,这些惯用法中的绝大多数是为特定的,单一的目的而存在的:查看具有给定参数集的特定函数调用是否有效,合法的C ++代码.他们并不真的在乎函数是否完全使用 T .具体来说,测试 T 就是这些习惯用法中的一些如何产生重要信息的方法.即是否可以将 T 传递给所述函数.

寻找特定的功能签名几乎总是达到目的的手段,而不是最终目标.

概念(尤其是表达式)是本身.它使您可以直接问问题.因为实际上,您并不在乎 Function 是否具有采用 T 的参数;您会在乎 Function(t)是否为合法代码.确切的实现方式是实现细节.

我能想到有人可能想将模板约束在精确的签名上(而不是参数匹配)的唯一原因是打败了隐式转换.但是您真的不应该尝试破坏像这样的基本语言功能.如果某人编写的类型可以隐式转换为另一种类型,则他们有权使用该语言所定义的那种转换.即,可以像使用其他类型一样以多种方式使用它.

也就是说,如果 Function(t)是您受约束的模板代码实际上要执行的操作,则该模板的用户将有权提供使该编译器在以下限制范围内的代码:C ++语言.不在您的个人观点范围之内,即哪种功能的优劣.

概念与基类不同,在基类中您可以确定每种方法的确切签名,并且用户必须严格遵守.概念是约束模板定义的模式.概念约束中的表达式是希望在模板中使用的表达式.仅当计划在受该概念限制的模板中使用该表达式时,才将其放入该概念中.

您不使用功能签名;您调用函数.因此,您限制了可以使用哪些参数调用哪些函数的概念.您是在说您必须让我这样做",而不是提供此签名".


话虽如此...您想要的东西通常是不可能的;)

您可以采用多种机制来实现它,但是在所有情况下,它们都不能完全满足您的要求.

一个函数的名称解析为一个重载集,该重载集包含可以调用的所有函数.当且仅当该签名是重载集中的功能之一时,此名称才能转换为指向特定函数签名的指针.因此,从理论上讲,您可以这样做:

 模板< typename T>概念has_f = require(){static_cast< void(*)(T const&)>(& Function);}; 

但是,由于名称 Function 依赖于 T (就C ++而言),因此必须在使用过程中进行解析.模板的两阶段名称查找的第一阶段.这意味着您要关心的所有 Function 重载必须在定义之前声明 只是用适当的 T 实例化.

我认为这足以宣布它不能用作解决方案.即使它起作用,它也只能起作用".在3种情况下:

    已知/要求
  1. Function 是实际函数,而不是具有 operator()重载的全局对象.因此,如果 T 的提供者想要提供全局函子而不是常规函数(出于多种原因),即使 Function(t)是100%完全有效,合法的,并且不会执行由于某些原因而必须停止的可怕的隐式转换.

  2. 表达式 Function(t)不应使用ADL查找要调用的实际 Function .

  3. Function 不是模板函数.

这些可能性中没有一种与隐式转换有关.如果您要调用 Function(t),那么ADL可以找到它,使用模板参数推导实例化它,或者用户可以用一些全局lambda来实现它,这100%可以./p>

您的第二好的选择是依靠重载解析的工作方式.C ++仅允许在运算符重载中进行单个用户定义的转换.这样,您可以创建一个类型,该类型将使用函数调用表达式中的一个用户定义的转换来代替 T .并且该转换应该是对 T 本身的转换.

您将像这样使用它:

  template< typename T>udc_killer类{上市://永远不会被调用.运算符T const&();};模板< typename T>概念has_f = require(){Function(udc_killer< T> {});}; 

这当然仍然保留标准转换,因此如果 T int ,则无法区分采用 float 的函数,或从基类派生的类.您也无法检测到 Function 在第一个参数之后是否具有任何默认参数.

总体而言,您仍未检测到签名,仅是可呼叫性.因为这就是您应该开始关心的所有事情.

Consider this simple check for whether a (global) function is defined:

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?

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. 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. They don't really care if a function exactly takes T; testing for T specifically is just how a few of those idioms work to produce the important information. Namely whether you can pass a T to said function.

Looking for a specific function signature was almost always a means to an end, not the final goal.

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; you care whether Function(t) is legitimate code or not. 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. 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; you call functions. So you constrain a concept on what functions can be called with which 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. 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. 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. 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.

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:

  1. Function is known/required to be an actual function, rather than a global object with an operator() overload. 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.

  2. The expression Function(t) is not expected to use ADL to find the actual Function to call.

  3. Function is not a template 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.

Your second-best bet is to rely on how overload resolution works. C++ only permits a single user-defined conversion in operator overloading. As such, you can create a type which will consume that one user-defined conversion in the function call expression in lieu of T. And that conversion should be a conversion to T itself.

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. You also can't detect if Function has any default parameters after the first one.

Overall, you're still not detecting the signature, merely call-ability. Because that's all you should care about to begin with.

这篇关于检查(全局)函数的存在但不允许隐式转换的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆