2012-01-05 2 views
7

Это всего лишь вопрос о стиле: мне не нравится способ C++ для метапрограммирования шаблонов, который требует использования возвращаемого типа или добавления дополнительного фиктивного аргумента для трюков с помощью SFINAE , Таким образом, идея, которую я придумал, чтобы поставить SFINAE вещь в самом определении аргументов шаблона, например:boost :: enable_if not in function signature

#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[]>(); 
} 

В этом примере сделать г ++ жалуются:

../src/afg.cpp:10:97: error: redefinition of ‘template void asd()’

SFINAE там сам работает, потому что если удалить, например, один с disable_if, ошибка компилятора:

../src/afg.cpp:15:12: error: no matching function for call to ‘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>(); 
} 

Я использую C++ 0x материал вместо повышения, потому что до тех пор, как мне нужно, C++ 0x для использования аргументов шаблонов по умолчанию, я не вижу причин использовать boost, который является его предшественником.

ответ

6

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

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; 
} 
+0

wow, я не знал, что даже тип шаблона может быть динамическим! Не принимаем ваш ответ еще потому что сначала я должен проверить, что этот трюк может применяться к моему коду, но я уверен, что так оно и будет. –

8

Поскольку C++ 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; 
} 

Другой альтернатива не баловаться типа возвращаемого (который не доступен в некоторых случаях, например, операторы преобразования), которая существует с C++ 03 является использование аргументов по умолчанию :

template<typename T> 
void 
foo(T t, typename std::enable_if<some_trait<T>::value>::type* = nullptr); 

Я не использую эту форму, как мне не нравится «баловаться» с типами аргументов точно так же, как и с возвращаемым типом, и по соображениям совместимости (так что это не выполнимо во всех случаях).

2

Возможно, это не совсем то, о чем вы просите, но как насчет старой старой специализации?

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(); 
} 
+0

хорошо, но он более длинный, чем другой ответ. –

+3

По крайней мере, он печатает разные вещи для массивов и не-массивов ;-) – fredoverflow

2

So, is there a way to accomplish SFINAE not in the "normal" signature of a function, that is return type + argument list?

Ну, есть способ, чтобы получить тот же результат без использования 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[]>(); 
} 

Этот стиль гораздо более удобными для чтения ИМО, и широко используется в template- тяжелые библиотеки, такие как Boost. Spirit, потому что он быстрее компилируется и лучше работает с компиляторами, имеющими поддержку меньшего размера, чем Stellar/SFINAE (например, VC++ и Sun Studio).

Online demo.

+0

+1, для простого истинного/ложного вопроса, не нужно использовать SFINAE. Он будет только волосатым для более чем одного признака, поскольку вы не можете просто комбинировать их с логическими операторами. (Это заставляет меня задаться вопросом, можно ли перегрузить 'operator ||' и 'operator &&' для 'true_type' и' false_type' ... hm.) – Xeo

+0

@Xeo: 'std :: integral_constant 'должен иметь правильную семантику. – ildjarn

+0

Да, но это выглядит немного грязно, поэтому я задавался вопросом о перегрузке оператора. :) В основном это будет выглядеть лучше. – Xeo

8

Ну, я обычно использую эти макросы, чтобы сделать enable_if конструкции намного ровнее (они даже работают в большинстве C++ 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» не определено.