Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

boost::enable_if not in function signature

Tags:

c++

c++11

sfinae

This is just a question about style: I don't like the way of C++ for template metaprogramming that requires you to use the return type or add an extra dummy argument for the tricks with SFINAE. So, the idea I came up with is to put the SFINAE thing in the template arguments definition itself, like this:

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

This example make g++ complain:

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

SFINAE there itself works, because if I delete for example the one with disable_if, the compiler error is:

../src/afg.cpp:15:12: error: no matching function for call to ‘asd()’

Which is what I want.

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

EDIT: This is in the end what I'm going to try in the real code:

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

I use c++0x stuff instead of boost because as long as I need c++0x for using defaults of template arguments, I see no reason to use boost, which is its precursor.

like image 225
Lorenzo Pistone Avatar asked Jan 05 '12 13:01

Lorenzo Pistone


2 Answers

Well, I generally use these macros to make enable_if constructs a lot cleaner(they even work in most C++03 compilers):

#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

Then you would define your function like this:

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

The only thing is, you need to put parenthesis around the return type. If you forget them, the compiler will say something like 'ERROR_PARENTHESIS_MUST_BE_PLACED_AROUND_THE_RETURN_TYPE' is undefined.

like image 54
Paul Fultz II Avatar answered Sep 29 '22 20:09

Paul Fultz II


Since C++11 made it possible, I only ever use enable_if (or conversely disable_if) inside the template arguments, the way you're doing. If/when there are several overloads, then I use dummy, defaulted template arguments which makes the template parameter lists differ in arity. So to reuse your example that would be:

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

Another alternative to not messing the return type (that is not available in some cases, e.g. conversion operators) that has existed since C++03 is to use default arguments:

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

I don't use this form as I dislike 'messing' with the argument types just as much as with the return type, and for consistency reasons (since that's not doable in all cases).

like image 28
Luc Danton Avatar answered Sep 29 '22 18:09

Luc Danton