Reputation: 63154
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
}
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_size
, but not std::get
.
Upvotes: 17
Views: 1279
Reputation: 16300
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.
Upvotes: 0
Reputation: 303576
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 inTypes...
. 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.
Upvotes: 18
Reputation: 275820
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);
}
Upvotes: 6
Reputation: 69912
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.
Upvotes: 4