Ответ 1
В шаблон можно добавить аргумент типа по умолчанию:
template <int otherN, typename = typename std::enable_if<otherN >= N>::type>
explicit A(A<otherN> const &);
В метапрограмме шаблона можно использовать 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.
В шаблон можно добавить аргумент типа по умолчанию:
template <int otherN, typename = typename std::enable_if<otherN >= N>::type>
explicit A(A<otherN> const &);
В С++ 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);
Есть много способов вызвать SFINAE, будучи 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
чем ни одна из них.
Написание условий времени компиляции может быть либо вопросом явной специализации, либо вопросом оценки/неудачи выражения:
например:
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
(,
оператор отбрасывает предыдущие выражения) только тогда, когда все подвыражения могут быть скомпилированы.
Там может быть даже много других альтернатив. Надеюсь, между всем этим вы можете найти что-то полезное.
Принятый ответ хорош для большинства случаев, но терпит неудачу, если присутствуют две такие перегрузки конструктора с различными условиями. Я ищу решение и в этом случае.
Да: принятое решение работает, но не для двух альтернативных конструкторов, как, например,
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 &);