Making `std::get` play nice with SFINAE

后端 未结 4 634
南笙
南笙 2021-02-05 10:20

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

template 
auto foo(C &c) -> declty         


        
相关标签:
4条回答
  • 2021-02-05 10:59

    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.

    0 讨论(0)
  • 2021-02-05 11:08

    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);
    }
    
    0 讨论(0)
  • 2021-02-05 11:14

    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.

    0 讨论(0)
  • 2021-02-05 11:17

    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.

    0 讨论(0)
提交回复
热议问题