Подтвердить что ты не робот

Как использовать sfinae для выбора конструкторов?

В метапрограмме шаблона можно использовать SFINAE для типа возврата, чтобы выбрать определенную функцию-член-шаблон, т.е.

template<int N> struct A {
  int sum() const noexcept
  { return _sum<N-1>(); }
private:
  int _data[N];
  template<int I> typename std::enable_if< I,int>::type _sum() const noexcept
  { return _sum<I-1>() + _data[I]; }
  template<int I> typename std::enable_if<!I,int>::type _sum() const noexcept
  { return _data[I]; }
};

Однако это не работает над конструкторами. Предположим, я хочу объявить конструктор

template<int N> struct A {
   /* ... */
   template<int otherN>
   explicit(A<otherN> const&); // only sensible if otherN >= N
};

но запретите его для otherN < N.

Итак, можно использовать SFINAE здесь? Меня интересуют только решения, которые позволяют автоматическое вычитание параметров шаблона, так что

A<4> a4{};
A<5> a5{};
A<6> a6{a4};  // doesn't compile
A<3> a3{a5};  // compiles and automatically finds the correct constructor

Примечание. Это очень упрощенный пример, в котором SFINAE может быть избыточным и static_assert может быть достаточным. Тем не менее, я хочу знать, может ли я использовать использовать SFINAE.

4b9b3361

Ответ 1

В шаблон можно добавить аргумент типа по умолчанию:

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

Ответ 2

В С++ 11 вы можете использовать параметр шаблона по умолчанию:

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

Однако, если ваш компилятор еще не поддерживает параметры шаблона по умолчанию, или вам нужно несколько перегрузок, вы можете использовать параметр функции по умолчанию, например:

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

Ответ 3

Есть много способов вызвать SFINAE, будучи enable_if только одним из них. Прежде всего:

Wats это std :: enable_if?

Это просто так:

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;

Идея состоит в том, чтобы сделать typename enable_if<false>::type ошибкой, следовательно, любое объявление шаблона, содержащее его, пропускается.

Так как же этот выбор функции триггера?

Отключение функций

Идея сделать объявление ошибочным в какой-то части:

По типу возврата

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

По фактическому параметру

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

По параметру шаблона

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

Выбор функций

Вы можете параметризовать различные альтернативы с помощью таких хитростей:

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"

Это вызовет первую/вторую/третью/четвертую функцию, если condition3 выполнено, чем condition2 чем condition1 чем ни одна из них.

Другие триггеры SFINAE

Написание условий времени компиляции может быть либо вопросом явной специализации, либо вопросом оценки/неудачи выражения:

например:

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

так что is_vector<int>::value равно false но is_vecttor<vector<int> >::value равно true

Или с помощью самоанализа, как

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

так что is_container<X>::value будет true если задано X x, вы можете скомпилировать std::begin(x) и т.д.

Хитрость заключается в том, что decltype(...) на самом деле void (, оператор отбрасывает предыдущие выражения) только тогда, когда все подвыражения могут быть скомпилированы.


Там может быть даже много других альтернатив. Надеюсь, между всем этим вы можете найти что-то полезное.

Ответ 4

Принятый ответ хорош для большинства случаев, но терпит неудачу, если присутствуют две такие перегрузки конструктора с различными условиями. Я ищу решение и в этом случае.

Да: принятое решение работает, но не для двух альтернативных конструкторов, как, например,

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

потому что, как указано на этой странице

Распространенной ошибкой является объявление двух шаблонов функций, которые отличаются только аргументами шаблона по умолчанию. Это недопустимо, поскольку аргументы шаблона по умолчанию не являются частью сигнатуры шаблона функции, а объявление двух разных шаблонов функций с одной и той же сигнатурой недопустимо.

Как предложено на той же странице, вы можете обойти эту проблему, применяя SFINAE к типу параметра шаблона значения (не типа) следующим образом

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