Boost:: enable_if не в сигнатуре функции
Это всего лишь вопрос о стиле: мне не нравится способ С++ для метапрограммирования шаблонов, который требует использования типа возврата или добавления дополнительного фиктивного аргумента для трюков с помощью SFINAE. Итак, идея, которую я придумал, заключается в том, чтобы поместить элемент SFINAE в определение аргументов шаблона, например:
#include <iostream>
#include <boost/type_traits/is_array.hpp>
#include <boost/utility/enable_if.hpp>
using namespace std;
template <typename T, typename B=typename boost::enable_if< boost::is_array<T> >::type > void asd(){
cout<<"This is for arrays"<<endl;
}
template <typename T, typename B=typename boost::disable_if< boost::is_array<T> >::type > void asd(){
cout<<"This is for NON arrays"<<endl;
}
int main() {
asd<int>();
asd<int[]>();
}
В этом примере g++ жалуется:
../src/afg.cpp: 10: 97: ошибка: переопределение "template void asd()
SFINAE там работает, потому что, если я удалю, например, с disable_if
, ошибка компилятора:
../src/afg.cpp: 15: 12: ошибка: нет соответствующей функции для вызова 'asd()
Это то, что я хочу.
Итак, есть ли способ выполнить SFINAE не в "нормальной" сигнатуре функции, которая является типом возвращаемого типа + аргументом?
EDIT:
Это, в конце концов, то, что я собираюсь попробовать в реальном коде:
#include <iostream>
#include <type_traits>
using namespace std;
template <typename T, typename enable_if< is_array<T>::value, int >::type =0 > void asd(){
cout<<"This is for arrays"<<endl;
}
template <typename T, typename enable_if< !is_array<T>::value, int >::type =0 > void asd(){
cout<<"This is for NON arrays"<<endl;
}
int main() {
asd<int[]>();
asd<int>();
}
Я использую С++ 0x вместо boost, потому что, если мне нужно С++ 0x для использования аргументов шаблонов по умолчанию, я не вижу причин использовать boost, который является его предшественником.
Ответы
Ответ 1
Аргументы шаблона по умолчанию не являются частью сигнатуры шаблонов функций. Но тип параметров шаблона. Таким образом, вы можете сделать следующее и иметь возможность перегрузить его.
template <
typename T,
typename boost::enable_if<
boost::is_array<T>, int
>::type = 0
>
void asd() {
cout<<"This is for arrays"<<endl;
}
template <
typename T,
typename boost::disable_if<
boost::is_array<T>, int
>::type = 0
>
void asd() {
cout<<"This is for arrays"<<endl;
}
Ответ 2
Так как С++ 11 сделал это возможным, я всегда использую enable_if
(или наоборот disable_if
) внутри аргументов шаблона, как вы это делаете. Если/при наличии нескольких перегрузок, я использую фиктивные аргументы шаблона по умолчанию, которые делают списки параметров шаблона различающимися по своей сути. Поэтому для повторного использования вашего примера:
template<
typename T
, typename B = typename boost::enable_if<
boost::is_array<T>
>::type
>
void asd() {
cout << "This is for arrays" << endl;
}
template<
typename T
, typename B = typename boost::disable_if<
boost::is_array<T>
>::type
, typename = void
>
void asd() {
cout << "This is for arrays" << endl;
}
Еще одна альтернатива не испортить возвращаемый тип (который недоступен в некоторых случаях, например, операторы преобразования), существовавший с тех пор, как С++ 03 должен использовать аргументы по умолчанию:
template<typename T>
void
foo(T t, typename std::enable_if<some_trait<T>::value>::type* = nullptr);
Я не использую эту форму, поскольку мне не нравится "messing" с типами аргументов так же, как и с типом возвращаемого значения, и по причинам согласованности (поскольку это не выполняется во всех случаях).
Ответ 3
Ну, я обычно использую эти макросы, чтобы сделать конструкцию enable_if намного чище (они даже работают в большинстве компиляторов С++ 03):
#define ERROR_PARENTHESIS_MUST_BE_PLACED_AROUND_THE_RETURN_TYPE(...) __VA_ARGS__>::type
#define FUNCTION_REQUIRES(...) typename boost::enable_if<boost::mpl::and_<__VA_ARGS__, boost::mpl::bool_<true> >, ERROR_PARENTHESIS_MUST_BE_PLACED_AROUND_THE_RETURN_TYPE
#define EXCLUDE(...) typename boost::mpl::not_<typename boost::mpl::or_<__VA_ARGS__, boost::mpl::bool_<false> >::type >::type
Затем вы определяете свою функцию следующим образом:
template <typename T >
FUNCTION_REQUIRES(is_array<T>)
(void) asd(){
cout<<"This is for arrays"<<endl;
}
template <typename T >
FUNCTION_REQUIRES(EXCLUDE(is_array<T>))
(void) asd(){
cout<<"This is for NON arrays"<<endl;
}
Единственное, вам нужно поставить круглые скобки вокруг возвращаемого типа. Если вы забудете их, компилятор скажет что-то вроде "ERROR_PARENTHESIS_MUST_BE_PLACED_AROUND_THE_RETURN_TYPE" undefined.
Ответ 4
Это не совсем то, о чем вы просите, но как насчет старой старой специализации?
template<typename T>
struct asd
{
static void fgh()
{
std::cout << "not an array\n";
}
};
template<typename T>
struct asd<T[]>
{
static void fgh()
{
std::cout << "an array of unknown size\n";
}
};
template<typename T, size_t N>
struct asd<T[N]>
{
static void fgh()
{
std::cout << "an array of known size\n";
}
};
int main()
{
asd<int>::fgh();
asd<int[]>::fgh();
asd<int[42]>::fgh();
}
Ответ 5
Итак, есть ли способ выполнить SFINAE не в "нормальной" сигнатуре функции, которая является типом возвращаемого типа + аргументом?
Ну, есть способ получить тот же результат без использования SFINAE вообще — перегрузка:
#include <iostream>
#include <type_traits>
void asd_impl(std::true_type&&)
{
std::cout << "This is for arrays\n";
}
void asd_impl(std::false_type&&)
{
std::cout << "This is not for arrays\n";
}
template<typename T>
void asd()
{
asd_impl(std::is_array<T>());
}
int main()
{
asd<int>();
asd<int[]>();
}
Этот стиль является более читаемым IMO и широко используется в библиотеках с тяжелыми шаблонами, таких как Boost. Spirit, потому что он быстрее компилируется и лучше работает с компиляторами, имеющими поддержку менее чем для stellar template/SFINAE (например, VС++ и Sun Studio).
онлайн-демонстрация