【问题标题】:Making `std::get` play nice with SFINAE使用 SFINAE 让 `std::get` 玩得更好
【发布时间】:2017-06-02 04:28:32
【问题描述】:

std::get 似乎对 SFINAE 不友好,如以下测试用例所示:

template <class T, class C>
auto foo(C &c) -> decltype(std::get<T>(c)) {
    return std::get<T>(c);
}

template <class>
void foo(...) { }

int main() {
    std::tuple<int> tuple{42};

    foo<int>(tuple);    // Works fine
    foo<double>(tuple); // Crashes and burns
}

See it live on Coliru

目标是将第二次调用foo 转向第二次过载。在实践中,libstdc++ 给出:

/usr/local/bin/../lib/gcc/x86_64-pc-linux-gnu/6.3.0/../../../../include/c++/6.3.0/tuple:1290:14: fatal error: no matching function for call to '__get_helper2'
    { return std::__get_helper2<_Tp>(__t); }
             ^~~~~~~~~~~~~~~~~~~~~~~

libc++更直接,直接static_assert爆:

/usr/include/c++/v1/tuple:801:5: fatal error: static_assert failed "type not found in type list"
    static_assert ( value != -1, "type not found in type list" );
    ^               ~~~~~~~~~~~

我真的不想实现洋葱层检查 C 是否是 std::tuple 特化,并在其参数中寻找 T...

std::get 是否有理由对 SFINAE 不友好?有没有比上面列出的更好的解决方法?

我找到了something about std::tuple_element,但没有找到std::get

【问题讨论】:

  • “SFINAE”的“S”部分代表“替代”。在这里,将模板参数替换为第一个foo 没有问题。 TdoubleC 就是那个元组类型。替换成功!
  • @SamVarshavchik 但返回类型涉及形成无效的函数调用。如果std::get 对 SFINAE 友好(即,让自己脱离无效调用的重载集),这将触发替换失败。因此我的问题。
  • @SamVarshavchik see this snippet 为例。
  • std::get 的模板参数是一个类时,它的返回类型是显式的,而不是像元组索引常量那样是auto。所以,decltype() 很高兴。 template&lt; class T, class... Types &gt; constexpr T&amp; get(tuple&lt;Types...&gt;&amp; t); -- 游戏结束。
  • @SamVarshavchik 你在干什么。

标签: c++ c++14 language-lawyer sfinae stdtuple


【解决方案1】:

std::get&lt;T&gt; 显然不是 SFINAE 友好的,根据 [tuple.elem]:

template <class T, class... Types>
  constexpr T& get(tuple<Types...>& t) noexcept;
// and the other like overloads

要求: T 类型在 Types... 中只出现一次。 否则,程序格式不正确。

std::get&lt;I&gt; 也明显不是 SFINAE 友好的。


至于其他问题:

std::get 是否有理由对 SFINAE 不友好?

不知道。通常,这不是需要 SFINAE 编辑的点。所以我想这不被认为是需要做的事情。硬错误比滚动浏览一堆不可行的候选选项更容易理解。如果您认为 std::get&lt;T&gt; 有充分的理由对 SFINAE 友好,您可以提交有关它的 LWG 问题。

有没有比上面列出的更好的解决方法?

当然。您可以编写自己的 SFINAE 友好版本 get(请注意,它使用 C++17 fold expression):

template <class T, class... Types,
    std::enable_if_t<(std::is_same<T, Types>::value + ...) == 1, int> = 0>
constexpr T& my_get(tuple<Types...>& t) noexcept {
    return std::get<T>(t);
}

然后按照您的意愿进行操作。

【讨论】:

  • 冒昧地编辑了答案以提及此 C++17 代码。
  • @SergeyA 谢谢!
【解决方案2】:

不要在std::get 上进行 SFINAE;这是不允许的。

这里有两种比较友好的方式来测试你是否可以using std::get; get&lt;X&gt;(t)

template<class T,std::size_t I>
using can_get=std::integral_constant<bool, I<std::tuple_size<T>::value>;

namespace helper{
  template<class T, class Tuple>
  struct can_get_type:std::false_type{};
  template<class T, class...Ts>
  struct can_get_type<T,std::tuple<Ts...>>:
    std::integral_constant<bool, (std::is_same_v<T,Ts>+...)==1>
  {};
}
template<class T,class Tuple>
using can_get=typename helpers::can_get_type<T,Tuple>::type;

然后你的代码如下:

template <class T, class C, std::enable_if_t<can_get_type<C,T>{},int> =0>
decltype(auto) foo(C &c) {
  return std::get<T>(c);
}

【讨论】:

    【解决方案3】:

    来自 N4527(我认为它仍在标准中):

    第 20.4.2.6 (8) 节:

    要求:类型 T 在 Types.... 中只出现一次。否则,程序是非良构的。

    根据标准,上面的程序格式不正确。

    讨论结束。

    【讨论】:

      【解决方案4】:

      STL 中几乎没有任何函数对 SFINAE 友好,这是默认设置。

      也许它纯粹是历史性的。 (如“C++ 的所有默认值都错误”)。

      但也许事后证明 SFINAE 友好性是有代价的(例如编译时间)。 我没有证据,但我认为可以公平地说 SF 代码需要更长的时间来编译,因为它必须在拒绝替代方案时“继续尝试”,而不是在第一个错误时摆脱困境。 正如@Barry 所说,这也有精神成本,因为 SFINAE 比硬错误更难推理。 (至少在“概念”清晰之前。)

      如果用户想要 SFINAE,它可以在非 SFINAE 友好的基础上(通过大量努力)在特征的帮助下构建。

      例如,一个人总是可以写(@Barry 写了相当于std::get

      template<class In, class Out, class=std::enable_if_t<std::is_assignable<std::iterator_traits<Out>::reference, std::iterator_traits<In>::reference> >
      Out friendly_copy(In first, In last, Out d_last){
         return std::copy(first, last, d_first);
      }
      

      老实说,我发现自己以这种方式包装了许多 STL 函数,但要做到这一点需要做很多工作。 所以,我想有一个适合 SFINAE 的 STL 版本的地方。 从某种意义上说,如果将requires 添加到当前函数的签名中,就会出现这种情况。 我不知道这是否完全是计划,但这可能是在语言中引入概念的副作用。 我希望如此。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-03-18
        • 1970-01-01
        • 1970-01-01
        • 2011-09-14
        相关资源
        最近更新 更多