17

std::get does not seem to be SFINAE-friendly, as shown by the following test case:

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

The goal is to divert the second call to foo towards the second overload. In practice, libstdc++ gives:

/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++ is more direct, with a straight static_assert detonation:

/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" );
    ^               ~~~~~~~~~~~

I would really like not to implement onion layers checking whether C is an std::tuple specialization, and looking for T inside its parameters...

Is there a reason for std::get not to be SFINAE-friendly? Is there a better workaround than what is outlined above?

I've found something about std::tuple_element, but not std::get.

Community
  • 1
  • 1
Quentin
  • 62,093
  • 7
  • 131
  • 191
  • The "S" part of "SFINAE" stands for "substitution". Here, there were no problems substituting the template parameters into the first `foo`. `T` is `double`. `C` is that tuple type. Substitution succeeded! – Sam Varshavchik Jan 17 '17 at 23:01
  • 3
    @SamVarshavchik but the return type involves forming an invalid function call. Which would trigger a substitution failure, if `std::get` was SFINAE-friendly (i.e. let itself out of the overload set for an invalid call). Hence my question. – Quentin Jan 17 '17 at 23:03
  • @SamVarshavchik [see this snippet](http://coliru.stacked-crooked.com/a/f22c57a5c4f2fb37) for an example. – Quentin Jan 17 '17 at 23:07
  • `extern void foo(); template void bar() { }` -- this will compile just fine, despite the compiler not having the foggiest what's inside `foo()`. Substitution does ***not*** involve actually generating the code. – Sam Varshavchik Jan 17 '17 at 23:10
  • @SamVarshavchik the function's content is irrelevant indeed, but the trailing `-> decltype(std::get(c))` is not. – Quentin Jan 17 '17 at 23:14
  • 1
    When `std::get`'s template parameter being a class, its return type is explicit, rather than being `auto`, as would be the case for a tuple index constant. So, `decltype()` is happy. `template< class T, class... Types > constexpr T& get(tuple& t);` -- game over. – Sam Varshavchik Jan 17 '17 at 23:17
  • @SamVarshavchik I know that -- that's exactly my issue. `std::get` could very well be fitted with, for example, `std::enable_if` so it doesn't accept invalid arguments, instead of blowing up after substitution. That's the whole point of SFINAE. – Quentin Jan 17 '17 at 23:22
  • 7
    @SamVarshavchik What are you going on about. – Barry Jan 17 '17 at 23:39

4 Answers4

18

std::get<T> is explicitly not SFINAE-friendly, as per [tuple.elem]:

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

Requires: The type T occurs exactly once in Types.... Otherwise, the program is ill-formed.

std::get<I> is also explicitly not SFINAE-friendly.


As far as the other questions:

Is there a reason for std::get not to be SFINAE-friendly?

Don't know. Typically, this isn't a point that needs to be SFINAE-ed on. So I guess it wasn't considered something that needed to be done. Hard errors are a lot easier to understand than scrolling through a bunch of non-viable candidate options. If you believe there to be compelling reason for std::get<T> to be SFINAE-friendly, you could submit an LWG issue about it.

Is there a better workaround than what is outlined above?

Sure. You could write your own SFINAE-friendly verison of get (please note, it uses 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);
}

And then do with that as you wish.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
Barry
  • 286,269
  • 29
  • 621
  • 977
6

Don't SFINAE on std::get; that is not permitted.

Here are two relatively sfinae friendly ways to test if you can 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;

Then your code reads:

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);
}
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
4

From N4527 (I presume it's still in the standard):

§ 20.4.2.6 (8):

Requires: The type T occurs exactly once in Types.... Otherwise, the program is ill-formed.

The program above is ill-formed, according to the standard.

End of discussion.

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
0

Almost no function in STL is SFINAE-friendly, this is the default.

Maybe it is purely historical. (as in "C++ has all the defaults wrong").

But perhaps a post-facto justification could be that SFINAE-friendlyness has a cost (e.g. compile time). I don't have proof, but I think it is fair to say that SF-code takes longer to compile because it has to "keep trying" when rejecting alternatives instead of bailing out on the first error. As @Barry said it has also a mental cost because SFINAE is harder to reason about than hard errors. (At least before one has the "concepts" clear.)

If the user wants SFINAE it can be built (with a lot of effort) on top of non-SFINAE friendly with help of traits.

For example, one can always write (@Barry wrote the equivalent for 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);
}

Honestly, I find myself wrapping many STL functions this way, but it is a lot of work to get it right. So, I guess that there is a place for a SFINAE-friendly version of STL. In some sense this is comming if requires are added to the signatures of the current functions. I don't know if this is the plan exactly but it might be a side effect of introducing concepts to the language. I hope so.

alfC
  • 14,261
  • 4
  • 67
  • 118