使`std :: get`与SFINAE一起玩得很好

时间:2017-01-17 22:53:55

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

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

4 个答案:

答案 0 :(得分:16)

根据[tuple.elem],

std::get<T>显然不是SFINAE友好的:

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

需要:类型TTypes...中只出现一次。 否则,该程序格式不正确。

std::get<I>也明确不是SFINAE友好的。

至于其他问题:

  

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

不知道。通常情况下,这不是一个需要SFINAE编辑的点。所以我想我们并没有考虑到需要做的事情。滚动查看一堆不可行的候选选项,比较容易理解硬错误。如果您认为std::get<T>有令人信服的理由对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);
}

然后按照你的意愿去做。

答案 1 :(得分:5)

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

如果可以using std::get; get<X>(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);
}

答案 2 :(得分:3)

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

§20.4.2.6(8):

  

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

根据标准,上述计划格式不正确。

讨论结束。

答案 3 :(得分:0)

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添加到当前函数的签名中,这将开始。 我不知道这到底是不是计划,但这可能是向语言引入概念的副作用。 我希望是这样。