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



假设我有以下代码,其中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}),其中superset是struct {long long x;},对于long longint都具有重载的typecast运算符,后者具有窄检查。到目前为止,这是我最好的想法,因为当添加void bar(long long x);时,它会产生ambiguous call编译时错误,让我们知道在适当的时候更新代码库的位置。不过,它看起来不像你会放在GSL里的东西,所以我不太满意

更新

有很多好的答案,但它们都必须按函数实现,而不是按参数实现。理想情况下,解决方案应该看起来像GSL,你只需要对有缩小风险的论点做些什么。访问者模式在这里可能很有用,假设xi和yi是有缩小风险的整数参数,我们只需要将quz(xi,yi,z,w)重写为superint_visit(quz, superint{xi},superint{xi},z,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});
}

尽管我在这里接受了答案,但我仍然希望听到任何能够实现上述目标的人的声音!

您可以利用这样一个事实,即重载解析有利于非模板函数的完美参数类型匹配,而不是函数模板:

#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);不可用,则对类型为long long的参数abar(a)的任何调用都将有利于非收缩函数模板重载(C(,其主模板已被删除,仅允许在T通过专用化(C.SP1(恰好为long long(无转换(时进行调用。一旦(B(处的void bar(long long);可用,通过过载分辨率,它将被选择为比函数模板候选者更好的可行候选者。

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

// foo.h
#pragma once
void foo(long long y);
// foo.cpp
#include "foo.h"
#include "gsl/gsl_narrow"
#include "the_lib/bar.h"
namespace {
template <typename T>
void bar(T) = delete;
template<>
void bar<long long>(long long y) { bar(narrow<int>(y)); }
}  // namespace
void foo(long long y) {
bar(y);
}

您没有bar?没问题。这是为x获取decltype的问题。这可以通过获取整个函数的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));
}

获取第一个参数的类型可以使用boost::callable_traits::args来完成,这将为您提供std::tuple和所有参数的类型,然后可以使用std::元组_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来区分这两者。

如果bar库接口发生更改,客户端应该添加apt并重新编译。在这里,你真正想要的是保持这些变化的中心地位。

在我看来,你或多或少已经回答了自己的问题。

最新更新