Use of enable_if to match only classes which have a certain static data member, and that has only specific values
Asked Answered
O

2

2

I want to specialize a function for a subset of classes which: have a certain static data member variable, and such variable has only certain possible values.

The code below illustrates the intent, but it does not compile unless I comment out the lines related to the B classes in main. This is because code is not a member of the Bx classes, but the enable_if condition is valid if the template argument has a code member variables. How should I modify it?

Unfortunately I work with very old compilers, hence no support for C++11: I compile with the option -std=c++03.

Thanks

#include <iostream>
#include <boost/core/enable_if.hpp>
#include <boost/mpl/or.hpp>
#include <boost/mpl/not.hpp>
#include <boost/mpl/bool.hpp>
using std::cout;
using namespace boost;
using namespace boost::mpl;

template <int N> struct A1 { static const int code = N; };
template <int N> struct A2 { static const int code = N; };
// ... other classes with static data member 'code'
template <int N> struct AN { static const int code = N; };

struct B1{};
struct B2{};
// ... other classes potentially passd as argument to the foo function
struct BN{};

template <typename T>
struct Condition : or_<bool_<T::code == 1>, bool_<T::code == 2> > {};


template <typename T>
typename enable_if<not_<Condition<T> >, void>::type
   foo(const T& arg) { cout << "This class does not have a static member code or its value is not 1 or 2\n"; }

template <typename T>
typename enable_if<Condition<T>, void>::type
   foo(const T& arg) { cout << "This class has a static member code and its value is " << T::code << "\n"; }

int main()
{
    foo(A1<0>()); // this should match the 1st version of foo
    foo(A2<1>()); // this should match the 2nd version of foo
    foo(AN<2>()); // this should match the 2nd version of foo
    foo(B1());    // this should match the 1st version of foo
    foo(BN());    // this should match the 1st version of foo
}
Obryant answered 29/9, 2018 at 5:29 Comment(5)
Why doesn't your code work?Polonaise
Because code is not a member of the B classes and It is used in the instantiation of enable_if. I have updated the question.Obryant
Can you use this? boost.org/doc/libs/1_68_0/libs/tti/doc/html/…Polonaise
If you can't use C++11, I suppose you must write foo(B1()), not foo(B1{})Faux
@max66, thanks, I updated question.Obryant
F
1

The real problem is that you need a C++03 solution, so you can use SFINAE but not all language improvement that are available starting from C++11.

Anyway, I propose you a solution that is convoluted like yours (maybe more) but is completely boost free.

If you define a trivial bool wrapper (that can roughly substitute the C++11 std::true_type and std::false_type)

template <bool B>
struct bool_wrapper
 { static const bool value = B; };

you can define your condition as follows

template <typename, typename = bool_wrapper<true> >
struct cond : public bool_wrapper<false>
 { };

template <typename T>
struct cond<T, bool_wrapper<(1 == T::code) || (2 == T::code)> >
   : public bool_wrapper<true>
 { };

and if you define a enable_if type traits (the same as the C++11 std::enable_if)

template <bool, typename = void>
struct enable_if
 { };

template <typename T>
struct enable_if<true, T>
 { typedef T type; };

you can SFINAE enable/disable your foo() functions

template <typename T>
typename enable_if<false == cond<T>::value>::type foo (T const & arg)
 { std::cout << "no static member code or value not 1 and not 2\n"; }

template <typename T>
typename enable_if<true == cond<T>::value>::type foo (T const & arg)
 { std::cout << "static member code and its value is " << T::code << "\n"; }

The following is a full working C++98 example

#include <iostream>

template <int N> struct A1 { static const int code = N; };
template <int N> struct A2 { static const int code = N; };
// ... 
template <int N> struct AN { static const int code = N; };

struct B1{};
struct B2{};
// ...
struct BN{};

template <bool B>
struct bool_wrapper
 { static const bool value = B; };

template <typename, typename = bool_wrapper<true> >
struct cond : public bool_wrapper<false>
 { };

template <typename T>
struct cond<T, bool_wrapper<(1 == T::code) || (2 == T::code)> >
   : public bool_wrapper<true>
 { };

template <bool, typename = void>
struct enable_if
 { };

template <typename T>
struct enable_if<true, T>
 { typedef T type; };


template <typename T>
typename enable_if<false == cond<T>::value>::type foo (T const & arg)
 { std::cout << "no static member code or value not 1 and not 2\n"; }

template <typename T>
typename enable_if<true == cond<T>::value>::type foo (T const & arg)
 { std::cout << "static member code and its value is " << T::code << "\n"; }

int main ()
 {
   foo(A1<0>()); // match the 1st version of foo
   foo(A2<1>()); // match the 2nd version of foo
   foo(AN<2>()); // match the 2nd version of foo
   foo(B1());    // match the 1st version of foo
   foo(BN());    // match the 1st version of foo
 }

I don't know the boost classes your using but I suppose you can modify your code (to works almost as my no-boost solution) as follows

template <typename, typename = bool_<true> >
struct Condition : public bool_<false>
 { };

template <typename T>
struct Condition<T, bool_<(1 == T::code) || (2 == T::code)> >
   : public bool_<true>
 { };

-- EDIT --

The OP ask

I do not understand how it works for the case A1<0>. The specialization of Condition should be the preferred match, with the 2nd argument expanding to bool_. That class inherits from bool_, so, it should pick the wrong versions of foo. However it works. How is it possible?

Well... when you write foo(A1<0>()), the compiler must understand if cond<A1<0>>::value is true or false to enable the first version of foo() or the second one.

So the compiler must implement cond<A1<0>>. But there isn't a cond template class that receive only a typename. Anyway, the compiler find that

template <typename, typename = bool_wrapper<true> >
struct cond;

matches using the default value for the second template parameter.

There aren't alternative, so there isn't ambiguity, so cond< A<1> > become cond< A<1>, bool_wrapper<true> >

Now the compiler must choose between the main version of cond<typename, typename> (the one that inherit from bool_wrapper<false>) and the specialization (the one that inherit from bool_wrapper<true>).

cond< A<1>, bool_wrapper<true> > surely matches the main version, but matches also the specialization? If matches the also the specialization, the compiler must prefer the specialization.

So we need to see if cond< A<0>, bool_wrapper<true> > matches the specialization.

Using A<0> as T, we have that the specialization become

cond< A<0>, bool_wrapper<(1 == A<0>::code) || (2 == A<0>::code)> >

that is

cond< A<0>, bool_wrapper<(1 == 0) || (2 == 0)> >

that is

cond< A<0>, bool_wrapper<false || false> >

that is

cond< A<0>, bool_wrapper<false> >

and this doesn't matches cond< A<0>, bool_wrapper<true> >.

So cond< A<0> >, that is cond< A<0>, bool_wrapper<true> >, matches only the main version of cond<typename, typename>, so inherit from bool_wrapper<false>.

Now we can give a look at cond< A<1> >.

As for cond< A<0> >, the only template cond that matches cond< A<1> > is cond<typename, typename> with the second typename with the default value.

So cond< A<1> > is cond< A<1>, bool_wrapper<true> >.

But cond< A<1>, bool_wrapper<true> > matches only the main version of cond<typename, typename> or also the specialization?

We can see that using A<1> as T, we have that the specialization become

cond< A<1>, bool_wrapper<(1 == A<1>::code) || (2 == A<1>::code)> >

that is

cond< A<1>, bool_wrapper<(1 == 1) || (2 == 1)> >

that is

cond< A<1>, bool_wrapper<true || false> >

that is

cond< A<1>, bool_wrapper<true> >

and this matches cond< A<1>, bool_wrapper<true> >.

So, for cond< A<1> >, AKA cond< A<1>, bool_wrapper<true>, both versions of cond<typename, typename> match, so the compiler must choose the specialization, so cond< A<1> > inherit from bool_wrapper<true>.

Faux answered 29/9, 2018 at 12:4 Comment(3)
This is nice. It avoids splitting the condition in 2 parts, as the existence of the static data member is checked implicitly. I do not understand how it works for the case A1<0>. The specialization of Condition should be the preferred match, with the 2nd argument expanding to bool_<false>. That class inherits from bool_<true>, so, it should pick the wrong versions of foo. However it works. How is it possible?Obryant
@Obryant - Answer improved. Hope this helps.Faux
thanks for your answer. I mark this one as correct as it works and it is more elegant than mine. Regarding my further question, I did not articulate properly, as there is not much space in comment. As a a result, I do not think the further explanation addresses it. So I opened a new question here #52584333. You may consider removing extra details from this answer and reply to that one.Obryant
O
1

Based on J. Zwinck's suggestion, I got it to work.

Putting the two conditions in an and_template class does not work for the same reason as stated above. However, I can put the first condition (class has member variable code) in an enable_if, then the second condition (code has specific values) inside the class (see example below).

If is a bit convoluted though. If anybody can suggest a more elegant solution I will accept it.

#include <boost/tti/has_static_member_data.hpp>

BOOST_TTI_HAS_STATIC_MEMBER_DATA(code)

template <typename T, typename Enable = void>
struct Condition : false_type {};;

template <typename T>
struct Condition<T, typename enable_if<bool_<has_static_member_data_code<T,const int>::value> >::type>
{
    typedef or_<bool_<T::code == 1>, bool_<T::code == 2> > type;
    const static bool value = type::value;
};
Obryant answered 29/9, 2018 at 7:31 Comment(0)
F
1

The real problem is that you need a C++03 solution, so you can use SFINAE but not all language improvement that are available starting from C++11.

Anyway, I propose you a solution that is convoluted like yours (maybe more) but is completely boost free.

If you define a trivial bool wrapper (that can roughly substitute the C++11 std::true_type and std::false_type)

template <bool B>
struct bool_wrapper
 { static const bool value = B; };

you can define your condition as follows

template <typename, typename = bool_wrapper<true> >
struct cond : public bool_wrapper<false>
 { };

template <typename T>
struct cond<T, bool_wrapper<(1 == T::code) || (2 == T::code)> >
   : public bool_wrapper<true>
 { };

and if you define a enable_if type traits (the same as the C++11 std::enable_if)

template <bool, typename = void>
struct enable_if
 { };

template <typename T>
struct enable_if<true, T>
 { typedef T type; };

you can SFINAE enable/disable your foo() functions

template <typename T>
typename enable_if<false == cond<T>::value>::type foo (T const & arg)
 { std::cout << "no static member code or value not 1 and not 2\n"; }

template <typename T>
typename enable_if<true == cond<T>::value>::type foo (T const & arg)
 { std::cout << "static member code and its value is " << T::code << "\n"; }

The following is a full working C++98 example

#include <iostream>

template <int N> struct A1 { static const int code = N; };
template <int N> struct A2 { static const int code = N; };
// ... 
template <int N> struct AN { static const int code = N; };

struct B1{};
struct B2{};
// ...
struct BN{};

template <bool B>
struct bool_wrapper
 { static const bool value = B; };

template <typename, typename = bool_wrapper<true> >
struct cond : public bool_wrapper<false>
 { };

template <typename T>
struct cond<T, bool_wrapper<(1 == T::code) || (2 == T::code)> >
   : public bool_wrapper<true>
 { };

template <bool, typename = void>
struct enable_if
 { };

template <typename T>
struct enable_if<true, T>
 { typedef T type; };


template <typename T>
typename enable_if<false == cond<T>::value>::type foo (T const & arg)
 { std::cout << "no static member code or value not 1 and not 2\n"; }

template <typename T>
typename enable_if<true == cond<T>::value>::type foo (T const & arg)
 { std::cout << "static member code and its value is " << T::code << "\n"; }

int main ()
 {
   foo(A1<0>()); // match the 1st version of foo
   foo(A2<1>()); // match the 2nd version of foo
   foo(AN<2>()); // match the 2nd version of foo
   foo(B1());    // match the 1st version of foo
   foo(BN());    // match the 1st version of foo
 }

I don't know the boost classes your using but I suppose you can modify your code (to works almost as my no-boost solution) as follows

template <typename, typename = bool_<true> >
struct Condition : public bool_<false>
 { };

template <typename T>
struct Condition<T, bool_<(1 == T::code) || (2 == T::code)> >
   : public bool_<true>
 { };

-- EDIT --

The OP ask

I do not understand how it works for the case A1<0>. The specialization of Condition should be the preferred match, with the 2nd argument expanding to bool_. That class inherits from bool_, so, it should pick the wrong versions of foo. However it works. How is it possible?

Well... when you write foo(A1<0>()), the compiler must understand if cond<A1<0>>::value is true or false to enable the first version of foo() or the second one.

So the compiler must implement cond<A1<0>>. But there isn't a cond template class that receive only a typename. Anyway, the compiler find that

template <typename, typename = bool_wrapper<true> >
struct cond;

matches using the default value for the second template parameter.

There aren't alternative, so there isn't ambiguity, so cond< A<1> > become cond< A<1>, bool_wrapper<true> >

Now the compiler must choose between the main version of cond<typename, typename> (the one that inherit from bool_wrapper<false>) and the specialization (the one that inherit from bool_wrapper<true>).

cond< A<1>, bool_wrapper<true> > surely matches the main version, but matches also the specialization? If matches the also the specialization, the compiler must prefer the specialization.

So we need to see if cond< A<0>, bool_wrapper<true> > matches the specialization.

Using A<0> as T, we have that the specialization become

cond< A<0>, bool_wrapper<(1 == A<0>::code) || (2 == A<0>::code)> >

that is

cond< A<0>, bool_wrapper<(1 == 0) || (2 == 0)> >

that is

cond< A<0>, bool_wrapper<false || false> >

that is

cond< A<0>, bool_wrapper<false> >

and this doesn't matches cond< A<0>, bool_wrapper<true> >.

So cond< A<0> >, that is cond< A<0>, bool_wrapper<true> >, matches only the main version of cond<typename, typename>, so inherit from bool_wrapper<false>.

Now we can give a look at cond< A<1> >.

As for cond< A<0> >, the only template cond that matches cond< A<1> > is cond<typename, typename> with the second typename with the default value.

So cond< A<1> > is cond< A<1>, bool_wrapper<true> >.

But cond< A<1>, bool_wrapper<true> > matches only the main version of cond<typename, typename> or also the specialization?

We can see that using A<1> as T, we have that the specialization become

cond< A<1>, bool_wrapper<(1 == A<1>::code) || (2 == A<1>::code)> >

that is

cond< A<1>, bool_wrapper<(1 == 1) || (2 == 1)> >

that is

cond< A<1>, bool_wrapper<true || false> >

that is

cond< A<1>, bool_wrapper<true> >

and this matches cond< A<1>, bool_wrapper<true> >.

So, for cond< A<1> >, AKA cond< A<1>, bool_wrapper<true>, both versions of cond<typename, typename> match, so the compiler must choose the specialization, so cond< A<1> > inherit from bool_wrapper<true>.

Faux answered 29/9, 2018 at 12:4 Comment(3)
This is nice. It avoids splitting the condition in 2 parts, as the existence of the static data member is checked implicitly. I do not understand how it works for the case A1<0>. The specialization of Condition should be the preferred match, with the 2nd argument expanding to bool_<false>. That class inherits from bool_<true>, so, it should pick the wrong versions of foo. However it works. How is it possible?Obryant
@Obryant - Answer improved. Hope this helps.Faux
thanks for your answer. I mark this one as correct as it works and it is more elegant than mine. Regarding my further question, I did not articulate properly, as there is not much space in comment. As a a result, I do not think the further explanation addresses it. So I opened a new question here #52584333. You may consider removing extra details from this answer and reply to that one.Obryant

© 2022 - 2024 — McMap. All rights reserved.