是否只有在需要时才可以缩小?

问题描述

假设我有以下代码,其中在另一个库中定义了bar

void bar(int x);  /* this declaration comes from some included header file */

void foo(long long y)
{
    return bar(y);
}

为了使故障可检测,GSL指示我如下使用gsl::narrow

void bar(int x);  /* this declaration comes from some included header file */

void foo(long long y)
{
    return bar(narrow<int>(y));
}

我的问题是,假设我知道void bar(long long x);可能会出现在库的将来版本中,则第一个版本将自动切换为使用它(根据推论规则),而第二个版本则不会。在void bar(long long x);不可用时,有什么方法可以检测到缩小失败,并在释放时切换到仅调用它?

我尝试过:

  • bar(narrow(y));,但是无法推导出狭窄的模板参数。
  • bar(superint{y}),其中superint是struct {long long x;}long long都带有重载类型转换运算符的int,后者的检查范围很窄。到目前为止,这是我最好的主意,因为在添加ambiguous call时会出现void bar(long long x);编译时错误,使我们知道在适当的时候更新代码库的地方。虽然看起来不像您要在GSL中放入的内容,所以我不太满意。.

更新

有很多好的答案,但是都必须按功能而不是参数来实现。理想情况下,解决方案应类似于GSL,在其中您可以对可能会缩小的参数进行一些处理。访客模式在这里可能很有用,假设xi和yi是有缩小风险的整数参数,我们只需要将quz(xi,yi,z,w)重写为superint_visit(quz,superint{xi},w);。像这样:

struct superint
{
    long long x;
    operator long long() { return x; }
    operator int()       { return narrow<int>(x); }
};

template <typename F,typename... Args>
auto superint_visit(F func,Args&&... args)
{
    /* if replacing 'superint' with 'long long' in Args 
       gives a function that is declared,call that function 
       (using static_cast<long long> on all 'superint' args 
       to resolve ambiguity). Otherwise,use static_cast<int> 
       on all 'superint' args and call that function instead. */
}

以上所有内容都可以存在于gsl名称空间中,而我只需要将函数编写为:

void foo(long long x)
{
    return superint_visit(bar,superint{x});
}

即使我在这里接受了答案,我仍然想听听任何能使上述事情发生的人!

解决方法

您不拥有bar吗?没问题。为decltype获取x只是一个问题。您可以通过获取整个函数的decltype并编写一个template来恢复参数类型来做到这一点:

template <typename>
struct argType;

template <typename R,typename A>
struct argType<R(A)>
{
    using type = A;
};

void foo(long long y)
{     
    bar(narrow<typename argType<decltype(bar)>::type>(y));
}
,

您可以利用以下事实:与函数模板相比,重载解析有利于非模板函数的完美参数类型匹配:

#include <iostream>

// (A)
void bar(int y) { std::cout << "bar(int)\n"; }
// (B)
//void bar(long long) { std::cout << "bar(long long)\n"; }

// (C)
template <typename T>
void bar(T) = delete;

// (C.SP1)
template<>
void bar<long long>(long long y) { bar(narrow<int>(y)); }

int main() {
    long long a = 12LL;
    bar(a); // bar(int)
            // bar(long long) if `bar(long long)` above is available.
}

如果void bar(long long);不可用,则对类型为bar(a)的参数a的{​​{1}}的任何调用将有利于非缩小函数模板重载(C),其重载主模板已删除,仅允许通过专业化(C.SP1)在long long恰好是T(无转换)的情况下调用。一旦(B)处的long long可用,通过重载解析,它将被选择为比功能模板候选者更好的可行候选者。

如果您担心在库本身编译时引入额外的void bar(long long);重载(上面的函数模板)可能会破坏bar的重载解析,则可以为{{1}添加一个公共包装器},并将重载决议委托放到上面定义包装器的TU中,通过在未命名的命名空间中声明它来为添加的bar重载应用内部链接。例如:

bar
,

可以使用boost::callable_traits::args完成第一个参数的类型,这将为您提供 具有所有参数类型的std::tuple,然后可以使用std::tuple_element

获取第一个参数的类型
#include <boost/callable_traits/args.hpp>

void bar(int y);

void foo(long long y)
{
  bar(narrow<std::tuple_element<0,boost::callable_traits::args_t<decltype(bar)>>::type>(y));
}
,

已经有非常好的创意解决方案发布到该问题。但是它们都有依赖于库实现的问题-例如。如果存在栏重载,则客户端代码不会编译。

从我的角度来看,在这种情况下,bar的客户真正想要的是:

“我想使用接受一个特定整数参数的条,如果存在其他重载则不相关”。

与库无关的非内部直接方法也是最简单的方法-带有整数参数的细条包装器。这样,它与任何库实现细节无关:

void bar(int x);  /* this declaration comes from some included header file */

template <typename T,typename = std::enable_if_v<std::is_integral_v<T>>>
inline void mybar(T x)
{
     // express directly which bar overload you want to use here 
     bar(narrow<int>(x));  // <- the only place you need to ajudst if bar interface changes,btw. use the overload from bar which you really want
}

void foo(long long y)
{
    return mybar(y);  // use mybar instead of bar within your code
}

如果存在int和long的bar重载,那么您可以通过专门设置自己的mybar来区分这两者。

如果条形库界面发生更改,则客户端应无论如何添加并重新编译。您真正想要的是将这些更改保持在中心位置。

我认为您或多或少已经回答了自己的问题。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...