How to use sfinae for selecting constructors?

后端 未结 5 1830
北海茫月
北海茫月 2020-11-29 04:24

In template meta programming, one can use SFINAE on the return type to choose a certain template member function, i.e.

template struct          


        
相关标签:
5条回答
  • 2020-11-29 05:10

    There are many ways to trigger SFINAE, being enable_if just one of them. First of all:

    Wats is std::enable_if ?

    It's just this:

    template<bool, class T=void> enable_if{ typedef T type; };
    template<class T> enable_if<false,T> {};
    template<bool b, class T=void> using enable_if_t = typename enable_f<b,T>::type;
    

    The idea is to make typename enable_if<false>::type to be an error, hence make any template declaration containing it skipped.

    So how can this trigger function selection?

    Disabling functions

    The idea is making the declaration erroneous in some part:

    By return type

    template<class Type>
    std::enable_if_t<cond<Type>::value,Return_type> function(Type);
    

    By a actual parameter

    template<class Type>
    return_type function(Type param, std::enable_if_t<cond<Type>::value,int> =0) 
    

    By a template parameter

    template<class Type, 
        std::enable_if_t<cond<Type>::value,int> =0> //note the space between > and =
    return_type function(Type param) 
    

    Selecting functions

    You can parametrise different alternatives with tricks like this:

    tempplate<int N> struct ord: ord<N-1>{};
    struct ord<0> {};
    
    template<class T, std::enable_if<condition3, int> =0>
    retval func(ord<3>, T param) { ... }
    
    template<class T, std::enable_if<condition2, int> =0>
    retval func(ord<2>, T param) { ... }
    
    template<class T, std::enable_if<condition1, int> =0>
    retval func(ord<1>, T param) { ... }
    
    template<class T> // default one
    retval func(ord<0>, T param) { ... }
    
    // THIS WILL BE THE FUCNTION YOU'LL CALL
    template<class T>
    retval func(T param) { return func(ord<9>{},param); } //any "more than 3 value"
    

    This will call the first/second/third/fourth function if condition3 is satisfied, than condition2 than condition1 than none of them.

    Other SFINAE triggers

    Writing compile-time conditions can be either a matter of explicit specialization or a matter of unevaluated expression success/failure:

    for example:

    template<class T, class = void>
    struct is_vector: std::false_type {};
    template<class X>
    struct is_vector<vector<X> >:: std::true_type {};
    

    so that is_vector<int>::value is false but is_vecttor<vector<int> >::value is true

    Or, by means of introspection, like

    template<class T>
    struct is_container<class T, class = void>: std::false_type {};
    
    template<class T>
    struct is_container<T, decltype(
      std::begin(std::declval<T>()),
      std::end(std::declval<T>()),
      std::size(std::declval<T>()),
      void(0))>: std::true_type {};
    

    so that is_container<X>::value will be true if given X x, you can compile std::begin(x) etc.

    The trick is that the decltype(...) is actually void (the , operator discards the previous expressions) only if all the sub-expressions are compilable.


    There can be even many other alternatives. Hope between all this you can find something useful.

    0 讨论(0)
  • 2020-11-29 05:12

    The accepted answer is good for most cases, but fails if two such constructor overloads with different conditions are present. I'm looking for a solution in that case too.

    Yes: the accepted solution works but not for two alternative constructor as, by example,

    template <int otherN, typename = typename std::enable_if<otherN == 1>::type>
    explicit A(A<otherN> const &);
    
    template <int otherN, typename = typename std::enable_if<otherN != 1>::type>
    explicit A(A<otherN> const &);
    

    because, as stated in this page,

    A common mistake is to declare two function templates that differ only in their default template arguments. This is illegal because default template arguments are not part of function template's signature, and declaring two different function templates with the same signature is illegal.

    As proposed in the same page, you can go around this problem applying SFINAE, modifying the signature, to the type of a value (not type) template parameter as follows

    template <int otherN, typename std::enable_if<otherN == 1, bool>::type = true>
    explicit A(A<otherN> const &);
    
    template <int otherN, typename std::enable_if<otherN != 1, bool>::type = true>
    explicit A(A<otherN> const &);
    
    0 讨论(0)
  • 2020-11-29 05:13

    With C++20 you can use the requires keyword

    With C++20 you can get rid of SFINAE.

    The requires keyword is a simple substitute for enable_if!

    Note that the case where otherN == N is a special case, as it falls to the default copy ctor, so if you want to take care of that you have to implement it separately:

    template<int N> struct A {
       A() {}    
    
       // handle the case of otherN == N with copy ctor
       explicit A(A<N> const& other) { /* ... */ }
    
       // handle the case of otherN > N, see the requires below
       template<int otherN> requires (otherN > N)
       explicit A(A<otherN> const& other) { /* ... */ }
    
       // handle the case of otherN < N, can add requires or not
       template<int otherN>
       explicit A(A<otherN> const& other) { /* ... */ }
    };
    

    The requires clause gets a constant expression that evaluates to true or false deciding thus whether to consider this method in the overload resolution, if the requires clause is true the method is preferred over another one that has no requires clause, as it is more specialized.

    Code: https://godbolt.org/z/RD6pcE

    0 讨论(0)
  • 2020-11-29 05:16

    You can add a defaulted type argument to the template:

    template <int otherN, typename = typename std::enable_if<otherN >= N>::type>
    explicit A(A<otherN> const &);
    
    0 讨论(0)
  • 2020-11-29 05:22

    In C++11, you can use a defaulted template parameter:

    template <int otherN, class = typename std::enable_if<otherN >= N>::type>
    explicit A(A<otherN> const &);
    

    However, if your compiler doesn't support defaulted template parameters yet, or you need multiple overloads, then you can use a defaulted function parameter like this:

    template <int otherN>
    explicit A(A<otherN> const &, typename std::enable_if<otherN >= N>::type* = 0);
    
    0 讨论(0)
提交回复
热议问题