是否可以仅在需要时缩小范围?
Is it possible to narrow only if needed?
假设我有以下代码,其中 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));
,但是narrow的模板参数无法推导。
bar(superint{y})
,其中 superint 是一个 struct {long long x;}
,对 long long
和 int
都有重载的类型转换运算符,后者具有窄检查。到目前为止,这是我最好的想法,因为它在添加 void bar(long long x);
时给出了 ambiguous call
编译时错误,让我们知道在适当的时候更新我们的代码库的地方。它看起来不像你会放在 GSL 中的东西,所以我不太满意..
更新
有很多好的答案,但它们都必须按函数而不是按参数来实现。理想情况下,解决方案应该类似于 GSL,您只需对有缩小风险的论点做一些事情。访问者模式在这里可能很有用,我们只需要将 quz(xi,yi,z,w)
重写为 superint_visit(quz, superint{xi},superint{xi},z,w);
假设 xi 和 yi 是有缩小风险的整数参数。类似于:
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});
}
即使我在这里接受了答案,我仍然希望听到任何可以实现上述目标的人!
已经针对该问题发布了非常好的创意解决方案。但是它们都有依赖于库实现的问题——例如。如果存在栏重载,则客户端代码不会编译。
在我看来,在这种情况下酒吧的客户真正想要的是:
“我想使用接受一个特定整数参数的栏,如果存在其他重载则无关”。
library-independent non-intrinsic 直接的方法也是最简单的方法 - 一个接受整数参数的细条包装器。这样它就独立于任何库实现细节:
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 的栏重载,那么您可以通过简单地专门化您自己的 mybar 来区分这两者。
如果bar库接口改变了,客户端还是应该addapt重新编译。您真正想要的是将这些更改放在中心位置。
在我看来,你已经或多或少地回答了你自己的问题。
您不拥有 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));
}
您可以利用这样一个事实,即重载解析有利于 non-template 函数在函数模板上的完美参数类型匹配:
#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
类型的参数 a
调用 bar(a)
将支持 non-narrowing 函数模板重载( C),其主模板已被删除,仅允许在 T
正好是 long long
(无转换)时通过特化 (C.SP1) 进行调用。一旦 (B) 处的 void bar(long long);
可用,它将被重载决议选为比函数模板候选者更好的可行候选者。
如果您担心在编译库本身时引入额外的 bar
重载(上面的函数模板)可能会破坏 bar
的重载解析,您可以添加一个 public 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);
}
可以使用 boost::callable_traits::args 获取第一个参数的类型,这会给你一个
std::tuple with types of all arguments, which then may be used to get type of first argument using 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 */
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));
,但是narrow的模板参数无法推导。bar(superint{y})
,其中 superint 是一个struct {long long x;}
,对long long
和int
都有重载的类型转换运算符,后者具有窄检查。到目前为止,这是我最好的想法,因为它在添加void bar(long long x);
时给出了ambiguous call
编译时错误,让我们知道在适当的时候更新我们的代码库的地方。它看起来不像你会放在 GSL 中的东西,所以我不太满意..
更新
有很多好的答案,但它们都必须按函数而不是按参数来实现。理想情况下,解决方案应该类似于 GSL,您只需对有缩小风险的论点做一些事情。访问者模式在这里可能很有用,我们只需要将 quz(xi,yi,z,w)
重写为 superint_visit(quz, superint{xi},superint{xi},z,w);
假设 xi 和 yi 是有缩小风险的整数参数。类似于:
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});
}
即使我在这里接受了答案,我仍然希望听到任何可以实现上述目标的人!
已经针对该问题发布了非常好的创意解决方案。但是它们都有依赖于库实现的问题——例如。如果存在栏重载,则客户端代码不会编译。
在我看来,在这种情况下酒吧的客户真正想要的是:
“我想使用接受一个特定整数参数的栏,如果存在其他重载则无关”。
library-independent non-intrinsic 直接的方法也是最简单的方法 - 一个接受整数参数的细条包装器。这样它就独立于任何库实现细节:
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 的栏重载,那么您可以通过简单地专门化您自己的 mybar 来区分这两者。
如果bar库接口改变了,客户端还是应该addapt重新编译。您真正想要的是将这些更改放在中心位置。
在我看来,你已经或多或少地回答了你自己的问题。
您不拥有 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));
}
您可以利用这样一个事实,即重载解析有利于 non-template 函数在函数模板上的完美参数类型匹配:
#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
类型的参数 a
调用 bar(a)
将支持 non-narrowing 函数模板重载( C),其主模板已被删除,仅允许在 T
正好是 long long
(无转换)时通过特化 (C.SP1) 进行调用。一旦 (B) 处的 void bar(long long);
可用,它将被重载决议选为比函数模板候选者更好的可行候选者。
如果您担心在编译库本身时引入额外的 bar
重载(上面的函数模板)可能会破坏 bar
的重载解析,您可以添加一个 public 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);
}
可以使用 boost::callable_traits::args 获取第一个参数的类型,这会给你一个 std::tuple with types of all arguments, which then may be used to get type of first argument using 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));
}