Создание "std:: get" играть хорошо с SFINAE

std::get не кажется, что SFINAE-friendly, как показано в следующем тестовом примере:

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
}

увидеть его жить на 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 дружественных? Есть ли лучшее решение, чем то, что описано выше?

я нашел что-то про std::tuple_element, но не std::get.

3 ответов


std::get<T> явно не является SFINAE-дружественным, согласно [кортеж.elem]:

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

требуется: тип T происходит ровно один раз в Types.... в противном случае, программа плохо сформированные.

std::get<I> также явно не является SFINAE-дружественным.


что касается других вопросов:

по делу std::get не быть SFINAE дружественных?

не знаю. Как правило, это не тот момент, на который нужно обращать внимание. Так что я думаю, это не считалось чем-то, что нужно было делать. Жесткие ошибки намного проще понять, чем прокручивать кучу нежизнеспособных вариантов кандидатов. Если вы считаете, что для std::get<T> чтобы быть SFINAE-friendly, вы можете отправить вопрос LWG об этом.

есть ли лучшее решение, чем то, что изложено наверху?

конечно. Вы можете написать свою собственную SFINAE-дружественную версию get (обратите внимание, он использует C++17 фолд выражение):

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);
}

а потом делайте с этим, как хотите.


не SFINAE на 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);
}

из N4527 (я предполагаю, что он все еще в стандарте):

§ 20.4.2.6 (8):

Requires: тип T встречается ровно один раз в типах.... В противном случае, программа плохо сформированные.

программа выше плохо сформирована, в соответствии со стандартом.

конец дискуссии.