【问题标题】:Is it possible to narrow only if needed?是否可以仅在需要时缩小?
【发布时间】:2020-08-21 10:32:28
【问题描述】:

假设我有以下代码,其中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 longint,具有重载类型转换运算符,后者具有窄检查。这是我最好的主意,因为当添加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});
}

即使我在这里接受了答案,我仍然希望听到任何可以实现上述目标的人的意见!

【问题讨论】:

  • 如何将 bar 设为模板?这样你就可以调用 bar(y) 并在 bar 实现中做出所有与大小相关的决定。对于它的客户,bar 应该能够处理任何整数类型。我认为这就是您基本上要寻找的。​​span>
  • 不是我的图书馆@StPiere

标签: c++ guideline-support-library


【解决方案1】:

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

#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); 不可用,则对a 类型为long long 的参数的任何调用bar(a) 将支持非窄化函数模板重载(C),其主模板已被删除以仅允许当T 恰好是long long(无转换)时通过专业化(C.SP1)调用。一旦 (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);
}

【讨论】:

  • 很好,但这可能会破坏库本身的重载解析,具体取决于您何时将模板重载呈现给编译器。
  • @Bathsheba 是的,好点。我想您可以为bar 添加一个公共包装器,并将重载决议委托放在定义包装器的TU 中,为添加的bar 重载应用内部链接。
【解决方案2】:

您不拥有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));
}

【讨论】:

  • 不错的解决方案。但是在每次调用 bar 时都需要写这个是不那么可读的。也许使用自定义函数添加一级间接会更合适
  • @StPiere 您可以通过 typedef 或使用 typename argType&lt;decltype(bar)&gt;::type 类型来缩短它
  • 我想知道std中是否已经有一个很好的解决方案,比如std::result_of,它可能会给出bar的返回值类型。
  • @Bashheba,当然,这样会更好。人们只需要在每次调用时知道窄转换应该转换的类型。从设计的角度来看,我个人觉得这很奇怪,但这不是问题的主题。但仍然很好。
  • 我的意思是,如果库也有void bar(char),则不能采用bar 的类型,并且当添加void bar(long long) 时,代码也将无法编译(允许删除解决方法:))。但如果 void bar(long long) 替换 void bar(int),它会编译(让解决方法到位)。
【解决方案3】:

可以使用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));
}

【讨论】:

  • 这与pointed out by @Jarod32 在对另一个答案的评论中具有相同的限制:库中不应有bar 的其他重载,并且未来的bar(long long) 重载必须替换@ 987654328@一。
  • 不错。使用 Boost 可以优雅地实现这一点。 +1。
【解决方案4】:

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

在我看来,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 来区分这两者。

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-14
    • 2017-01-03
    • 1970-01-01
    • 2012-09-09
    相关资源
    最近更新 更多