boost::enable_if not in function signature
Asked Answered
K

5

7

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.

Keri answered 5/1, 2012 at 13:17 Comment(0)
C
6

Default template arguments are not part of the signature of function templates. But the type of template parameters is. So you can do the following and be able to overload it

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;
}
Cruiserweight answered 5/1, 2012 at 20:0 Comment(1)
wow, I didn't know that even the type of the template could be dynamic! Not accepting your our answer yet because I have to check first that this trick can apply to my code, but I'm pretty sure it will.Keri
G
9

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.

Glyceryl answered 9/2, 2012 at 23:27 Comment(0)
H
8

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).

Hob answered 5/1, 2012 at 13:37 Comment(0)
C
6

Default template arguments are not part of the signature of function templates. But the type of template parameters is. So you can do the following and be able to overload it

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;
}
Cruiserweight answered 5/1, 2012 at 20:0 Comment(1)
wow, I didn't know that even the type of the template could be dynamic! Not accepting your our answer yet because I have to check first that this trick can apply to my code, but I'm pretty sure it will.Keri
G
2

This may not exactly be what you're asking for, but how about good old template specialization?

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();
}
Gorey answered 5/1, 2012 at 13:40 Comment(1)
At least it prints different things for arrays and non-arrays ;-)Gorey
R
2

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

Well, there's a way to obtain the same result without using SFINAE at all — overloading:

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

This style is far more readable IMO, and is used extensively in template-heavy libraries such as Boost.Spirit because it tends to compile faster and works better with compilers having less-than-stellar template/SFINAE support (e.g. VC++ and Sun Studio).

Online demo.

Retribution answered 5/1, 2012 at 19:12 Comment(5)
+1, for simple true/false question, one doesn't need to employ SFINAE. It'll only get hairy for more than one trait, since you can't simply combine them with logical operators. (Which makes me wonder if one could overload operator|| and operator&& for true_type and false_type... hm.)Bode
@Bode : std::integral_constant<bool, cond1::value || cond2::value> should have the proper semantics.Retribution
Yeah, but that looks kinda messy, that's why I wondered about operator overloading. :) It would basically do that just look nicer.Bode
@Bode : Yeah, agreed. I suppose it should be technically possible to do partial specialization of std::integral_constant<bool, V> to add logical operators, but I'm not sure whether that's legal, and I'm not sure what effect it might have on logical short-circuiting behavior.Retribution
Overloaded logical operators don't short-curcuit. Also, I just tested and the operators are not found. :( Also, partial specialization in the std namespace is not allowed IIRC, as such this is also no good. :|Bode

© 2022 - 2024 — McMap. All rights reserved.